Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,236 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/basestorage.h"
#include "backends/cloud/cloudmanager.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/formats/json.h"
namespace Cloud {
BaseStorage::BaseStorage() {}
BaseStorage::BaseStorage(const Common::String &token, const Common::String &refreshToken, bool enabled):
_token(token), _refreshToken(refreshToken) {
_isEnabled = enabled;
}
BaseStorage::~BaseStorage() {}
void BaseStorage::getAccessToken(const Common::String &code, Networking::ErrorCallback callback) {
Networking::JsonCallback innerCallback = new Common::CallbackBridge<BaseStorage, const Networking::ErrorResponse &, const Networking::JsonResponse &>(this, &BaseStorage::codeFlowComplete, callback);
Networking::ErrorCallback errorCallback = new Common::CallbackBridge<BaseStorage, const Networking::ErrorResponse &, const Networking::ErrorResponse &>(this, &BaseStorage::codeFlowFailed, callback);
Common::String url = Common::String::format("https://cloud.scummvm.org/%s/token/%s", cloudProvider().c_str(), code.c_str());
Networking::HttpJsonRequest *request = new Networking::HttpJsonRequest(innerCallback, errorCallback, url);
addRequest(request);
}
void BaseStorage::codeFlowComplete(Networking::ErrorCallback callback, const Networking::JsonResponse &response) {
bool success = true;
Common::String callbackMessage = "OK";
const Common::JSONValue *json = response.value;
if (json == nullptr) {
debug(9, "BaseStorage::codeFlowComplete: got NULL instead of JSON!");
success = false;
callbackMessage = "Incorrect JSON.";
}
if (success && !json->isObject()) {
debug(9, "BaseStorage::codeFlowComplete: passed JSON is not an object!");
success = false;
callbackMessage = "Incorrect JSON.";
}
Common::JSONObject result;
if (success) {
result = json->asObject();
if (!Networking::HttpJsonRequest::jsonContainsAttribute(result, "error", "BaseStorage::codeFlowComplete")) {
warning("BaseStorage: bad response, no 'error' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
callbackMessage = "Incorrect JSON.";
}
}
if (success && result.getVal("error")->asBool()) {
Common::String errorMessage = "{error: true}, message is missing";
if (Networking::HttpJsonRequest::jsonContainsString(result, "message", "BaseStorage::codeFlowComplete")) {
errorMessage = result.getVal("message")->asString();
}
warning("BaseStorage: response says error occurred: %s", errorMessage.c_str());
success = false;
callbackMessage = errorMessage;
}
if (success && !Networking::HttpJsonRequest::jsonContainsObject(result, "oauth", "BaseStorage::codeFlowComplete")) {
warning("BaseStorage: bad response, no 'oauth' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
callbackMessage = "Incorrect JSON.";
}
Common::JSONObject oauth;
bool requiresRefreshToken = needsRefreshToken();
if (success) {
oauth = result.getVal("oauth")->asObject();
if (!Networking::HttpJsonRequest::jsonContainsString(oauth, "access_token", "BaseStorage::codeFlowComplete") ||
!Networking::HttpJsonRequest::jsonContainsString(oauth, "refresh_token", "BaseStorage::codeFlowComplete", !requiresRefreshToken)) {
warning("BaseStorage: bad response, no 'access_token' or 'refresh_token' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
callbackMessage = "Incorrect JSON.";
}
}
if (success) {
_token = oauth.getVal("access_token")->asString();
if (requiresRefreshToken) {
_refreshToken = oauth.getVal("refresh_token")->asString();
}
CloudMan.replaceStorage(this, storageIndex());
ConfMan.flushToDisk();
}
if (!success)
CloudMan.removeStorage(this);
if (callback)
(*callback)(Networking::ErrorResponse(nullptr, false, !success, callbackMessage, -1));
delete json;
delete callback;
}
void BaseStorage::codeFlowFailed(Networking::ErrorCallback callback, const Networking::ErrorResponse &error) {
debug(9, "BaseStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
debug(9, "%s", error.response.c_str());
CloudMan.removeStorage(this);
if (callback)
(*callback)(error);
delete callback;
}
void BaseStorage::refreshAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback) {
if (_refreshToken == "") {
warning("BaseStorage: no refresh token available to get new access token.");
if (callback) (*callback)(BoolResponse(nullptr, false));
return;
}
Networking::JsonCallback innerCallback = new Common::CallbackBridge<BaseStorage, const BoolResponse &, const Networking::JsonResponse &>(this, &BaseStorage::tokenRefreshed, callback);
if (errorCallback == nullptr)
errorCallback = getErrorPrintingCallback();
Common::String url = Common::String::format("https://cloud.scummvm.org/%s/refresh", cloudProvider().c_str());
Networking::HttpJsonRequest *request = new Networking::HttpJsonRequest(innerCallback, errorCallback, url);
request->addHeader("X-ScummVM-Refresh-Token: " + _refreshToken);
addRequest(request);
}
void BaseStorage::tokenRefreshed(BoolCallback callback, const Networking::JsonResponse &response) {
bool success = true;
const Common::JSONValue *json = response.value;
if (json == nullptr) {
debug(9, "BaseStorage::tokenRefreshed: got NULL instead of JSON!");
success = false;
}
if (success && !json->isObject()) {
debug(9, "BaseStorage::tokenRefreshed: passed JSON is not an object!");
success = false;
}
Common::JSONObject result;
if (success) {
result = json->asObject();
if (!Networking::HttpJsonRequest::jsonContainsAttribute(result, "error", "BaseStorage::tokenRefreshed")) {
warning("BaseStorage: bad response, no 'error' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
}
}
if (success && result.getVal("error")->asBool()) {
Common::String errorMessage = "{error: true}, message is missing";
if (Networking::HttpJsonRequest::jsonContainsString(result, "message", "BaseStorage::tokenRefreshed")) {
errorMessage = result.getVal("message")->asString();
}
warning("BaseStorage: response says error occurred: %s", errorMessage.c_str());
success = false;
}
if (success && !Networking::HttpJsonRequest::jsonContainsObject(result, "oauth", "BaseStorage::tokenRefreshed")) {
warning("BaseStorage: bad response, no 'oauth' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
}
Common::JSONObject oauth;
bool requiresRefreshToken = !canReuseRefreshToken();
if (success) {
oauth = result.getVal("oauth")->asObject();
if (!Networking::HttpJsonRequest::jsonContainsString(oauth, "access_token", "BaseStorage::tokenRefreshed") ||
!Networking::HttpJsonRequest::jsonContainsString(oauth, "refresh_token", "BaseStorage::tokenRefreshed", !requiresRefreshToken)) {
warning("BaseStorage: bad response, no 'access_token' or 'refresh_token' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
}
}
if (success) {
debug(9, "%s", json->stringify(true).c_str()); // TODO: remove when done testing against cloud.scummvm.org
_token = oauth.getVal("access_token")->asString();
if (requiresRefreshToken) {
_refreshToken = oauth.getVal("refresh_token")->asString();
}
CloudMan.save(); //ask CloudManager to save our new access_token and refresh_token
}
if (callback)
(*callback)(BoolResponse(nullptr, success));
delete json;
delete callback;
}
void BaseStorage::saveIsEnabledFlag(const Common::String &keyPrefix) const {
ConfMan.set(keyPrefix + "enabled", _isEnabled ? "true" : "false", ConfMan.kCloudDomain);
}
bool BaseStorage::loadIsEnabledFlag(const Common::String &keyPrefix) {
if (!ConfMan.hasKey(keyPrefix + "enabled", ConfMan.kCloudDomain))
return false;
Common::String enabled = ConfMan.get(keyPrefix + "enabled", ConfMan.kCloudDomain);
return (enabled == "true");
}
void BaseStorage::removeIsEnabledFlag(const Common::String &keyPrefix) {
ConfMan.removeKey(keyPrefix + "enabled", ConfMan.kCloudDomain);
}
} // End of namespace Cloud

View File

@@ -0,0 +1,103 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_BASE_STORAGE_H
#define BACKENDS_CLOUD_BASE_STORAGE_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
class BaseStorage: public Cloud::Storage {
protected:
/** Storage's access and refresh tokens. */
Common::String _token, _refreshToken;
/**
* Gets token from cloud.scummvm.org using given code.
* Base implementation for storages with common auth procedure.
*/
virtual void getAccessToken(const Common::String &code, Networking::ErrorCallback callback);
/**
* Handles JSON response which should contain access token requested
* with getAccessToken().
*/
virtual void codeFlowComplete(Networking::ErrorCallback callback, const Networking::JsonResponse &response);
/**
* Handles network errors occurred while getting access token requested
* with getAccessToken().
*/
virtual void codeFlowFailed(Networking::ErrorCallback callback, const Networking::ErrorResponse &error);
/**
* Return cloud provider name, used in cloud.scummvm.org endpoints.
* @return cloud provider (for example, "dropbox").
*/
virtual Common::String cloudProvider() = 0;
/**
* Return CloudManager's StorageID for this storage.
* @return StorageID corresponding to this storage (for example,
* kStorageDropboxId).
*/
virtual uint32 storageIndex() = 0;
/**
* Return whether storage needs refresh_token to work.
*/
virtual bool needsRefreshToken() = 0;
/**
* Return whether to expect new refresh_token on refresh.
*/
virtual bool canReuseRefreshToken() = 0;
private:
void tokenRefreshed(BoolCallback callback, const Networking::JsonResponse &response);
protected:
/** Helper function to save Storage::_isEnabled into config. */
void saveIsEnabledFlag(const Common::String &keyPrefix) const;
/** Helper function to load Storage::_isEnabled value from config. */
static bool loadIsEnabledFlag(const Common::String &keyPrefix);
/** Helper function to remove Storage::_isEnabled from config. */
static void removeIsEnabledFlag(const Common::String &keyPrefix);
public:
BaseStorage();
BaseStorage(const Common::String &token, const Common::String &refreshToken, bool enabled = false);
~BaseStorage() override;
/**
* Gets new access_token. Pass a callback, so you could
* continue your work when new token is available.
*/
virtual void refreshAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr);
};
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,194 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/box/boxlistdirectorybyidrequest.h"
#include "backends/cloud/box/boxstorage.h"
#include "backends/cloud/box/boxtokenrefresher.h"
#include "backends/cloud/iso8601.h"
#include "backends/cloud/storage.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
namespace Cloud {
namespace Box {
#define BOX_LIST_DIRECTORY_LIMIT 1000
#define BOX_FOLDERS_API_LINK "https://api.box.com/2.0/folders/%s/items?offset=%u&limit=%u&fields=%s"
BoxListDirectoryByIdRequest::BoxListDirectoryByIdRequest(BoxStorage *storage, const Common::String &id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _requestedId(id), _storage(storage), _listDirectoryCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
BoxListDirectoryByIdRequest::~BoxListDirectoryByIdRequest() {
_ignoreCallback = true;
if (_workingRequest) _workingRequest->finish();
delete _listDirectoryCallback;
}
void BoxListDirectoryByIdRequest::start() {
_ignoreCallback = true;
if (_workingRequest) _workingRequest->finish();
_files.clear();
_ignoreCallback = false;
makeRequest(0);
}
void BoxListDirectoryByIdRequest::makeRequest(uint32 offset) {
Common::String url = Common::String::format(
BOX_FOLDERS_API_LINK,
_requestedId.c_str(),
offset,
BOX_LIST_DIRECTORY_LIMIT,
"id,type,name,size,modified_at"
);
Networking::JsonCallback callback = new Common::Callback<BoxListDirectoryByIdRequest, const Networking::JsonResponse &>(this, &BoxListDirectoryByIdRequest::responseCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<BoxListDirectoryByIdRequest, const Networking::ErrorResponse &>(this, &BoxListDirectoryByIdRequest::errorCallback);
Networking::HttpJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str());
request->addHeader("Authorization: Bearer " + _storage->accessToken());
_workingRequest = ConnMan.addRequest(request);
}
void BoxListDirectoryByIdRequest::responseCallback(const Networking::JsonResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback) {
delete response.value;
return;
}
if (response.request)
_date = response.request->date();
Networking::ErrorResponse error(this, "BoxListDirectoryByIdRequest::responseCallback: unknown error");
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
const Common::JSONValue *json = response.value;
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
if (!json->isObject()) {
error.response = "Passed JSON is not an object!";
finishError(error);
delete json;
return;
}
Common::JSONObject responseObject = json->asObject();
//debug(9, "%s", json->stringify(true).c_str());
//TODO: handle error messages passed as JSON
/*
if (responseObject.contains("error") || responseObject.contains("error_summary")) {
warning("Box returned error: %s", responseObject.getVal("error_summary")->asString().c_str());
error.failed = true;
error.response = json->stringify();
finishError(error);
delete json;
return;
}
*/
//check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
if (responseObject.contains("entries")) {
if (!responseObject.getVal("entries")->isArray()) {
error.response = Common::String::format(
"\"entries\" found, but that's not an array!\n%s",
responseObject.getVal("entries")->stringify(true).c_str()
);
finishError(error);
delete json;
return;
}
Common::JSONArray items = responseObject.getVal("entries")->asArray();
for (uint32 i = 0; i < items.size(); ++i) {
if (!Networking::HttpJsonRequest::jsonIsObject(items[i], "BoxListDirectoryByIdRequest")) continue;
Common::JSONObject item = items[i]->asObject();
if (!Networking::HttpJsonRequest::jsonContainsString(item, "id", "BoxListDirectoryByIdRequest")) continue;
if (!Networking::HttpJsonRequest::jsonContainsString(item, "name", "BoxListDirectoryByIdRequest")) continue;
if (!Networking::HttpJsonRequest::jsonContainsString(item, "type", "BoxListDirectoryByIdRequest")) continue;
if (!Networking::HttpJsonRequest::jsonContainsString(item, "modified_at", "BoxListDirectoryByIdRequest")) continue;
if (!Networking::HttpJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxListDirectoryByIdRequest")) continue;
Common::String id = item.getVal("id")->asString();
Common::String name = item.getVal("name")->asString();
bool isDirectory = (item.getVal("type")->asString() == "folder");
uint32 size;
if (item.getVal("size")->isString()) {
size = item.getVal("size")->asString().asUint64();
} else {
size = item.getVal("size")->asIntegerNumber();
}
uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString());
//as we list directory by id, we can't determine full path for the file, so we leave it empty
_files.push_back(StorageFile(id, "", name, size, timestamp, isDirectory));
}
}
uint32 received = 0;
uint32 totalCount = 0;
if (responseObject.contains("total_count") && responseObject.getVal("total_count")->isIntegerNumber())
totalCount = responseObject.getVal("total_count")->asIntegerNumber();
if (responseObject.contains("offset") && responseObject.getVal("offset")->isIntegerNumber())
received = responseObject.getVal("offset")->asIntegerNumber();
if (responseObject.contains("limit") && responseObject.getVal("limit")->isIntegerNumber())
received += responseObject.getVal("limit")->asIntegerNumber();
bool hasMore = (received < totalCount);
if (hasMore) makeRequest(received);
else finishListing(_files);
delete json;
}
void BoxListDirectoryByIdRequest::errorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback) return;
if (error.request) _date = error.request->date();
finishError(error);
}
void BoxListDirectoryByIdRequest::handle() {}
void BoxListDirectoryByIdRequest::restart() { start(); }
Common::String BoxListDirectoryByIdRequest::date() const { return _date; }
void BoxListDirectoryByIdRequest::finishListing(Common::Array<StorageFile> &files) {
Request::finishSuccess();
if (_listDirectoryCallback) (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
}
} // End of namespace Box
} // End of namespace Cloud

View File

@@ -0,0 +1,62 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_BOX_BOXLISTDIRECTORYBYIDREQUEST_H
#define BACKENDS_CLOUD_BOX_BOXLISTDIRECTORYBYIDREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace Box {
class BoxStorage;
class BoxListDirectoryByIdRequest: public Networking::Request {
Common::String _requestedId;
BoxStorage *_storage;
Storage::ListDirectoryCallback _listDirectoryCallback;
Common::Array<StorageFile> _files;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _date;
void start();
void makeRequest(uint32 offset);
void responseCallback(const Networking::JsonResponse &response);
void errorCallback(const Networking::ErrorResponse &error);
void finishListing(Common::Array<StorageFile> &files);
public:
BoxListDirectoryByIdRequest(BoxStorage *storage, const Common::String &id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb);
~BoxListDirectoryByIdRequest() override;
void handle() override;
void restart() override;
Common::String date() const override;
};
} // End of namespace Box
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,243 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/box/boxstorage.h"
#include "backends/cloud/box/boxlistdirectorybyidrequest.h"
#include "backends/cloud/box/boxtokenrefresher.h"
#include "backends/cloud/box/boxuploadrequest.h"
#include "backends/cloud/cloudmanager.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/formats/json.h"
namespace Cloud {
namespace Box {
#define BOX_API_FOLDERS "https://api.box.com/2.0/folders"
#define BOX_API_FILES_CONTENT "https://api.box.com/2.0/files/%s/content"
#define BOX_API_USERS_ME "https://api.box.com/2.0/users/me"
BoxStorage::BoxStorage(const Common::String &token, const Common::String &refreshToken, bool enabled):
IdStorage(token, refreshToken, enabled) {}
BoxStorage::BoxStorage(const Common::String &code, Networking::ErrorCallback cb) {
getAccessToken(code, cb);
}
BoxStorage::BoxStorage(const Networking::JsonResponse &codeFlowJson, Networking::ErrorCallback cb) {
codeFlowComplete(cb, codeFlowJson);
}
BoxStorage::~BoxStorage() {}
Common::String BoxStorage::cloudProvider() { return "box"; }
uint32 BoxStorage::storageIndex() { return kStorageBoxId; }
bool BoxStorage::needsRefreshToken() { return true; }
bool BoxStorage::canReuseRefreshToken() { return false; }
void BoxStorage::saveConfig(const Common::String &keyPrefix) {
ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
saveIsEnabledFlag(keyPrefix);
}
Common::String BoxStorage::name() const {
return "Box";
}
void BoxStorage::infoInnerCallback(StorageInfoCallback outerCallback, const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
if (!json) {
warning("BoxStorage::infoInnerCallback: NULL passed instead of JSON");
delete outerCallback;
return;
}
if (!Networking::HttpJsonRequest::jsonIsObject(json, "BoxStorage::infoInnerCallback")) {
delete json;
delete outerCallback;
return;
}
Common::JSONObject jsonInfo = json->asObject();
Common::String uid, displayName, email;
uint64 quotaUsed = 0, quotaAllocated = 0;
// can check that "type": "user"
// there is also "max_upload_size", "phone" and "avatar_url"
if (Networking::HttpJsonRequest::jsonContainsString(jsonInfo, "id", "BoxStorage::infoInnerCallback"))
uid = jsonInfo.getVal("id")->asString();
if (Networking::HttpJsonRequest::jsonContainsString(jsonInfo, "name", "BoxStorage::infoInnerCallback"))
displayName = jsonInfo.getVal("name")->asString();
if (Networking::HttpJsonRequest::jsonContainsString(jsonInfo, "login", "BoxStorage::infoInnerCallback"))
email = jsonInfo.getVal("login")->asString();
if (Networking::HttpJsonRequest::jsonContainsIntegerNumber(jsonInfo, "space_amount", "BoxStorage::infoInnerCallback"))
quotaAllocated = jsonInfo.getVal("space_amount")->asIntegerNumber();
if (Networking::HttpJsonRequest::jsonContainsIntegerNumber(jsonInfo, "space_used", "BoxStorage::infoInnerCallback"))
quotaUsed = jsonInfo.getVal("space_used")->asIntegerNumber();
Common::String username = email;
if (username == "") username = displayName;
if (username == "") username = uid;
CloudMan.setStorageUsername(kStorageBoxId, username);
if (outerCallback) {
(*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, displayName, email, quotaUsed, quotaAllocated)));
delete outerCallback;
}
delete json;
}
Networking::Request *BoxStorage::listDirectoryById(const Common::String &id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
if (!callback)
callback = getPrintFilesCallback();
return addRequest(new BoxListDirectoryByIdRequest(this, id, callback, errorCallback));
}
void BoxStorage::createDirectoryInnerCallback(BoolCallback outerCallback, const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
if (!json) {
warning("BoxStorage::createDirectoryInnerCallback: NULL passed instead of JSON");
delete outerCallback;
return;
}
if (outerCallback) {
if (Networking::HttpJsonRequest::jsonIsObject(json, "BoxStorage::createDirectoryInnerCallback")) {
Common::JSONObject jsonInfo = json->asObject();
(*outerCallback)(BoolResponse(nullptr, jsonInfo.contains("id")));
} else {
(*outerCallback)(BoolResponse(nullptr, false));
}
delete outerCallback;
}
delete json;
}
Networking::Request *BoxStorage::createDirectoryWithParentId(const Common::String &parentId, const Common::String &directoryName, BoolCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
Common::String url = BOX_API_FOLDERS;
Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, const BoolResponse &, const Networking::JsonResponse &>(this, &BoxStorage::createDirectoryInnerCallback, callback);
Networking::HttpJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, url.c_str());
request->addHeader("Authorization: Bearer " + accessToken());
request->addHeader("Content-Type: application/json");
Common::JSONObject parentObject;
parentObject.setVal("id", new Common::JSONValue(parentId));
Common::JSONObject jsonRequestParameters;
jsonRequestParameters.setVal("name", new Common::JSONValue(directoryName));
jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject));
Common::JSONValue value(jsonRequestParameters);
request->addPostField(Common::JSON::stringify(&value));
return addRequest(request);
}
Networking::Request *BoxStorage::upload(const Common::String &remotePath, const Common::Path &localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
return addRequest(new BoxUploadRequest(this, remotePath, localPath, callback, errorCallback));
}
Networking::Request *BoxStorage::upload(const Common::String &path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
warning("BoxStorage::upload(ReadStream) not implemented");
if (errorCallback)
(*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "BoxStorage::upload(ReadStream) not implemented", -1));
delete callback;
delete errorCallback;
return nullptr;
}
bool BoxStorage::uploadStreamSupported() {
return false;
}
Networking::Request *BoxStorage::streamFileById(const Common::String &id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
if (callback) {
Common::String url = Common::String::format(BOX_API_FILES_CONTENT, id.c_str());
Common::String header = "Authorization: Bearer " + _token;
Networking::RequestHeaders *headersList = new Networking::RequestHeaders();
headersList->push_back(header);
Networking::NetworkReadStream *stream = Networking::NetworkReadStream::make(url.c_str(), headersList, "");
(*callback)(Networking::NetworkReadStreamResponse(nullptr, stream));
}
delete callback;
delete errorCallback;
return nullptr;
}
Networking::Request *BoxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, const StorageInfoResponse &, const Networking::JsonResponse &>(this, &BoxStorage::infoInnerCallback, callback);
Networking::HttpJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, BOX_API_USERS_ME);
request->addHeader("Authorization: Bearer " + _token);
return addRequest(request);
}
Common::String BoxStorage::savesDirectoryPath() { return "scummvm/saves/"; }
BoxStorage *BoxStorage::loadFromConfig(const Common::String &keyPrefix) {
if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
warning("BoxStorage: no access_token found");
return nullptr;
}
if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
warning("BoxStorage: no refresh_token found");
return nullptr;
}
Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
return new BoxStorage(accessToken, refreshToken, loadIsEnabledFlag(keyPrefix));
}
void BoxStorage::removeFromConfig(const Common::String &keyPrefix) {
ConfMan.removeKey(keyPrefix + "access_token", ConfMan.kCloudDomain);
ConfMan.removeKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
removeIsEnabledFlag(keyPrefix);
}
Common::String BoxStorage::getRootDirectoryId() {
return "0";
}
} // End of namespace Box
} // End of namespace Cloud

View File

@@ -0,0 +1,123 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_BOX_BOXSTORAGE_H
#define BACKENDS_CLOUD_BOX_BOXSTORAGE_H
#include "backends/cloud/id/idstorage.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace Box {
class BoxStorage: public Id::IdStorage {
/** This private constructor is called from loadFromConfig(). */
BoxStorage(const Common::String &token, const Common::String &refreshToken, bool enabled);
/** Constructs StorageInfo based on JSON response from cloud. */
void infoInnerCallback(StorageInfoCallback outerCallback, const Networking::JsonResponse &json);
void createDirectoryInnerCallback(BoolCallback outerCallback, const Networking::JsonResponse &response);
protected:
/**
* @return "box"
*/
Common::String cloudProvider() override;
/**
* @return kStorageBoxId
*/
uint32 storageIndex() override;
bool needsRefreshToken() override;
bool canReuseRefreshToken() override;
public:
/** This constructor uses OAuth code flow to get tokens. */
BoxStorage(const Common::String &code, Networking::ErrorCallback cb);
/** This constructor extracts tokens from JSON acquired via OAuth code flow. */
BoxStorage(const Networking::JsonResponse &codeFlowJson, Networking::ErrorCallback cb);
~BoxStorage() override;
/**
* Storage methods, which are used by CloudManager to save
* storage in configuration file.
*/
/**
* Save storage data using ConfMan.
* @param keyPrefix all saved keys must start with this prefix.
* @note every Storage must write keyPrefix + "type" key
* with common value (e.g. "Dropbox").
*/
void saveConfig(const Common::String &keyPrefix) override;
/**
* Return unique storage name.
* @returns some unique storage name (for example, "Dropbox (user@example.com)")
*/
Common::String name() const override;
/** Public Cloud API comes down there. */
Networking::Request *listDirectoryById(const Common::String &id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) override;
Networking::Request *createDirectoryWithParentId(const Common::String &parentId, const Common::String &directoryName, BoolCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns UploadStatus struct with info about uploaded file. */
Networking::Request *upload(const Common::String &remotePath, const Common::Path &localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) override;
Networking::Request *upload(const Common::String &path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns whether Storage supports upload(ReadStream). */
bool uploadStreamSupported() override;
/** Returns pointer to Networking::NetworkReadStream. */
Networking::Request *streamFileById(const Common::String &path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns the StorageInfo struct. */
Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns storage's saves directory path with the trailing slash. */
Common::String savesDirectoryPath() override;
/**
* Load token and user id from configs and return BoxStorage for those.
* @return pointer to the newly created BoxStorage or 0 if some problem occurred.
*/
static BoxStorage *loadFromConfig(const Common::String &keyPrefix);
/**
* Remove all BoxStorage-related data from config.
*/
static void removeFromConfig(const Common::String &keyPrefix);
Common::String getRootDirectoryId() override;
Common::String accessToken() const { return _token; }
};
} // End of namespace Box
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,120 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/box/boxtokenrefresher.h"
#include "backends/cloud/box/boxstorage.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/debug.h"
#include "common/formats/json.h"
namespace Cloud {
namespace Box {
BoxTokenRefresher::BoxTokenRefresher(BoxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url):
HttpJsonRequest(callback, ecb, url), _parentStorage(parent) {}
BoxTokenRefresher::~BoxTokenRefresher() {}
void BoxTokenRefresher::tokenRefreshed(const Storage::BoolResponse &response) {
if (!response.value) {
//failed to refresh token, notify user with NULL in original callback
warning("BoxTokenRefresher: failed to refresh token");
finishError(Networking::ErrorResponse(this, false, true, "BoxTokenRefresher::tokenRefreshed: failed to refresh token", -1));
return;
}
//update headers: first change header with token, then pass those to request
for (uint32 i = 0; i < _headersList.size(); ++i) {
if (_headersList[i].contains("Authorization")) {
_headersList[i] = "Authorization: Bearer " + _parentStorage->accessToken();
}
}
//successfully received refreshed token, can restart the original request now
retry(0);
}
void BoxTokenRefresher::finishJson(const Common::JSONValue *json) {
if (!json) {
//that's probably not an error (200 OK)
HttpJsonRequest::finishJson(nullptr);
return;
}
if (jsonIsObject(json, "BoxTokenRefresher")) {
Common::JSONObject result = json->asObject();
if (result.contains("type") && result.getVal("type")->isString() && result.getVal("type")->asString() == "error") {
//new token needed => request token & then retry original request
long httpCode = -1;
if (_stream) {
httpCode = _stream->httpResponseCode();
debug(9, "BoxTokenRefresher: code %ld", httpCode);
}
bool irrecoverable = true;
Common::String code, message;
if (jsonContainsString(result, "code", "BoxTokenRefresher")) {
code = result.getVal("code")->asString();
debug(9, "BoxTokenRefresher: code = %s", code.c_str());
}
if (jsonContainsString(result, "message", "BoxTokenRefresher")) {
message = result.getVal("message")->asString();
debug(9, "BoxTokenRefresher: message = %s", message.c_str());
}
//TODO: decide when token refreshment will help
//for now refreshment is used only when HTTP 401 is passed in finishError()
//if (code == "unauthenticated") irrecoverable = false;
if (irrecoverable) {
finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpCode));
delete json;
return;
}
pause();
delete json;
_parentStorage->refreshAccessToken(new Common::Callback<BoxTokenRefresher, const Storage::BoolResponse &>(this, &BoxTokenRefresher::tokenRefreshed));
return;
}
}
//notify user of success
HttpJsonRequest::finishJson(json);
}
void BoxTokenRefresher::finishError(const Networking::ErrorResponse &error, Networking::RequestState state) {
if (error.httpResponseCode == 401) { // invalid_token
pause();
_parentStorage->refreshAccessToken(new Common::Callback<BoxTokenRefresher, const Storage::BoolResponse &>(this, &BoxTokenRefresher::tokenRefreshed));
return;
}
// there are also 400 == invalid_request and 403 == insufficient_scope
// but TokenRefresher is there to refresh token when it's invalid only
Request::finishError(error);
}
} // End of namespace Box
} // End of namespace Cloud

View File

@@ -0,0 +1,48 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_BOX_BOXTOKENREFRESHER_H
#define BACKENDS_CLOUD_BOX_BOXTOKENREFRESHER_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace Box {
class BoxStorage;
class BoxTokenRefresher: public Networking::HttpJsonRequest {
BoxStorage *_parentStorage;
void tokenRefreshed(const Storage::BoolResponse &response);
void finishJson(const Common::JSONValue *json) override;
void finishError(const Networking::ErrorResponse &error, Networking::RequestState state = Networking::FINISHED) override;
public:
BoxTokenRefresher(BoxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url);
~BoxTokenRefresher() override;
};
} // End of namespace Box
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,231 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/box/boxuploadrequest.h"
#include "backends/cloud/box/boxstorage.h"
#include "backends/cloud/box/boxtokenrefresher.h"
#include "backends/cloud/iso8601.h"
#include "backends/cloud/storage.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
namespace Cloud {
namespace Box {
#define BOX_API_FILES "https://upload.box.com/api/2.0/files"
BoxUploadRequest::BoxUploadRequest(BoxStorage *storage, const Common::String &path, const Common::Path &localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _localPath(localPath), _uploadCallback(callback),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
BoxUploadRequest::~BoxUploadRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _uploadCallback;
}
void BoxUploadRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_resolvedId = ""; //used to update file contents
_parentId = ""; //used to create file within parent directory
_ignoreCallback = false;
resolveId();
}
void BoxUploadRequest::resolveId() {
//check whether such file already exists
Storage::UploadCallback innerCallback = new Common::Callback<BoxUploadRequest, const Storage::UploadResponse &>(this, &BoxUploadRequest::idResolvedCallback);
Networking::ErrorCallback innerErrorCallback = new Common::Callback<BoxUploadRequest, const Networking::ErrorResponse &>(this, &BoxUploadRequest::idResolveFailedCallback);
_workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback);
}
void BoxUploadRequest::idResolvedCallback(const Storage::UploadResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback) return;
_resolvedId = response.value.id();
upload();
}
void BoxUploadRequest::idResolveFailedCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback) return;
//not resolved => error or no such file
if (error.response.contains("no such file found in its parent directory")) {
//parent's id after the '\n'
Common::String parentId = error.response;
for (uint32 i = 0; i < parentId.size(); ++i)
if (parentId[i] == '\n') {
parentId.erase(0, i + 1);
break;
}
_parentId = parentId;
upload();
return;
}
finishError(error);
}
void BoxUploadRequest::upload() {
Common::String name = _savePath;
for (uint32 i = name.size(); i > 0; --i) {
if (name[i - 1] == '/' || name[i - 1] == '\\') {
name.erase(0, i);
break;
}
}
Common::String url = BOX_API_FILES;
if (_resolvedId != "")
url += "/" + _resolvedId;
url += "/content";
Networking::JsonCallback callback = new Common::Callback<BoxUploadRequest, const Networking::JsonResponse &>(this, &BoxUploadRequest::uploadedCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<BoxUploadRequest, const Networking::ErrorResponse &>(this, &BoxUploadRequest::notUploadedCallback);
Networking::HttpJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str());
request->addHeader("Authorization: Bearer " + _storage->accessToken());
Common::JSONObject jsonRequestParameters;
if (_resolvedId == "") {
Common::JSONObject parentObject;
parentObject.setVal("id", new Common::JSONValue(_parentId));
jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject));
jsonRequestParameters.setVal("name", new Common::JSONValue(name));
}
Common::JSONValue value(jsonRequestParameters);
request->addFormField("attributes", Common::JSON::stringify(&value));
request->addFormFile("file", _localPath);
_workingRequest = ConnMan.addRequest(request);
}
void BoxUploadRequest::uploadedCallback(const Networking::JsonResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback) return;
Networking::ErrorResponse error(this, false, true, "", -1);
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq) {
const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
if (stream) {
long code = stream->httpResponseCode();
error.httpResponseCode = code;
}
}
if (error.httpResponseCode != 200 && error.httpResponseCode != 201)
warning("BoxUploadRequest: looks like an error (bad HTTP code)");
//check JSON and show warnings if it's malformed
const Common::JSONValue *json = response.value;
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
if (!json->isObject()) {
error.response = "Passed JSON is not an object!";
finishError(error);
delete json;
return;
}
Common::JSONObject object = json->asObject();
if (Networking::HttpJsonRequest::jsonContainsArray(object, "entries", "BoxUploadRequest")) {
Common::JSONArray entries = object.getVal("entries")->asArray();
if (entries.size() == 0) {
warning("BoxUploadRequest: 'entries' found, but it's empty");
} else if (!Networking::HttpJsonRequest::jsonIsObject(entries[0], "BoxUploadRequest")) {
warning("BoxUploadRequest: 'entries' first item is not an object");
} else {
Common::JSONObject item = entries[0]->asObject();
if (Networking::HttpJsonRequest::jsonContainsString(item, "id", "BoxUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsString(item, "name", "BoxUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsString(item, "type", "BoxUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsString(item, "modified_at", "BoxUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxUploadRequest")) {
//finished
Common::String id = item.getVal("id")->asString();
Common::String name = item.getVal("name")->asString();
bool isDirectory = (item.getVal("type")->asString() == "folder");
uint32 size;
if (item.getVal("size")->isString()) {
size = item.getVal("size")->asString().asUint64();
} else {
size = item.getVal("size")->asIntegerNumber();
}
uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString());
finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory));
delete json;
return;
}
}
}
//TODO: check errors
/*
if (object.contains("error")) {
warning("Box returned error: %s", json->stringify(true).c_str());
delete json;
error.response = json->stringify(true);
finishError(error);
return;
}
*/
warning("BoxUploadRequest: no file info to return");
finishUpload(StorageFile(_savePath, 0, 0, false));
delete json;
}
void BoxUploadRequest::notUploadedCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback) return;
finishError(error);
}
void BoxUploadRequest::handle() {}
void BoxUploadRequest::restart() { start(); }
void BoxUploadRequest::finishUpload(const StorageFile &file) {
Request::finishSuccess();
if (_uploadCallback)
(*_uploadCallback)(Storage::UploadResponse(this, file));
}
} // End of namespace Box
} // End of namespace Cloud

