1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "backends/cloud/box/boxuploadrequest.h"
24 #include "backends/cloud/box/boxstorage.h"
25 #include "backends/cloud/box/boxtokenrefresher.h"
26 #include "backends/cloud/iso8601.h"
27 #include "backends/cloud/storage.h"
28 #include "backends/networking/curl/connectionmanager.h"
29 #include "backends/networking/curl/curljsonrequest.h"
30 #include "backends/networking/curl/networkreadstream.h"
31 #include "common/json.h"
32
33 namespace Cloud {
34 namespace Box {
35
36 #define BOX_API_FILES "https://upload.box.com/api/2.0/files"
37
BoxUploadRequest(BoxStorage * storage,Common::String path,Common::String localPath,Storage::UploadCallback callback,Networking::ErrorCallback ecb)38 BoxUploadRequest::BoxUploadRequest(BoxStorage *storage, Common::String path, Common::String localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
39 Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _localPath(localPath), _uploadCallback(callback),
40 _workingRequest(nullptr), _ignoreCallback(false) {
41 start();
42 }
43
~BoxUploadRequest()44 BoxUploadRequest::~BoxUploadRequest() {
45 _ignoreCallback = true;
46 if (_workingRequest)
47 _workingRequest->finish();
48 delete _uploadCallback;
49 }
50
start()51 void BoxUploadRequest::start() {
52 _ignoreCallback = true;
53 if (_workingRequest)
54 _workingRequest->finish();
55 _resolvedId = ""; //used to update file contents
56 _parentId = ""; //used to create file within parent directory
57 _ignoreCallback = false;
58
59 resolveId();
60 }
61
resolveId()62 void BoxUploadRequest::resolveId() {
63 //check whether such file already exists
64 Storage::UploadCallback innerCallback = new Common::Callback<BoxUploadRequest, Storage::UploadResponse>(this, &BoxUploadRequest::idResolvedCallback);
65 Networking::ErrorCallback innerErrorCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::idResolveFailedCallback);
66 _workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback);
67 }
68
idResolvedCallback(Storage::UploadResponse response)69 void BoxUploadRequest::idResolvedCallback(Storage::UploadResponse response) {
70 _workingRequest = nullptr;
71 if (_ignoreCallback) return;
72 _resolvedId = response.value.id();
73 upload();
74 }
75
idResolveFailedCallback(Networking::ErrorResponse error)76 void BoxUploadRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
77 _workingRequest = nullptr;
78 if (_ignoreCallback) return;
79
80 //not resolved => error or no such file
81 if (error.response.contains("no such file found in its parent directory")) {
82 //parent's id after the '\n'
83 Common::String parentId = error.response;
84 for (uint32 i = 0; i < parentId.size(); ++i)
85 if (parentId[i] == '\n') {
86 parentId.erase(0, i + 1);
87 break;
88 }
89
90 _parentId = parentId;
91 upload();
92 return;
93 }
94
95 finishError(error);
96 }
97
upload()98 void BoxUploadRequest::upload() {
99 Common::String name = _savePath;
100 for (uint32 i = name.size(); i > 0; --i) {
101 if (name[i - 1] == '/' || name[i - 1] == '\\') {
102 name.erase(0, i);
103 break;
104 }
105 }
106
107 Common::String url = BOX_API_FILES;
108 if (_resolvedId != "")
109 url += "/" + _resolvedId;
110 url += "/content";
111 Networking::JsonCallback callback = new Common::Callback<BoxUploadRequest, Networking::JsonResponse>(this, &BoxUploadRequest::uploadedCallback);
112 Networking::ErrorCallback failureCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::notUploadedCallback);
113 Networking::CurlJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str());
114 request->addHeader("Authorization: Bearer " + _storage->accessToken());
115
116 Common::JSONObject jsonRequestParameters;
117 if (_resolvedId == "") {
118 Common::JSONObject parentObject;
119 parentObject.setVal("id", new Common::JSONValue(_parentId));
120 jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject));
121 jsonRequestParameters.setVal("name", new Common::JSONValue(name));
122 }
123
124 Common::JSONValue value(jsonRequestParameters);
125 request->addFormField("attributes", Common::JSON::stringify(&value));
126 request->addFormFile("file", _localPath);
127
128 _workingRequest = ConnMan.addRequest(request);
129 }
130
uploadedCallback(Networking::JsonResponse response)131 void BoxUploadRequest::uploadedCallback(Networking::JsonResponse response) {
132 _workingRequest = nullptr;
133 if (_ignoreCallback) return;
134
135 Networking::ErrorResponse error(this, false, true, "", -1);
136 Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
137 if (rq) {
138 const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
139 if (stream) {
140 long code = stream->httpResponseCode();
141 error.httpResponseCode = code;
142 }
143 }
144
145 if (error.httpResponseCode != 200 && error.httpResponseCode != 201)
146 warning("BoxUploadRequest: looks like an error (bad HTTP code)");
147
148 //check JSON and show warnings if it's malformed
149 Common::JSONValue *json = response.value;
150 if (json == nullptr) {
151 error.response = "Failed to parse JSON, null passed!";
152 finishError(error);
153 return;
154 }
155
156 if (!json->isObject()) {
157 error.response = "Passed JSON is not an object!";
158 finishError(error);
159 delete json;
160 return;
161 }
162
163 Common::JSONObject object = json->asObject();
164 if (Networking::CurlJsonRequest::jsonContainsArray(object, "entries", "BoxUploadRequest")) {
165 Common::JSONArray entries = object.getVal("entries")->asArray();
166 if (entries.size() == 0) {
167 warning("BoxUploadRequest: 'entries' found, but it's empty");
168 } else if (!Networking::CurlJsonRequest::jsonIsObject(entries[0], "BoxUploadRequest")) {
169 warning("BoxUploadRequest: 'entries' first item is not an object");
170 } else {
171 Common::JSONObject item = entries[0]->asObject();
172
173 if (Networking::CurlJsonRequest::jsonContainsString(item, "id", "BoxUploadRequest") &&
174 Networking::CurlJsonRequest::jsonContainsString(item, "name", "BoxUploadRequest") &&
175 Networking::CurlJsonRequest::jsonContainsString(item, "type", "BoxUploadRequest") &&
176 Networking::CurlJsonRequest::jsonContainsString(item, "modified_at", "BoxUploadRequest") &&
177 Networking::CurlJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxUploadRequest")) {
178
179 //finished
180 Common::String id = item.getVal("id")->asString();
181 Common::String name = item.getVal("name")->asString();
182 bool isDirectory = (item.getVal("type")->asString() == "folder");
183 uint32 size;
184 if (item.getVal("size")->isString()) {
185 size = item.getVal("size")->asString().asUint64();
186 } else {
187 size = item.getVal("size")->asIntegerNumber();
188 }
189 uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString());
190
191 finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory));
192 delete json;
193 return;
194 }
195 }
196 }
197
198 //TODO: check errors
199 /*
200 if (object.contains("error")) {
201 warning("Box returned error: %s", json->stringify(true).c_str());
202 delete json;
203 error.response = json->stringify(true);
204 finishError(error);
205 return;
206 }
207 */
208
209 warning("BoxUploadRequest: no file info to return");
210 finishUpload(StorageFile(_savePath, 0, 0, false));
211
212 delete json;
213 }
214
notUploadedCallback(Networking::ErrorResponse error)215 void BoxUploadRequest::notUploadedCallback(Networking::ErrorResponse error) {
216 _workingRequest = nullptr;
217 if (_ignoreCallback) return;
218 finishError(error);
219 }
220
handle()221 void BoxUploadRequest::handle() {}
222
restart()223 void BoxUploadRequest::restart() { start(); }
224
finishUpload(StorageFile file)225 void BoxUploadRequest::finishUpload(StorageFile file) {
226 Request::finishSuccess();
227 if (_uploadCallback)
228 (*_uploadCallback)(Storage::UploadResponse(this, file));
229 }
230
231 } // End of namespace Box
232 } // End of namespace Cloud
233