View File

@@ -0,0 +1,63 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_BOX_BOXUPLOADREQUEST_H
#define BACKENDS_CLOUD_BOX_BOXUPLOADREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace Box {
class BoxStorage;
class BoxUploadRequest: public Networking::Request {
BoxStorage *_storage;
Common::String _savePath;
Common::Path _localPath;
Storage::UploadCallback _uploadCallback;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _resolvedId, _parentId;
void start();
void resolveId();
void idResolvedCallback(const Storage::UploadResponse &response);
void idResolveFailedCallback(const Networking::ErrorResponse &error);
void upload();
void uploadedCallback(const Networking::JsonResponse &response);
void notUploadedCallback(const Networking::ErrorResponse &error);
void finishUpload(const StorageFile &status);
public:
BoxUploadRequest(BoxStorage *storage, const Common::String &path, const Common::Path &localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
~BoxUploadRequest() override;
void handle() override;
void restart() override;
};
} // End of namespace Box
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,186 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/cloudicon.h"
#include "common/memstream.h"
#include "common/system.h"
#include "image/png.h"
namespace Cloud {
const float CloudIcon::ALPHA_SPEED = 0.0005f;
const float CloudIcon::ALPHA_MAX = 1.f;
const float CloudIcon::ALPHA_MIN = 0.6f;
CloudIcon::CloudIcon() {
initIcons();
hide();
_lastUpdateTime = g_system->getMillis();
}
CloudIcon::~CloudIcon() {
_icon.free();
_disabledIcon.free();
_alphaIcon.free();
}
void CloudIcon::show(CloudIcon::Type icon, int duration) {
if (_type == icon) {
return; // Nothing to do
}
if (icon != kNone) {
_state = kShown;
_type = icon;
if (duration) {
_hideTime = g_system->getMillis() + duration;
} else {
_hideTime = 0;
}
} else {
_state = kGoingToHide;
}
}
void CloudIcon::hide() {
_state = kHidden;
_type = kNone;
_hideTime = 0;
_currentAlpha = 0;
_alphaRising = true;
}
CloudIcon::Type CloudIcon::getShownType() const {
return _type;
}
bool CloudIcon::needsUpdate() const {
uint32 delaySinceLastUpdate = g_system->getMillis(true) - _lastUpdateTime;
return delaySinceLastUpdate >= UPDATE_DELAY_MIN_MILLIS;
}
void CloudIcon::update() {
uint32 currentTime = g_system->getMillis(true);
uint32 delaySinceLastUpdate = currentTime - _lastUpdateTime;
_lastUpdateTime = currentTime;
switch (_state) {
default:
// fallthrough intended
case kHidden:
return; // Nothing to do
case kShown:
if (_alphaRising) {
if (_currentAlpha < ALPHA_MIN)
_currentAlpha += 5 * ALPHA_SPEED * delaySinceLastUpdate;
else
_currentAlpha += ALPHA_SPEED * delaySinceLastUpdate;
if (_currentAlpha > ALPHA_MAX) {
_currentAlpha = ALPHA_MAX;
_alphaRising = false;
}
} else {
_currentAlpha -= ALPHA_SPEED * delaySinceLastUpdate;
if (_currentAlpha < ALPHA_MIN) {
_currentAlpha = ALPHA_MIN;
_alphaRising = true;
}
}
if (_hideTime != 0 && _hideTime <= currentTime) {
_hideTime = 0;
_state = kGoingToHide;
}
break;
case kGoingToHide:
_currentAlpha -= 5 * ALPHA_SPEED * delaySinceLastUpdate;
if (_currentAlpha <= 0) {
hide();
}
break;
}
if (!_icon.getPixels() || !_disabledIcon.getPixels()) {
// Loading the icons failed. Don't try to draw them.
return;
}
if (_state != kHidden) {
makeAlphaIcon((_type == kDisabled ? _disabledIcon : _icon), _currentAlpha);
g_system->displayActivityIconOnOSD(&_alphaIcon);
} else {
g_system->displayActivityIconOnOSD(nullptr);
}
}
#include "backends/cloud/cloudicon_data.h"
#include "backends/cloud/cloudicon_disabled_data.h"
void CloudIcon::initIcons() {
loadIcon(_icon, cloudicon_data, ARRAYSIZE(cloudicon_data));
loadIcon(_disabledIcon, cloudicon_disabled_data, ARRAYSIZE(cloudicon_disabled_data));
}
void CloudIcon::loadIcon(Graphics::Surface &icon, const byte *data, uint32 size) {
Image::PNGDecoder decoder;
Common::MemoryReadStream stream(data, size);
if (!decoder.loadStream(stream)) {
warning("CloudIcon::loadIcon: error decoding PNG");
return;
}
const Graphics::Surface *s = decoder.getSurface();
icon.copyFrom(*s);
}
void CloudIcon::makeAlphaIcon(const Graphics::Surface &icon, float alpha) {
_alphaIcon.copyFrom(icon);
byte *pixels = (byte *)_alphaIcon.getPixels();
for (int y = 0; y < _alphaIcon.h; y++) {
byte *row = pixels + y * _alphaIcon.pitch;
for (int x = 0; x < _alphaIcon.w; x++) {
uint32 srcColor;
if (_alphaIcon.format.bytesPerPixel == 2)
srcColor = READ_UINT16(row);
else if (_alphaIcon.format.bytesPerPixel == 3)
srcColor = READ_UINT24(row);
else
srcColor = READ_UINT32(row);
// Update color's alpha
byte r, g, b, a;
_alphaIcon.format.colorToARGB(srcColor, a, r, g, b);
a = (byte)(a * alpha);
uint32 color = _alphaIcon.format.ARGBToColor(a, r, g, b);
if (_alphaIcon.format.bytesPerPixel == 2)
*((uint16 *)row) = color;
else
*((uint32 *)row) = color;
row += _alphaIcon.format.bytesPerPixel;
}
}
}
} // End of namespace Cloud

View File

@@ -0,0 +1,89 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_CLOUDICON_H
#define BACKENDS_CLOUD_CLOUDICON_H
#include "graphics/surface.h"
namespace Cloud {
class CloudIcon {
public:
CloudIcon();
~CloudIcon();
/**
* The type of cloud icon to show
*/
enum Type {
kNone, /** Hide the currently shown icon if any */
kSyncing, /** Cloud syncing icon */
kDisabled /** Cloud syncing not available icon */
};
/**
* Select the icon to show on the OSD
*
* @param icon Icon type to show. Use kNone to hide the current icon if any.
* @param duration Duration in milliseconds the icon stays visible on screen. 0 means the icon stays indefinitely.
*/
void show(Type icon, int duration = 0);
/** The currently visible icon. kNone means no icon is shown. */
Type getShownType() const;
/** Returns true if the icon state needs to be checked for changes */
bool needsUpdate() const;
/** Update the icon visible on the OSD */
void update();
private:
static const float ALPHA_SPEED, ALPHA_MAX, ALPHA_MIN;
static const int UPDATE_DELAY_MIN_MILLIS = 10;
enum State {
kHidden,
kShown,
kGoingToHide
};
State _state;
Type _type;
Graphics::Surface _icon, _disabledIcon, _alphaIcon;
float _currentAlpha;
bool _alphaRising;
uint32 _hideTime;
uint32 _lastUpdateTime;
void initIcons();
void loadIcon(Graphics::Surface &icon, const byte *data, uint32 size);
void makeAlphaIcon(const Graphics::Surface &icon, float alpha);
void hide();
};
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,110 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// This is a PNG file dumped into array.
// $ recode data..d1 <dists/cloudicon.png >cloudicon_data.h
// The tool is from https://github.com/pinard/Recode
static const byte cloudicon_data[] = {
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68,
82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 6, 0, 0, 0, 115,
122, 122, 244, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8, 8, 8,
124, 8, 100, 136, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11,
18, 0, 0, 11, 18, 1, 210, 221, 126, 252, 0, 0, 0, 22, 116,
69, 88, 116, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109,
101, 0, 48, 54, 47, 48, 51, 47, 49, 54, 159, 192, 233, 192, 0,
0, 0, 28, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101,
0, 65, 100, 111, 98, 101, 32, 70, 105, 114, 101, 119, 111, 114, 107,
115, 32, 67, 83, 54, 232, 188, 178, 140, 0, 0, 4, 50, 73, 68,
65, 84, 88, 133, 197, 151, 109, 104, 150, 101, 20, 199, 127, 247, 227,
179, 105, 51, 23, 65, 181, 150, 224, 154, 214, 132, 194, 249, 33, 165,
22, 189, 231, 194, 210, 250, 16, 171, 180, 55, 42, 152, 68, 65, 100,
52, 233, 5, 146, 144, 144, 26, 249, 169, 62, 164, 80, 89, 152, 25,
18, 226, 42, 49, 87, 88, 180, 94, 96, 96, 246, 234, 180, 70, 50,
66, 214, 55, 247, 22, 133, 247, 255, 244, 225, 58, 247, 158, 107, 143,
219, 243, 60, 186, 192, 3, 135, 251, 220, 215, 117, 223, 231, 127, 238,
235, 252, 239, 235, 58, 39, 105, 250, 206, 56, 147, 146, 55, 85, 252,
108, 13, 112, 59, 176, 12, 88, 2, 204, 7, 230, 248, 220, 48, 208,
15, 244, 2, 221, 64, 23, 48, 86, 137, 211, 228, 146, 158, 178, 43,
80, 7, 172, 3, 218, 129, 179, 43, 241, 9, 140, 0, 155, 129, 87,
128, 193, 146, 15, 47, 248, 178, 100, 0, 237, 64, 39, 112, 14, 96,
174, 20, 217, 153, 228, 162, 0, 50, 25, 2, 58, 128, 45, 83, 1,
228, 53, 121, 10, 170, 129, 183, 128, 213, 126, 47, 215, 49, 224, 39,
224, 19, 160, 7, 56, 2, 204, 0, 154, 128, 27, 128, 229, 110, 215,
120, 64, 181, 132, 149, 184, 30, 120, 4, 248, 183, 24, 40, 185, 248,
243, 147, 86, 160, 154, 144, 195, 91, 252, 43, 5, 140, 2, 31, 2,
27, 129, 195, 83, 125, 141, 75, 19, 240, 2, 129, 47, 179, 61, 144,
4, 216, 7, 172, 44, 14, 34, 105, 232, 62, 41, 128, 109, 192, 189,
14, 126, 2, 24, 0, 30, 3, 246, 150, 1, 46, 150, 54, 15, 184,
1, 200, 251, 216, 123, 192, 253, 19, 2, 152, 183, 119, 66, 0, 237,
192, 27, 110, 159, 0, 250, 128, 187, 128, 67, 167, 8, 158, 201, 98,
224, 3, 160, 209, 131, 72, 128, 53, 68, 156, 200, 153, 192, 181, 206,
68, 167, 219, 50, 49, 96, 226, 78, 19, 135, 162, 103, 138, 117, 169,
137, 46, 19, 163, 38, 82, 19, 63, 152, 120, 220, 196, 12, 159, 63,
104, 98, 149, 137, 99, 238, 211, 28, 163, 46, 243, 145, 147, 192, 117,
157, 68, 173, 219, 195, 18, 143, 74, 28, 137, 230, 139, 181, 77, 162,
71, 98, 165, 68, 141, 68, 78, 98, 145, 196, 107, 18, 59, 252, 30,
137, 3, 18, 207, 72, 140, 249, 125, 173, 99, 33, 65, 114, 209, 110,
131, 192, 218, 65, 2, 105, 4, 108, 245, 165, 74, 125, 165, 206, 5,
214, 2, 151, 3, 7, 128, 119, 128, 95, 253, 189, 169, 228, 105, 224,
85, 183, 171, 128, 29, 192, 29, 4, 82, 142, 18, 246, 151, 177, 164,
126, 151, 1, 220, 3, 188, 79, 32, 222, 40, 97, 167, 235, 243, 151,
207, 2, 190, 5, 154, 35, 231, 223, 0, 45, 101, 242, 127, 12, 152,
75, 97, 191, 88, 12, 124, 237, 254, 18, 96, 21, 176, 35, 227, 192,
50, 207, 15, 38, 126, 49, 113, 56, 202, 243, 221, 38, 154, 139, 114,
223, 82, 130, 23, 153, 214, 155, 88, 20, 221, 255, 104, 226, 104, 116,
223, 106, 42, 144, 112, 169, 137, 196, 131, 248, 56, 10, 166, 222, 196,
141, 21, 128, 77, 165, 223, 155, 120, 42, 34, 246, 158, 200, 247, 18,
19, 228, 21, 178, 60, 223, 151, 41, 1, 190, 112, 251, 60, 224, 171,
104, 238, 116, 36, 33, 240, 224, 32, 240, 25, 176, 31, 120, 194, 199,
27, 161, 112, 26, 102, 167, 154, 1, 127, 186, 253, 98, 9, 240, 81,
2, 97, 43, 149, 231, 61, 128, 129, 104, 108, 78, 28, 64, 44, 25,
105, 218, 74, 56, 156, 13, 252, 76, 248, 43, 42, 145, 140, 176, 249,
226, 137, 188, 133, 20, 12, 123, 68, 9, 225, 171, 127, 7, 46, 40,
227, 180, 82, 112, 128, 153, 126, 189, 148, 194, 105, 57, 12, 5, 18,
246, 71, 68, 185, 214, 237, 191, 166, 65, 190, 98, 237, 243, 235, 205,
209, 88, 127, 188, 19, 246, 250, 53, 39, 177, 194, 237, 157, 37, 118,
193, 83, 213, 183, 37, 102, 73, 92, 39, 145, 196, 152, 57, 75, 193,
82, 246, 249, 21, 75, 89, 104, 41, 205, 150, 178, 222, 82, 250, 163,
241, 211, 213, 253, 150, 178, 201, 82, 174, 180, 148, 185, 150, 146, 248,
120, 183, 165, 133, 20, 116, 153, 24, 113, 123, 150, 137, 103, 77, 28,
55, 113, 141, 137, 173, 38, 134, 60, 61, 167, 178, 236, 3, 38, 54,
152, 184, 213, 255, 253, 39, 221, 55, 142, 213, 101, 42, 252, 5, 99,
132, 202, 101, 45, 97, 175, 94, 14, 172, 0, 118, 1, 15, 185, 78,
71, 86, 19, 138, 217, 196, 117, 179, 99, 146, 204, 126, 125, 188, 30,
168, 35, 236, 255, 181, 132, 3, 233, 15, 224, 54, 202, 87, 64, 229,
164, 5, 216, 9, 92, 232, 224, 67, 192, 66, 188, 88, 205, 69, 185,
26, 180, 148, 14, 207, 81, 206, 82, 230, 89, 202, 110, 75, 185, 108,
26, 249, 191, 202, 82, 222, 181, 148, 243, 163, 220, 119, 56, 22, 150,
78, 172, 7, 144, 216, 34, 177, 205, 237, 188, 196, 2, 137, 61, 18,
15, 158, 6, 243, 31, 144, 216, 37, 209, 224, 190, 18, 137, 237, 142,
49, 254, 92, 50, 115, 211, 164, 69, 233, 71, 64, 43, 140, 23, 165,
127, 19, 26, 142, 231, 8, 117, 192, 84, 82, 69, 56, 118, 215, 3,
55, 17, 54, 160, 172, 40, 253, 148, 80, 168, 78, 44, 74, 171, 59,
39, 237, 11, 170, 129, 55, 129, 251, 40, 108, 205, 2, 254, 1, 126,
243, 96, 122, 129, 163, 62, 223, 0, 92, 237, 160, 141, 17, 112, 38,
219, 129, 135, 139, 193, 1, 146, 170, 151, 43, 106, 76, 106, 139, 198,
229, 192, 73, 20, 96, 12, 152, 177, 253, 56, 101, 26, 147, 36, 191,
177, 226, 214, 108, 13, 133, 214, 172, 220, 75, 35, 14, 90, 190, 53,
203, 189, 84, 113, 119, 156, 53, 167, 173, 192, 21, 252, 95, 205, 105,
178, 225, 204, 182, 231, 255, 1, 200, 91, 112, 221, 160, 249, 68, 42,
0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
};

View File

@@ -0,0 +1,116 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// This is a PNG file dumped into array.
// $ recode data..d1 <dists/cloudicon_disabled.png >cloudicon_disabled_data.h
// The tool is from https://github.com/pinard/Recode
static const byte cloudicon_disabled_data[] = {
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68,
82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 6, 0, 0, 0, 115,
122, 122, 244, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8, 8, 8,
124, 8, 100, 136, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11,
18, 0, 0, 11, 18, 1, 210, 221, 126, 252, 0, 0, 0, 22, 116,
69, 88, 116, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109,
101, 0, 48, 54, 47, 48, 51, 47, 49, 54, 159, 192, 233, 192, 0,
0, 0, 28, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101,
0, 65, 100, 111, 98, 101, 32, 70, 105, 114, 101, 119, 111, 114, 107,
115, 32, 67, 83, 54, 232, 188, 178, 140, 0, 0, 4, 139, 73, 68,
65, 84, 88, 133, 197, 215, 91, 168, 86, 69, 20, 7, 240, 223, 254,
244, 120, 236, 120, 57, 26, 94, 10, 31, 34, 35, 11, 36, 203, 74,
212, 160, 204, 212, 110, 166, 248, 208, 205, 172, 151, 144, 136, 158, 82,
80, 168, 32, 233, 165, 160, 32, 130, 236, 165, 34, 204, 74, 18, 52,
36, 169, 232, 34, 24, 221, 243, 218, 133, 74, 77, 195, 44, 200, 91,
122, 188, 156, 172, 102, 159, 221, 195, 204, 246, 219, 126, 231, 226, 17,
138, 22, 12, 123, 246, 236, 153, 245, 95, 107, 205, 127, 205, 172, 157,
21, 254, 95, 233, 27, 122, 63, 183, 5, 179, 48, 13, 87, 98, 52,
6, 167, 111, 71, 176, 11, 27, 177, 14, 107, 209, 222, 27, 165, 217,
137, 211, 207, 25, 137, 197, 152, 143, 65, 189, 209, 137, 163, 120, 1,
79, 98, 111, 143, 147, 143, 247, 172, 108, 62, 158, 194, 16, 20, 169,
105, 232, 151, 82, 171, 24, 80, 74, 27, 22, 225, 197, 110, 13, 104,
235, 122, 188, 31, 94, 194, 93, 21, 192, 14, 49, 172, 223, 226, 109,
124, 130, 29, 232, 131, 49, 184, 22, 55, 166, 126, 75, 131, 65, 175,
225, 94, 252, 213, 201, 128, 131, 93, 131, 191, 137, 27, 42, 192, 199,
241, 6, 158, 192, 246, 238, 188, 73, 50, 6, 143, 98, 78, 50, 164,
140, 200, 123, 34, 135, 78, 49, 34, 219, 215, 89, 193, 171, 152, 151,
192, 3, 246, 224, 1, 188, 123, 26, 224, 170, 92, 134, 143, 49, 160,
50, 86, 96, 5, 238, 174, 78, 108, 204, 130, 249, 98, 216, 75, 240,
109, 184, 13, 63, 156, 1, 248, 68, 44, 21, 189, 47, 165, 67, 140,
196, 60, 172, 87, 225, 68, 182, 167, 62, 105, 100, 2, 106, 77, 11,
118, 139, 123, 186, 163, 59, 164, 53, 76, 16, 195, 125, 29, 250, 15,
227, 167, 73, 12, 237, 27, 73, 91, 75, 142, 28, 21, 185, 51, 60,
141, 181, 225, 98, 41, 59, 106, 33, 185, 26, 88, 28, 104, 77, 253,
163, 129, 251, 3, 59, 42, 223, 79, 105, 171, 184, 53, 240, 73, 224,
150, 64, 203, 32, 106, 99, 185, 160, 224, 236, 16, 245, 118, 4, 54,
5, 166, 4, 22, 6, 218, 211, 218, 214, 132, 37, 32, 75, 238, 181,
224, 55, 12, 76, 222, 191, 140, 251, 144, 39, 79, 135, 98, 1, 198,
98, 11, 150, 227, 251, 50, 204, 67, 113, 121, 90, 156, 57, 185, 127,
135, 154, 184, 9, 95, 160, 9, 43, 49, 59, 69, 225, 24, 206, 65,
123, 150, 54, 247, 14, 188, 158, 214, 30, 23, 79, 186, 109, 9, 252,
44, 124, 142, 113, 149, 232, 127, 134, 201, 196, 88, 79, 66, 115, 5,
252, 24, 182, 114, 224, 32, 35, 230, 212, 207, 139, 75, 241, 169, 168,
47, 195, 157, 88, 89, 110, 193, 180, 64, 145, 250, 223, 5, 182, 87,
194, 125, 123, 96, 92, 195, 22, 76, 14, 201, 227, 177, 226, 65, 144,
227, 111, 28, 198, 151, 113, 131, 135, 5, 46, 169, 172, 249, 38, 176,
187, 242, 62, 35, 160, 150, 199, 197, 19, 114, 178, 156, 34, 231, 173,
244, 180, 154, 115, 115, 166, 166, 57, 167, 180, 193, 34, 3, 7, 165,
61, 11, 137, 105, 27, 112, 160, 62, 111, 235, 106, 22, 166, 126, 71,
206, 59, 165, 238, 156, 43, 114, 100, 155, 98, 120, 218, 146, 206, 2,
83, 241, 225, 26, 134, 165, 253, 27, 173, 65, 134, 138, 137, 222, 154,
222, 139, 228, 249, 87, 233, 217, 133, 76, 159, 19, 47, 169, 89, 226,
129, 214, 71, 188, 192, 134, 148, 231, 64, 121, 171, 21, 248, 85, 244,
232, 177, 30, 192, 59, 90, 82, 6, 193, 9, 108, 234, 30, 28, 30,
9, 209, 128, 74, 214, 71, 204, 190, 121, 231, 201, 5, 228, 220, 218,
248, 97, 136, 200, 246, 102, 106, 29, 234, 73, 190, 5, 135, 186, 7,
135, 201, 9, 167, 111, 227, 135, 50, 2, 71, 146, 69, 153, 232, 245,
206, 192, 136, 234, 196, 86, 49, 13, 154, 162, 113, 39, 211, 101, 51,
126, 239, 25, 28, 154, 19, 206, 133, 234, 119, 195, 17, 234, 36, 220,
85, 33, 202, 213, 169, 191, 175, 36, 92, 139, 152, 106, 3, 212, 9,
119, 76, 100, 251, 126, 157, 9, 218, 69, 219, 150, 158, 211, 42, 99,
187, 114, 245, 147, 112, 83, 122, 214, 2, 51, 83, 127, 85, 16, 89,
62, 94, 60, 61, 202, 20, 58, 36, 38, 244, 126, 93, 159, 146, 93,
180, 101, 129, 254, 129, 107, 2, 89, 26, 219, 24, 42, 6, 188, 95,
153, 124, 81, 202, 251, 37, 3, 249, 101, 188, 120, 114, 148, 223, 219,
197, 212, 56, 208, 123, 240, 245, 129, 167, 3, 19, 3, 163, 42, 6,
172, 11, 234, 36, 92, 43, 242, 105, 32, 250, 227, 161, 89, 44, 45,
98, 196, 139, 14, 178, 146, 112, 27, 210, 179, 23, 178, 7, 203, 240,
248, 156, 152, 251, 15, 38, 221, 146, 138, 181, 212, 73, 216, 46, 214,
112, 11, 82, 180, 103, 138, 71, 237, 40, 145, 52, 29, 216, 221, 194,
220, 41, 49, 0, 103, 36, 129, 185, 152, 158, 116, 101, 9, 171, 29,
178, 85, 245, 121, 213, 235, 184, 90, 215, 229, 248, 89, 172, 112, 190,
62, 83, 112, 209, 145, 85, 226, 229, 147, 105, 188, 142, 43, 172, 220,
155, 179, 40, 29, 201, 85, 6, 239, 204, 153, 155, 243, 117, 47, 216,
222, 216, 38, 229, 188, 146, 51, 188, 162, 119, 81, 194, 82, 205, 130,
178, 189, 24, 120, 173, 97, 108, 80, 34, 102, 111, 73, 87, 182, 123,
2, 107, 2, 231, 133, 184, 213, 89, 96, 69, 194, 56, 57, 47, 91,
222, 57, 100, 253, 68, 130, 92, 175, 94, 148, 254, 129, 15, 240, 176,
88, 7, 116, 39, 77, 226, 181, 187, 68, 172, 146, 154, 69, 78, 101,
98, 77, 57, 91, 99, 81, 250, 82, 215, 138, 202, 178, 188, 44, 78,
37, 67, 254, 196, 143, 201, 152, 141, 98, 217, 86, 224, 60, 92, 149,
64, 207, 175, 0, 151, 178, 66, 119, 101, 249, 243, 61, 184, 163, 254,
99, 210, 218, 48, 94, 94, 5, 101, 13, 162, 1, 176, 100, 251, 97,
167, 249, 49, 233, 115, 179, 250, 111, 78, 23, 109, 115, 193, 178, 130,
62, 5, 151, 20, 52, 23, 241, 76, 200, 10, 106, 149, 103, 173, 50,
158, 21, 28, 45, 120, 174, 96, 110, 193, 71, 61, 232, 151, 61, 219,
115, 4, 170, 82, 254, 156, 206, 16, 47, 197, 127, 231, 231, 244, 153,
222, 27, 240, 159, 200, 63, 153, 185, 24, 191, 162, 246, 71, 153, 0,
0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
};

View File

@@ -0,0 +1,601 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/cloudmanager.h"
#include "backends/cloud/box/boxstorage.h"
#include "backends/cloud/dropbox/dropboxstorage.h"
#include "backends/cloud/onedrive/onedrivestorage.h"
#include "backends/cloud/googledrive/googledrivestorage.h"
#include "common/formats/json.h"
#include "common/translation.h"
#include "common/config-manager.h"
#include "common/str.h"
#ifdef USE_SDL_NET
#include "backends/networking/sdl_net/localwebserver.h"
#endif
namespace Common {
DECLARE_SINGLETON(Cloud::CloudManager);
}
namespace Cloud {
const char *const CloudManager::kStoragePrefix = "storage_";
CloudManager::CloudManager() : _currentStorageIndex(0), _activeStorage(nullptr) {}
CloudManager::~CloudManager() {
g_system->getEventManager()->getEventDispatcher()->unregisterSource(this);
delete _activeStorage;
freeStorages();
}
Common::String CloudManager::getStorageConfigName(uint32 index) const {
switch (index) {
case kStorageNoneId: return "<none>";
case kStorageDropboxId: return "Dropbox";
case kStorageOneDriveId: return "OneDrive";
case kStorageGoogleDriveId: return "GoogleDrive";
case kStorageBoxId: return "Box";
default:
break;
}
assert(false); // Unhandled StorageID value
return "";
}
void CloudManager::loadStorage() {
switch (_currentStorageIndex) {
case kStorageDropboxId:
_activeStorage = Dropbox::DropboxStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
break;
case kStorageOneDriveId:
_activeStorage = OneDrive::OneDriveStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
break;
case kStorageGoogleDriveId:
_activeStorage = GoogleDrive::GoogleDriveStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
break;
case kStorageBoxId:
_activeStorage = Box::BoxStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
break;
default:
_activeStorage = nullptr;
break;
}
if (!_activeStorage) {
_currentStorageIndex = kStorageNoneId;
}
}
void CloudManager::init() {
//init configs structs
for (uint32 i = 0; i < kStorageTotal; ++i) {
Common::String name = getStorageConfigName(i);
StorageConfig config;
config.name = name;
config.username = "";
config.lastSyncDate = "";
config.usedBytes = 0;
if (ConfMan.hasKey(kStoragePrefix + name + "_username", ConfMan.kCloudDomain))
config.username = ConfMan.get(kStoragePrefix + name + "_username", ConfMan.kCloudDomain);
if (ConfMan.hasKey(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain))
config.lastSyncDate = ConfMan.get(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain);
if (ConfMan.hasKey(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain))
config.usedBytes = ConfMan.get(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain).asUint64();
_storages.push_back(config);
}
//load an active storage if there is any
_currentStorageIndex = kStorageNoneId;
if (ConfMan.hasKey("current_storage", ConfMan.kCloudDomain))
_currentStorageIndex = ConfMan.getInt("current_storage", ConfMan.kCloudDomain);
loadStorage();
g_system->getEventManager()->getEventDispatcher()->registerSource(this, false);
}
void CloudManager::save() {
for (uint32 i = 0; i < _storages.size(); ++i) {
if (i == kStorageNoneId)
continue;
Common::String name = getStorageConfigName(i);
ConfMan.set(kStoragePrefix + name + "_username", _storages[i].username, ConfMan.kCloudDomain);
ConfMan.set(kStoragePrefix + name + "_lastSync", _storages[i].lastSyncDate, ConfMan.kCloudDomain);
ConfMan.set(kStoragePrefix + name + "_usedBytes", Common::String::format("%llu", (unsigned long long)_storages[i].usedBytes), ConfMan.kCloudDomain);
}
ConfMan.set("current_storage", Common::String::format("%u", _currentStorageIndex), ConfMan.kCloudDomain);
if (_activeStorage)
_activeStorage->saveConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
ConfMan.flushToDisk();
}
void CloudManager::replaceStorage(Storage *storage, uint32 index) {
freeStorages();
if (!storage)
error("CloudManager::replaceStorage: NULL storage passed");
if (index >= kStorageTotal)
error("CloudManager::replaceStorage: invalid index passed");
if (_activeStorage != nullptr && _activeStorage->isWorking()) {
warning("CloudManager::replaceStorage: replacing Storage while the other is working");
if (_activeStorage->isDownloading())
_activeStorage->cancelDownload();
if (_activeStorage->isSyncing())
_activeStorage->cancelSync();
removeStorage(_activeStorage);
} else {
delete _activeStorage;
}
_activeStorage = storage;
_currentStorageIndex = index;
if (_storages[index].username == "") {
// options' Cloud tab believes Storage is connected once it has non-empty username
_storages[index].username = Common::convertFromU32String(_("<syncing...>"));
_storages[index].lastSyncDate = Common::convertFromU32String(_("<right now>"));
_storages[index].usedBytes = 0;
}
save();
//do what should be done on first Storage connect
if (_activeStorage) {
_activeStorage->info(nullptr, nullptr); //automatically calls setStorageUsername()
}
}
void CloudManager::removeStorage(Storage *storage) {
// can't just delete it as it's mostly likely the one who calls the method
// it would be freed on freeStorages() call (on next Storage connect or replace)
_storagesToRemove.push_back(storage);
}
void CloudManager::freeStorages() {
for (uint32 i = 0; i < _storagesToRemove.size(); ++i)
delete _storagesToRemove[i];
_storagesToRemove.clear();
}
void CloudManager::passNoStorageConnected(Networking::ErrorCallback errorCallback) const {
if (errorCallback == nullptr)
return;
(*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "No Storage connected!", -1));
}
Storage *CloudManager::getCurrentStorage() const {
return _activeStorage;
}
uint32 CloudManager::getStorageIndex() const {
return _currentStorageIndex;
}
Common::StringArray CloudManager::listStorages() const {
Common::StringArray result;
for (uint32 i = 0; i < _storages.size(); ++i) {
result.push_back(_storages[i].name);
}
return result;
}
bool CloudManager::switchStorage(uint32 index) {
if (index >= _storages.size()) {
warning("CloudManager::switchStorage: invalid index passed");
return false;
}
Storage *storage = getCurrentStorage();
if (storage && storage->isWorking()) {
warning("CloudManager::switchStorage: another storage is working now");
return false;
}
_currentStorageIndex = index;
loadStorage();
save();
return true;
}
Common::String CloudManager::getStorageUsername(uint32 index) {
if (index >= _storages.size())
return "";
return _storages[index].username;
}
uint64 CloudManager::getStorageUsedSpace(uint32 index) {
if (index >= _storages.size())
return 0;
return _storages[index].usedBytes;
}
Common::String CloudManager::getStorageLastSync(uint32 index) {
if (index >= _storages.size())
return "";
if (index == _currentStorageIndex && isSyncing())
return "";
return _storages[index].lastSyncDate;
}
void CloudManager::setStorageUsername(uint32 index, const Common::String &name) {
if (index >= _storages.size())
return;
_storages[index].username = name;
save();
}
void CloudManager::setStorageUsedSpace(uint32 index, uint64 used) {
if (index >= _storages.size())
return;
_storages[index].usedBytes = used;
save();
}
void CloudManager::setStorageLastSync(uint32 index, const Common::String &date) {
if (index >= _storages.size())
return;
_storages[index].lastSyncDate = date;
save();
}
void CloudManager::connectStorage(uint32 index, const Common::String &code, Networking::ErrorCallback cb) {
freeStorages();
switch (index) {
case kStorageDropboxId:
new Dropbox::DropboxStorage(code, cb);
break;
case kStorageOneDriveId:
new OneDrive::OneDriveStorage(code, cb);
break;
case kStorageGoogleDriveId:
new GoogleDrive::GoogleDriveStorage(code, cb);
break;
case kStorageBoxId:
new Box::BoxStorage(code, cb);
break;
default:
break;
}
// in these constructors Storages request token using the passed code
// when the token is received, they call replaceStorage()
// or removeStorage(), if some error occurred
// thus, no memory leak happens
}
void CloudManager::connectStorage(uint32 index, Networking::JsonResponse codeFlowJson, Networking::ErrorCallback cb) {
freeStorages();
switch (index) {
case kStorageDropboxId:
new Dropbox::DropboxStorage(codeFlowJson, cb);
break;
case kStorageOneDriveId:
new OneDrive::OneDriveStorage(codeFlowJson, cb);
break;
case kStorageGoogleDriveId:
new GoogleDrive::GoogleDriveStorage(codeFlowJson, cb);
break;
case kStorageBoxId:
new Box::BoxStorage(codeFlowJson, cb);
break;
default:
break;
}
// in these constructors Storages extract tokens from the passed JSON
// they call replaceStorage(), if the tokens are found,
// or removeStorage(), if some error occurred
// thus, no memory leak happens
}
bool CloudManager::connectStorage(Networking::JsonResponse codeFlowJson, Networking::ErrorCallback cb) {
const Common::JSONValue *json = codeFlowJson.value;
if (json == nullptr || !json->isObject()) {
return false;
}
Common::JSONObject result = json->asObject();
if (!result.contains("storage")) {
return false;
}
Common::JSONValue *storageValue = result.getVal("storage");
if (!storageValue->isString()) {
return false;
}
uint32 storageId = kStorageNoneId;
Common::String storage = storageValue->asString();
if (storage == "dropbox")
storageId = kStorageDropboxId;
else if (storage == "onedrive")
storageId = kStorageOneDriveId;
else if (storage == "gdrive")
storageId = kStorageGoogleDriveId;
else if (storage == "box")
storageId = kStorageBoxId;
if (storageId == kStorageNoneId) {
return false;
}
connectStorage(storageId, codeFlowJson, cb);
return true;
}
void CloudManager::disconnectStorage(uint32 index) {
if (index >= kStorageTotal)
error("CloudManager::disconnectStorage: invalid index passed");
Common::String name = getStorageConfigName(index);
switch (index) {
case kStorageDropboxId:
Dropbox::DropboxStorage::removeFromConfig(kStoragePrefix + name + "_");
break;
case kStorageOneDriveId:
OneDrive::OneDriveStorage::removeFromConfig(kStoragePrefix + name + "_");
break;
case kStorageGoogleDriveId:
GoogleDrive::GoogleDriveStorage::removeFromConfig(kStoragePrefix + name + "_");
break;
case kStorageBoxId:
Box::BoxStorage::removeFromConfig(kStoragePrefix + name + "_");
break;
default:
break;
}
switchStorage(kStorageNoneId);
ConfMan.removeKey(kStoragePrefix + name + "_username", ConfMan.kCloudDomain);
ConfMan.removeKey(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain);
ConfMan.removeKey(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain);
StorageConfig config;
config.name = name;
config.username = "";
config.lastSyncDate = "";
config.usedBytes = 0;
_storages[index] = config;
}
Networking::Request *CloudManager::listDirectory(const Common::String &path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
Storage *storage = getCurrentStorage();
if (storage) {
return storage->listDirectory(path, callback, errorCallback, recursive);
} else {
passNoStorageConnected(errorCallback);
delete callback;
delete errorCallback;
}
return nullptr;
}
Networking::Request *CloudManager::downloadFolder(const Common::String &remotePath, const Common::Path &localPath, Storage::FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
Storage *storage = getCurrentStorage();
if (storage) {
return storage->downloadFolder(remotePath, localPath, callback, errorCallback, recursive);
} else {
passNoStorageConnected(errorCallback);
delete callback;
delete errorCallback;
}
return nullptr;
}
Networking::Request *CloudManager::info(Storage::StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
Storage *storage = getCurrentStorage();
if (storage) {
return storage->info(callback, errorCallback);
} else {
passNoStorageConnected(errorCallback);
delete callback;
delete errorCallback;
}
return nullptr;
}
Common::String CloudManager::savesDirectoryPath() {
Storage *storage = getCurrentStorage();
if (storage)
return storage->savesDirectoryPath();
return "";
}
bool CloudManager::canSyncFilename(const Common::String &filename) const {
if (filename == "" || filename[0] == '.')
return false;
return true;
}
bool CloudManager::isStorageEnabled() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->isEnabled();
return false;
}
void CloudManager::enableStorage() {
Storage *storage = getCurrentStorage();
if (storage) {
storage->enable();
save();
}
}
SavesSyncRequest *CloudManager::syncSaves(Storage::BoolCallback callback, Networking::ErrorCallback errorCallback) {
Storage *storage = getCurrentStorage();
if (storage) {
setStorageLastSync(_currentStorageIndex, "???"); //TODO get the date
return storage->syncSaves(callback, errorCallback);
} else {
passNoStorageConnected(errorCallback);
delete callback;
delete errorCallback;
}
return nullptr;
}
bool CloudManager::isWorking() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->isWorking();
return false;
}
///// SavesSyncRequest-related /////
bool CloudManager::isSyncing() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->isSyncing();
return false;
}
double CloudManager::getSyncDownloadingProgress() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->getSyncDownloadingProgress();
return 1;
}
void CloudManager::getSyncDownloadingInfo(Storage::SyncDownloadingInfo &info) const {
Storage *storage = getCurrentStorage();
if (storage)
storage->getSyncDownloadingInfo(info);
}
double CloudManager::getSyncProgress() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->getSyncProgress();
return 1;
}
Common::Array<Common::String> CloudManager::getSyncingFiles() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->getSyncingFiles();
return Common::Array<Common::String>();
}
void CloudManager::cancelSync() const {
Storage *storage = getCurrentStorage();
if (storage)
storage->cancelSync();
}
void CloudManager::showCloudDisabledIcon() {
_icon.show(CloudIcon::kDisabled, 3000);
}
///// DownloadFolderRequest-related /////
bool CloudManager::startDownload(const Common::String &remotePath, const Common::Path &localPath) const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->startDownload(remotePath, localPath);
return false;
}
void CloudManager::cancelDownload() const {
Storage *storage = getCurrentStorage();
if (storage)
storage->cancelDownload();
}
void CloudManager::setDownloadTarget(GUI::CommandReceiver *target) const {
Storage *storage = getCurrentStorage();
if (storage)
storage->setDownloadTarget(target);
}
bool CloudManager::isDownloading() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->isDownloading();
return false;
}
double CloudManager::getDownloadingProgress() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->getDownloadingProgress();
return 1;
}
uint64 CloudManager::getDownloadBytesNumber() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->getDownloadBytesNumber();
return 0;
}
uint64 CloudManager::getDownloadTotalBytesNumber() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->getDownloadTotalBytesNumber();
return 0;
}
uint64 CloudManager::getDownloadSpeed() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->getDownloadSpeed();
return 0;
}
Common::String CloudManager::getDownloadRemoteDirectory() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->getDownloadRemoteDirectory();
return "";
}
Common::Path CloudManager::getDownloadLocalDirectory() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->getDownloadLocalDirectory();
return Common::Path();
}
bool CloudManager::pollEvent(Common::Event &event) {
if (_icon.needsUpdate()) {
if (_icon.getShownType() != CloudIcon::kDisabled) {
if (isWorking()) {
_icon.show(CloudIcon::kSyncing);
} else {
_icon.show(CloudIcon::kNone);
}
}
_icon.update();
}
return false;
}
} // End of namespace Cloud

View File

@@ -0,0 +1,329 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CLOUD_CLOUDMANAGER_H
#define CLOUD_CLOUDMANAGER_H
#include "backends/cloud/storage.h"
#include "backends/cloud/cloudicon.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "common/array.h"
#include "common/singleton.h"
#include "common/str-array.h"
#include "common/events.h"
namespace GUI {
class CommandReceiver;
}
namespace Cloud {
// The actual indexes in CloudManager's array
enum StorageID {
kStorageNoneId = 0,
kStorageDropboxId = 1,
kStorageOneDriveId = 2,
kStorageGoogleDriveId = 3,
kStorageBoxId = 4,
kStorageTotal
};
class CloudManager : public Common::Singleton<CloudManager>, public Common::EventSource {
static const char *const kStoragePrefix;
struct StorageConfig {
Common::String name, username;
uint64 usedBytes;
Common::String lastSyncDate;
};
Common::Array<StorageConfig> _storages;
uint _currentStorageIndex;
Storage *_activeStorage;
Common::Array<Storage *> _storagesToRemove;
CloudIcon _icon;
void loadStorage();
Common::String getStorageConfigName(uint32 index) const;
/** Frees memory used by storages which failed to connect. */
void freeStorages();
/** Calls the error callback with a special "no storage connected" message. */
void passNoStorageConnected(Networking::ErrorCallback errorCallback) const;
/**
* Common::EventSource interface
*
* The cloud manager registers itself as an event source even if does not
* actually produce events as a mean to be polled periodically by the GUI
* or engine code.
*
* The periodical polling is used to update the OSD icon indicating
* background sync activity.
*/
bool pollEvent(Common::Event &event) override;
public:
CloudManager();
~CloudManager() override;
/**
* Loads all information from configs and creates current Storage instance.
*
* @note It's called once on startup in scummvm_main().
*/
void init();
/**
* Saves all information into configuration file.
*/
void save();
/**
* Replace active Storage.
* @note this method automatically saves the changes with ConfMan.
*
* @param storage Cloud::Storage to replace active storage with.
* @param index one of Cloud::StorageID enum values to indicate what storage type is replaced.
*/
void replaceStorage(Storage *storage, uint32 index);
/** Adds storage in the list of storages to remove later. */
void removeStorage(Storage *storage);
/**
* Returns active Storage, which could be used to interact
* with cloud storage.
*
* @return active Cloud::Storage or null, if there is no active Storage.
*/
Cloud::Storage *getCurrentStorage() const;
/**
* Return active Storage's index.
*
* @return active Storage's index.
*/
uint32 getStorageIndex() const;
/**
* Return Storages names as list.
*
* @return a list of Storages names.
*/
Common::StringArray listStorages() const;
/**
* Changes the storage to the one with given index.
*
* @param new Storage's index.
*/
bool switchStorage(uint32 index);
/**
* Return username used by Storage.
*
* @param Storage's index.
* @returns username or "" if index is invalid (no such Storage).
*/
Common::String getStorageUsername(uint32 index);
/**
* Return space used by Storage.
*
* @param Storage's index.
* @returns used space in bytes or 0 if index is invalid (no such Storage).
*/
uint64 getStorageUsedSpace(uint32 index);
/**
* Return Storage's last sync date.
*
* @param Storage's index.
* @returns last sync date or "" if index is invalid (no such Storage).
It also returns "" if there never was any sync
or if storage is syncing right now.
*/
Common::String getStorageLastSync(uint32 index);
/**
* Set Storage's username.
* Automatically saves changes to the config.
*
* @param index Storage's index.
* @param name username to set
*/
void setStorageUsername(uint32 index, const Common::String &name);
/**
* Set Storage's used space field.
* Automatically saves changes to the config.
*
* @param index Storage's index.
* @param used value to set
*/
void setStorageUsedSpace(uint32 index, uint64 used);
/**
* Set Storage's last sync date.
* Automatically saves changes to the config.
*
* @param index Storage's index.
* @param date date to set
*/
void setStorageLastSync(uint32 index, const Common::String &date);
/**
* Replace Storage which has given index with a
* storage created with given code.
*
* @param index Storage's index
* @param code OAuth2 code received from user
* @param cb callback to notify of success or error
*/
void connectStorage(uint32 index, const Common::String &code, Networking::ErrorCallback cb = nullptr);
/**
* Replace Storage which has given index with a
* storage created with given JSON response.
*
* @param index Storage's index
* @param codeFlowJson OAuth2 code flow JSON response (acquired from cloud.scummvm.org)
* @param cb callback to notify of success or error
*/
void connectStorage(uint32 index, Networking::JsonResponse codeFlowJson, Networking::ErrorCallback cb = nullptr);
/**
* From given JSON response, extract Storage index
* and replace Storage that has this index with a
* storage created with given JSON.
*
* @param codeFlowJson OAuth2 code flow JSON response (acquired from cloud.scummvm.org)
* @param cb callback to notify of success or error
* @returns whether Storage index was found and is correct
*/
bool connectStorage(Networking::JsonResponse codeFlowJson, Networking::ErrorCallback cb = nullptr);
/**
* Remove Storage with a given index from config.
*
* @param index Storage's index
*/
void disconnectStorage(uint32 index);
/** Returns ListDirectoryResponse with list of files. */
Networking::Request *listDirectory(const Common::String &path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
/** Returns Common::Array<StorageFile> with list of files, which were not downloaded. */
Networking::Request *downloadFolder(const Common::String &remotePath, const Common::Path &localPath, Storage::FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
/** Return the StorageInfo struct. */
Networking::Request *info(Storage::StorageInfoCallback callback, Networking::ErrorCallback errorCallback);
/** Returns storage's saves directory path with the trailing slash. */
Common::String savesDirectoryPath();
/** Returns whether given filename could be uploaded to or downloaded from storage. */
bool canSyncFilename(const Common::String &filename) const;
/** Returns whether current Storage is manually enabled by user (or false, if there is no active Storage). */
bool isStorageEnabled() const;
/** Sets Storage::_isEnabled to true and updates the config. */
void enableStorage();
/**
* Starts saves syncing process in currently active storage if there is any.
*/
SavesSyncRequest *syncSaves(Cloud::Storage::BoolCallback callback = nullptr, Networking::ErrorCallback errorCallback = nullptr);
/** Returns whether there are any requests running. */
bool isWorking() const;
///// SavesSyncRequest-related /////
/** Returns whether there is a SavesSyncRequest running. */
bool isSyncing() const;
/** Returns a number in [0, 1] range which represents current sync downloading progress (1 = complete). */
double getSyncDownloadingProgress() const;
/** Fills a struct with numbers about current sync downloading progress. */
void getSyncDownloadingInfo(Storage::SyncDownloadingInfo &info) const;
/** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */
double getSyncProgress() const;
/** Returns an array of saves names which are not yet synced (thus cannot be used). */
Common::Array<Common::String> getSyncingFiles() const;
/** Cancels running sync. */
void cancelSync() const;
/** Shows a "cloud disabled" icon for three seconds. */
void showCloudDisabledIcon();
///// DownloadFolderRequest-related /////
/** Starts a folder download. */
bool startDownload(const Common::String &remotePath, const Common::Path &localPath) const;
/** Cancels running download. */
void cancelDownload() const;
/** Sets FolderDownloadRequest's target to given CommandReceiver. */
void setDownloadTarget(GUI::CommandReceiver *target) const;
/** Returns whether there is a FolderDownloadRequest running. */
bool isDownloading() const;
/** Returns a number in [0, 1] range which represents current download progress (1 = complete). */
double getDownloadingProgress() const;
/** Returns a number of bytes that is downloaded in current download progress. */
uint64 getDownloadBytesNumber() const;
/** Returns a total number of bytes to be downloaded in current download progress. */
uint64 getDownloadTotalBytesNumber() const;
/** Returns download speed of current download progress. */
uint64 getDownloadSpeed() const;
/** Returns remote directory path. */
Common::String getDownloadRemoteDirectory() const;
/** Returns local directory path. */
Common::Path getDownloadLocalDirectory() const;
};
/** Shortcut for accessing the connection manager. */
#define CloudMan Cloud::CloudManager::instance()
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,137 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/downloadrequest.h"
#include "backends/networking/http/connectionmanager.h"
#include "common/textconsole.h"
namespace Cloud {
DownloadRequest::DownloadRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb, const Common::String &remoteFileId, Common::DumpFile *dumpFile):
Request(nullptr, ecb), _boolCallback(callback), _localFile(dumpFile), _remoteFileId(remoteFileId), _storage(storage),
_remoteFileStream(nullptr), _workingRequest(nullptr), _ignoreCallback(false), _buffer(new byte[DOWNLOAD_REQUEST_BUFFER_SIZE]) {
start();
}
DownloadRequest::~DownloadRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _boolCallback;
delete _localFile;
delete[] _buffer;
}
void DownloadRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_remoteFileStream = nullptr;
//TODO: add some way to reopen DumpFile, so DownloadRequest could be restarted
_ignoreCallback = false;
_workingRequest = _storage->streamFileById(
_remoteFileId,
new Common::Callback<DownloadRequest, const Networking::NetworkReadStreamResponse &>(this, &DownloadRequest::streamCallback),
new Common::Callback<DownloadRequest, const Networking::ErrorResponse &>(this, &DownloadRequest::streamErrorCallback)
);
}
void DownloadRequest::streamCallback(const Networking::NetworkReadStreamResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
_remoteFileStream = response.value;
}
void DownloadRequest::streamErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishError(error);
}
void DownloadRequest::handle() {
if (!_localFile) {
warning("DownloadRequest: no file to write");
finishError(Networking::ErrorResponse(this, false, true, "DownloadRequest::handle: no file to write into", -1));
return;
}
if (!_localFile->isOpen()) {
warning("DownloadRequest: failed to open file to write");
finishError(Networking::ErrorResponse(this, false, true, "DownloadRequest::handle: failed to open file to write", -1));
return;
}
if (!_remoteFileStream) {
//waiting for callback
return;
}
uint32 readBytes = _remoteFileStream->read(_buffer, DOWNLOAD_REQUEST_BUFFER_SIZE);
if (readBytes != 0)
if (_localFile->write(_buffer, readBytes) != readBytes) {
warning("DownloadRequest: unable to write all received bytes into output file");
finishError(Networking::ErrorResponse(this, false, true, "DownloadRequest::handle: failed to write all bytes into a file", -1));
return;
}
if (_remoteFileStream->eos()) {
if (_remoteFileStream->httpResponseCode() != 200) {
warning("DownloadRequest: HTTP response code is not 200 OK (it's %ld)", _remoteFileStream->httpResponseCode());
//TODO: do something about it actually
// the problem is file's already downloaded, stream is over
// so we can't return error message anymore
}
finishDownload(_remoteFileStream->httpResponseCode() == 200);
_localFile->close(); //yes, I know it's closed automatically in ~DumpFile()
}
}
void DownloadRequest::restart() {
warning("DownloadRequest: can't restart as there are no means to reopen DumpFile");
finishError(Networking::ErrorResponse(this, false, true, "DownloadRequest::restart: can't restart as there are no means to reopen DumpFile", -1));
//start();
}
void DownloadRequest::finishDownload(bool success) {
Request::finishSuccess();
if (_boolCallback)
(*_boolCallback)(Storage::BoolResponse(this, success));
}
void DownloadRequest::finishError(const Networking::ErrorResponse &error, Networking::RequestState state) {
if (_localFile)
_localFile->close();
Request::finishError(error);
}
double DownloadRequest::getProgress() const {
if (_remoteFileStream)
return _remoteFileStream->getProgress();
return 0;
}
} // End of namespace Cloud

View File

@@ -0,0 +1,63 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_DOWNLOADREQUEST_H
#define BACKENDS_CLOUD_DOWNLOADREQUEST_H
#include "backends/networking/http/request.h"
#include "backends/networking/http/networkreadstream.h"
#include "backends/cloud/storage.h"
#include "common/file.h"
namespace Cloud {
#define DOWNLOAD_REQUEST_BUFFER_SIZE 1 * 1024 * 1024
class DownloadRequest: public Networking::Request {
Storage::BoolCallback _boolCallback;
Common::DumpFile *_localFile;
Common::String _remoteFileId;
Storage *_storage;
Networking::NetworkReadStream *_remoteFileStream;
Request *_workingRequest;
bool _ignoreCallback;
byte *_buffer;
void start();
void streamCallback(const Networking::NetworkReadStreamResponse &response);
void streamErrorCallback(const Networking::ErrorResponse &error);
void finishDownload(bool success);
void finishError(const Networking::ErrorResponse &error, Networking::RequestState state = Networking::FINISHED) override;
public:
DownloadRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb, const Common::String &remoteFileId, Common::DumpFile *dumpFile);
~DownloadRequest() override;
void handle() override;
void restart() override;
/** Returns a number in range [0, 1], where 1 is "complete". */
double getProgress() const;
};
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,138 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/dropbox/dropboxcreatedirectoryrequest.h"
#include "backends/cloud/dropbox/dropboxstorage.h"
#include "backends/cloud/dropbox/dropboxtokenrefresher.h"
#include "backends/cloud/storage.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
namespace Cloud {
namespace Dropbox {
#define DROPBOX_API_CREATE_FOLDER "https://api.dropboxapi.com/2/files/create_folder"
DropboxCreateDirectoryRequest::DropboxCreateDirectoryRequest(DropboxStorage *storage, const Common::String &path, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _storage(storage), _path(path), _boolCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
DropboxCreateDirectoryRequest::~DropboxCreateDirectoryRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _boolCallback;
}
void DropboxCreateDirectoryRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_ignoreCallback = false;
Networking::JsonCallback innerCallback = new Common::Callback<DropboxCreateDirectoryRequest, const Networking::JsonResponse &>(this, &DropboxCreateDirectoryRequest::responseCallback);
Networking::ErrorCallback errorResponseCallback = new Common::Callback<DropboxCreateDirectoryRequest, const Networking::ErrorResponse &>(this, &DropboxCreateDirectoryRequest::errorCallback);
Networking::HttpJsonRequest *request = new DropboxTokenRefresher(_storage, innerCallback, errorResponseCallback, DROPBOX_API_CREATE_FOLDER);
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->addHeader("Content-Type: application/json");
Common::JSONObject jsonRequestParameters;
jsonRequestParameters.setVal("path", new Common::JSONValue(_path));
Common::JSONValue value(jsonRequestParameters);
request->addPostField(Common::JSON::stringify(&value));
_workingRequest = ConnMan.addRequest(request);
}
void DropboxCreateDirectoryRequest::responseCallback(const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
_workingRequest = nullptr;
if (_ignoreCallback) {
delete json;
return;
}
if (response.request) _date = response.request->date();
Networking::ErrorResponse error(this, "DropboxCreateDirectoryRequest::responseCallback: unknown error");
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
if (!json->isObject()) {
error.response = "Passed JSON is not an object!";
finishError(error);
delete json;
return;
}
Common::JSONObject info = json->asObject();
if (info.contains("id")) {
finishCreation(true);
} else {
if (Networking::HttpJsonRequest::jsonContainsString(info, "error_summary", "DropboxCreateDirectoryRequest")) {
Common::String summary = info.getVal("error_summary")->asString();
if (summary.contains("path") && summary.contains("conflict") && summary.contains("folder")) {
// existing directory - not an error for CreateDirectoryRequest
finishCreation(false);
delete json;
return;
}
}
error.response = json->stringify(true);
finishError(error);
}
delete json;
}
void DropboxCreateDirectoryRequest::errorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void DropboxCreateDirectoryRequest::handle() {}
void DropboxCreateDirectoryRequest::restart() { start(); }
Common::String DropboxCreateDirectoryRequest::date() const { return _date; }
void DropboxCreateDirectoryRequest::finishCreation(bool success) {
Request::finishSuccess();
if (_boolCallback)
(*_boolCallback)(Storage::BoolResponse(this, success));
}
} // End of namespace Dropbox
} // End of namespace Cloud

View File

@@ -0,0 +1,58 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_DROPBOX_DROPBOXCREATEDIRECTORYREQUEST_H
#define BACKENDS_CLOUD_DROPBOX_DROPBOXCREATEDIRECTORYREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/request.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace Dropbox {
class DropboxStorage;
class DropboxCreateDirectoryRequest: public Networking::Request {
DropboxStorage *_storage;
Common::String _path;
Storage::BoolCallback _boolCallback;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _date;
void start();
void responseCallback(const Networking::JsonResponse &response);
void errorCallback(const Networking::ErrorResponse &error);
void finishCreation(bool success);
public:
DropboxCreateDirectoryRequest(DropboxStorage *storage, const Common::String &path, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
~DropboxCreateDirectoryRequest() override;
void handle() override;
void restart() override;
Common::String date() const override;
};
} // End of namespace Dropbox
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,193 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/dropbox/dropboxinforequest.h"
#include "backends/cloud/dropbox/dropboxstorage.h"
#include "backends/cloud/dropbox/dropboxtokenrefresher.h"
#include "backends/cloud/cloudmanager.h"
#include "backends/cloud/storage.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
namespace Cloud {
namespace Dropbox {
#define DROPBOX_API_GET_CURRENT_ACCOUNT "https://api.dropboxapi.com/2/users/get_current_account"
#define DROPBOX_API_GET_SPACE_USAGE "https://api.dropboxapi.com/2/users/get_space_usage"
DropboxInfoRequest::DropboxInfoRequest(DropboxStorage *storage, Storage::StorageInfoCallback cb, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _storage(storage), _infoCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
DropboxInfoRequest::~DropboxInfoRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _infoCallback;
}
void DropboxInfoRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_ignoreCallback = false;
Networking::JsonCallback innerCallback = new Common::Callback<DropboxInfoRequest, const Networking::JsonResponse &>(this, &DropboxInfoRequest::userResponseCallback);
Networking::ErrorCallback errorResponseCallback = new Common::Callback<DropboxInfoRequest, const Networking::ErrorResponse &>(this, &DropboxInfoRequest::errorCallback);
Networking::HttpJsonRequest *request = new DropboxTokenRefresher(_storage, innerCallback, errorResponseCallback, DROPBOX_API_GET_CURRENT_ACCOUNT);
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->addHeader("Content-Type: application/json");
request->addPostField("null"); //use POST
_workingRequest = ConnMan.addRequest(request);
}
void DropboxInfoRequest::userResponseCallback(const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
_workingRequest = nullptr;
if (_ignoreCallback) {
delete json;
return;
}
Networking::ErrorResponse error(this, "DropboxInfoRequest::userResponseCallback: unknown error");
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
if (!json->isObject()) {
error.response = "Passed JSON is not an object!";
finishError(error);
delete json;
return;
}
//Dropbox documentation states there are no errors for this API method
Common::JSONObject info = json->asObject();
if (Networking::HttpJsonRequest::jsonContainsAttribute(info, "name", "DropboxInfoRequest") &&
Networking::HttpJsonRequest::jsonIsObject(info.getVal("name"), "DropboxInfoRequest")) {
Common::JSONObject nameInfo = info.getVal("name")->asObject();
if (Networking::HttpJsonRequest::jsonContainsString(nameInfo, "display_name", "DropboxInfoRequest")) {
_name = nameInfo.getVal("display_name")->asString();
}
}
if (Networking::HttpJsonRequest::jsonContainsString(info, "account_id", "DropboxInfoRequest")) {
_uid = info.getVal("account_id")->asString();
}
if (Networking::HttpJsonRequest::jsonContainsString(info, "email", "DropboxInfoRequest")) {
_email = info.getVal("email")->asString();
}
CloudMan.setStorageUsername(kStorageDropboxId, _email);
delete json;
Networking::JsonCallback innerCallback = new Common::Callback<DropboxInfoRequest, const Networking::JsonResponse &>(this, &DropboxInfoRequest::quotaResponseCallback);
Networking::ErrorCallback errorResponseCallback = new Common::Callback<DropboxInfoRequest, const Networking::ErrorResponse &>(this, &DropboxInfoRequest::errorCallback);
Networking::HttpJsonRequest *request = new DropboxTokenRefresher(_storage, innerCallback, errorResponseCallback, DROPBOX_API_GET_SPACE_USAGE);
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->addHeader("Content-Type: application/json");
request->addPostField("null"); //use POST
_workingRequest = ConnMan.addRequest(request);
}
void DropboxInfoRequest::quotaResponseCallback(const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
_workingRequest = nullptr;
if (_ignoreCallback) {
delete json;
return;
}
Networking::ErrorResponse error(this, "DropboxInfoRequest::quotaResponseCallback: unknown error");
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
if (!json->isObject()) {
error.response = "Passed JSON is not an object!";
finishError(error);
delete json;
return;
}
//Dropbox documentation states there are no errors for this API method
Common::JSONObject info = json->asObject();
if (!Networking::HttpJsonRequest::jsonContainsIntegerNumber(info, "used", "DropboxInfoRequest")) {
error.response = "Passed JSON misses 'used' attribute!";
finishError(error);
delete json;
return;
}
uint64 used = info.getVal("used")->asIntegerNumber(), allocated = 0;
if (Networking::HttpJsonRequest::jsonContainsAttribute(info, "allocation", "DropboxInfoRequest") &&
Networking::HttpJsonRequest::jsonIsObject(info.getVal("allocation"), "DropboxInfoRequest")) {
Common::JSONObject allocation = info.getVal("allocation")->asObject();
if (!Networking::HttpJsonRequest::jsonContainsIntegerNumber(allocation, "allocated", "DropboxInfoRequest")) {
error.response = "Passed JSON misses 'allocation/allocated' attribute!";
finishError(error);
delete json;
return;
}
allocated = allocation.getVal("allocated")->asIntegerNumber();
}
finishInfo(StorageInfo(_uid, _name, _email, used, allocated));
delete json;
}
void DropboxInfoRequest::errorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback) return;
finishError(error);
}
void DropboxInfoRequest::handle() {}
void DropboxInfoRequest::restart() { start(); }
void DropboxInfoRequest::finishInfo(const StorageInfo &info) {
Request::finishSuccess();
if (_infoCallback)
(*_infoCallback)(Storage::StorageInfoResponse(this, info));
}
} // End of namespace Dropbox
} // End of namespace Cloud

View File

@@ -0,0 +1,57 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_DROPBOX_DROPBOXINFOREQUEST_H
#define BACKENDS_CLOUD_DROPBOX_DROPBOXINFOREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/request.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace Dropbox {
class DropboxStorage;
class DropboxInfoRequest: public Networking::Request {
DropboxStorage *_storage;
Common::String _uid, _name, _email;
Storage::StorageInfoCallback _infoCallback;
Request *_workingRequest;
bool _ignoreCallback;
void start();
void userResponseCallback(const Networking::JsonResponse &response);
void quotaResponseCallback(const Networking::JsonResponse &response);
void errorCallback(const Networking::ErrorResponse &error);
void finishInfo(const StorageInfo &info);
public:
DropboxInfoRequest(DropboxStorage *storage, Storage::StorageInfoCallback cb, Networking::ErrorCallback ecb);
~DropboxInfoRequest() override;
void handle() override;
void restart() override;
};
} // End of namespace Dropbox
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,224 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/dropbox/dropboxlistdirectoryrequest.h"
#include "backends/cloud/dropbox/dropboxstorage.h"
#include "backends/cloud/dropbox/dropboxtokenrefresher.h"
#include "backends/cloud/iso8601.h"
#include "backends/cloud/storage.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
#include "common/debug.h"
namespace Cloud {
namespace Dropbox {
#define DROPBOX_API_LIST_FOLDER "https://api.dropboxapi.com/2/files/list_folder"
#define DROPBOX_API_LIST_FOLDER_CONTINUE "https://api.dropboxapi.com/2/files/list_folder/continue"
DropboxListDirectoryRequest::DropboxListDirectoryRequest(DropboxStorage *storage, const Common::String &path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive):
Networking::Request(nullptr, ecb), _requestedPath(path), _requestedRecursive(recursive), _listDirectoryCallback(cb),
_storage(storage), _workingRequest(nullptr), _ignoreCallback(false) {
start();
}
DropboxListDirectoryRequest::~DropboxListDirectoryRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _listDirectoryCallback;
}
void DropboxListDirectoryRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_files.clear();
_ignoreCallback = false;
Networking::JsonCallback callback = new Common::Callback<DropboxListDirectoryRequest, const Networking::JsonResponse &>(this, &DropboxListDirectoryRequest::responseCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<DropboxListDirectoryRequest, const Networking::ErrorResponse &>(this, &DropboxListDirectoryRequest::errorCallback);
Networking::HttpJsonRequest *request = new DropboxTokenRefresher(_storage, callback, failureCallback, DROPBOX_API_LIST_FOLDER);
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->addHeader("Content-Type: application/json");
Common::JSONObject jsonRequestParameters;
jsonRequestParameters.setVal("path", new Common::JSONValue(_requestedPath));
jsonRequestParameters.setVal("recursive", new Common::JSONValue(_requestedRecursive));
jsonRequestParameters.setVal("include_media_info", new Common::JSONValue(false));
jsonRequestParameters.setVal("include_deleted", new Common::JSONValue(false));
Common::JSONValue value(jsonRequestParameters);
request->addPostField(Common::JSON::stringify(&value));
_workingRequest = ConnMan.addRequest(request);
}
void DropboxListDirectoryRequest::responseCallback(const Networking::JsonResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback) {
delete response.value;
return;
}
if (response.request)
_date = response.request->date();
Networking::ErrorResponse error(this, "DropboxListDirectoryRequest::responseCallback: unknown error");
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
const Common::JSONValue *json = response.value;
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
if (!json->isObject()) {
error.response = "Passed JSON is not an object!";
finishError(error);
delete json;
return;
}
Common::JSONObject responseObject = json->asObject();
if (responseObject.contains("error") || responseObject.contains("error_summary")) {
if (responseObject.contains("error_summary") && responseObject.getVal("error_summary")->isString()) {
warning("Dropbox returned error: %s", responseObject.getVal("error_summary")->asString().c_str());
}
error.failed = true;
error.response = json->stringify();
finishError(error);
delete json;
return;
}
//check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
if (responseObject.contains("entries")) {
if (!responseObject.getVal("entries")->isArray()) {
error.response = Common::String::format(
"\"entries\" found, but that's not an array!\n%s",
responseObject.getVal("entries")->stringify(true).c_str()
);
finishError(error);
delete json;
return;
}
Common::JSONArray items = responseObject.getVal("entries")->asArray();
for (uint32 i = 0; i < items.size(); ++i) {
if (!Networking::HttpJsonRequest::jsonIsObject(items[i], "DropboxListDirectoryRequest"))
continue;
Common::JSONObject item = items[i]->asObject();
if (!Networking::HttpJsonRequest::jsonContainsString(item, "path_lower", "DropboxListDirectoryRequest"))
continue;
if (!Networking::HttpJsonRequest::jsonContainsString(item, ".tag", "DropboxListDirectoryRequest"))
continue;
Common::String path = item.getVal("path_lower")->asString();
bool isDirectory = (item.getVal(".tag")->asString() == "folder");
uint32 size = 0, timestamp = 0;
if (!isDirectory) {
if (!Networking::HttpJsonRequest::jsonContainsString(item, "server_modified", "DropboxListDirectoryRequest"))
continue;
if (!Networking::HttpJsonRequest::jsonContainsIntegerNumber(item, "size", "DropboxListDirectoryRequest"))
continue;
size = item.getVal("size")->asIntegerNumber();
timestamp = ISO8601::convertToTimestamp(item.getVal("server_modified")->asString());
}
_files.push_back(StorageFile(path, size, timestamp, isDirectory));
}
}
bool hasMore = false;
if (responseObject.contains("has_more")) {
if (!responseObject.getVal("has_more")->isBool()) {
warning("DropboxListDirectoryRequest: \"has_more\" is not a boolean");
debug(9, "%s", responseObject.getVal("has_more")->stringify(true).c_str());
error.response = "\"has_more\" is not a boolean!";
finishError(error);
delete json;
return;
}
hasMore = responseObject.getVal("has_more")->asBool();
}
if (hasMore) {
if (!Networking::HttpJsonRequest::jsonContainsString(responseObject, "cursor", "DropboxListDirectoryRequest")) {
error.response = "\"has_more\" found, but \"cursor\" is not (or it's not a string)!";
finishError(error);
delete json;
return;
}
Networking::JsonCallback callback = new Common::Callback<DropboxListDirectoryRequest, const Networking::JsonResponse &>(this, &DropboxListDirectoryRequest::responseCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<DropboxListDirectoryRequest, const Networking::ErrorResponse &>(this, &DropboxListDirectoryRequest::errorCallback);
Networking::HttpJsonRequest *request = new DropboxTokenRefresher(_storage, callback, failureCallback, DROPBOX_API_LIST_FOLDER_CONTINUE);
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->addHeader("Content-Type: application/json");
Common::JSONObject jsonRequestParameters;
jsonRequestParameters.setVal("cursor", new Common::JSONValue(responseObject.getVal("cursor")->asString()));
Common::JSONValue value(jsonRequestParameters);
request->addPostField(Common::JSON::stringify(&value));
_workingRequest = ConnMan.addRequest(request);
} else {
finishListing(_files);
}
delete json;
}
void DropboxListDirectoryRequest::errorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void DropboxListDirectoryRequest::handle() {}
void DropboxListDirectoryRequest::restart() { start(); }
Common::String DropboxListDirectoryRequest::date() const { return _date; }
void DropboxListDirectoryRequest::finishListing(const Common::Array<StorageFile> &files) {
Request::finishSuccess();
if (_listDirectoryCallback)
(*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
}
} // End of namespace Dropbox
} // End of namespace Cloud

View File

@@ -0,0 +1,62 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_DROPBOX_DROPBOXLISTDIRECTORYREQUEST_H
#define BACKENDS_CLOUD_DROPBOX_DROPBOXLISTDIRECTORYREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace Dropbox {
class DropboxStorage;
class DropboxListDirectoryRequest: public Networking::Request {
Common::String _requestedPath;
bool _requestedRecursive;
Storage::ListDirectoryCallback _listDirectoryCallback;
DropboxStorage *_storage;
Common::Array<StorageFile> _files;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _date;
void start();
void responseCallback(const Networking::JsonResponse &response);
void errorCallback(const Networking::ErrorResponse &error);
void finishListing(const Common::Array<StorageFile> &files);
public:
DropboxListDirectoryRequest(DropboxStorage *storage, const Common::String &path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false);
~DropboxListDirectoryRequest() override;
void handle() override;
void restart() override;
Common::String date() const override;
};
} // End of namespace Dropbox
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,131 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/dropbox/dropboxstorage.h"
#include "backends/cloud/dropbox/dropboxcreatedirectoryrequest.h"
#include "backends/cloud/dropbox/dropboxinforequest.h"
#include "backends/cloud/dropbox/dropboxlistdirectoryrequest.h"
#include "backends/cloud/dropbox/dropboxuploadrequest.h"
#include "backends/cloud/cloudmanager.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/formats/json.h"
namespace Cloud {
namespace Dropbox {
#define DROPBOX_API_FILES_DOWNLOAD "https://content.dropboxapi.com/2/files/download"
DropboxStorage::DropboxStorage(const Common::String &accessToken, const Common::String &refreshToken, bool enabled):
BaseStorage(accessToken, refreshToken, enabled) {}
DropboxStorage::DropboxStorage(const Common::String &code, Networking::ErrorCallback cb): BaseStorage() {
getAccessToken(code, cb);
}
DropboxStorage::DropboxStorage(const Networking::JsonResponse &codeFlowJson, Networking::ErrorCallback cb) : BaseStorage() {
codeFlowComplete(cb, codeFlowJson);
}
DropboxStorage::~DropboxStorage() {}
Common::String DropboxStorage::cloudProvider() { return "dropbox"; }
uint32 DropboxStorage::storageIndex() { return kStorageDropboxId; }
bool DropboxStorage::needsRefreshToken() { return true; }
bool DropboxStorage::canReuseRefreshToken() { return true; }
void DropboxStorage::saveConfig(const Common::String &keyPrefix) {
ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
saveIsEnabledFlag(keyPrefix);
}
Common::String DropboxStorage::name() const {
return "Dropbox";
}
Networking::Request *DropboxStorage::listDirectory(const Common::String &path, ListDirectoryCallback outerCallback, Networking::ErrorCallback errorCallback, bool recursive) {
return addRequest(new DropboxListDirectoryRequest(this, path, outerCallback, errorCallback, recursive));
}
Networking::Request *DropboxStorage::upload(const Common::String &path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
return addRequest(new DropboxUploadRequest(this, path, contents, callback, errorCallback));
}
Networking::Request *DropboxStorage::streamFileById(const Common::String &path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
Common::JSONObject jsonRequestParameters;
jsonRequestParameters.setVal("path", new Common::JSONValue(path));
Common::JSONValue value(jsonRequestParameters);
Networking::HttpRequest *request = new Networking::HttpRequest(nullptr, nullptr, DROPBOX_API_FILES_DOWNLOAD); //TODO: is it OK to pass no callbacks?
request->addHeader("Authorization: Bearer " + _token);
request->addHeader("Dropbox-API-Arg: " + Common::JSON::stringify(&value));
request->addHeader("Content-Type: "); //required to be empty (as we do POST, it's usually app/form-url-encoded)
Networking::NetworkReadStreamResponse response = request->execute();
if (callback)
(*callback)(response);
return request; // no leak here, response.request == request
}
Networking::Request *DropboxStorage::createDirectory(const Common::String &path, BoolCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
return addRequest(new DropboxCreateDirectoryRequest(this, path, callback, errorCallback));
}
Networking::Request *DropboxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
return addRequest(new DropboxInfoRequest(this, callback, errorCallback));
}
Common::String DropboxStorage::savesDirectoryPath() { return "/saves/"; }
DropboxStorage *DropboxStorage::loadFromConfig(const Common::String &keyPrefix) {
if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
warning("DropboxStorage: no access_token found");
return nullptr;
}
if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
warning("DropboxStorage: no refresh_token found");
return nullptr;
}
Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
return new DropboxStorage(accessToken, refreshToken, loadIsEnabledFlag(keyPrefix));
}
void DropboxStorage::removeFromConfig(const Common::String &keyPrefix) {
ConfMan.removeKey(keyPrefix + "access_token", ConfMan.kCloudDomain);
ConfMan.removeKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
removeIsEnabledFlag(keyPrefix);
}
} // End of namespace Dropbox
} // End of namespace Cloud

View File

@@ -0,0 +1,116 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_DROPBOX_STORAGE_H
#define BACKENDS_CLOUD_DROPBOX_STORAGE_H
#include "backends/cloud/basestorage.h"
#include "common/callback.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace Dropbox {
class DropboxStorage: public Cloud::BaseStorage {
/** This private constructor is called from loadFromConfig(). */
DropboxStorage(const Common::String &token, const Common::String &refreshToken, bool enabled);
protected:
/**
* @return "dropbox"
*/
Common::String cloudProvider() override;
/**
* @return kStorageDropboxId
*/
uint32 storageIndex() override;
bool needsRefreshToken() override;
bool canReuseRefreshToken() override;
public:
/** This constructor uses OAuth code flow to get tokens. */
DropboxStorage(const Common::String &code, Networking::ErrorCallback cb);
/** This constructor extracts tokens from JSON acquired via OAuth code flow. */
DropboxStorage(const Networking::JsonResponse &codeFlowJson, Networking::ErrorCallback cb);
~DropboxStorage() override;
/**
* Storage methods, which are used by CloudManager to save
* storage in configuration file.
*/
/**
* Save storage data using ConfMan.
* @param keyPrefix all saved keys must start with this prefix.
* @note every Storage must write keyPrefix + "type" key
* with common value (e.g. "Dropbox").
*/
void saveConfig(const Common::String &keyPrefix) override;
/**
* Return unique storage name.
* @returns some unique storage name (for example, "Dropbox (user@example.com)")
*/
Common::String name() const override;
/** Public Cloud API comes down there. */
/** Returns ListDirectoryStatus struct with list of files. */
Networking::Request *listDirectory(const Common::String &path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false) override;
/** Returns UploadStatus struct with info about uploaded file. */
Networking::Request *upload(const Common::String &path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns pointer to Networking::NetworkReadStream. */
Networking::Request *streamFileById(const Common::String &path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) override;
/** Calls the callback when finished. */
Networking::Request *createDirectory(const Common::String &path, BoolCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns the StorageInfo struct. */
Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns storage's saves directory path with the trailing slash. */
Common::String savesDirectoryPath() override;
/**
* Load token and user id from configs and return DropboxStorage for those.
* @return pointer to the newly created DropboxStorage or 0 if some problem occurred.
*/
static DropboxStorage *loadFromConfig(const Common::String &keyPrefix);
/**
* Remove all DropboxStorage-related data from config.
*/
static void removeFromConfig(const Common::String &keyPrefix);
Common::String accessToken() const { return _token; }
};
} // End of namespace Dropbox
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,107 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/dropbox/dropboxtokenrefresher.h"
#include "backends/cloud/dropbox/dropboxstorage.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/debug.h"
#include "common/formats/json.h"
namespace Cloud {
namespace Dropbox {
DropboxTokenRefresher::DropboxTokenRefresher(DropboxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url):
HttpJsonRequest(callback, ecb, url), _parentStorage(parent) {}
DropboxTokenRefresher::~DropboxTokenRefresher() {}
void DropboxTokenRefresher::tokenRefreshed(const Storage::BoolResponse &response) {
if (!response.value) {
//failed to refresh token, notify user with NULL in original callback
warning("DropboxTokenRefresher: failed to refresh token");
finishError(Networking::ErrorResponse(this, false, true, "DropboxTokenRefresher::tokenRefreshed: failed to refresh token", -1));
return;
}
//update headers: first change header with token, then pass those to request
for (uint32 i = 0; i < _headersList.size(); ++i) {
if (_headersList[i].contains("Authorization")) {
_headersList[i] = "Authorization: Bearer " + _parentStorage->accessToken();
}
}
//successfully received refreshed token, can restart the original request now
retry(0);
}
void DropboxTokenRefresher::finishJson(const Common::JSONValue *json) {
if (!json) {
//that's probably not an error (200 OK)
HttpJsonRequest::finishJson(nullptr);
return;
}
if (jsonIsObject(json, "DropboxTokenRefresher")) {
Common::JSONObject result = json->asObject();
if (result.contains("error") || result.contains("error_summary")) {
long httpCode = -1;
if (_stream) {
httpCode = _stream->httpResponseCode();
debug(9, "DropboxTokenRefresher: code %ld", httpCode);
}
bool irrecoverable = true;
if (jsonContainsString(result, "error_summary", "DropboxTokenRefresher")) {
if (result.getVal("error_summary")->asString().contains("expired_access_token")) {
irrecoverable = false;
}
}
if (irrecoverable) {
finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpCode));
delete json;
return;
}
pause();
delete json;
_parentStorage->refreshAccessToken(new Common::Callback<DropboxTokenRefresher, const Storage::BoolResponse &>(this, &DropboxTokenRefresher::tokenRefreshed));
return;
}
}
//notify user of success
HttpJsonRequest::finishJson(json);
}
void DropboxTokenRefresher::finishError(const Networking::ErrorResponse &error, Networking::RequestState state) {
if (error.httpResponseCode == 401) {
pause();
_parentStorage->refreshAccessToken(new Common::Callback<DropboxTokenRefresher, const Storage::BoolResponse &>(this, &DropboxTokenRefresher::tokenRefreshed));
return;
}
Request::finishError(error);
}
} // End of namespace Dropbox
} // End of namespace Cloud

View File

@@ -0,0 +1,48 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_DROPBOX_DROPBOXTOKENREFRESHER_H
#define BACKENDS_CLOUD_DROPBOX_DROPBOXTOKENREFRESHER_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace Dropbox {
class DropboxStorage;
class DropboxTokenRefresher: public Networking::HttpJsonRequest {
DropboxStorage *_parentStorage;
void tokenRefreshed(const Storage::BoolResponse &response);
void finishJson(const Common::JSONValue *json) override;
void finishError(const Networking::ErrorResponse &error, Networking::RequestState state = Networking::FINISHED) override;
public:
DropboxTokenRefresher(DropboxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url);
~DropboxTokenRefresher() override;
};
} // End of namespace Dropbox
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,205 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/dropbox/dropboxuploadrequest.h"
#include "backends/cloud/dropbox/dropboxstorage.h"
#include "backends/cloud/dropbox/dropboxtokenrefresher.h"
#include "backends/cloud/iso8601.h"
#include "backends/cloud/storage.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
namespace Cloud {
namespace Dropbox {
#define DROPBOX_API_FILES_UPLOAD "https://content.dropboxapi.com/2/files/upload"
#define DROPBOX_API_FILES_UPLOAD_SESSION "https://content.dropboxapi.com/2/files/upload_session/"
DropboxUploadRequest::DropboxUploadRequest(DropboxStorage *storage, const Common::String &path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
DropboxUploadRequest::~DropboxUploadRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _contentsStream;
delete _uploadCallback;
}
void DropboxUploadRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
if (!_contentsStream) {
warning("DropboxUploadRequest: cannot start because stream is invalid");
finishError(Networking::ErrorResponse(this, false, true, "DropboxUploadRequest::start: cannot start because stream is invalid", -1));
return;
}
if (!_contentsStream->seek(0)) {
warning("DropboxUploadRequest: cannot restart because stream couldn't seek(0)");
finishError(Networking::ErrorResponse(this, false, true, "DropboxUploadRequest::start: cannot restart because stream couldn't seek(0)", -1));
return;
}
_ignoreCallback = false;
uploadNextPart();
}
void DropboxUploadRequest::uploadNextPart() {
const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024;
Common::String url = DROPBOX_API_FILES_UPLOAD_SESSION;
Common::JSONObject jsonRequestParameters;
if (_contentsStream->pos() == 0 || _sessionId == "") {
if ((uint32)_contentsStream->size() <= UPLOAD_PER_ONE_REQUEST) {
url = DROPBOX_API_FILES_UPLOAD;
jsonRequestParameters.setVal("path", new Common::JSONValue(_savePath));
jsonRequestParameters.setVal("mode", new Common::JSONValue("overwrite"));
jsonRequestParameters.setVal("autorename", new Common::JSONValue(false));
jsonRequestParameters.setVal("mute", new Common::JSONValue(false));
} else {
url += "start";
jsonRequestParameters.setVal("close", new Common::JSONValue(false));
}
} else {
if ((uint32)(_contentsStream->size() - _contentsStream->pos()) <= UPLOAD_PER_ONE_REQUEST) {
url += "finish";
Common::JSONObject jsonCursor, jsonCommit;
jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId));
jsonCursor.setVal("offset", new Common::JSONValue((long long int)_contentsStream->pos()));
jsonCommit.setVal("path", new Common::JSONValue(_savePath));
jsonCommit.setVal("mode", new Common::JSONValue("overwrite"));
jsonCommit.setVal("autorename", new Common::JSONValue(false));
jsonCommit.setVal("mute", new Common::JSONValue(false));
jsonRequestParameters.setVal("cursor", new Common::JSONValue(jsonCursor));
jsonRequestParameters.setVal("commit", new Common::JSONValue(jsonCommit));
} else {
url += "append_v2";
Common::JSONObject jsonCursor;
jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId));
jsonCursor.setVal("offset", new Common::JSONValue((long long int)_contentsStream->pos()));
jsonRequestParameters.setVal("cursor", new Common::JSONValue(jsonCursor));
jsonRequestParameters.setVal("close", new Common::JSONValue(false));
}
}
Common::JSONValue value(jsonRequestParameters);
Networking::JsonCallback callback = new Common::Callback<DropboxUploadRequest, const Networking::JsonResponse &>(this, &DropboxUploadRequest::partUploadedCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<DropboxUploadRequest, const Networking::ErrorResponse &>(this, &DropboxUploadRequest::partUploadedErrorCallback);
Networking::HttpJsonRequest *request = new DropboxTokenRefresher(_storage, callback, failureCallback, url.c_str());
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->addHeader("Content-Type: application/octet-stream");
request->addHeader("Dropbox-API-Arg: " + Common::JSON::stringify(&value));
byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST];
uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST);
request->setBuffer(buffer, size);
_workingRequest = ConnMan.addRequest(request);
}
void DropboxUploadRequest::partUploadedCallback(const Networking::JsonResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
Networking::ErrorResponse error(this, false, true, "", -1);
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
const Common::JSONValue *json = response.value;
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
bool needsFinishRequest = false;
if (json->isObject()) {
Common::JSONObject object = json->asObject();
//debug(9, "%s", json->stringify(true).c_str());
if (object.contains("error") || object.contains("error_summary")) {
if (Networking::HttpJsonRequest::jsonContainsString(object, "error_summary", "DropboxUploadRequest")) {
warning("Dropbox returned error: %s", object.getVal("error_summary")->asString().c_str());
}
error.response = json->stringify(true);
finishError(error);
delete json;
return;
}
if (Networking::HttpJsonRequest::jsonContainsString(object, "path_lower", "DropboxUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsString(object, "server_modified", "DropboxUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsIntegerNumber(object, "size", "DropboxUploadRequest")) {
//finished
Common::String path = object.getVal("path_lower")->asString();
uint32 size = object.getVal("size")->asIntegerNumber();
uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("server_modified")->asString());
finishUpload(StorageFile(path, size, timestamp, false));
return;
}
if (_sessionId == "") {
if (Networking::HttpJsonRequest::jsonContainsString(object, "session_id", "DropboxUploadRequest"))
_sessionId = object.getVal("session_id")->asString();
needsFinishRequest = true;
}
}
if (!needsFinishRequest && (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1)) {
warning("DropboxUploadRequest: no file info to return");
finishUpload(StorageFile(_savePath, 0, 0, false));
} else {
uploadNextPart();
}
delete json;
}
void DropboxUploadRequest::partUploadedErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishError(error);
}
void DropboxUploadRequest::handle() {}
void DropboxUploadRequest::restart() { start(); }
void DropboxUploadRequest::finishUpload(const StorageFile &file) {
Request::finishSuccess();
if (_uploadCallback)
(*_uploadCallback)(Storage::UploadResponse(this, file));
}
} // End of namespace Dropbox
} // End of namespace Cloud

View File

@@ -0,0 +1,61 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_DROPBOX_DROPBOXUPLOADREQUEST_H
#define BACKENDS_CLOUD_DROPBOX_DROPBOXUPLOADREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace Dropbox {
class DropboxStorage;
class DropboxUploadRequest: public Networking::Request {
DropboxStorage *_storage;
Common::String _savePath;
Common::SeekableReadStream *_contentsStream;
Storage::UploadCallback _uploadCallback;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _sessionId;
void start();
void uploadNextPart();
void partUploadedCallback(const Networking::JsonResponse &response);
void partUploadedErrorCallback(const Networking::ErrorResponse &error);
void finishUpload(const StorageFile &status);
public:
DropboxUploadRequest(DropboxStorage *storage, const Common::String &path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
~DropboxUploadRequest() override;
void handle() override;
void restart() override;
};
} // End of namespace Dropbox
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,198 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/folderdownloadrequest.h"
#include "backends/cloud/downloadrequest.h"
#include "backends/cloud/id/iddownloadrequest.h"
#include "common/debug.h"
#include "gui/downloaddialog.h"
#include "backends/networking/http/connectionmanager.h"
#include "cloudmanager.h"
namespace Cloud {
FolderDownloadRequest::FolderDownloadRequest(Storage *storage, Storage::FileArrayCallback callback, Networking::ErrorCallback ecb, const Common::String &remoteDirectoryPath, const Common::Path &localDirectoryPath, bool recursive):
Request(nullptr, ecb), CommandSender(nullptr), _storage(storage), _fileArrayCallback(callback),
_remoteDirectoryPath(remoteDirectoryPath), _localDirectoryPath(localDirectoryPath), _recursive(recursive),
_workingRequest(nullptr), _ignoreCallback(false), _totalFiles(0) {
start();
}
FolderDownloadRequest::~FolderDownloadRequest() {
sendCommand(GUI::kDownloadEndedCmd, 0);
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _fileArrayCallback;
}
void FolderDownloadRequest::start() {
//cleanup
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_currentFile = StorageFile();
_pendingFiles.clear();
_failedFiles.clear();
_ignoreCallback = false;
_totalFiles = 0;
_downloadedBytes = _totalBytes = _wasDownloadedBytes = _currentDownloadSpeed = 0;
//list directory first
_workingRequest = _storage->listDirectory(
_remoteDirectoryPath,
new Common::Callback<FolderDownloadRequest, const Storage::ListDirectoryResponse &>(this, &FolderDownloadRequest::directoryListedCallback),
new Common::Callback<FolderDownloadRequest, const Networking::ErrorResponse &>(this, &FolderDownloadRequest::directoryListedErrorCallback),
_recursive
);
}
void FolderDownloadRequest::directoryListedCallback(const Storage::ListDirectoryResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
_pendingFiles = response.value;
// remove all directories
// non-empty directories would be created by DumpFile, and empty ones are just ignored
// also skip all hidden files (with names starting with '.') or with other names that are forbidden to sync in CloudManager
for (Common::Array<StorageFile>::iterator i = _pendingFiles.begin(); i != _pendingFiles.end();)
if (i->isDirectory() || !CloudMan.canSyncFilename(i->name()))
_pendingFiles.erase(i);
else {
_totalBytes += i->size();
++i;
}
_totalFiles = _pendingFiles.size();
downloadNextFile();
}
void FolderDownloadRequest::directoryListedErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishError(error);
}
void FolderDownloadRequest::fileDownloadedCallback(const Storage::BoolResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (!response.value) _failedFiles.push_back(_currentFile);
_downloadedBytes += _currentFile.size();
downloadNextFile();
}
void FolderDownloadRequest::fileDownloadedErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
fileDownloadedCallback(Storage::BoolResponse(error.request, false));
}
void FolderDownloadRequest::downloadNextFile() {
do {
if (_pendingFiles.empty()) {
sendCommand(GUI::kDownloadEndedCmd, 0);
finishDownload(_failedFiles);
return;
}
_currentFile = _pendingFiles.back();
_pendingFiles.pop_back();
} while (_currentFile.isDirectory()); // directories are actually removed earlier, in the directoryListedCallback()
sendCommand(GUI::kDownloadProgressCmd, (int)(getProgress() * 100));
Common::String remotePath = _currentFile.path();
Common::String localPathStr = remotePath;
if (!_remoteDirectoryPath.empty()) {
if (remotePath.hasPrefix(_remoteDirectoryPath)) {
localPathStr.erase(0, _remoteDirectoryPath.size());
if (_remoteDirectoryPath.lastChar() != '/' && _remoteDirectoryPath.lastChar() != '\\')
localPathStr.erase(0, 1);
} else {
warning("FolderDownloadRequest: Can't process the following paths:");
warning("remote directory: %s", _remoteDirectoryPath.c_str());
warning("remote file under that directory: %s", remotePath.c_str());
}
}
Common::Path localPath(localPathStr);
if (!_localDirectoryPath.empty()) {
localPath = _localDirectoryPath.join(localPath);
}
debug(9, "FolderDownloadRequest: %s -> %s", remotePath.c_str(), localPath.toString(Common::Path::kNativeSeparator).c_str());
_workingRequest = _storage->downloadById(
_currentFile.id(), localPath,
new Common::Callback<FolderDownloadRequest, const Storage::BoolResponse &>(this, &FolderDownloadRequest::fileDownloadedCallback),
new Common::Callback<FolderDownloadRequest, const Networking::ErrorResponse &>(this, &FolderDownloadRequest::fileDownloadedErrorCallback)
);
}
void FolderDownloadRequest::handle() {
uint32 microsecondsPassed = Networking::ConnectionManager::getCloudRequestsPeriodInMicroseconds();
uint64 currentDownloadedBytes = getDownloadedBytes();
uint64 downloadedThisPeriod = currentDownloadedBytes - _wasDownloadedBytes;
_currentDownloadSpeed = downloadedThisPeriod * (1000000L / microsecondsPassed);
_wasDownloadedBytes = currentDownloadedBytes;
}
void FolderDownloadRequest::restart() { start(); }
void FolderDownloadRequest::finishDownload(Common::Array<StorageFile> &files) {
Request::finishSuccess();
if (_fileArrayCallback)
(*_fileArrayCallback)(Storage::FileArrayResponse(this, files));
}
double FolderDownloadRequest::getProgress() const {
if (_totalFiles == 0 || _totalBytes == 0)
return 0;
return (double)getDownloadedBytes() / (double)getTotalBytesToDownload();
}
uint64 FolderDownloadRequest::getDownloadedBytes() const {
if (_totalFiles == 0)
return 0;
double currentFileProgress = 0;
DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest);
if (downloadRequest != nullptr) {
currentFileProgress = downloadRequest->getProgress();
} else {
Id::IdDownloadRequest *idDownloadRequest = dynamic_cast<Id::IdDownloadRequest *>(_workingRequest);
if (idDownloadRequest != nullptr)
currentFileProgress = idDownloadRequest->getProgress();
}
return _downloadedBytes + (uint64)(currentFileProgress * _currentFile.size());
}
uint64 FolderDownloadRequest::getTotalBytesToDownload() const {
return _totalBytes;
}
uint64 FolderDownloadRequest::getDownloadSpeed() const {
return _currentDownloadSpeed;
}
} // End of namespace Cloud

View File

@@ -0,0 +1,79 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_FOLDERDOWNLOADREQUEST_H
#define BACKENDS_CLOUD_FOLDERDOWNLOADREQUEST_H
#include "backends/networking/http/request.h"
#include "backends/cloud/storage.h"
#include "gui/object.h"
namespace Cloud {
class FolderDownloadRequest: public Networking::Request, public GUI::CommandSender {
Storage *_storage;
Storage::FileArrayCallback _fileArrayCallback;
Common::String _remoteDirectoryPath;
Common::Path _localDirectoryPath;
bool _recursive;
Common::Array<StorageFile> _pendingFiles, _failedFiles;
StorageFile _currentFile;
Request *_workingRequest;
bool _ignoreCallback;
uint32 _totalFiles;
uint64 _downloadedBytes, _totalBytes, _wasDownloadedBytes, _currentDownloadSpeed;
void start();
void directoryListedCallback(const Storage::ListDirectoryResponse &response);
void directoryListedErrorCallback(const Networking::ErrorResponse &error);
void fileDownloadedCallback(const Storage::BoolResponse &response);
void fileDownloadedErrorCallback(const Networking::ErrorResponse &error);
void downloadNextFile();
void finishDownload(Common::Array<StorageFile> &files);
public:
FolderDownloadRequest(Storage *storage, Storage::FileArrayCallback callback, Networking::ErrorCallback ecb, const Common::String &remoteDirectoryPath, const Common::Path &localDirectoryPath, bool recursive);
~FolderDownloadRequest() override;
void handle() override;
void restart() override;
/** Returns a number in range [0, 1], where 1 is "complete". */
double getProgress() const;
/** Returns a number of downloaded bytes. */
uint64 getDownloadedBytes() const;
/** Returns a total number of bytes to download. */
uint64 getTotalBytesToDownload() const;
/** Returns average download speed for the last second. */
uint64 getDownloadSpeed() const;
/** Returns remote directory path. */
Common::String getRemotePath() const { return _remoteDirectoryPath; }
/** Returns local directory path. */
Common::Path getLocalPath() const { return _localDirectoryPath; }
};
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,162 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h"
#include "backends/cloud/googledrive/googledrivestorage.h"
#include "backends/cloud/iso8601.h"
#include "backends/cloud/storage.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
#include "googledrivetokenrefresher.h"
namespace Cloud {
namespace GoogleDrive {
#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/drive/v3/files?spaces=drive&fields=files%28id,mimeType,modifiedTime,name,size%29,nextPageToken&orderBy=folder,name"
//files(id,mimeType,modifiedTime,name,size),nextPageToken
GoogleDriveListDirectoryByIdRequest::GoogleDriveListDirectoryByIdRequest(GoogleDriveStorage *storage, const Common::String &id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _requestedId(id), _storage(storage), _listDirectoryCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
GoogleDriveListDirectoryByIdRequest::~GoogleDriveListDirectoryByIdRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _listDirectoryCallback;
}
void GoogleDriveListDirectoryByIdRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_files.clear();
_ignoreCallback = false;
makeRequest("");
}
void GoogleDriveListDirectoryByIdRequest::makeRequest(const Common::String &pageToken) {
Common::String url = GOOGLEDRIVE_API_FILES;
if (pageToken != "")
url += "&pageToken=" + pageToken;
url += "&q=%27" + _requestedId + "%27+in+parents";
Networking::JsonCallback callback = new Common::Callback<GoogleDriveListDirectoryByIdRequest, const Networking::JsonResponse &>(this, &GoogleDriveListDirectoryByIdRequest::responseCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveListDirectoryByIdRequest, const Networking::ErrorResponse &>(this, &GoogleDriveListDirectoryByIdRequest::errorCallback);
Networking::HttpJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
request->addHeader("Authorization: Bearer " + _storage->accessToken());
_workingRequest = ConnMan.addRequest(request);
}
void GoogleDriveListDirectoryByIdRequest::responseCallback(const Networking::JsonResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback) {
delete response.value;
return;
}
if (response.request)
_date = response.request->date();
Networking::ErrorResponse error(this, "GoogleDriveListDirectoryByIdRequest::responseCallback");
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
const Common::JSONValue *json = response.value;
if (json) {
Common::JSONObject responseObject = json->asObject();
///debug("%s", json->stringify(true).c_str());
if (responseObject.contains("error") || responseObject.contains("error_summary")) {
warning("GoogleDrive returned error: %s", responseObject.getVal("error_summary")->asString().c_str());
error.failed = true;
error.response = json->stringify();
finishError(error);
delete json;
return;
}
//TODO: check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
if (responseObject.contains("files") && responseObject.getVal("files")->isArray()) {
Common::JSONArray items = responseObject.getVal("files")->asArray();
for (uint32 i = 0; i < items.size(); ++i) {
Common::JSONObject item = items[i]->asObject();
Common::String id = item.getVal("id")->asString();
Common::String name = item.getVal("name")->asString();
bool isDirectory = (item.getVal("mimeType")->asString() == "application/vnd.google-apps.folder");
uint32 size = 0, timestamp = 0;
if (item.contains("size") && item.getVal("size")->isString())
size = item.getVal("size")->asString().asUint64();
if (item.contains("modifiedTime") && item.getVal("modifiedTime")->isString())
timestamp = ISO8601::convertToTimestamp(item.getVal("modifiedTime")->asString());
//as we list directory by id, we can't determine full path for the file, so we leave it empty
_files.push_back(StorageFile(id, "", name, size, timestamp, isDirectory));
}
}
bool hasMore = (responseObject.contains("nextPageToken"));
if (hasMore) {
Common::String token = responseObject.getVal("nextPageToken")->asString();
makeRequest(token);
} else {
finishListing(_files);
}
} else {
warning("null, not json");
error.failed = true;
finishError(error);
}
delete json;
}
void GoogleDriveListDirectoryByIdRequest::errorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void GoogleDriveListDirectoryByIdRequest::handle() {}
void GoogleDriveListDirectoryByIdRequest::restart() { start(); }
Common::String GoogleDriveListDirectoryByIdRequest::date() const { return _date; }
void GoogleDriveListDirectoryByIdRequest::finishListing(Common::Array<StorageFile> &files) {
Request::finishSuccess();
if (_listDirectoryCallback)
(*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
}
} // End of namespace GoogleDrive
} // End of namespace Cloud

View File

@@ -0,0 +1,62 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVELISTDIRECTORYBYIDREQUEST_H
#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVELISTDIRECTORYBYIDREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace GoogleDrive {
class GoogleDriveStorage;
class GoogleDriveListDirectoryByIdRequest: public Networking::Request {
Common::String _requestedId;
GoogleDriveStorage *_storage;
Storage::ListDirectoryCallback _listDirectoryCallback;
Common::Array<StorageFile> _files;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _date;
void start();
void makeRequest(const Common::String &pageToken);
void responseCallback(const Networking::JsonResponse &response);
void errorCallback(const Networking::ErrorResponse &error);
void finishListing(Common::Array<StorageFile> &files);
public:
GoogleDriveListDirectoryByIdRequest(GoogleDriveStorage *storage, const Common::String &id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb);
~GoogleDriveListDirectoryByIdRequest() override;
void handle() override;
void restart() override;
Common::String date() const override;
};
} // End of namespace GoogleDrive
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,249 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/googledrive/googledrivestorage.h"
#include "backends/cloud/cloudmanager.h"
#include "backends/cloud/googledrive/googledrivetokenrefresher.h"
#include "backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h"
#include "backends/cloud/googledrive/googledriveuploadrequest.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/formats/json.h"
#include "common/debug.h"
namespace Cloud {
namespace GoogleDrive {
#define GOOGLEDRIVE_API_FILES_ALT_MEDIA "https://www.googleapis.com/drive/v3/files/%s?alt=media"
#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/drive/v3/files"
#define GOOGLEDRIVE_API_ABOUT "https://www.googleapis.com/drive/v3/about?fields=storageQuota,user"
GoogleDriveStorage::GoogleDriveStorage(const Common::String &token, const Common::String &refreshToken, bool enabled):
IdStorage(token, refreshToken, enabled) {}
GoogleDriveStorage::GoogleDriveStorage(const Common::String &code, Networking::ErrorCallback cb) {
getAccessToken(code, cb);
}
GoogleDriveStorage::GoogleDriveStorage(const Networking::JsonResponse &codeFlowJson, Networking::ErrorCallback cb) {
codeFlowComplete(cb, codeFlowJson);
}
GoogleDriveStorage::~GoogleDriveStorage() {}
Common::String GoogleDriveStorage::cloudProvider() { return "gdrive"; }
uint32 GoogleDriveStorage::storageIndex() { return kStorageGoogleDriveId; }
bool GoogleDriveStorage::needsRefreshToken() { return true; }
bool GoogleDriveStorage::canReuseRefreshToken() { return true; }
void GoogleDriveStorage::saveConfig(const Common::String &keyPrefix) {
ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
saveIsEnabledFlag(keyPrefix);
}
Common::String GoogleDriveStorage::name() const {
return "Google Drive";
}
void GoogleDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
if (!json) {
warning("GoogleDriveStorage::infoInnerCallback: NULL passed instead of JSON");
delete outerCallback;
return;
}
if (!Networking::HttpJsonRequest::jsonIsObject(json, "GoogleDriveStorage::infoInnerCallback")) {
delete json;
delete outerCallback;
return;
}
Common::JSONObject jsonInfo = json->asObject();
Common::String uid, displayName, email;
uint64 quotaUsed = 0, quotaAllocated = 0;
if (Networking::HttpJsonRequest::jsonContainsAttribute(jsonInfo, "user", "GoogleDriveStorage::infoInnerCallback") &&
Networking::HttpJsonRequest::jsonIsObject(jsonInfo.getVal("user"), "GoogleDriveStorage::infoInnerCallback")) {
//"me":true, "kind":"drive#user","photoLink": "",
//"displayName":"Alexander Tkachev","emailAddress":"alexander@tkachov.ru","permissionId":""
Common::JSONObject user = jsonInfo.getVal("user")->asObject();
if (Networking::HttpJsonRequest::jsonContainsString(user, "permissionId", "GoogleDriveStorage::infoInnerCallback"))
uid = user.getVal("permissionId")->asString(); //not sure it's user's id, but who cares anyway?
if (Networking::HttpJsonRequest::jsonContainsString(user, "displayName", "GoogleDriveStorage::infoInnerCallback"))
displayName = user.getVal("displayName")->asString();
if (Networking::HttpJsonRequest::jsonContainsString(user, "emailAddress", "GoogleDriveStorage::infoInnerCallback"))
email = user.getVal("emailAddress")->asString();
}
if (Networking::HttpJsonRequest::jsonContainsAttribute(jsonInfo, "storageQuota", "GoogleDriveStorage::infoInnerCallback") &&
Networking::HttpJsonRequest::jsonIsObject(jsonInfo.getVal("storageQuota"), "GoogleDriveStorage::infoInnerCallback")) {
//"usageInDrive":"6332462","limit":"18253611008","usage":"6332462","usageInDriveTrash":"0"
Common::JSONObject storageQuota = jsonInfo.getVal("storageQuota")->asObject();
if (Networking::HttpJsonRequest::jsonContainsString(storageQuota, "usage", "GoogleDriveStorage::infoInnerCallback")) {
Common::String usage = storageQuota.getVal("usage")->asString();
quotaUsed = usage.asUint64();
}
if (Networking::HttpJsonRequest::jsonContainsString(storageQuota, "limit", "GoogleDriveStorage::infoInnerCallback")) {
Common::String limit = storageQuota.getVal("limit")->asString();
quotaAllocated = limit.asUint64();
}
}
CloudMan.setStorageUsername(kStorageGoogleDriveId, email);
if (outerCallback) {
(*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, displayName, email, quotaUsed, quotaAllocated)));
delete outerCallback;
}
delete json;
}
void GoogleDriveStorage::createDirectoryInnerCallback(BoolCallback outerCallback, const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
if (!json) {
warning("GoogleDriveStorage::createDirectoryInnerCallback: NULL passed instead of JSON");
delete outerCallback;
return;
}
if (outerCallback) {
if (Networking::HttpJsonRequest::jsonIsObject(json, "GoogleDriveStorage::createDirectoryInnerCallback")) {
Common::JSONObject jsonInfo = json->asObject();
(*outerCallback)(BoolResponse(nullptr, jsonInfo.contains("id")));
} else {
(*outerCallback)(BoolResponse(nullptr, false));
}
delete outerCallback;
}
delete json;
}
Networking::Request *GoogleDriveStorage::listDirectoryById(const Common::String &id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
if (!callback)
callback = new Common::Callback<GoogleDriveStorage, const FileArrayResponse &>(this, &GoogleDriveStorage::printFiles);
return addRequest(new GoogleDriveListDirectoryByIdRequest(this, id, callback, errorCallback));
}
Networking::Request *GoogleDriveStorage::upload(const Common::String &path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
return addRequest(new GoogleDriveUploadRequest(this, path, contents, callback, errorCallback));
}
Networking::Request *GoogleDriveStorage::streamFileById(const Common::String &id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
if (callback) {
Common::String url = Common::String::format(GOOGLEDRIVE_API_FILES_ALT_MEDIA, Common::percentEncodeString(id).c_str());
Common::String header = "Authorization: Bearer " + _token;
Networking::RequestHeaders *headersList = new Networking::RequestHeaders();
headersList->push_back(header);
Networking::NetworkReadStream *stream = Networking::NetworkReadStream::make(url.c_str(), headersList, "");
(*callback)(Networking::NetworkReadStreamResponse(nullptr, stream));
}
delete callback;
delete errorCallback;
return nullptr;
}
void GoogleDriveStorage::printInfo(const StorageInfoResponse &response) {
debug(9, "\nGoogleDriveStorage: user info:");
debug(9, "\tname: %s", response.value.name().c_str());
debug(9, "\temail: %s", response.value.email().c_str());
debug(9, "\tdisk usage: %llu/%llu",
(unsigned long long)response.value.used(),
(unsigned long long)response.value.available());
}
Networking::Request *GoogleDriveStorage::createDirectoryWithParentId(const Common::String &parentId, const Common::String &directoryName, BoolCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
Common::String url = GOOGLEDRIVE_API_FILES;
Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, const BoolResponse &, const Networking::JsonResponse &>(this, &GoogleDriveStorage::createDirectoryInnerCallback, callback);
Networking::HttpJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str());
request->addHeader("Authorization: Bearer " + accessToken());
request->addHeader("Content-Type: application/json");
Common::JSONArray parentsArray;
parentsArray.push_back(new Common::JSONValue(parentId));
Common::JSONObject jsonRequestParameters;
jsonRequestParameters.setVal("mimeType", new Common::JSONValue("application/vnd.google-apps.folder"));
jsonRequestParameters.setVal("name", new Common::JSONValue(directoryName));
jsonRequestParameters.setVal("parents", new Common::JSONValue(parentsArray));
Common::JSONValue value(jsonRequestParameters);
request->addPostField(Common::JSON::stringify(&value));
return addRequest(request);
}
Networking::Request *GoogleDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
if (!callback)
callback = new Common::Callback<GoogleDriveStorage, const StorageInfoResponse &>(this, &GoogleDriveStorage::printInfo);
Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, const StorageInfoResponse &, const Networking::JsonResponse &>(this, &GoogleDriveStorage::infoInnerCallback, callback);
Networking::HttpJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, GOOGLEDRIVE_API_ABOUT);
request->addHeader("Authorization: Bearer " + _token);
return addRequest(request);
}
Common::String GoogleDriveStorage::savesDirectoryPath() { return "scummvm/saves/"; }
GoogleDriveStorage *GoogleDriveStorage::loadFromConfig(const Common::String &keyPrefix) {
if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
warning("GoogleDriveStorage: no access_token found");
return nullptr;
}
if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
warning("GoogleDriveStorage: no refresh_token found");
return nullptr;
}
Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
return new GoogleDriveStorage(accessToken, refreshToken, loadIsEnabledFlag(keyPrefix));
}
void GoogleDriveStorage::removeFromConfig(const Common::String &keyPrefix) {
ConfMan.removeKey(keyPrefix + "access_token", ConfMan.kCloudDomain);
ConfMan.removeKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
removeIsEnabledFlag(keyPrefix);
}
Common::String GoogleDriveStorage::getRootDirectoryId() {
return "root";
}
} // End of namespace GoogleDrive
} // End of namespace Cloud

View File

@@ -0,0 +1,125 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVESTORAGE_H
#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVESTORAGE_H
#include "backends/cloud/id/idstorage.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace GoogleDrive {
class GoogleDriveStorage: public Id::IdStorage {
/** This private constructor is called from loadFromConfig(). */
GoogleDriveStorage(const Common::String &token, const Common::String &refreshToken, bool enabled);
/** Constructs StorageInfo based on JSON response from cloud. */
void infoInnerCallback(StorageInfoCallback outerCallback, const Networking::JsonResponse &json);
/** Returns bool based on JSON response from cloud. */
void createDirectoryInnerCallback(BoolCallback outerCallback, const Networking::JsonResponse &json);
void printInfo(const StorageInfoResponse &response);
protected:
/**
* @return "gdrive"
*/
Common::String cloudProvider() override;
/**
* @return kStorageGoogleDriveId
*/
uint32 storageIndex() override;
bool needsRefreshToken() override;
bool canReuseRefreshToken() override;
public:
/** This constructor uses OAuth code flow to get tokens. */
GoogleDriveStorage(const Common::String &code, Networking::ErrorCallback cb);
/** This constructor extracts tokens from JSON acquired via OAuth code flow. */
GoogleDriveStorage(const Networking::JsonResponse &codeFlowJson, Networking::ErrorCallback cb);
~GoogleDriveStorage() override;
/**
* Storage methods, which are used by CloudManager to save
* storage in configuration file.
*/
/**
* Save storage data using ConfMan.
* @param keyPrefix all saved keys must start with this prefix.
* @note every Storage must write keyPrefix + "type" key
* with common value (e.g. "Dropbox").
*/
void saveConfig(const Common::String &keyPrefix) override;
/**
* Return unique storage name.
* @returns some unique storage name (for example, "Dropbox (user@example.com)")
*/
Common::String name() const override;
/** Public Cloud API comes down there. */
/** Returns Array<StorageFile> - the list of files. */
Networking::Request *listDirectoryById(const Common::String &id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns UploadStatus struct with info about uploaded file. */
Networking::Request *upload(const Common::String &path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns pointer to Networking::NetworkReadStream. */
Networking::Request *streamFileById(const Common::String &id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) override;
/** Calls the callback when finished. */
Networking::Request *createDirectoryWithParentId(const Common::String &parentId, const Common::String &directoryName, BoolCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns the StorageInfo struct. */
Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns storage's saves directory path with the trailing slash. */
Common::String savesDirectoryPath() override;
/**
* Load token and user id from configs and return GoogleDriveStorage for those.
* @return pointer to the newly created GoogleDriveStorage or 0 if some problem occurred.
*/
static GoogleDriveStorage *loadFromConfig(const Common::String &keyPrefix);
/**
* Remove all GoogleDriveStorage-related data from config.
*/
static void removeFromConfig(const Common::String &keyPrefix);
Common::String getRootDirectoryId() override;
Common::String accessToken() const { return _token; }
};
} // End of namespace GoogleDrive
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,108 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/googledrive/googledrivetokenrefresher.h"
#include "backends/cloud/googledrive/googledrivestorage.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/debug.h"
#include "common/formats/json.h"
namespace Cloud {
namespace GoogleDrive {
GoogleDriveTokenRefresher::GoogleDriveTokenRefresher(GoogleDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url):
HttpJsonRequest(callback, ecb, url), _parentStorage(parent) {}
GoogleDriveTokenRefresher::~GoogleDriveTokenRefresher() {}
void GoogleDriveTokenRefresher::tokenRefreshed(const Storage::BoolResponse &response) {
if (!response.value) {
//failed to refresh token, notify user with NULL in original callback
warning("GoogleDriveTokenRefresher: failed to refresh token");
finishError(Networking::ErrorResponse(this, false, true, "GoogleDriveTokenRefresher::tokenRefreshed: failed to refresh token", -1));
return;
}
//update headers: first change header with token, then pass those to request
for (uint32 i = 0; i < _headersList.size(); ++i) {
if (_headersList[i].contains("Authorization")) {
_headersList[i] = "Authorization: Bearer " + _parentStorage->accessToken();
}
}
//successfully received refreshed token, can restart the original request now
retry(0);
}
void GoogleDriveTokenRefresher::finishJson(const Common::JSONValue *json) {
if (!json) {
//that's probably not an error (200 OK)
HttpJsonRequest::finishJson(nullptr);
return;
}
if (jsonIsObject(json, "GoogleDriveTokenRefresher")) {
Common::JSONObject result = json->asObject();
long httpResponseCode = -1;
if (result.contains("error") && jsonIsObject(result.getVal("error"), "GoogleDriveTokenRefresher")) {
//new token needed => request token & then retry original request
if (_stream) {
httpResponseCode = _stream->httpResponseCode();
debug(9, "GoogleDriveTokenRefresher: code = %ld", httpResponseCode);
}
Common::JSONObject error = result.getVal("error")->asObject();
bool irrecoverable = true;
uint32 code = 0xFFFFFFFF; // Invalid
Common::String message;
if (jsonContainsIntegerNumber(error, "code", "GoogleDriveTokenRefresher")) {
code = error.getVal("code")->asIntegerNumber();
debug(9, "GoogleDriveTokenRefresher: code = %u", code);
}
if (jsonContainsString(error, "message", "GoogleDriveTokenRefresher")) {
message = error.getVal("message")->asString();
debug(9, "GoogleDriveTokenRefresher: message = %s", message.c_str());
}
if (code == 401 || message == "Invalid Credentials")
irrecoverable = false;
if (irrecoverable) {
finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpResponseCode));
delete json;
return;
}
pause();
delete json;
_parentStorage->refreshAccessToken(new Common::Callback<GoogleDriveTokenRefresher, const Storage::BoolResponse &>(this, &GoogleDriveTokenRefresher::tokenRefreshed));
return;
}
}
//notify user of success
HttpJsonRequest::finishJson(json);
}
} // End of namespace GoogleDrive
} // End of namespace Cloud

View File

@@ -0,0 +1,47 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVETOKENREFRESHER_H
#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVETOKENREFRESHER_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace GoogleDrive {
class GoogleDriveStorage;
class GoogleDriveTokenRefresher: public Networking::HttpJsonRequest {
GoogleDriveStorage *_parentStorage;
void tokenRefreshed(const Storage::BoolResponse &response);
void finishJson(const Common::JSONValue *json) override;
public:
GoogleDriveTokenRefresher(GoogleDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url);
~GoogleDriveTokenRefresher() override;
};
} // End of namespace GoogleDrive
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,344 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/googledrive/googledriveuploadrequest.h"
#include "backends/cloud/googledrive/googledrivestorage.h"
#include "backends/cloud/iso8601.h"
#include "backends/cloud/storage.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
#include "googledrivetokenrefresher.h"
namespace Cloud {
namespace GoogleDrive {
#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/upload/drive/v3/files"
GoogleDriveUploadRequest::GoogleDriveUploadRequest(GoogleDriveStorage *storage, const Common::String &path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
GoogleDriveUploadRequest::~GoogleDriveUploadRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _contentsStream;
delete _uploadCallback;
}
void GoogleDriveUploadRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
if (_contentsStream == nullptr || !_contentsStream->seek(0)) {
warning("GoogleDriveUploadRequest: cannot restart because stream couldn't seek(0)");
finishError(Networking::ErrorResponse(this, false, true, "GoogleDriveUploadRequest::start: couldn't restart because failed to seek(0)", -1));
return;
}
_resolvedId = ""; //used to update file contents
_parentId = ""; //used to create file within parent directory
_serverReceivedBytes = 0;
_ignoreCallback = false;
resolveId();
}
void GoogleDriveUploadRequest::resolveId() {
//check whether such file already exists
Storage::UploadCallback innerCallback = new Common::Callback<GoogleDriveUploadRequest, const Storage::UploadResponse &>(this, &GoogleDriveUploadRequest::idResolvedCallback);
Networking::ErrorCallback innerErrorCallback = new Common::Callback<GoogleDriveUploadRequest, const Networking::ErrorResponse &>(this, &GoogleDriveUploadRequest::idResolveFailedCallback);
_workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback);
}
void GoogleDriveUploadRequest::idResolvedCallback(const Storage::UploadResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
_resolvedId = response.value.id();
startUpload();
}
void GoogleDriveUploadRequest::idResolveFailedCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
//not resolved => error or no such file
if (error.response.contains("no such file found in its parent directory")) {
//parent's id after the '\n'
Common::String parentId = error.response;
for (uint32 i = 0; i < parentId.size(); ++i)
if (parentId[i] == '\n') {
parentId.erase(0, i + 1);
break;
}
_parentId = parentId;
startUpload();
return;
}
finishError(error);
}
void GoogleDriveUploadRequest::startUpload() {
Common::String name = _savePath;
for (uint32 i = name.size(); i > 0; --i) {
if (name[i - 1] == '/' || name[i - 1] == '\\') {
name.erase(0, i);
break;
}
}
Common::String url = GOOGLEDRIVE_API_FILES;
if (_resolvedId != "")
url += "/" + Common::percentEncodeString(_resolvedId);
url += "?uploadType=resumable&fields=id,mimeType,modifiedTime,name,size";
Networking::JsonCallback callback = new Common::Callback<GoogleDriveUploadRequest, const Networking::JsonResponse &>(this, &GoogleDriveUploadRequest::startUploadCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveUploadRequest, const Networking::ErrorResponse &>(this, &GoogleDriveUploadRequest::startUploadErrorCallback);
Networking::HttpJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->addHeader("Content-Type: application/json");
if (_resolvedId != "")
request->usePatch();
Common::JSONObject jsonRequestParameters;
if (_resolvedId != "") {
jsonRequestParameters.setVal("id", new Common::JSONValue(_resolvedId));
} else {
Common::JSONArray parentsArray;
parentsArray.push_back(new Common::JSONValue(_parentId));
jsonRequestParameters.setVal("parents", new Common::JSONValue(parentsArray));
}
jsonRequestParameters.setVal("name", new Common::JSONValue(name));
Common::JSONValue value(jsonRequestParameters);
request->addPostField(Common::JSON::stringify(&value));
_workingRequest = ConnMan.addRequest(request);
}
void GoogleDriveUploadRequest::startUploadCallback(const Networking::JsonResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
Networking::ErrorResponse error(this, false, true, "GoogleDriveUploadRequest::startUploadCallback", -1);
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq) {
const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
if (stream) {
long code = stream->httpResponseCode();
if (code == 200) {
Common::HashMap<Common::String, Common::String> headers = stream->responseHeadersMap();
if (headers.contains("location")) {
_uploadUrl = headers["location"];
uploadNextPart();
return;
} else {
error.response += ": response must provide Location header, but it's not there";
}
} else {
error.response += ": response is not 200 OK";
}
error.httpResponseCode = code;
} else {
error.response += ": missing response stream [improbable]";
}
} else {
error.response += ": missing request object [improbable]";
}
const Common::JSONValue *json = response.value;
delete json;
finishError(error);
}
void GoogleDriveUploadRequest::startUploadErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishError(error);
}
void GoogleDriveUploadRequest::uploadNextPart() {
const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024;
Common::String url = _uploadUrl;
Networking::JsonCallback callback = new Common::Callback<GoogleDriveUploadRequest, const Networking::JsonResponse &>(this, &GoogleDriveUploadRequest::partUploadedCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveUploadRequest, const Networking::ErrorResponse &>(this, &GoogleDriveUploadRequest::partUploadedErrorCallback);
Networking::HttpJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->usePut();
uint32 oldPos = _contentsStream->pos();
if (oldPos != _serverReceivedBytes) {
if (!_contentsStream->seek(_serverReceivedBytes)) {
warning("GoogleDriveUploadRequest: cannot upload because stream couldn't seek(%llu)", (unsigned long long)_serverReceivedBytes);
finishError(Networking::ErrorResponse(this, false, true, "GoogleDriveUploadRequest::uploadNextPart: seek() didn't work", -1));
return;
}
oldPos = _serverReceivedBytes;
}
byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST];
uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST);
if (size != 0)
request->setBuffer(buffer, size);
if (_uploadUrl != "") {
if (_contentsStream->pos() == 0)
request->addHeader(Common::String::format("Content-Length: 0"));
else
request->addHeader(Common::String::format("Content-Range: bytes %u-%lu/%lu", oldPos, long(_contentsStream->pos() - 1), long(_contentsStream->size())));
}
_workingRequest = ConnMan.addRequest(request);
}
bool GoogleDriveUploadRequest::handleHttp308(const Networking::NetworkReadStream *stream) {
//308 Resume Incomplete, with Range: X-Y header
if (!stream)
return false;
if (stream->httpResponseCode() != 308)
return false; //seriously
Common::HashMap<Common::String, Common::String> headers = stream->responseHeadersMap();
if (headers.contains("range")) {
Common::String range = headers["range"];
for (int rangeTry = 0; rangeTry < 2; ++rangeTry) {
const char *needle = (rangeTry == 0 ? "0-" : "bytes=0-"); //if it lost the first part, I refuse to talk with it
uint32 needleLength = (rangeTry == 0 ? 2 : 8);
if (range.hasPrefix(needle)) {
range.erase(0, needleLength);
_serverReceivedBytes = range.asUint64() + 1;
uploadNextPart();
return true;
}
}
}
return false;
}
void GoogleDriveUploadRequest::partUploadedCallback(const Networking::JsonResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
Networking::ErrorResponse error(this, false, true, "", -1);
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq) {
const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
if (stream) {
long code = stream->httpResponseCode();
error.httpResponseCode = code;
if (code == 308 && handleHttp308(stream)) {
delete response.value;
return;
}
}
}
const Common::JSONValue *json = response.value;
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
if (json->isObject()) {
Common::JSONObject object = json->asObject();
if (object.contains("error")) {
warning("GoogleDrive returned error: %s", json->stringify(true).c_str());
error.response = json->stringify(true);
finishError(error);
delete json;
return;
}
if (Networking::HttpJsonRequest::jsonContainsString(object, "id", "GoogleDriveUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsString(object, "name", "GoogleDriveUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsString(object, "mimeType", "GoogleDriveUploadRequest")) {
//finished
Common::String id = object.getVal("id")->asString();
Common::String name = object.getVal("name")->asString();
bool isDirectory = (object.getVal("mimeType")->asString() == "application/vnd.google-apps.folder");
uint32 size = 0, timestamp = 0;
if (Networking::HttpJsonRequest::jsonContainsString(object, "size", "GoogleDriveUploadRequest", true))
size = object.getVal("size")->asString().asUint64();
if (Networking::HttpJsonRequest::jsonContainsString(object, "modifiedTime", "GoogleDriveUploadRequest", true))
timestamp = ISO8601::convertToTimestamp(object.getVal("modifiedTime")->asString());
finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory));
return;
}
}
if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) {
warning("GoogleDriveUploadRequest: no file info to return");
finishUpload(StorageFile(_savePath, 0, 0, false));
} else {
uploadNextPart();
}
delete json;
}
void GoogleDriveUploadRequest::partUploadedErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
Networking::HttpJsonRequest *rq = (Networking::HttpJsonRequest *)error.request;
if (rq) {
const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
if (stream) {
long code = stream->httpResponseCode();
if (code == 308 && handleHttp308(stream)) {
return;
}
}
}
finishError(error);
}
void GoogleDriveUploadRequest::handle() {}
void GoogleDriveUploadRequest::restart() { start(); }
void GoogleDriveUploadRequest::finishUpload(const StorageFile &file) {
Request::finishSuccess();
if (_uploadCallback)
(*_uploadCallback)(Storage::UploadResponse(this, file));
}
} // End of namespace GoogleDrive
} // End of namespace Cloud

View File

@@ -0,0 +1,69 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVEUPLOADREQUEST_H
#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVEUPLOADREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace GoogleDrive {
class GoogleDriveStorage;
class GoogleDriveUploadRequest: public Networking::Request {
GoogleDriveStorage *_storage;
Common::String _savePath;
Common::SeekableReadStream *_contentsStream;
Storage::UploadCallback _uploadCallback;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _resolvedId, _parentId;
Common::String _uploadUrl;
uint64 _serverReceivedBytes;
void start();
void resolveId();
void idResolvedCallback(const Storage::UploadResponse &response);
void idResolveFailedCallback(const Networking::ErrorResponse &error);
void startUpload();
void startUploadCallback(const Networking::JsonResponse &response);
void startUploadErrorCallback(const Networking::ErrorResponse &error);
void uploadNextPart();
void partUploadedCallback(const Networking::JsonResponse &response);
void partUploadedErrorCallback(const Networking::ErrorResponse &error);
bool handleHttp308(const Networking::NetworkReadStream *stream);
void finishUpload(const StorageFile &status);
public:
GoogleDriveUploadRequest(GoogleDriveStorage *storage, const Common::String &path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
~GoogleDriveUploadRequest() override;
void handle() override;
void restart() override;
};
} // End of namespace GoogleDrive
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,162 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/id/idcreatedirectoryrequest.h"
#include "backends/cloud/id/idstorage.h"
#include "common/debug.h"
namespace Cloud {
namespace Id {
IdCreateDirectoryRequest::IdCreateDirectoryRequest(IdStorage *storage, const Common::String &parentPath, const Common::String &directoryName, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb),
_requestedParentPath(parentPath), _requestedDirectoryName(directoryName), _storage(storage), _boolCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
IdCreateDirectoryRequest::~IdCreateDirectoryRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _boolCallback;
}
void IdCreateDirectoryRequest::start() {
//cleanup
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_workingRequest = nullptr;
_ignoreCallback = false;
//the only exception when we create parent folder - is when it's ScummVM/ base folder
Common::String prefix = _requestedParentPath;
if (prefix.size() > 7)
prefix.erase(7);
if (prefix.equalsIgnoreCase("ScummVM")) {
Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, const Storage::BoolResponse &>(this, &IdCreateDirectoryRequest::createdBaseDirectoryCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, const Networking::ErrorResponse &>(this, &IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback);
_workingRequest = _storage->createDirectory("ScummVM", callback, failureCallback);
return;
}
resolveId();
}
void IdCreateDirectoryRequest::createdBaseDirectoryCallback(const Storage::BoolResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (response.request)
_date = response.request->date();
resolveId();
}
void IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void IdCreateDirectoryRequest::resolveId() {
//check whether such folder already exists
Storage::UploadCallback innerCallback = new Common::Callback<IdCreateDirectoryRequest, const Storage::UploadResponse &>(this, &IdCreateDirectoryRequest::idResolvedCallback);
Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdCreateDirectoryRequest, const Networking::ErrorResponse &>(this, &IdCreateDirectoryRequest::idResolveFailedCallback);
Common::String path = _requestedParentPath;
if (_requestedParentPath != "")
path += "/";
path += _requestedDirectoryName;
_workingRequest = _storage->resolveFileId(path, innerCallback, innerErrorCallback);
}
void IdCreateDirectoryRequest::idResolvedCallback(const Storage::UploadResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (response.request)
_date = response.request->date();
//resolved => folder already exists
finishCreation(false);
}
void IdCreateDirectoryRequest::idResolveFailedCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
//not resolved => folder not exists
if (error.response.contains("no such file found in its parent directory")) {
//parent's id after the '\n'
Common::String parentId = error.response;
for (uint32 i = 0; i < parentId.size(); ++i)
if (parentId[i] == '\n') {
parentId.erase(0, i + 1);
break;
}
Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, const Storage::BoolResponse &>(this, &IdCreateDirectoryRequest::createdDirectoryCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, const Networking::ErrorResponse &>(this, &IdCreateDirectoryRequest::createdDirectoryErrorCallback);
_workingRequest = _storage->createDirectoryWithParentId(parentId, _requestedDirectoryName, callback, failureCallback);
return;
}
finishError(error);
}
void IdCreateDirectoryRequest::createdDirectoryCallback(const Storage::BoolResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (response.request)
_date = response.request->date();
finishCreation(response.value);
}
void IdCreateDirectoryRequest::createdDirectoryErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void IdCreateDirectoryRequest::handle() {}
void IdCreateDirectoryRequest::restart() { start(); }
Common::String IdCreateDirectoryRequest::date() const { return _date; }
void IdCreateDirectoryRequest::finishCreation(bool success) {
Request::finishSuccess();
if (_boolCallback)
(*_boolCallback)(Storage::BoolResponse(this, success));
}
} // End of namespace Id
} // End of namespace Cloud

View File

@@ -0,0 +1,64 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ID_IDCREATEDIRECTORYREQUEST_H
#define BACKENDS_CLOUD_ID_IDCREATEDIRECTORYREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace Id {
class IdStorage;
class IdCreateDirectoryRequest: public Networking::Request {
Common::String _requestedParentPath;
Common::String _requestedDirectoryName;
IdStorage *_storage;
Storage::BoolCallback _boolCallback;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _date;
void start();
void createdBaseDirectoryCallback(const Storage::BoolResponse &response);
void createdBaseDirectoryErrorCallback(const Networking::ErrorResponse &error);
void resolveId();
void idResolvedCallback(const Storage::UploadResponse &response);
void idResolveFailedCallback(const Networking::ErrorResponse &error);
void createdDirectoryCallback(const Storage::BoolResponse &response);
void createdDirectoryErrorCallback(const Networking::ErrorResponse &error);
void finishCreation(bool success);
public:
IdCreateDirectoryRequest(IdStorage *storage, const Common::String &parentPath, const Common::String &directoryName, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
~IdCreateDirectoryRequest() override;
void handle() override;
void restart() override;
Common::String date() const override;
};
} // End of namespace Id
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,107 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/id/iddownloadrequest.h"
#include "backends/cloud/id/idstorage.h"
#include "backends/cloud/downloadrequest.h"
namespace Cloud {
namespace Id {
IdDownloadRequest::IdDownloadRequest(IdStorage *storage, const Common::String &remotePath, const Common::Path &localPath, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _requestedFile(remotePath), _requestedLocalFile(localPath), _storage(storage), _boolCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
IdDownloadRequest::~IdDownloadRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _boolCallback;
}
void IdDownloadRequest::start() {
//cleanup
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_workingRequest = nullptr;
_ignoreCallback = false;
//find file's id
Storage::UploadCallback innerCallback = new Common::Callback<IdDownloadRequest, const Storage::UploadResponse &>(this, &IdDownloadRequest::idResolvedCallback);
Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdDownloadRequest, const Networking::ErrorResponse &>(this, &IdDownloadRequest::idResolveFailedCallback);
_workingRequest = _storage->resolveFileId(_requestedFile, innerCallback, innerErrorCallback);
}
void IdDownloadRequest::idResolvedCallback(const Storage::UploadResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
Storage::BoolCallback innerCallback = new Common::Callback<IdDownloadRequest, const Storage::BoolResponse &>(this, &IdDownloadRequest::downloadCallback);
Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdDownloadRequest, const Networking::ErrorResponse &>(this, &IdDownloadRequest::downloadErrorCallback);
_workingRequest = _storage->downloadById(response.value.id(), _requestedLocalFile, innerCallback, innerErrorCallback);
}
void IdDownloadRequest::idResolveFailedCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishError(error);
}
void IdDownloadRequest::downloadCallback(const Storage::BoolResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishDownload(response.value);
}
void IdDownloadRequest::downloadErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishError(error);
}
void IdDownloadRequest::handle() {}
void IdDownloadRequest::restart() { start(); }
void IdDownloadRequest::finishDownload(bool success) {
Request::finishSuccess();
if (_boolCallback)
(*_boolCallback)(Storage::BoolResponse(this, success));
}
double IdDownloadRequest::getProgress() const {
DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest);
if (downloadRequest == nullptr)
return 0; // resolving id still
// id resolve is 10 % and download is the other 90 %
return 0.1 + 0.9 * downloadRequest->getProgress(); // downloading
}
} // End of namespace Id
} // End of namespace Cloud

View File

@@ -0,0 +1,62 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ID_IDDOWNLOADREQUEST_H
#define BACKENDS_CLOUD_ID_IDDOWNLOADREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace Id {
class IdStorage;
class IdDownloadRequest: public Networking::Request {
Common::String _requestedFile;
Common::Path _requestedLocalFile;
IdStorage *_storage;
Storage::BoolCallback _boolCallback;
Request *_workingRequest;
bool _ignoreCallback;
void start();
void idResolvedCallback(const Storage::UploadResponse &response);
void idResolveFailedCallback(const Networking::ErrorResponse &error);
void downloadCallback(const Storage::BoolResponse &response);
void downloadErrorCallback(const Networking::ErrorResponse &error);
void finishDownload(bool success);
public:
IdDownloadRequest(IdStorage *storage, const Common::String &remotePath, const Common::Path &localPath, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
~IdDownloadRequest() override;
void handle() override;
void restart() override;
/** Returns a number in range [0, 1], where 1 is "complete". */
double getProgress() const;
};
} // End of namespace Id
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,140 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/id/idlistdirectoryrequest.h"
#include "backends/cloud/id/idstorage.h"
namespace Cloud {
namespace Id {
IdListDirectoryRequest::IdListDirectoryRequest(IdStorage *storage, const Common::String &path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive):
Networking::Request(nullptr, ecb),
_requestedPath(path), _requestedRecursive(recursive), _storage(storage), _listDirectoryCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
IdListDirectoryRequest::~IdListDirectoryRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _listDirectoryCallback;
}
void IdListDirectoryRequest::start() {
//cleanup
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_workingRequest = nullptr;
_files.clear();
_directoriesQueue.clear();
_currentDirectory = StorageFile();
_ignoreCallback = false;
//find out that directory's id
Storage::UploadCallback innerCallback = new Common::Callback<IdListDirectoryRequest, const Storage::UploadResponse &>(this, &IdListDirectoryRequest::idResolvedCallback);
Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdListDirectoryRequest, const Networking::ErrorResponse &>(this, &IdListDirectoryRequest::idResolveErrorCallback);
_workingRequest = _storage->resolveFileId(_requestedPath, innerCallback, innerErrorCallback);
}
void IdListDirectoryRequest::idResolvedCallback(const Storage::UploadResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (response.request)
_date = response.request->date();
StorageFile directory = response.value;
directory.setPath(_requestedPath);
_directoriesQueue.push_back(directory);
listNextDirectory();
}
void IdListDirectoryRequest::idResolveErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void IdListDirectoryRequest::listNextDirectory() {
if (_directoriesQueue.empty()) {
finishListing(_files);
return;
}
_currentDirectory = _directoriesQueue.back();
_directoriesQueue.pop_back();
Storage::FileArrayCallback callback = new Common::Callback<IdListDirectoryRequest, const Storage::FileArrayResponse &>(this, &IdListDirectoryRequest::listedDirectoryCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<IdListDirectoryRequest, const Networking::ErrorResponse &>(this, &IdListDirectoryRequest::listedDirectoryErrorCallback);
_workingRequest = _storage->listDirectoryById(_currentDirectory.id(), callback, failureCallback);
}
void IdListDirectoryRequest::listedDirectoryCallback(const Storage::FileArrayResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (response.request)
_date = response.request->date();
for (uint32 i = 0; i < response.value.size(); ++i) {
StorageFile file = response.value[i];
Common::String path = _currentDirectory.path();
if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\')
path += '/';
path += file.name();
file.setPath(path);
_files.push_back(file);
if (_requestedRecursive && file.isDirectory()) {
_directoriesQueue.push_back(file);
}
}
listNextDirectory();
}
void IdListDirectoryRequest::listedDirectoryErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void IdListDirectoryRequest::handle() {}
void IdListDirectoryRequest::restart() { start(); }
Common::String IdListDirectoryRequest::date() const { return _date; }
void IdListDirectoryRequest::finishListing(const Common::Array<StorageFile> &files) {
Request::finishSuccess();
if (_listDirectoryCallback)
(*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
}
} // End of namespace Id
} // End of namespace Cloud

View File

@@ -0,0 +1,65 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ID_IDLISTDIRECTORYREQUEST_H
#define BACKENDS_CLOUD_ID_IDLISTDIRECTORYREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace Id {
class IdStorage;
class IdListDirectoryRequest: public Networking::Request {
Common::String _requestedPath;
bool _requestedRecursive;
IdStorage *_storage;
Storage::ListDirectoryCallback _listDirectoryCallback;
Common::Array<StorageFile> _files;
Common::Array<StorageFile> _directoriesQueue;
StorageFile _currentDirectory;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _date;
void start();
void idResolvedCallback(const Storage::UploadResponse &response);
void idResolveErrorCallback(const Networking::ErrorResponse &error);
void listNextDirectory();
void listedDirectoryCallback(const Storage::FileArrayResponse &response);
void listedDirectoryErrorCallback(const Networking::ErrorResponse &error);
void finishListing(const Common::Array<StorageFile> &files);
public:
IdListDirectoryRequest(IdStorage *storage, const Common::String &path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false);
~IdListDirectoryRequest() override;
void handle() override;
void restart() override;
Common::String date() const override;
};
} // End of namespace Id
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,135 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/id/idresolveidrequest.h"
#include "backends/cloud/id/idstorage.h"
namespace Cloud {
namespace Id {
IdResolveIdRequest::IdResolveIdRequest(IdStorage *storage, const Common::String &path, Storage::UploadCallback cb, Networking::ErrorCallback ecb, bool recursive):
Networking::Request(nullptr, ecb),
_requestedPath(path), _storage(storage), _uploadCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
IdResolveIdRequest::~IdResolveIdRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _uploadCallback;
}
void IdResolveIdRequest::start() {
//cleanup
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_workingRequest = nullptr;
_currentDirectory = "";
_currentDirectoryId = _storage->getRootDirectoryId();
_ignoreCallback = false;
listNextDirectory(StorageFile(_currentDirectoryId, 0, 0, true));
}
void IdResolveIdRequest::listNextDirectory(const StorageFile &fileToReturn) {
if (_currentDirectory.equalsIgnoreCase(_requestedPath)) {
finishFile(fileToReturn);
return;
}
Storage::FileArrayCallback callback = new Common::Callback<IdResolveIdRequest, const Storage::FileArrayResponse &>(this, &IdResolveIdRequest::listedDirectoryCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<IdResolveIdRequest, const Networking::ErrorResponse &>(this, &IdResolveIdRequest::listedDirectoryErrorCallback);
_workingRequest = _storage->listDirectoryById(_currentDirectoryId, callback, failureCallback);
}
void IdResolveIdRequest::listedDirectoryCallback(const Storage::FileArrayResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
Common::String currentLevelName = _requestedPath;
///debug(9, "'%s'", currentLevelName.c_str());
if (_currentDirectory.size())
currentLevelName.erase(0, _currentDirectory.size());
if (currentLevelName.size() && (currentLevelName[0] == '/' || currentLevelName[0] == '\\'))
currentLevelName.erase(0, 1);
///debug(9, "'%s'", currentLevelName.c_str());
for (uint32 i = 0; i < currentLevelName.size(); ++i) {
if (currentLevelName[i] == '/' || currentLevelName[i] == '\\') {
currentLevelName.erase(i);
///debug(9, "'%s'", currentLevelName.c_str());
break;
}
}
Common::String path = _currentDirectory;
if (path != "")
path += "/";
path += currentLevelName;
bool lastLevel = (path.equalsIgnoreCase(_requestedPath));
///debug(9, "IdResolveIdRequest: searching for '%s' in '%s'", currentLevelName.c_str(), _currentDirectory.c_str());
const Common::Array<StorageFile> &files = response.value;
bool found = false;
for (uint32 i = 0; i < files.size(); ++i) {
if ((files[i].isDirectory() || lastLevel) && files[i].name().equalsIgnoreCase(currentLevelName)) {
if (_currentDirectory != "")
_currentDirectory += "/";
_currentDirectory += files[i].name();
_currentDirectoryId = files[i].id();
///debug(9, "IdResolveIdRequest: found it! new directory and its id: '%s', '%s'", _currentDirectory.c_str(), _currentDirectoryId.c_str());
listNextDirectory(files[i]);
found = true;
break;
}
}
if (!found) {
if (lastLevel)
finishError(Networking::ErrorResponse(this, false, true, Common::String("no such file found in its parent directory\n") + _currentDirectoryId, 404));
else
finishError(Networking::ErrorResponse(this, false, true, "subdirectory not found", 400));
}
}
void IdResolveIdRequest::listedDirectoryErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishError(error);
}
void IdResolveIdRequest::handle() {}
void IdResolveIdRequest::restart() { start(); }
void IdResolveIdRequest::finishFile(const StorageFile &file) {
Request::finishSuccess();
if (_uploadCallback)
(*_uploadCallback)(Storage::UploadResponse(this, file));
}
} // End of namespace Id
} // End of namespace Cloud

View File

@@ -0,0 +1,59 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ID_IDRESOLVEIDREQUEST_H
#define BACKENDS_CLOUD_ID_IDRESOLVEIDREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace Id {
class IdStorage;
class IdResolveIdRequest: public Networking::Request {
Common::String _requestedPath;
IdStorage *_storage;
Storage::UploadCallback _uploadCallback;
Common::String _currentDirectory;
Common::String _currentDirectoryId;
Request *_workingRequest;
bool _ignoreCallback;
void start();
void listNextDirectory(const StorageFile &fileToReturn);
void listedDirectoryCallback(const Storage::FileArrayResponse &response);
void listedDirectoryErrorCallback(const Networking::ErrorResponse &error);
void finishFile(const StorageFile &file);
public:
IdResolveIdRequest(IdStorage *storage, const Common::String &path, Storage::UploadCallback cb, Networking::ErrorCallback ecb, bool recursive = false); //TODO: why upload?
~IdResolveIdRequest() override;
void handle() override;
void restart() override;
};
} // End of namespace Id
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,112 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/id/idstorage.h"
#include "backends/cloud/id/idcreatedirectoryrequest.h"
#include "backends/cloud/id/iddownloadrequest.h"
#include "backends/cloud/id/idlistdirectoryrequest.h"
#include "backends/cloud/id/idresolveidrequest.h"
#include "backends/cloud/id/idstreamfilerequest.h"
#include "common/debug.h"
namespace Cloud {
namespace Id {
IdStorage::IdStorage() {}
IdStorage::IdStorage(const Common::String &token, const Common::String &refreshToken, bool enabled):
BaseStorage(token, refreshToken, enabled) {}
IdStorage::~IdStorage() {}
void IdStorage::printFiles(const FileArrayResponse &response) {
debug(9, "IdStorage: files:");
const Common::Array<StorageFile> &files = response.value;
for (uint32 i = 0; i < files.size(); ++i) {
debug(9, "\t%s%s", files[i].name().c_str(), files[i].isDirectory() ? " (directory)" : "");
debug(9, "\t%s", files[i].path().c_str());
debug(9, "\t%s", files[i].id().c_str());
debug(9, " ");
}
}
void IdStorage::printBool(const BoolResponse &response) {
debug(9, "IdStorage: bool: %s", response.value ? "true" : "false");
}
void IdStorage::printFile(const UploadResponse &response) {
debug(9, "\nIdStorage: uploaded file info:");
debug(9, "\tid: %s", response.value.path().c_str());
debug(9, "\tname: %s", response.value.name().c_str());
debug(9, "\tsize: %u", response.value.size());
debug(9, "\ttimestamp: %u", response.value.timestamp());
}
Storage::ListDirectoryCallback IdStorage::getPrintFilesCallback() {
return new Common::Callback<IdStorage, const FileArrayResponse &>(this, &IdStorage::printFiles);
}
Networking::Request *IdStorage::resolveFileId(const Common::String &path, UploadCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
if (!callback)
callback = new Common::Callback<IdStorage, const UploadResponse &>(this, &IdStorage::printFile);
return addRequest(new IdResolveIdRequest(this, path, callback, errorCallback));
}
Networking::Request *IdStorage::listDirectory(const Common::String &path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
if (!callback)
callback = new Common::Callback<IdStorage, const FileArrayResponse &>(this, &IdStorage::printFiles);
return addRequest(new IdListDirectoryRequest(this, path, callback, errorCallback, recursive));
}
Networking::Request *IdStorage::createDirectory(const Common::String &path, BoolCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
if (!callback)
callback = new Common::Callback<IdStorage, const BoolResponse &>(this, &IdStorage::printBool);
//find out the parent path and directory name
Common::String parentPath = "", directoryName = path;
for (uint32 i = path.size(); i > 0; --i) {
if (path[i - 1] == '/' || path[i - 1] == '\\') {
parentPath = path;
parentPath.erase(i - 1);
directoryName.erase(0, i);
break;
}
}
return addRequest(new IdCreateDirectoryRequest(this, parentPath, directoryName, callback, errorCallback));
}
Networking::Request *IdStorage::streamFile(const Common::String &path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) {
return addRequest(new IdStreamFileRequest(this, path, outerCallback, errorCallback));
}
Networking::Request *IdStorage::download(const Common::String &remotePath, const Common::Path &localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) {
return addRequest(new IdDownloadRequest(this, remotePath, localPath, callback, errorCallback));
}
} // End of namespace Id
} // End of namespace Cloud

View File

@@ -0,0 +1,84 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ID_IDSTORAGE_H
#define BACKENDS_CLOUD_ID_IDSTORAGE_H
#include "backends/cloud/basestorage.h"
#include "backends/networking/http/httpjsonrequest.h"
/*
* Id::IdStorage is a special base class, which is created
* to simplify adding new storages which use ids instead of
* paths in their API.
*
* Some Requests are already implemented, and Storage based
* on IdStorage needs to override/implement a few basic things.
*
* For example, ListDirectoryRequest and ResolveIdRequests are
* based on listDirectoryById() and getRootDirectoryId() methods.
* Implementing these you'll get id resolving and directory
* listing by path.
*/
namespace Cloud {
namespace Id {
class IdStorage: public Cloud::BaseStorage {
protected:
void printFiles(const FileArrayResponse &response);
void printBool(const BoolResponse &response);
void printFile(const UploadResponse &response);
ListDirectoryCallback getPrintFilesCallback();
public:
IdStorage();
IdStorage(const Common::String &token, const Common::String &refreshToken, bool enabled);
~IdStorage() override;
/** Public Cloud API comes down there. */
/** Returns StorageFile with the resolved file's id. */
virtual Networking::Request *resolveFileId(const Common::String &path, UploadCallback callback, Networking::ErrorCallback errorCallback);
/** Returns ListDirectoryStatus struct with list of files. */
Networking::Request *listDirectory(const Common::String &path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false) override;
virtual Networking::Request *listDirectoryById(const Common::String &id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) = 0;
/** Calls the callback when finished. */
Networking::Request *createDirectory(const Common::String &path, BoolCallback callback, Networking::ErrorCallback errorCallback) override;
virtual Networking::Request *createDirectoryWithParentId(const Common::String &parentId, const Common::String &name, BoolCallback callback, Networking::ErrorCallback errorCallback) = 0;
/** Returns pointer to Networking::NetworkReadStream. */
Networking::Request *streamFile(const Common::String &path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) override;
virtual Networking::Request *streamFileById(const Common::String &id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) override = 0;
/** Calls the callback when finished. */
Networking::Request *download(const Common::String &remotePath, const Common::Path &localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) override;
virtual Common::String getRootDirectoryId() = 0;
};
} // End of namespace Id
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,97 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/id/idstreamfilerequest.h"
#include "backends/cloud/id/idstorage.h"
namespace Cloud {
namespace Id {
IdStreamFileRequest::IdStreamFileRequest(IdStorage *storage, const Common::String &path, Networking::NetworkReadStreamCallback cb, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _requestedFile(path), _storage(storage), _streamCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
IdStreamFileRequest::~IdStreamFileRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _streamCallback;
}
void IdStreamFileRequest::start() {
//cleanup
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_workingRequest = nullptr;
_ignoreCallback = false;
//find file's id
Storage::UploadCallback innerCallback = new Common::Callback<IdStreamFileRequest, const Storage::UploadResponse &>(this, &IdStreamFileRequest::idResolvedCallback);
Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdStreamFileRequest, const Networking::ErrorResponse &>(this, &IdStreamFileRequest::idResolveFailedCallback);
_workingRequest = _storage->resolveFileId(_requestedFile, innerCallback, innerErrorCallback);
}
void IdStreamFileRequest::idResolvedCallback(const Storage::UploadResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
Networking::NetworkReadStreamCallback innerCallback = new Common::Callback<IdStreamFileRequest, const Networking::NetworkReadStreamResponse &>(this, &IdStreamFileRequest::streamFileCallback);
Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdStreamFileRequest, const Networking::ErrorResponse &>(this, &IdStreamFileRequest::streamFileErrorCallback);
_workingRequest = _storage->streamFileById(response.value.id(), innerCallback, innerErrorCallback);
}
void IdStreamFileRequest::idResolveFailedCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishError(error);
}
void IdStreamFileRequest::streamFileCallback(const Networking::NetworkReadStreamResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishStream(response.value);
}
void IdStreamFileRequest::streamFileErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishError(error);
}
void IdStreamFileRequest::handle() {}
void IdStreamFileRequest::restart() { start(); }
void IdStreamFileRequest::finishStream(Networking::NetworkReadStream *stream) {
Request::finishSuccess();
if (_streamCallback)
(*_streamCallback)(Networking::NetworkReadStreamResponse(this, stream));
}
} // End of namespace Id
} // End of namespace Cloud

View File

@@ -0,0 +1,58 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ID_IDSTREAMFILEREQUEST_H
#define BACKENDS_CLOUD_ID_IDSTREAMFILEREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace Id {
class IdStorage;
class IdStreamFileRequest: public Networking::Request {
Common::String _requestedFile;
IdStorage *_storage;
Networking::NetworkReadStreamCallback _streamCallback;
Request *_workingRequest;
bool _ignoreCallback;
void start();
void idResolvedCallback(const Storage::UploadResponse &response);
void idResolveFailedCallback(const Networking::ErrorResponse &error);
void streamFileCallback(const Networking::NetworkReadStreamResponse &response);
void streamFileErrorCallback(const Networking::ErrorResponse &error);
void finishStream(Networking::NetworkReadStream *stream);
public:
IdStreamFileRequest(IdStorage *storage, const Common::String &path, Networking::NetworkReadStreamCallback cb, Networking::ErrorCallback ecb);
~IdStreamFileRequest() override;
void handle() override;
void restart() override;
};
} // End of namespace Id
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,99 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/iso8601.h"
#include "common/str.h"
namespace {
Common::String getSubstring(const Common::String &s, uint32 beginning, uint32 ending) {
//beginning inclusive, ending exclusive
Common::String result = s;
result.erase(ending);
result.erase(0, beginning);
return result;
}
int find(const char *cstr, uint32 startPosition, char needle) {
const char *res = strchr(cstr + startPosition, needle);
if (res == nullptr)
return -1;
return res - cstr;
}
}
namespace Cloud {
namespace ISO8601 {
uint32 convertToTimestamp(const Common::String &iso8601Date) {
//2015-05-12T15:50:38Z
const char *cstr = iso8601Date.c_str();
int firstHyphen = find(cstr, 0, '-');
int secondHyphen = find(cstr, firstHyphen + 1, '-');
int tSeparator = find(cstr, secondHyphen + 1, 'T');
int firstColon = find(cstr, tSeparator + 1, ':');
int secondColon = find(cstr, firstColon + 1, ':');
int zSeparator = find(cstr, secondColon + 1, 'Z');
if (zSeparator == -1)
zSeparator = find(cstr, secondColon + 1, '-'); // Box's RFC 3339
//now note '+1' which means if there ever was '-1' result of find(), we still did a valid find() from 0th char
Common::String year = getSubstring(iso8601Date, 0, firstHyphen);
Common::String month = getSubstring(iso8601Date, firstHyphen + 1, secondHyphen);
Common::String day = getSubstring(iso8601Date, secondHyphen + 1, tSeparator);
Common::String hour = getSubstring(iso8601Date, tSeparator + 1, firstColon);
Common::String minute = getSubstring(iso8601Date, firstColon + 1, secondColon);
Common::String second = getSubstring(iso8601Date, secondColon + 1, zSeparator);
//now note only 'ending' argument was not '+1' (which means I could've make that function such that -1 means 'until the end')
int Y = atoi(year.c_str());
int M = atoi(month.c_str());
int D = atoi(day.c_str());
int h = atoi(hour.c_str());
int m = atoi(minute.c_str());
int s = atoi(second.c_str());
//ok, now I compose a timestamp based on my basic perception of time/date
//yeah, I know about leap years and leap seconds and all, but still we don't care there
uint32 days = D - 1;
for (int i = 1970; i < Y; ++i)
if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0))
days += 366;
else
days += 365;
int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
for (int i = 1; i < M; ++i) {
days += mdays[i - 1];
if (i == 2)
if ((Y % 4 == 0 && Y % 100 != 0) || (Y % 400 == 0))
days += 1;
}
uint32 hours = days * 24 + h;
uint32 minutes = hours * 60 + m;
return minutes * 60 + s;
}
} // End of namespace ISO8601
} // End of namespace Cloud

36
backends/cloud/iso8601.h Normal file
View File

@@ -0,0 +1,36 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ISO8601_H
#define BACKENDS_CLOUD_ISO8601_H
#include "common/str.h"
namespace Cloud {
namespace ISO8601 {
/** Returns timestamp corresponding to given ISO 8601 date */
uint32 convertToTimestamp(const Common::String &iso8601Date);
} // End of namespace ISO8601
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,149 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/onedrive/onedrivecreatedirectoryrequest.h"
#include "backends/cloud/onedrive/onedrivestorage.h"
#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
namespace Cloud {
namespace OneDrive {
#define ONEDRIVE_API_SPECIAL_APPROOT "https://graph.microsoft.com/v1.0/drive/special/approot"
OneDriveCreateDirectoryRequest::OneDriveCreateDirectoryRequest(OneDriveStorage *storage, const Common::String &path, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _storage(storage), _path(path), _boolCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
OneDriveCreateDirectoryRequest::~OneDriveCreateDirectoryRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _boolCallback;
}
void OneDriveCreateDirectoryRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_ignoreCallback = false;
Common::String name = _path, parent = _path;
if (name.size() != 0) {
uint32 i = name.size() - 1;
while (true) {
parent.deleteLastChar();
if (name[i] == '/' || name[i] == '\\') {
name.erase(0, i + 1);
break;
}
if (i == 0)
break;
--i;
}
}
Common::String url = ONEDRIVE_API_SPECIAL_APPROOT;
if (parent != "")
url += ":/" + Common::percentEncodeString(parent) + ":";
url += "/children";
Networking::JsonCallback innerCallback = new Common::Callback<OneDriveCreateDirectoryRequest, const Networking::JsonResponse &>(this, &OneDriveCreateDirectoryRequest::responseCallback);
Networking::ErrorCallback errorResponseCallback = new Common::Callback<OneDriveCreateDirectoryRequest, const Networking::ErrorResponse &>(this, &OneDriveCreateDirectoryRequest::errorCallback);
Networking::HttpJsonRequest *request = new OneDriveTokenRefresher(_storage, innerCallback, errorResponseCallback, url.c_str());
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->addHeader("Content-Type: application/json");
Common::JSONObject jsonRequestParameters;
jsonRequestParameters.setVal("name", new Common::JSONValue(name));
jsonRequestParameters.setVal("folder", new Common::JSONValue(Common::JSONObject()));
Common::JSONValue value(jsonRequestParameters);
request->addPostField(Common::JSON::stringify(&value));
_workingRequest = ConnMan.addRequest(request);
}
void OneDriveCreateDirectoryRequest::responseCallback(const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
_workingRequest = nullptr;
if (_ignoreCallback) {
delete json;
return;
}
if (response.request)
_date = response.request->date();
Networking::ErrorResponse error(this, "OneDriveCreateDirectoryRequest::responseCallback: unknown error");
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
if (!json->isObject()) {
error.response = "Passed JSON is not an object!";
finishError(error);
delete json;
return;
}
Common::JSONObject info = json->asObject();
if (info.contains("id")) {
finishCreation(true);
} else {
error.response = json->stringify(true);
finishError(error);
}
delete json;
}
void OneDriveCreateDirectoryRequest::errorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void OneDriveCreateDirectoryRequest::handle() {}
void OneDriveCreateDirectoryRequest::restart() { start(); }
Common::String OneDriveCreateDirectoryRequest::date() const { return _date; }
void OneDriveCreateDirectoryRequest::finishCreation(bool success) {
Request::finishSuccess();
if (_boolCallback)
(*_boolCallback)(Storage::BoolResponse(this, success));
}
} // End of namespace OneDrive
} // End of namespace Cloud

View File

@@ -0,0 +1,58 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVECREATEDIRECTORYREQUEST_H
#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVECREATEDIRECTORYREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/request.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace OneDrive {
class OneDriveStorage;
class OneDriveCreateDirectoryRequest: public Networking::Request {
OneDriveStorage *_storage;
Common::String _path;
Storage::BoolCallback _boolCallback;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _date;
void start();
void responseCallback(const Networking::JsonResponse &response);
void errorCallback(const Networking::ErrorResponse &error);
void finishCreation(bool success);
public:
OneDriveCreateDirectoryRequest(OneDriveStorage *storage, const Common::String &path, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
~OneDriveCreateDirectoryRequest() override;
void handle() override;
void restart() override;
Common::String date() const override;
};
} // End of namespace OneDrive
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,194 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/onedrive/onedrivelistdirectoryrequest.h"
#include "backends/cloud/onedrive/onedrivestorage.h"
#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
#include "backends/cloud/iso8601.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
namespace Cloud {
namespace OneDrive {
#define ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN "https://graph.microsoft.com/v1.0/drive/special/approot:/%s:/children"
#define ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN_ROOT_ITSELF "https://graph.microsoft.com/v1.0/drive/special/approot/children"
OneDriveListDirectoryRequest::OneDriveListDirectoryRequest(OneDriveStorage *storage, const Common::String &path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive):
Networking::Request(nullptr, ecb),
_requestedPath(path), _requestedRecursive(recursive), _storage(storage), _listDirectoryCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
OneDriveListDirectoryRequest::~OneDriveListDirectoryRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _listDirectoryCallback;
}
void OneDriveListDirectoryRequest::start() {
//cleanup
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_workingRequest = nullptr;
_files.clear();
_directoriesQueue.clear();
_currentDirectory = "";
_ignoreCallback = false;
_directoriesQueue.push_back(_requestedPath);
listNextDirectory();
}
void OneDriveListDirectoryRequest::listNextDirectory() {
if (_directoriesQueue.empty()) {
finishListing(_files);
return;
}
_currentDirectory = _directoriesQueue.back();
_directoriesQueue.pop_back();
if (_currentDirectory != "" && _currentDirectory.lastChar() != '/' && _currentDirectory.lastChar() != '\\')
_currentDirectory += '/';
Common::String dir = _currentDirectory;
dir.deleteLastChar();
Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN, Common::percentEncodeString(dir).c_str());
if (dir == "") url = Common::String(ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN_ROOT_ITSELF);
makeRequest(url);
}
void OneDriveListDirectoryRequest::makeRequest(const Common::String &url) {
Networking::JsonCallback callback = new Common::Callback<OneDriveListDirectoryRequest, const Networking::JsonResponse &>(this, &OneDriveListDirectoryRequest::listedDirectoryCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveListDirectoryRequest, const Networking::ErrorResponse &>(this, &OneDriveListDirectoryRequest::listedDirectoryErrorCallback);
Networking::HttpJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
request->addHeader("Authorization: bearer " + _storage->accessToken());
_workingRequest = ConnMan.addRequest(request);
}
void OneDriveListDirectoryRequest::listedDirectoryCallback(const Networking::JsonResponse &response) {
_workingRequest = nullptr;
const Common::JSONValue *json = response.value;
if (_ignoreCallback) {
delete json;
return;
}
if (response.request)
_date = response.request->date();
Networking::ErrorResponse error(this, "OneDriveListDirectoryRequest::listedDirectoryCallback: unknown error");
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
if (!json->isObject()) {
error.response = "Passed JSON is not an object!";
finishError(error);
delete json;
return;
}
Common::JSONObject object = json->asObject();
//check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
if (!Networking::HttpJsonRequest::jsonContainsArray(object, "value", "OneDriveListDirectoryRequest")) {
error.response = "\"value\" not found or that's not an array!";
finishError(error);
delete json;
return;
}
Common::JSONArray items = object.getVal("value")->asArray();
for (uint32 i = 0; i < items.size(); ++i) {
if (!Networking::HttpJsonRequest::jsonIsObject(items[i], "OneDriveListDirectoryRequest")) continue;
Common::JSONObject item = items[i]->asObject();
if (!Networking::HttpJsonRequest::jsonContainsAttribute(item, "folder", "OneDriveListDirectoryRequest", true)) continue;
if (!Networking::HttpJsonRequest::jsonContainsString(item, "name", "OneDriveListDirectoryRequest")) continue;
if (!Networking::HttpJsonRequest::jsonContainsIntegerNumber(item, "size", "OneDriveListDirectoryRequest")) continue;
if (!Networking::HttpJsonRequest::jsonContainsString(item, "lastModifiedDateTime", "OneDriveListDirectoryRequest")) continue;
Common::String path = _currentDirectory + item.getVal("name")->asString();
bool isDirectory = item.contains("folder");
uint32 size = item.getVal("size")->asIntegerNumber();
uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("lastModifiedDateTime")->asString());
StorageFile file(path, size, timestamp, isDirectory);
_files.push_back(file);
if (_requestedRecursive && file.isDirectory()) {
_directoriesQueue.push_back(file.path());
}
}
bool hasMore = object.contains("@odata.nextLink");
if (hasMore) {
if (!Networking::HttpJsonRequest::jsonContainsString(object, "@odata.nextLink", "OneDriveListDirectoryRequest")) {
error.response = "\"@odata.nextLink\" is not a string!";
finishError(error);
delete json;
return;
}
makeRequest(object.getVal("@odata.nextLink")->asString());
} else {
listNextDirectory();
}
delete json;
}
void OneDriveListDirectoryRequest::listedDirectoryErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void OneDriveListDirectoryRequest::handle() {}
void OneDriveListDirectoryRequest::restart() { start(); }
Common::String OneDriveListDirectoryRequest::date() const { return _date; }
void OneDriveListDirectoryRequest::finishListing(const Common::Array<StorageFile> &files) {
Request::finishSuccess();
if (_listDirectoryCallback)
(*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
}
} // End of namespace OneDrive
} // End of namespace Cloud

View File

@@ -0,0 +1,65 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVELISTDIRECTORYREQUEST_H
#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVELISTDIRECTORYREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace OneDrive {
class OneDriveStorage;
class OneDriveListDirectoryRequest: public Networking::Request {
Common::String _requestedPath;
bool _requestedRecursive;
OneDriveStorage *_storage;
Storage::ListDirectoryCallback _listDirectoryCallback;
Common::Array<StorageFile> _files;
Common::Array<Common::String> _directoriesQueue;
Common::String _currentDirectory;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _date;
void start();
void listNextDirectory();
void listedDirectoryCallback(const Networking::JsonResponse &response);
void listedDirectoryErrorCallback(const Networking::ErrorResponse &error);
void makeRequest(const Common::String &url);
void finishListing(const Common::Array<StorageFile> &files);
public:
OneDriveListDirectoryRequest(OneDriveStorage *storage, const Common::String &path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false);
~OneDriveListDirectoryRequest() override;
void handle() override;
void restart() override;
Common::String date() const override;
};
} // End of namespace OneDrive
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,220 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/onedrive/onedrivestorage.h"
#include "backends/cloud/cloudmanager.h"
#include "backends/cloud/onedrive/onedrivecreatedirectoryrequest.h"
#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
#include "backends/cloud/onedrive/onedrivelistdirectoryrequest.h"
#include "backends/cloud/onedrive/onedriveuploadrequest.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/formats/json.h"
namespace Cloud {
namespace OneDrive {
#define ONEDRIVE_API_SPECIAL_APPROOT_ID "https://graph.microsoft.com/v1.0/drive/special/approot:/"
#define ONEDRIVE_API_SPECIAL_APPROOT "https://graph.microsoft.com/v1.0/drive/special/approot"
OneDriveStorage::OneDriveStorage(const Common::String &token, const Common::String &refreshToken, bool enabled):
BaseStorage(token, refreshToken, enabled) {}
OneDriveStorage::OneDriveStorage(const Common::String &code, Networking::ErrorCallback cb) {
getAccessToken(code, cb);
}
OneDriveStorage::OneDriveStorage(const Networking::JsonResponse &codeFlowJson, Networking::ErrorCallback cb) {
codeFlowComplete(cb, codeFlowJson);
}
OneDriveStorage::~OneDriveStorage() {}
Common::String OneDriveStorage::cloudProvider() { return "onedrive"; }
uint32 OneDriveStorage::storageIndex() { return kStorageOneDriveId; }
bool OneDriveStorage::needsRefreshToken() { return true; }
bool OneDriveStorage::canReuseRefreshToken() { return false; }
void OneDriveStorage::saveConfig(const Common::String &keyPrefix) {
ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
saveIsEnabledFlag(keyPrefix);
}
Common::String OneDriveStorage::name() const {
return "OneDrive";
}
void OneDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
if (!json) {
warning("OneDriveStorage::infoInnerCallback: NULL passed instead of JSON");
delete outerCallback;
return;
}
if (!Networking::HttpJsonRequest::jsonIsObject(json, "OneDriveStorage::infoInnerCallback")) {
delete json;
delete outerCallback;
return;
}
Common::JSONObject jsonInfo = json->asObject();
Common::String uid, displayName, email;
uint64 quotaUsed = 0, quotaAllocated = 26843545600LL; // 25 GB, because I actually don't know any way to find out the real one
if (Networking::HttpJsonRequest::jsonContainsObject(jsonInfo, "createdBy", "OneDriveStorage::infoInnerCallback")) {
Common::JSONObject createdBy = jsonInfo.getVal("createdBy")->asObject();
if (Networking::HttpJsonRequest::jsonContainsObject(createdBy, "user", "OneDriveStorage::infoInnerCallback")) {
Common::JSONObject user = createdBy.getVal("user")->asObject();
if (Networking::HttpJsonRequest::jsonContainsString(user, "id", "OneDriveStorage::infoInnerCallback"))
uid = user.getVal("id")->asString();
if (Networking::HttpJsonRequest::jsonContainsString(user, "displayName", "OneDriveStorage::infoInnerCallback"))
displayName = user.getVal("displayName")->asString();
}
}
if (Networking::HttpJsonRequest::jsonContainsIntegerNumber(jsonInfo, "size", "OneDriveStorage::infoInnerCallback")) {
quotaUsed = jsonInfo.getVal("size")->asIntegerNumber();
}
Common::String username = email;
if (username == "")
username = displayName;
if (username == "")
username = uid;
CloudMan.setStorageUsername(kStorageOneDriveId, username);
if (outerCallback) {
(*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, displayName, email, quotaUsed, quotaAllocated)));
delete outerCallback;
}
delete json;
}
void OneDriveStorage::fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, const Networking::JsonResponse &response) {
const Common::JSONValue *json = response.value;
if (!json) {
warning("OneDriveStorage::fileInfoCallback: NULL passed instead of JSON");
if (outerCallback)
(*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr));
delete outerCallback;
return;
}
if (!Networking::HttpJsonRequest::jsonIsObject(json, "OneDriveStorage::fileInfoCallback")) {
if (outerCallback)
(*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr));
delete json;
delete outerCallback;
return;
}
Common::JSONObject result = response.value->asObject();
if (!Networking::HttpJsonRequest::jsonContainsString(result, "@microsoft.graph.downloadUrl", "OneDriveStorage::fileInfoCallback")) {
warning("OneDriveStorage: downloadUrl not found in passed JSON");
debug(9, "%s", response.value->stringify().c_str());
if (outerCallback)
(*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr));
delete json;
delete outerCallback;
return;
}
const char *url = result.getVal("@microsoft.graph.downloadUrl")->asString().c_str();
if (outerCallback)
(*outerCallback)(Networking::NetworkReadStreamResponse(
response.request,
Networking::NetworkReadStream::make(url, nullptr, "")
));
delete json;
delete outerCallback;
}
Networking::Request *OneDriveStorage::listDirectory(const Common::String &path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
debug(9, "OneDrive: `ls \"%s\"`", path.c_str());
return addRequest(new OneDriveListDirectoryRequest(this, path, callback, errorCallback, recursive));
}
Networking::Request *OneDriveStorage::upload(const Common::String &path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
debug(9, "OneDrive: `upload \"%s\"`", path.c_str());
return addRequest(new OneDriveUploadRequest(this, path, contents, callback, errorCallback));
}
Networking::Request *OneDriveStorage::streamFileById(const Common::String &path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) {
debug(9, "OneDrive: `download \"%s\"`", path.c_str());
Common::String url = ONEDRIVE_API_SPECIAL_APPROOT_ID + Common::percentEncodeString(path);
Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, const Networking::NetworkReadStreamResponse &, const Networking::JsonResponse &>(this, &OneDriveStorage::fileInfoCallback, outerCallback);
Networking::HttpJsonRequest *request = new OneDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str());
request->addHeader("Authorization: bearer " + _token);
return addRequest(request);
}
Networking::Request *OneDriveStorage::createDirectory(const Common::String &path, BoolCallback callback, Networking::ErrorCallback errorCallback) {
debug(9, "OneDrive: `mkdir \"%s\"`", path.c_str());
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
return addRequest(new OneDriveCreateDirectoryRequest(this, path, callback, errorCallback));
}
Networking::Request *OneDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
debug(9, "OneDrive: `info`");
Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, const StorageInfoResponse &, const Networking::JsonResponse &>(this, &OneDriveStorage::infoInnerCallback, callback);
Networking::HttpJsonRequest *request = new OneDriveTokenRefresher(this, innerCallback, errorCallback, ONEDRIVE_API_SPECIAL_APPROOT);
request->addHeader("Authorization: bearer " + _token);
return addRequest(request);
}
Common::String OneDriveStorage::savesDirectoryPath() { return "saves/"; }
OneDriveStorage *OneDriveStorage::loadFromConfig(const Common::String &keyPrefix) {
if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
warning("OneDriveStorage: no access_token found");
return nullptr;
}
if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
warning("OneDriveStorage: no refresh_token found");
return nullptr;
}
Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
return new OneDriveStorage(accessToken, refreshToken, loadIsEnabledFlag(keyPrefix));
}
void OneDriveStorage::removeFromConfig(const Common::String &keyPrefix) {
ConfMan.removeKey(keyPrefix + "access_token", ConfMan.kCloudDomain);
ConfMan.removeKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
removeIsEnabledFlag(keyPrefix);
}
} // End of namespace OneDrive
} // End of namespace Cloud

View File

@@ -0,0 +1,120 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H
#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H
#include "backends/cloud/basestorage.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace OneDrive {
class OneDriveStorage: public Cloud::BaseStorage {
/** This private constructor is called from loadFromConfig(). */
OneDriveStorage(const Common::String &token, const Common::String &refreshToken, bool enabled);
/** Constructs StorageInfo based on JSON response from cloud. */
void infoInnerCallback(StorageInfoCallback outerCallback, const Networking::JsonResponse &json);
void fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, const Networking::JsonResponse &response);
protected:
/**
* @return "onedrive"
*/
Common::String cloudProvider() override;
/**
* @return kStorageOneDriveId
*/
uint32 storageIndex() override;
bool needsRefreshToken() override;
bool canReuseRefreshToken() override;
public:
/** This constructor uses OAuth code flow to get tokens. */
OneDriveStorage(const Common::String &code, Networking::ErrorCallback cb);
/** This constructor extracts tokens from JSON acquired via OAuth code flow. */
OneDriveStorage(const Networking::JsonResponse &codeFlowJson, Networking::ErrorCallback cb);
~OneDriveStorage() override;
/**
* Storage methods, which are used by CloudManager to save
* storage in configuration file.
*/
/**
* Save storage data using ConfMan.
* @param keyPrefix all saved keys must start with this prefix.
* @note every Storage must write keyPrefix + "type" key
* with common value (e.g. "Dropbox").
*/
void saveConfig(const Common::String &keyPrefix) override;
/**
* Return unique storage name.
* @returns some unique storage name (for example, "Dropbox (user@example.com)")
*/
Common::String name() const override;
/** Public Cloud API comes down there. */
/** Returns ListDirectoryStatus struct with list of files. */
Networking::Request *listDirectory(const Common::String &path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false) override;
/** Returns UploadStatus struct with info about uploaded file. */
Networking::Request *upload(const Common::String &path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns pointer to Networking::NetworkReadStream. */
Networking::Request *streamFileById(const Common::String &path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) override;
/** Calls the callback when finished. */
Networking::Request *createDirectory(const Common::String &path, BoolCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns the StorageInfo struct. */
Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) override;
/** Returns storage's saves directory path with the trailing slash. */
Common::String savesDirectoryPath() override;
/**
* Load token and user id from configs and return OneDriveStorage for those.
* @return pointer to the newly created OneDriveStorage or 0 if some problem occurred.
*/
static OneDriveStorage *loadFromConfig(const Common::String &keyPrefix);
/**
* Remove all OneDriveStorage-related data from config.
*/
static void removeFromConfig(const Common::String &keyPrefix);
Common::String accessToken() const { return _token; }
};
} // End of namespace OneDrive
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,142 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
#include "backends/cloud/onedrive/onedrivestorage.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/debug.h"
#include "common/formats/json.h"
namespace Cloud {
namespace OneDrive {
OneDriveTokenRefresher::OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url):
HttpJsonRequest(callback, ecb, url), _parentStorage(parent) {}
OneDriveTokenRefresher::~OneDriveTokenRefresher() {}
void OneDriveTokenRefresher::tokenRefreshed(const Storage::BoolResponse &response) {
if (!response.value) {
//failed to refresh token, notify user with NULL in original callback
warning("OneDriveTokenRefresher: failed to refresh token");
finishError(Networking::ErrorResponse(this, false, true, "OneDriveTokenRefresher::tokenRefreshed: failed to refresh token", -1));
return;
}
//update headers: first change header with token, then pass those to request
for (uint32 i = 0; i < _headersList.size(); ++i) {
if (_headersList[i].contains("Authorization")) {
_headersList[i] = "Authorization: bearer " + _parentStorage->accessToken();
}
}
//successfully received refreshed token, can restart the original request now
retry(0);
}
void OneDriveTokenRefresher::finishJson(const Common::JSONValue *json) {
if (!json) {
//that's probably not an error (200 OK)
HttpJsonRequest::finishJson(nullptr);
return;
}
if (jsonIsObject(json, "OneDriveTokenRefresher")) {
Common::JSONObject result = json->asObject();
long httpResponseCode = -1;
if (result.contains("error") && jsonIsObject(result.getVal("error"), "OneDriveTokenRefresher")) {
//new token needed => request token & then retry original request
if (_stream) {
httpResponseCode = _stream->httpResponseCode();
debug(9, "OneDriveTokenRefresher: code = %ld", httpResponseCode);
}
Common::JSONObject error = result.getVal("error")->asObject();
bool irrecoverable = true;
Common::String code, message;
if (jsonContainsString(error, "code", "OneDriveTokenRefresher")) {
code = error.getVal("code")->asString();
debug(9, "OneDriveTokenRefresher: code = %s", code.c_str());
}
if (jsonContainsString(error, "message", "OneDriveTokenRefresher")) {
message = error.getVal("message")->asString();
debug(9, "OneDriveTokenRefresher: message = %s", message.c_str());
}
//determine whether token refreshing would help in this situation
if (code == "itemNotFound") {
if (message.contains("application ID"))
irrecoverable = false;
}
if (code == "unauthenticated" || code == "InvalidAuthenticationToken")
irrecoverable = false;
if (irrecoverable) {
Common::String errorContents = json->stringify(true);
finishErrorIrrecoverable(Networking::ErrorResponse(this, false, true, errorContents, httpResponseCode));
delete json;
return;
}
pause();
delete json;
_parentStorage->refreshAccessToken(new Common::Callback<OneDriveTokenRefresher, const Storage::BoolResponse &>(this, &OneDriveTokenRefresher::tokenRefreshed));
return;
}
}
//notify user of success
HttpJsonRequest::finishJson(json);
}
void OneDriveTokenRefresher::finishError(const Networking::ErrorResponse &error, Networking::RequestState state) {
if (error.failed) {
Common::JSONValue *value = Common::JSON::parse(error.response);
//somehow OneDrive returns JSON with '.' in unexpected places, try fixing it
if (!value) {
Common::String fixedResponse = error.response;
for (uint32 i = 0; i < fixedResponse.size(); ++i) {
if (fixedResponse[i] == '.')
fixedResponse.replace(i, 1, " ");
}
value = Common::JSON::parse(fixedResponse);
}
if (value) {
finishJson(value);
return;
}
}
Request::finishError(error, state); //call closest base class's method
}
void OneDriveTokenRefresher::finishErrorIrrecoverable(const Networking::ErrorResponse &error, Networking::RequestState state) {
// don't try to fix JSON as this is irrecoverable version
Request::finishError(error, state); // call closest base class's method
}
} // End of namespace OneDrive
} // End of namespace Cloud

View File

@@ -0,0 +1,50 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVETOKENREFRESHER_H
#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVETOKENREFRESHER_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
namespace Cloud {
namespace OneDrive {
class OneDriveStorage;
class OneDriveTokenRefresher: public Networking::HttpJsonRequest {
OneDriveStorage *_parentStorage;
void tokenRefreshed(const Storage::BoolResponse &response);
void finishJson(const Common::JSONValue *json) override;
void finishError(const Networking::ErrorResponse &error, Networking::RequestState state = Networking::FINISHED) override;
void finishErrorIrrecoverable(const Networking::ErrorResponse &error, Networking::RequestState state = Networking::FINISHED);
public:
OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url);
~OneDriveTokenRefresher() override;
};
} // End of namespace OneDrive
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,192 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/onedrive/onedriveuploadrequest.h"
#include "backends/cloud/onedrive/onedrivestorage.h"
#include "backends/cloud/iso8601.h"
#include "backends/cloud/storage.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/networkreadstream.h"
#include "common/formats/json.h"
#include "onedrivetokenrefresher.h"
namespace Cloud {
namespace OneDrive {
#define ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD "https://graph.microsoft.com/v1.0/drive/special/approot:/%s:/upload.createSession"
#define ONEDRIVE_API_SPECIAL_APPROOT_CONTENT "https://graph.microsoft.com/v1.0/drive/special/approot:/%s:/content"
OneDriveUploadRequest::OneDriveUploadRequest(OneDriveStorage *storage, const Common::String &path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback),
_workingRequest(nullptr), _ignoreCallback(false) {
start();
}
OneDriveUploadRequest::~OneDriveUploadRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _contentsStream;
delete _uploadCallback;
}
void OneDriveUploadRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
if (_contentsStream == nullptr) {
warning("OneDriveUploadRequest: cannot restart because no stream given");
finishError(Networking::ErrorResponse(this, false, true, "OneDriveUploadRequest::start: can't restart, because no stream given", -1));
return;
}
if (!_contentsStream->seek(0)) {
warning("OneDriveUploadRequest: cannot restart because stream couldn't seek(0)");
finishError(Networking::ErrorResponse(this, false, true, "OneDriveUploadRequest::start: can't restart, because seek(0) didn't work", -1));
return;
}
_ignoreCallback = false;
uploadNextPart();
}
void OneDriveUploadRequest::uploadNextPart() {
const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024;
if (_uploadUrl == "" && (uint32)_contentsStream->size() > UPLOAD_PER_ONE_REQUEST) {
Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD, Common::percentEncodeString(_savePath).c_str()); //folder must exist
Networking::JsonCallback callback = new Common::Callback<OneDriveUploadRequest, const Networking::JsonResponse &>(this, &OneDriveUploadRequest::partUploadedCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveUploadRequest, const Networking::ErrorResponse &>(this, &OneDriveUploadRequest::partUploadedErrorCallback);
Networking::HttpJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->setBuffer(new byte[1], 0); //use POST
_workingRequest = ConnMan.addRequest(request);
return;
}
Common::String url;
if (_uploadUrl == "") {
url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CONTENT, Common::percentEncodeString(_savePath).c_str());
} else {
url = _uploadUrl;
}
Networking::JsonCallback callback = new Common::Callback<OneDriveUploadRequest, const Networking::JsonResponse &>(this, &OneDriveUploadRequest::partUploadedCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveUploadRequest, const Networking::ErrorResponse &>(this, &OneDriveUploadRequest::partUploadedErrorCallback);
Networking::HttpJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
request->addHeader("Authorization: Bearer " + _storage->accessToken());
request->usePut();
uint32 oldPos = _contentsStream->pos();
byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST];
uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST);
request->setBuffer(buffer, size);
if (_uploadUrl != "") {
request->addHeader(Common::String::format("Content-Range: bytes %u-%lu/%lu", oldPos,
static_cast<unsigned long>(_contentsStream->pos() - 1),
static_cast<unsigned long>(_contentsStream->size())));
} else if (_contentsStream->size() == 0) {
warning("\"Sorry, OneDrive can't upload empty files\"");
finishUpload(StorageFile(_savePath, 0, 0, false));
delete request;
return;
}
_workingRequest = ConnMan.addRequest(request);
}
void OneDriveUploadRequest::partUploadedCallback(const Networking::JsonResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
Networking::ErrorResponse error(this, false, true, "", -1);
const Networking::HttpJsonRequest *rq = (const Networking::HttpJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
const Common::JSONValue *json = response.value;
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
if (json->isObject()) {
Common::JSONObject object = json->asObject();
if (object.contains("error")) {
warning("OneDriveUploadRequest: error: %s", json->stringify(true).c_str());
error.response = json->stringify(true);
finishError(error);
delete json;
return;
}
if (Networking::HttpJsonRequest::jsonContainsString(object, "id", "OneDriveUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsString(object, "name", "OneDriveUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsIntegerNumber(object, "size", "OneDriveUploadRequest") &&
Networking::HttpJsonRequest::jsonContainsString(object, "lastModifiedDateTime", "OneDriveUploadRequest")) {
//finished
Common::String path = _savePath;
uint32 size = object.getVal("size")->asIntegerNumber();
uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("lastModifiedDateTime")->asString());
finishUpload(StorageFile(path, size, timestamp, false));
return;
}
if (_uploadUrl == "") {
if (Networking::HttpJsonRequest::jsonContainsString(object, "uploadUrl", "OneDriveUploadRequest"))
_uploadUrl = object.getVal("uploadUrl")->asString();
}
}
if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) {
warning("OneDriveUploadRequest: no file info to return");
finishUpload(StorageFile(_savePath, 0, 0, false));
} else {
uploadNextPart();
}
delete json;
}
void OneDriveUploadRequest::partUploadedErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
finishError(error);
}
void OneDriveUploadRequest::handle() {}
void OneDriveUploadRequest::restart() { start(); }
void OneDriveUploadRequest::finishUpload(const StorageFile &file) {
Request::finishSuccess();
if (_uploadCallback)
(*_uploadCallback)(Storage::UploadResponse(this, file));
}
} // End of namespace OneDrive
} // End of namespace Cloud

View File

@@ -0,0 +1,60 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVEUPLOADREQUEST_H
#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVEUPLOADREQUEST_H
#include "backends/cloud/storage.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/request.h"
#include "common/callback.h"
namespace Cloud {
namespace OneDrive {
class OneDriveStorage;
class OneDriveUploadRequest: public Networking::Request {
OneDriveStorage *_storage;
Common::String _savePath;
Common::SeekableReadStream *_contentsStream;
Storage::UploadCallback _uploadCallback;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _uploadUrl;
void start();
void uploadNextPart();
void partUploadedCallback(const Networking::JsonResponse &response);
void partUploadedErrorCallback(const Networking::ErrorResponse &error);
void finishUpload(const StorageFile &status);
public:
OneDriveUploadRequest(OneDriveStorage *storage, const Common::String &path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
~OneDriveUploadRequest() override;
void handle() override;
void restart() override;
};
} // End of namespace OneDrive
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,489 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/savessyncrequest.h"
#include "backends/cloud/cloudmanager.h"
#include "backends/cloud/downloadrequest.h"
#include "backends/cloud/id/iddownloadrequest.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/saves/default/default-saves.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/formats/json.h"
#include "common/savefile.h"
#include "common/system.h"
#include "gui/saveload-dialog.h"
namespace Cloud {
SavesSyncRequest::SavesSyncRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb):
Request(nullptr, ecb), _storage(storage), _boolCallback(callback),
_workingRequest(nullptr), _ignoreCallback(false), _bytesToDownload(0), _bytesDownloaded(0) {
start();
}
SavesSyncRequest::~SavesSyncRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _boolCallback;
}
void SavesSyncRequest::start() {
//cleanup
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_currentDownloadingFile = StorageFile();
_currentUploadingFile = "";
_filesToDownload.clear();
_filesToUpload.clear();
_localFilesTimestamps.clear();
_totalFilesToHandle = 0;
_ignoreCallback = false;
//load timestamps
_localFilesTimestamps = DefaultSaveFileManager::loadTimestamps();
//list saves directory
Common::String dir = _storage->savesDirectoryPath();
if (dir.lastChar() == '/')
dir.deleteLastChar();
_workingRequest = _storage->listDirectory(
dir,
new Common::Callback<SavesSyncRequest, const Storage::ListDirectoryResponse &>(this, &SavesSyncRequest::directoryListedCallback),
new Common::Callback<SavesSyncRequest, const Networking::ErrorResponse &>(this, &SavesSyncRequest::directoryListedErrorCallback)
);
if (!_workingRequest) finishError(Networking::ErrorResponse(this, "SavesSyncRequest::start: Storage couldn't create Request to list directory"));
}
void SavesSyncRequest::directoryListedCallback(const Storage::ListDirectoryResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (response.request) _date = response.request->date();
if (_date.empty()) {
// This is from SaveLoadChooser::createDefaultSaveDescription
TimeDate curTime;
g_system->getTimeAndDate(curTime);
curTime.tm_year += 1900; // fixup year
curTime.tm_mon++; // fixup month
_date = Common::String::format("%04d-%02d-%02d / %02d:%02d:%02d", curTime.tm_year, curTime.tm_mon, curTime.tm_mday, curTime.tm_hour, curTime.tm_min, curTime.tm_sec);
debug(9, "SavesSyncRequest: using local time as fallback: %s", _date.c_str());
}
Common::HashMap<Common::String, bool> localFileNotAvailableInCloud;
for (auto &timestamp : _localFilesTimestamps) {
localFileNotAvailableInCloud[timestamp._key] = true;
}
// Determine which files to download and which files to upload
const Common::Array<StorageFile> &remoteFiles = response.value;
uint64 totalSize = 0;
debug(9, "SavesSyncRequest decisions:");
for (uint32 i = 0; i < remoteFiles.size(); ++i) {
const StorageFile &file = remoteFiles[i];
if (file.isDirectory())
continue;
totalSize += file.size();
if (file.name() == DefaultSaveFileManager::TIMESTAMPS_FILENAME || !CloudMan.canSyncFilename(file.name()))
continue;
Common::String name = file.name();
if (!_localFilesTimestamps.contains(name)) {
_filesToDownload.push_back(file);
debug(9, "- downloading file %s, because it is not present on local", name.c_str());
} else {
localFileNotAvailableInCloud[name] = false;
if (_localFilesTimestamps[name] == file.timestamp())
continue;
// We actually can have some files not only with timestamp < remote
// but also with timestamp > remote (when we have been using ANOTHER CLOUD and then switched back)
if (_localFilesTimestamps[name] > file.timestamp() || _localFilesTimestamps[name] == DefaultSaveFileManager::INVALID_TIMESTAMP)
_filesToUpload.push_back(file.name());
else
_filesToDownload.push_back(file);
if (_localFilesTimestamps[name] == DefaultSaveFileManager::INVALID_TIMESTAMP)
debug(9, "- uploading file %s, because it is has invalid timestamp", name.c_str());
else if (_localFilesTimestamps[name] > file.timestamp())
debug(9, "- uploading file %s, because it is %d seconds newer than remote\n\tlocal = %d; \tremote = %d", name.c_str(), _localFilesTimestamps[name] - file.timestamp(), _localFilesTimestamps[name], file.timestamp());
else
debug(9, "- downloading file %s, because it is %d seconds older than remote\n\tlocal = %d; \tremote = %d", name.c_str(), file.timestamp() - _localFilesTimestamps[name], _localFilesTimestamps[name], file.timestamp());
}
}
CloudMan.setStorageUsedSpace(CloudMan.getStorageIndex(), totalSize);
// Upload files which are unavailable in cloud
for (auto &localFile : localFileNotAvailableInCloud) {
if (localFile._key == DefaultSaveFileManager::TIMESTAMPS_FILENAME || !CloudMan.canSyncFilename(localFile._key))
continue;
if (localFile._value) {
_filesToUpload.push_back(localFile._key);
debug(9, "- uploading file %s, because it is not present on remote", localFile._key.c_str());
}
}
_bytesToDownload = 0;
_bytesDownloaded = 0;
debug(9, "\nSavesSyncRequest: ");
if (_filesToDownload.size() > 0) {
debug(9, "download files:");
for (uint32 i = 0; i < _filesToDownload.size(); ++i) {
debug(9, " %s", _filesToDownload[i].name().c_str());
_bytesToDownload += _filesToDownload[i].size();
}
debug(9, "%s", "");
} else {
debug(9, "nothing to download");
}
debug(9, "SavesSyncRequest: ");
if (_filesToUpload.size() > 0) {
debug(9, "upload files:");
for (uint32 i = 0; i < _filesToUpload.size(); ++i) {
debug(9, " %s", _filesToUpload[i].c_str());
}
} else {
debug(9, "nothing to upload");
}
_totalFilesToHandle = _filesToDownload.size() + _filesToUpload.size();
// Start downloading files
if (!_filesToDownload.empty()) {
downloadNextFile();
} else {
uploadNextFile();
}
}
void SavesSyncRequest::directoryListedErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.failed) debug(9, "%s", error.response.c_str());
bool irrecoverable = error.interrupted || error.failed;
if (error.failed) {
Common::JSONValue *value = Common::JSON::parse(error.response);
// Somehow OneDrive returns JSON with '.' in unexpected places, try fixing it
if (!value) {
Common::String fixedResponse = error.response;
for (uint32 i = 0; i < fixedResponse.size(); ++i) {
if (fixedResponse[i] == '.')
fixedResponse.replace(i, 1, " ");
}
value = Common::JSON::parse(fixedResponse);
}
if (value) {
if (value->isObject()) {
Common::JSONObject object = value->asObject();
// Dropbox-related error:
if (object.contains("error_summary") && object.getVal("error_summary")->isString()) {
Common::String summary = object.getVal("error_summary")->asString();
if (summary.contains("not_found")) {
irrecoverable = false;
}
}
// OneDrive-related error:
if (object.contains("error") && object.getVal("error")->isObject()) {
Common::JSONObject errorNode = object.getVal("error")->asObject();
if (Networking::HttpJsonRequest::jsonContainsString(errorNode, "code", "SavesSyncRequest")) {
Common::String code = errorNode.getVal("code")->asString();
if (code == "itemNotFound") {
irrecoverable = false;
}
}
}
}
delete value;
}
// Google Drive, Box and OneDrive-related ScummVM-based error
if (error.response.contains("subdirectory not found")) {
irrecoverable = false; //base "/ScummVM/" folder not found
} else if (error.response.contains("no such file found in its parent directory")) {
irrecoverable = false; //"Saves" folder within "/ScummVM/" not found
} else if (error.response.contains("itemNotFound") && error.response.contains("Item does not exist")) {
irrecoverable = false; //"saves" folder within application folder is not found
}
}
if (irrecoverable) {
finishError(error);
return;
}
// We're lucky - user just lacks his "/cloud/" folder - let's create one
Common::String dir = _storage->savesDirectoryPath();
if (dir.lastChar() == '/')
dir.deleteLastChar();
debug(9, "\nSavesSyncRequest: creating %s", dir.c_str());
_workingRequest = _storage->createDirectory(
dir,
new Common::Callback<SavesSyncRequest, const Storage::BoolResponse &>(this, &SavesSyncRequest::directoryCreatedCallback),
new Common::Callback<SavesSyncRequest, const Networking::ErrorResponse &>(this, &SavesSyncRequest::directoryCreatedErrorCallback)
);
if (!_workingRequest)
finishError(Networking::ErrorResponse(this, "SavesSyncRequest::directoryListedErrorCallback: Storage couldn't create Request to create remote directory"));
}
void SavesSyncRequest::directoryCreatedCallback(const Storage::BoolResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
//stop syncing if failed to create saves directory
if (!response.value) {
finishError(Networking::ErrorResponse(this, false, true, "SavesSyncRequest::directoryCreatedCallback: failed to create remote directory", -1));
return;
}
//continue with empty files list
Common::Array<StorageFile> files;
directoryListedCallback(Storage::ListDirectoryResponse(response.request, files));
}
void SavesSyncRequest::directoryCreatedErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
//stop syncing if failed to create saves directory
finishError(error);
}
void SavesSyncRequest::downloadNextFile() {
if (_filesToDownload.empty()) {
_currentDownloadingFile = StorageFile("", 0, 0, false); //so getFilesToDownload() would return an empty array
uploadNextFile();
return;
}
_currentDownloadingFile = _filesToDownload.back();
_filesToDownload.pop_back();
debug(9, "\nSavesSyncRequest: downloading %s (%d %%)", _currentDownloadingFile.name().c_str(), (int)(getProgress() * 100));
_workingRequest = _storage->downloadById(
_currentDownloadingFile.id(),
DefaultSaveFileManager::concatWithSavesPath(_currentDownloadingFile.name()),
new Common::Callback<SavesSyncRequest, const Storage::BoolResponse &>(this, &SavesSyncRequest::fileDownloadedCallback),
new Common::Callback<SavesSyncRequest, const Networking::ErrorResponse &>(this, &SavesSyncRequest::fileDownloadedErrorCallback)
);
if (!_workingRequest)
finishError(Networking::ErrorResponse(this, "SavesSyncRequest::downloadNextFile: Storage couldn't create Request to download a file"));
}
void SavesSyncRequest::fileDownloadedCallback(const Storage::BoolResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
//stop syncing if download failed
if (!response.value) {
//delete the incomplete file
g_system->getSavefileManager()->removeSavefile(_currentDownloadingFile.name());
finishError(Networking::ErrorResponse(this, false, true, "SavesSyncRequest::fileDownloadedCallback: failed to download a file", -1));
return;
}
//update local timestamp for downloaded file
_localFilesTimestamps[_currentDownloadingFile.name()] = _currentDownloadingFile.timestamp();
DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps);
_bytesDownloaded += _currentDownloadingFile.size();
//continue downloading files
downloadNextFile();
}
void SavesSyncRequest::fileDownloadedErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
//stop syncing if download failed
finishError(error);
}
void SavesSyncRequest::uploadNextFile() {
if (_filesToUpload.empty()) {
finishSync(true);
return;
}
_currentUploadingFile = _filesToUpload.back();
_filesToUpload.pop_back();
debug(9, "\nSavesSyncRequest: uploading %s (%d %%)", _currentUploadingFile.c_str(), (int)(getProgress() * 100));
if (_storage->uploadStreamSupported()) {
_workingRequest = _storage->upload(
_storage->savesDirectoryPath() + _currentUploadingFile,
g_system->getSavefileManager()->openRawFile(_currentUploadingFile),
new Common::Callback<SavesSyncRequest, const Storage::UploadResponse &>(this, &SavesSyncRequest::fileUploadedCallback),
new Common::Callback<SavesSyncRequest, const Networking::ErrorResponse &>(this, &SavesSyncRequest::fileUploadedErrorCallback)
);
} else {
_workingRequest = _storage->upload(
_storage->savesDirectoryPath() + _currentUploadingFile,
DefaultSaveFileManager::concatWithSavesPath(_currentUploadingFile),
new Common::Callback<SavesSyncRequest, const Storage::UploadResponse &>(this, &SavesSyncRequest::fileUploadedCallback),
new Common::Callback<SavesSyncRequest, const Networking::ErrorResponse &>(this, &SavesSyncRequest::fileUploadedErrorCallback)
);
}
if (!_workingRequest) finishError(Networking::ErrorResponse(this, "SavesSyncRequest::uploadNextFile: Storage couldn't create Request to upload a file"));
}
void SavesSyncRequest::fileUploadedCallback(const Storage::UploadResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
//update local timestamp for the uploaded file
_localFilesTimestamps[_currentUploadingFile] = response.value.timestamp();
DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps);
//continue uploading files
uploadNextFile();
}
void SavesSyncRequest::fileUploadedErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
//stop syncing if upload failed
finishError(error);
}
void SavesSyncRequest::handle() {}
void SavesSyncRequest::restart() { start(); }
double SavesSyncRequest::getDownloadingProgress() const {
if (_totalFilesToHandle == 0) {
if (_state == Networking::FINISHED)
return 1; //nothing to upload and download => Request ends soon
return 0; //directory not listed yet
}
if (_totalFilesToHandle == _filesToUpload.size())
return 1; //nothing to download => download complete
if (_bytesToDownload > 0) {
// can calculate more precise progress
return (double)(getDownloadedBytes()) / (double)(_bytesToDownload);
}
uint32 totalFilesToDownload = _totalFilesToHandle - _filesToUpload.size();
uint32 filesLeftToDownload = _filesToDownload.size() + (_currentDownloadingFile.name() != "" ? 1 : 0);
if (filesLeftToDownload > totalFilesToDownload)
filesLeftToDownload = totalFilesToDownload;
return (double)(totalFilesToDownload - filesLeftToDownload) / (double)(totalFilesToDownload);
}
void SavesSyncRequest::getDownloadingInfo(Storage::SyncDownloadingInfo &info) const {
info.bytesDownloaded = getDownloadedBytes();
info.bytesToDownload = getBytesToDownload();
uint32 totalFilesToDownload = _totalFilesToHandle - _filesToUpload.size();
uint32 filesLeftToDownload = _filesToDownload.size() + (_currentDownloadingFile.name() != "" ? 1 : 0);
if (filesLeftToDownload > totalFilesToDownload)
filesLeftToDownload = totalFilesToDownload;
info.filesDownloaded = totalFilesToDownload - filesLeftToDownload;
info.filesToDownload = totalFilesToDownload;
info.inProgress = (totalFilesToDownload > 0 && filesLeftToDownload > 0);
}
double SavesSyncRequest::getProgress() const {
if (_totalFilesToHandle == 0) {
if (_state == Networking::FINISHED)
return 1; //nothing to upload and download => Request ends soon
return 0; //directory not listed yet
}
return (double)(_totalFilesToHandle - _filesToDownload.size() - _filesToUpload.size()) / (double)(_totalFilesToHandle);
}
Common::Array<Common::String> SavesSyncRequest::getFilesToDownload() {
Common::Array<Common::String> result;
for (uint32 i = 0; i < _filesToDownload.size(); ++i)
result.push_back(_filesToDownload[i].name());
if (_currentDownloadingFile.name() != "")
result.push_back(_currentDownloadingFile.name());
return result;
}
uint32 SavesSyncRequest::getDownloadedBytes() const {
double currentFileProgress = 0;
if (const DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest))
currentFileProgress = downloadRequest->getProgress();
else if (const Id::IdDownloadRequest *idDownloadRequest = dynamic_cast<Id::IdDownloadRequest *>(_workingRequest))
currentFileProgress = idDownloadRequest->getProgress();
return _bytesDownloaded + currentFileProgress * _currentDownloadingFile.size();
}
uint32 SavesSyncRequest::getBytesToDownload() const {
return _bytesToDownload;
}
void SavesSyncRequest::finishError(const Networking::ErrorResponse &error, Networking::RequestState state) {
debug(9, "SavesSync::finishError");
//if we were downloading a file - remember the name
//and make the Request close() it, so we can delete it
Common::String name = _currentDownloadingFile.name();
if (_workingRequest) {
_ignoreCallback = true;
_workingRequest->finish();
_workingRequest = nullptr;
_ignoreCallback = false;
}
//unlock all the files by making getFilesToDownload() return empty array
_currentDownloadingFile = StorageFile();
_filesToDownload.clear();
//delete the incomplete file
if (name != "")
g_system->getSavefileManager()->removeSavefile(name);
Request::finishError(error);
}
void SavesSyncRequest::finishSync(bool success) {
Request::finishSuccess();
//update last successful sync date
debug(9, "SavesSyncRequest: last successful sync date: %s", _date.c_str());
CloudMan.setStorageLastSync(CloudMan.getStorageIndex(), _date);
if (_boolCallback)
(*_boolCallback)(Storage::BoolResponse(this, success));
}
} // End of namespace Cloud

View File

@@ -0,0 +1,85 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_SAVESSYNCREQUEST_H
#define BACKENDS_CLOUD_SAVESSYNCREQUEST_H
#include "backends/networking/http/request.h"
#include "backends/cloud/storage.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
namespace Cloud {
class SavesSyncRequest: public Networking::Request {
Storage *_storage;
Storage::BoolCallback _boolCallback;
Common::HashMap<Common::String, uint32> _localFilesTimestamps;
Common::Array<StorageFile> _filesToDownload;
Common::Array<Common::String> _filesToUpload;
StorageFile _currentDownloadingFile;
Common::String _currentUploadingFile;
Request *_workingRequest;
bool _ignoreCallback;
uint32 _totalFilesToHandle;
Common::String _date;
uint32 _bytesToDownload, _bytesDownloaded;
void start();
void directoryListedCallback(const Storage::ListDirectoryResponse &response);
void directoryListedErrorCallback(const Networking::ErrorResponse &error);
void directoryCreatedCallback(const Storage::BoolResponse &response);
void directoryCreatedErrorCallback(const Networking::ErrorResponse &error);
void fileDownloadedCallback(const Storage::BoolResponse &response);
void fileDownloadedErrorCallback(const Networking::ErrorResponse &error);
void fileUploadedCallback(const Storage::UploadResponse &response);
void fileUploadedErrorCallback(const Networking::ErrorResponse &error);
void downloadNextFile();
void uploadNextFile();
void finishError(const Networking::ErrorResponse &error, Networking::RequestState state = Networking::FINISHED) override;
void finishSync(bool success);
uint32 getDownloadedBytes() const;
uint32 getBytesToDownload() const;
public:
SavesSyncRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb);
~SavesSyncRequest() override;
void handle() override;
void restart() override;
/** Returns a number in range [0, 1], where 1 is "complete". */
double getDownloadingProgress() const;
/** Fills a struct with numbers about current sync downloading progress. */
void getDownloadingInfo(Storage::SyncDownloadingInfo &info) const;
/** Returns a number in range [0, 1], where 1 is "complete". */
double getProgress() const;
/** Returns an array of saves names which are not downloaded yet. */
Common::Array<Common::String> getFilesToDownload();
};
} // End of namespace Cloud
#endif

367
backends/cloud/storage.cpp Normal file
View File

@@ -0,0 +1,367 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/storage.h"
#include "backends/cloud/downloadrequest.h"
#include "backends/cloud/folderdownloadrequest.h"
#include "backends/cloud/savessyncrequest.h"
#include "backends/networking/http/connectionmanager.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/translation.h"
#include "common/osd_message_queue.h"
namespace Cloud {
Storage::Storage():
_runningRequestsCount(0), _savesSyncRequest(nullptr), _syncRestartRequestsed(false),
_downloadFolderRequest(nullptr), _isEnabled(false) {}
Storage::~Storage() {}
bool Storage::isEnabled() const {
return _isEnabled;
}
void Storage::enable() {
_isEnabled = true;
}
Networking::ErrorCallback Storage::getErrorPrintingCallback() {
return new Common::Callback<Storage, const Networking::ErrorResponse &>(this, &Storage::printErrorResponse);
}
void Storage::printErrorResponse(const Networking::ErrorResponse &error) {
debug(9, "Storage: error response (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
debug(9, "%s", error.response.c_str());
}
Networking::Request *Storage::addRequest(Networking::Request *request) {
_runningRequestsMutex.lock();
++_runningRequestsCount;
if (_runningRequestsCount == 1) {
g_system->taskStarted(OSystem::kCloudDownload);
debug(9, "Storage is working now");
}
_runningRequestsMutex.unlock();
return ConnMan.addRequest(request, new Common::Callback<Storage, Networking::Request *>(this, &Storage::requestFinishedCallback));
}
void Storage::requestFinishedCallback(Networking::Request *invalidRequestPointer) {
bool restartSync = false;
_runningRequestsMutex.lock();
if (invalidRequestPointer == _savesSyncRequest)
_savesSyncRequest = nullptr;
--_runningRequestsCount;
if (_syncRestartRequestsed)
restartSync = true;
if (_runningRequestsCount == 0) {
g_system->taskFinished(OSystem::kCloudDownload);
debug(9, "Storage is not working now");
}
_runningRequestsMutex.unlock();
if (restartSync)
syncSaves(nullptr, nullptr);
}
Networking::Request *Storage::upload(const Common::String &remotePath, const Common::Path &localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback) errorCallback = getErrorPrintingCallback();
Common::File *f = new Common::File();
if (!f->open(localPath)) {
warning("Storage: unable to open file to upload from");
if (errorCallback)
(*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "", -1));
delete errorCallback;
delete callback;
delete f;
return nullptr;
}
return upload(remotePath, f, callback, errorCallback);
}
bool Storage::uploadStreamSupported() {
return true;
}
Networking::Request *Storage::streamFile(const Common::String &path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
//most Storages use paths instead of ids, so this should work
return streamFileById(path, callback, errorCallback);
}
Networking::Request *Storage::download(const Common::String &remotePath, const Common::Path &localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) {
//most Storages use paths instead of ids, so this should work
return downloadById(remotePath, localPath, callback, errorCallback);
}
Networking::Request *Storage::downloadById(const Common::String &remoteId, const Common::Path &localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) {
if (!errorCallback) errorCallback = getErrorPrintingCallback();
Common::DumpFile *f = new Common::DumpFile();
if (!f->open(localPath, true)) {
warning("Storage: unable to open file to download into");
if (errorCallback) (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "", -1));
delete errorCallback;
delete callback;
delete f;
return nullptr;
}
return addRequest(new DownloadRequest(this, callback, errorCallback, remoteId, f));
}
Networking::Request *Storage::downloadFolder(const Common::String &remotePath, const Common::Path &localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
if (!_isEnabled) {
warning("Storage::downloadFolder: cannot be run while Storage is disabled");
if (errorCallback)
(*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "Storage is disabled.", -1));
return nullptr;
}
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
return addRequest(new FolderDownloadRequest(this, callback, errorCallback, remotePath, localPath, recursive));
}
SavesSyncRequest *Storage::syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback) {
_runningRequestsMutex.lock();
if (!_isEnabled) {
warning("Storage::syncSaves: cannot be run while Storage is disabled");
if (errorCallback)
(*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "Storage is disabled.", -1));
_runningRequestsMutex.unlock();
return nullptr;
}
if (_savesSyncRequest) {
warning("Storage::syncSaves: there is a sync in progress already");
_syncRestartRequestsed = true;
_runningRequestsMutex.unlock();
return _savesSyncRequest;
}
if (!callback)
callback = new Common::Callback<Storage, const BoolResponse &>(this, &Storage::savesSyncDefaultCallback);
if (!errorCallback)
errorCallback = new Common::Callback<Storage, const Networking::ErrorResponse &>(this, &Storage::savesSyncDefaultErrorCallback);
_savesSyncRequest = new SavesSyncRequest(this, callback, errorCallback);
_syncRestartRequestsed = false;
_runningRequestsMutex.unlock();
return (SavesSyncRequest *)addRequest(_savesSyncRequest); //who knows what that ConnMan could return in the future
}
bool Storage::isWorking() {
_runningRequestsMutex.lock();
bool working = _runningRequestsCount > 0;
_runningRequestsMutex.unlock();
return working;
}
///// SavesSyncRequest-related /////
bool Storage::isSyncing() {
_runningRequestsMutex.lock();
bool syncing = _savesSyncRequest != nullptr;
_runningRequestsMutex.unlock();
return syncing;
}
double Storage::getSyncDownloadingProgress() {
double result = 1;
_runningRequestsMutex.lock();
if (_savesSyncRequest)
result = _savesSyncRequest->getDownloadingProgress();
_runningRequestsMutex.unlock();
return result;
}
void Storage::getSyncDownloadingInfo(SyncDownloadingInfo& info) {
_runningRequestsMutex.lock();
if (_savesSyncRequest) {
_savesSyncRequest->getDownloadingInfo(info);
}
_runningRequestsMutex.unlock();
}
double Storage::getSyncProgress() {
double result = 1;
_runningRequestsMutex.lock();
if (_savesSyncRequest)
result = _savesSyncRequest->getProgress();
_runningRequestsMutex.unlock();
return result;
}
Common::Array<Common::String> Storage::getSyncingFiles() {
Common::Array<Common::String> result;
_runningRequestsMutex.lock();
if (_savesSyncRequest)
result = _savesSyncRequest->getFilesToDownload();
_runningRequestsMutex.unlock();
return result;
}
void Storage::cancelSync() {
_runningRequestsMutex.lock();
if (_savesSyncRequest)
_savesSyncRequest->finish();
_runningRequestsMutex.unlock();
}
void Storage::savesSyncDefaultCallback(const BoolResponse &response) {
_runningRequestsMutex.lock();
_savesSyncRequest = nullptr;
_runningRequestsMutex.unlock();
if (!response.value)
warning("SavesSyncRequest called success callback with `false` argument");
}
void Storage::savesSyncDefaultErrorCallback(const Networking::ErrorResponse &error) {
_runningRequestsMutex.lock();
_savesSyncRequest = nullptr;
_runningRequestsMutex.unlock();
printErrorResponse(error);
if (error.interrupted)
Common::OSDMessageQueue::instance().addMessage(_("Saved games sync was cancelled."));
else
Common::OSDMessageQueue::instance().addMessage(_("Saved games sync failed.\nCheck your Internet connection."));
}
///// DownloadFolderRequest-related /////
bool Storage::startDownload(const Common::String &remotePath, const Common::Path &localPath) {
_runningRequestsMutex.lock();
if (_downloadFolderRequest) {
warning("Storage::startDownload: there is a download in progress already");
_runningRequestsMutex.unlock();
return false;
}
_downloadFolderRequest = (FolderDownloadRequest *)downloadFolder(
remotePath, localPath,
new Common::Callback<Storage, const FileArrayResponse &>(this, &Storage::directoryDownloadedCallback),
new Common::Callback<Storage, const Networking::ErrorResponse &>(this, &Storage::directoryDownloadedErrorCallback),
true
);
_runningRequestsMutex.unlock();
return true;
}
void Storage::cancelDownload() {
_runningRequestsMutex.lock();
if (_downloadFolderRequest)
_downloadFolderRequest->finish();
_runningRequestsMutex.unlock();
}
void Storage::setDownloadTarget(GUI::CommandReceiver *target) {
_runningRequestsMutex.lock();
if (_downloadFolderRequest)
_downloadFolderRequest->setTarget(target);
_runningRequestsMutex.unlock();
}
bool Storage::isDownloading() {
_runningRequestsMutex.lock();
bool syncing = _downloadFolderRequest != nullptr;
_runningRequestsMutex.unlock();
return syncing;
}
double Storage::getDownloadingProgress() {
double result = 1;
_runningRequestsMutex.lock();
if (_downloadFolderRequest)
result = _downloadFolderRequest->getProgress();
_runningRequestsMutex.unlock();
return result;
}
uint64 Storage::getDownloadBytesNumber() {
uint64 result = 0;
_runningRequestsMutex.lock();
if (_downloadFolderRequest)
result = _downloadFolderRequest->getDownloadedBytes();
_runningRequestsMutex.unlock();
return result;
}
uint64 Storage::getDownloadTotalBytesNumber() {
uint64 result = 0;
_runningRequestsMutex.lock();
if (_downloadFolderRequest)
result = _downloadFolderRequest->getTotalBytesToDownload();
_runningRequestsMutex.unlock();
return result;
}
uint64 Storage::getDownloadSpeed() {
uint64 result = 0;
_runningRequestsMutex.lock();
if (_downloadFolderRequest)
result = _downloadFolderRequest->getDownloadSpeed();
_runningRequestsMutex.unlock();
return result;
}
Common::String Storage::getDownloadRemoteDirectory() {
Common::String result = "";
_runningRequestsMutex.lock();
if (_downloadFolderRequest)
result = _downloadFolderRequest->getRemotePath();
_runningRequestsMutex.unlock();
return result;
}
Common::Path Storage::getDownloadLocalDirectory() {
Common::Path result;
_runningRequestsMutex.lock();
if (_downloadFolderRequest)
result = _downloadFolderRequest->getLocalPath();
_runningRequestsMutex.unlock();
return result;
}
void Storage::directoryDownloadedCallback(const FileArrayResponse &response) {
_runningRequestsMutex.lock();
_downloadFolderRequest = nullptr;
_runningRequestsMutex.unlock();
Common::U32String message;
if (response.value.size()) {
message = Common::U32String::format(_("Download complete.\nFailed to download %u files."), response.value.size());
} else {
message = _("Download complete.");
}
Common::OSDMessageQueue::instance().addMessage(message);
}
void Storage::directoryDownloadedErrorCallback(const Networking::ErrorResponse &error) {
_runningRequestsMutex.lock();
_downloadFolderRequest = nullptr;
_runningRequestsMutex.unlock();
Common::OSDMessageQueue::instance().addMessage(_("Download failed."));
}
} // End of namespace Cloud

257
backends/cloud/storage.h Normal file
View File

@@ -0,0 +1,257 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_STORAGE_H
#define BACKENDS_CLOUD_STORAGE_H
#include "backends/cloud/storagefile.h"
#include "backends/cloud/storageinfo.h"
#include "backends/networking/http/request.h"
#include "backends/networking/http/httprequest.h"
#include "common/array.h"
#include "common/callback.h"
#include "common/mutex.h"
#include "common/path.h"
#include "common/stream.h"
#include "common/str.h"
namespace GUI {
class CommandReceiver;
}
namespace Cloud {
class SavesSyncRequest;
class FolderDownloadRequest;
class Storage {
public:
typedef Networking::Response<const Common::Array<StorageFile> &> FileArrayResponse;
typedef Networking::Response<const StorageInfo &> StorageInfoResponse;
typedef Networking::Response<bool> BoolResponse;
typedef Networking::Response<const StorageFile &> UploadResponse;
typedef Networking::Response<const Common::Array<StorageFile> &> ListDirectoryResponse;
typedef Common::BaseCallback<const FileArrayResponse &> *FileArrayCallback;
typedef Common::BaseCallback<const StorageInfoResponse &> *StorageInfoCallback;
typedef Common::BaseCallback<const BoolResponse &> *BoolCallback;
typedef Common::BaseCallback<const UploadResponse &> *UploadCallback;
typedef Common::BaseCallback<const ListDirectoryResponse &> *ListDirectoryCallback;
protected:
/** Keeps track of running requests. */
uint32 _runningRequestsCount;
Common::Mutex _runningRequestsMutex;
/** SavesSyncRequest-related */
SavesSyncRequest *_savesSyncRequest;
bool _syncRestartRequestsed;
/** FolderDownloadRequest-related */
FolderDownloadRequest *_downloadFolderRequest;
/** Whether user manually enabled the Storage. */
bool _isEnabled;
/** Returns default error callback (printErrorResponse). */
virtual Networking::ErrorCallback getErrorPrintingCallback();
/** Prints ErrorResponse contents with debug(). */
virtual void printErrorResponse(const Networking::ErrorResponse &error);
/**
* Adds request to the ConnMan, but also increases _runningRequestsCount.
* This method should be used by Storage implementations instead of
* direct ConnMan.addRequest() call.
*
* @return the same Request pointer, just as a shortcut
*/
virtual Networking::Request *addRequest(Networking::Request *request);
/**
* Decreases _runningRequestCount. It's called from ConnMan automatically.
* Passed pointer is dangling, but one can use the address to determine
* some special Requests (which addresses were remembered somewhere).
*/
virtual void requestFinishedCallback(Networking::Request *invalidRequestPointer);
public:
Storage();
virtual ~Storage();
/**
* Storage methods, which are used by CloudManager to save
* storage in configuration file.
*/
/**
* Save storage data using ConfMan.
* @param keyPrefix all saved keys must start with this prefix.
* @note every Storage must write keyPrefix + "type" key
* with common value (e.g. "Dropbox").
*/
virtual void saveConfig(const Common::String &keyPrefix) = 0;
/**
* Return unique storage name.
* @returns some unique storage name (for example, "Dropbox (user@example.com)")
*/
virtual Common::String name() const = 0;
/**
* Return whether Storage has been manually enabled by user.
*/
bool isEnabled() const;
/**
* Set _isEnabled to true.
*/
void enable();
/**
* Public Cloud API comes down there.
*
* All Cloud API methods return Networking::Request *, which
* might be used to control request. All methods also accept
* a callback, which is called, when request is complete.
*/
/** Returns ListDirectoryResponse with list of files. */
virtual Networking::Request *listDirectory(const Common::String &path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false) = 0;
/** Returns StorageFile with info about uploaded file. */
virtual Networking::Request *upload(const Common::String &path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) = 0;
virtual Networking::Request *upload(const Common::String &remotePath, const Common::Path &localPath, UploadCallback callback, Networking::ErrorCallback errorCallback);
/** Returns whether Storage supports upload(ReadStream). */
virtual bool uploadStreamSupported();
/** Returns pointer to Networking::NetworkReadStream. */
virtual Networking::Request *streamFile(const Common::String &path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
virtual Networking::Request *streamFileById(const Common::String &id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) = 0;
/** Calls the callback when finished. */
virtual Networking::Request *download(const Common::String &remotePath, const Common::Path &localPath, BoolCallback callback, Networking::ErrorCallback errorCallback);
virtual Networking::Request *downloadById(const Common::String &remoteId, const Common::Path &localPath, BoolCallback callback, Networking::ErrorCallback errorCallback);
/** Returns Common::Array<StorageFile> with list of files, which were not downloaded. */
virtual Networking::Request *downloadFolder(const Common::String &remotePath, const Common::Path &localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
/** Calls the callback when finished. */
virtual SavesSyncRequest *syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback);
/** Calls the callback when finished. */
virtual Networking::Request *createDirectory(const Common::String &path, BoolCallback callback, Networking::ErrorCallback errorCallback) = 0;
/**
* Returns the StorageInfo struct via <callback>.
* Calls the <errorCallback> if failed to get information.
*
* @note on success Storage should also call
* CloudMan.setStorageUsername().
*/
virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) = 0;
/** Returns storage's saves directory path with the trailing slash. */
virtual Common::String savesDirectoryPath() = 0;
/** Returns whether there are any requests running. */
virtual bool isWorking();
///// SavesSyncRequest-related /////
/** Returns whether there is a SavesSyncRequest running. */
virtual bool isSyncing();
/** Returns a number in [0, 1] range which represents current sync downloading progress (1 = complete). */
virtual double getSyncDownloadingProgress();
struct SyncDownloadingInfo {
uint64 bytesDownloaded = 0, bytesToDownload = 0;
uint64 filesDownloaded = 0, filesToDownload = 0;
bool inProgress = false;
};
/** Fills a struct with numbers about current sync downloading progress. */
virtual void getSyncDownloadingInfo(SyncDownloadingInfo &info);
/** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */
virtual double getSyncProgress();
/** Returns an array of saves names which are not yet synced (thus cannot be used). */
virtual Common::Array<Common::String> getSyncingFiles();
/** Cancels running sync. */
virtual void cancelSync();
protected:
/** Finishes the sync. Shows an OSD message. */
virtual void savesSyncDefaultCallback(const BoolResponse &response);
/** Finishes the sync. Shows an OSD message. */
virtual void savesSyncDefaultErrorCallback(const Networking::ErrorResponse &error);
public:
///// DownloadFolderRequest-related /////
/** Starts a folder download. */
virtual bool startDownload(const Common::String &remotePath, const Common::Path &localPath);
/** Cancels running download. */
virtual void cancelDownload();
/** Sets FolderDownloadRequest's target to given CommandReceiver. */
virtual void setDownloadTarget(GUI::CommandReceiver *target);
/** Returns whether there is a FolderDownloadRequest running. */
virtual bool isDownloading();
/** Returns a number in [0, 1] range which represents current download progress (1 = complete). */
virtual double getDownloadingProgress();
/** Returns a number of bytes that is downloaded in current download progress. */
virtual uint64 getDownloadBytesNumber();
/** Returns a total number of bytes to be downloaded in current download progress. */
virtual uint64 getDownloadTotalBytesNumber();
/** Returns download speed of current download progress. */
virtual uint64 getDownloadSpeed();
/** Returns remote directory path. */
virtual Common::String getDownloadRemoteDirectory();
/** Returns local directory path. */
virtual Common::Path getDownloadLocalDirectory();
protected:
/** Finishes the download. Shows an OSD message. */
virtual void directoryDownloadedCallback(const FileArrayResponse &response);
/** Finishes the download. Shows an OSD message. */
virtual void directoryDownloadedErrorCallback(const Networking::ErrorResponse &error);
};
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,67 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/cloud/storagefile.h"
namespace Cloud {
StorageFile::StorageFile() {
_id = "";
_path = "";
_name = "";
_size = 0;
_timestamp = 0;
_isDirectory = false;
}
StorageFile::StorageFile(const Common::String &pth, uint32 sz, uint32 ts, bool dir) {
_id = pth;
_path = pth;
_name = pth;
if (_name.size() != 0) {
uint32 i = _name.size() - 1;
while (true) {
if (_name[i] == '/' || _name[i] == '\\') {
_name.erase(0, i + 1);
break;
}
if (i == 0)
break;
--i;
}
}
_size = sz;
_timestamp = ts;
_isDirectory = dir;
}
StorageFile::StorageFile(const Common::String &fileId, const Common::String &filePath, const Common::String &fileName, uint32 sz, uint32 ts, bool dir) {
_id = fileId;
_path = filePath;
_name = fileName;
_size = sz;
_timestamp = ts;
_isDirectory = dir;
}
} // End of namespace Cloud

View File

@@ -0,0 +1,64 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_STORAGEFILE_H
#define BACKENDS_CLOUD_STORAGEFILE_H
#include "common/str.h"
namespace Cloud {
/**
* StorageFile represents a file storaged on remote cloud storage.
* It contains basic information about a file, and might be used
* when listing directories or syncing files.
*
* Some storages (Google Drive, for example) don't have an actual
* path notation to address files. Instead, they are using ids.
* As resolving id by path is not a fast operation, it's required
* to use ids if they are known, but user-friendly paths are
* necessary too, because these are used by Requests.
*
* If storage supports path notation, id would actually contain path.
*/
class StorageFile {
Common::String _id, _path, _name;
uint32 _size, _timestamp;
bool _isDirectory;
public:
StorageFile(); //invalid empty file
StorageFile(const Common::String &pth, uint32 sz, uint32 ts, bool dir);
StorageFile(const Common::String &fileId, const Common::String &filePath, const Common::String &fileName, uint32 sz, uint32 ts, bool dir);
Common::String id() const { return _id; }
Common::String path() const { return _path; }
Common::String name() const { return _name; }
uint32 size() const { return _size; }
uint32 timestamp() const { return _timestamp; }
bool isDirectory() const { return _isDirectory; }
void setPath(const Common::String &path_) { _path = path_; }
};
} // End of namespace Cloud
#endif

View File

@@ -0,0 +1,52 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_CLOUD_STORAGEINFO_H
#define BACKENDS_CLOUD_STORAGEINFO_H
#include "common/str.h"
namespace Cloud {
/**
* StorageInfo contains information about remote cloud storage.
* It's disk quota usage, owner name, and such.
*/
class StorageInfo {
Common::String _uid, _name, _email;
uint64 _usedBytes, _allocatedBytes;
public:
StorageInfo(const Common::String &uid_, const Common::String &name_, const Common::String &email_, uint64 used_, uint64 allocated):
_uid(uid_), _name(name_), _email(email_), _usedBytes(used_), _allocatedBytes(allocated) {}
Common::String uid() const { return _uid; }
Common::String name() const { return _name; }
Common::String email() const { return _email; }
uint64 used() const { return _usedBytes; }
uint64 available() const { return _allocatedBytes; }
};
} // End of namespace Cloud
#endif