diff options
author | Alexander Tkachev | 2016-06-03 15:14:12 +0600 |
---|---|---|
committer | Alexander Tkachev | 2016-08-24 16:07:55 +0600 |
commit | da3b7bd8d9f3d3828b8cea6dff60e5f43e7ad4b1 (patch) | |
tree | 35d3054efddffe2343b66ebb9a62cbb1c342494e | |
parent | b02b16ab98330f3471919a57ec0d4e087e2fa924 (diff) | |
download | scummvm-rg350-da3b7bd8d9f3d3828b8cea6dff60e5f43e7ad4b1.tar.gz scummvm-rg350-da3b7bd8d9f3d3828b8cea6dff60e5f43e7ad4b1.tar.bz2 scummvm-rg350-da3b7bd8d9f3d3828b8cea6dff60e5f43e7ad4b1.zip |
CLOUD: Add GoogleDriveStorage
It has its own GoogleDriveTokenRefresher and knows how to do info().
This commit also contains JSON int -> long long int fix and
CurlJsonRequest '\n' -> ' ' fix.
-rw-r--r-- | backends/cloud/cloudmanager.cpp | 15 | ||||
-rw-r--r-- | backends/cloud/dropbox/dropboxuploadrequest.cpp | 4 | ||||
-rw-r--r-- | backends/cloud/googledrive/googledrivestorage.cpp | 331 | ||||
-rw-r--r-- | backends/cloud/googledrive/googledrivestorage.h | 131 | ||||
-rw-r--r-- | backends/cloud/googledrive/googledrivetokenrefresher.cpp | 121 | ||||
-rw-r--r-- | backends/cloud/googledrive/googledrivetokenrefresher.h | 52 | ||||
-rw-r--r-- | backends/module.mk | 2 | ||||
-rw-r--r-- | backends/networking/curl/curljsonrequest.cpp | 6 | ||||
-rw-r--r-- | common/json.cpp | 6 | ||||
-rw-r--r-- | common/json.h | 6 |
10 files changed, 660 insertions, 14 deletions
diff --git a/backends/cloud/cloudmanager.cpp b/backends/cloud/cloudmanager.cpp index d18bb6ff9a..92e45e8811 100644 --- a/backends/cloud/cloudmanager.cpp +++ b/backends/cloud/cloudmanager.cpp @@ -23,6 +23,7 @@ #include "backends/cloud/cloudmanager.h" #include "backends/cloud/dropbox/dropboxstorage.h" #include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/googledrive/googledrivestorage.h" #include "common/config-manager.h" #include "common/debug.h" @@ -45,7 +46,8 @@ CloudManager::~CloudManager() { void CloudManager::init() { bool offerDropbox = false; - bool offerOneDrive = true; + bool offerOneDrive = false; + bool offerGoogleDrive = true; if (ConfMan.hasKey("storages_number", "cloud")) { int storages = ConfMan.getInt("storages_number", "cloud"); @@ -55,9 +57,10 @@ void CloudManager::init() { if (ConfMan.hasKey(keyPrefix + "type", "cloud")) { Common::String storageType = ConfMan.get(keyPrefix + "type", "cloud"); if (storageType == "Dropbox") loaded = Dropbox::DropboxStorage::loadFromConfig(keyPrefix); - else if (storageType == "OneDrive") { - loaded = OneDrive::OneDriveStorage::loadFromConfig(keyPrefix); - offerOneDrive = false; + else if (storageType == "OneDrive") loaded = OneDrive::OneDriveStorage::loadFromConfig(keyPrefix); + else if (storageType == "Google Drive") { + loaded = GoogleDrive::GoogleDriveStorage::loadFromConfig(keyPrefix); + offerGoogleDrive = false; } else warning("Unknown cloud storage type '%s' passed", storageType.c_str()); } else { warning("Cloud storage #%d (out of %d) is missing.", i, storages); @@ -82,6 +85,9 @@ void CloudManager::init() { } else if (offerOneDrive) { //OneDrive time OneDrive::OneDriveStorage::authThroughConsole(); + } else if (offerGoogleDrive) { + GoogleDrive::GoogleDriveStorage::authThroughConsole(); + _currentStorageIndex = 100; } } @@ -117,6 +123,7 @@ void CloudManager::syncSaves(Storage::BoolCallback callback, Networking::ErrorCa void CloudManager::testFeature() { Storage *storage = getCurrentStorage(); + if (storage) storage->info(nullptr, nullptr); } } // End of namespace Common diff --git a/backends/cloud/dropbox/dropboxuploadrequest.cpp b/backends/cloud/dropbox/dropboxuploadrequest.cpp index 50a1b8a612..bf8e43d7cb 100644 --- a/backends/cloud/dropbox/dropboxuploadrequest.cpp +++ b/backends/cloud/dropbox/dropboxuploadrequest.cpp @@ -79,7 +79,7 @@ void DropboxUploadRequest::uploadNextPart() { url += "finish"; Common::JSONObject jsonCursor, jsonCommit; jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId)); - jsonCursor.setVal("offset", new Common::JSONValue(_contentsStream->pos())); + 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)); @@ -90,7 +90,7 @@ void DropboxUploadRequest::uploadNextPart() { url += "append_v2"; Common::JSONObject jsonCursor; jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId)); - jsonCursor.setVal("offset", new Common::JSONValue(_contentsStream->pos())); + 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)); } diff --git a/backends/cloud/googledrive/googledrivestorage.cpp b/backends/cloud/googledrive/googledrivestorage.cpp new file mode 100644 index 0000000000..eef7f1f28d --- /dev/null +++ b/backends/cloud/googledrive/googledrivestorage.cpp @@ -0,0 +1,331 @@ +/* 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 2 +* 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, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/cloud/googledrive/googledrivetokenrefresher.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +namespace Cloud { +namespace GoogleDrive { + +char *GoogleDriveStorage::KEY; //can't use ConfMan there yet, loading it on instance creation/auth +char *GoogleDriveStorage::SECRET; //TODO: hide these secrets somehow + +void GoogleDriveStorage::loadKeyAndSecret() { + Common::String k = ConfMan.get("GOOGLE_DRIVE_KEY", "cloud"); + KEY = new char[k.size() + 1]; + memcpy(KEY, k.c_str(), k.size()); + KEY[k.size()] = 0; + + k = ConfMan.get("GOOGLE_DRIVE_SECRET", "cloud"); + SECRET = new char[k.size() + 1]; + memcpy(SECRET, k.c_str(), k.size()); + SECRET[k.size()] = 0; +} + +GoogleDriveStorage::GoogleDriveStorage(Common::String accessToken, Common::String refreshToken): + _token(accessToken), _refreshToken(refreshToken) {} + +GoogleDriveStorage::GoogleDriveStorage(Common::String code) { + getAccessToken(new Common::Callback<GoogleDriveStorage, BoolResponse>(this, &GoogleDriveStorage::codeFlowComplete), code); +} + +GoogleDriveStorage::~GoogleDriveStorage() {} + +void GoogleDriveStorage::getAccessToken(BoolCallback callback, Common::String code) { + bool codeFlow = (code != ""); + + if (!codeFlow && _refreshToken == "") { + warning("GoogleDriveStorage: no refresh token available to get new access token."); + if (callback) (*callback)(BoolResponse(nullptr, false)); + return; + } + + Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, BoolResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::tokenRefreshed, callback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, getErrorPrintingCallback(), "https://accounts.google.com/o/oauth2/token"); //TODO + if (codeFlow) { + request->addPostField("code=" + code); + request->addPostField("grant_type=authorization_code"); + } else { + request->addPostField("refresh_token=" + _refreshToken); + request->addPostField("grant_type=refresh_token"); + } + request->addPostField("client_id=" + Common::String(KEY)); + request->addPostField("client_secret=" + Common::String(SECRET)); + request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost"); + addRequest(request); +} + +void GoogleDriveStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("GoogleDriveStorage: got NULL instead of JSON"); + if (callback) (*callback)(BoolResponse(nullptr, false)); + return; + } + + Common::JSONObject result = json->asObject(); + if (!result.contains("access_token")) { + warning("Bad response, no token passed"); + debug("%s", json->stringify().c_str()); + if (callback) (*callback)(BoolResponse(nullptr, false)); + } else { + _token = result.getVal("access_token")->asString(); + if (!result.contains("refresh_token")) + warning("No refresh_token passed"); + else + _refreshToken = result.getVal("refresh_token")->asString(); + CloudMan.save(); //ask CloudManager to save our new refreshToken + if (callback) (*callback)(BoolResponse(nullptr, true)); + } + delete json; +} + +void GoogleDriveStorage::codeFlowComplete(BoolResponse response) { + if (!response.value) { + warning("GoogleDriveStorage: failed to get access token through code flow"); + return; + } + + ConfMan.removeKey("googledrive_code", "cloud"); + CloudMan.addStorage(this); + ConfMan.flushToDisk(); + debug("Done! You can use Google Drive now! Look:"); + CloudMan.testFeature(); +} + +void GoogleDriveStorage::saveConfig(Common::String keyPrefix) { + ConfMan.set(keyPrefix + "type", "Google Drive", "cloud"); + ConfMan.set(keyPrefix + "access_token", _token, "cloud"); + ConfMan.set(keyPrefix + "refresh_token", _refreshToken, "cloud"); +} + +namespace { +uint64 atoull(Common::String s) { + uint64 result = 0; + for (uint32 i = 0; i < s.size(); ++i) { + if (s[i] < '0' || s[i] > '9') break; + result = result * 10L + (s[i] - '0'); + } + return result; +} +} + +void GoogleDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (outerCallback) { + Common::JSONObject info = json->asObject(); + + Common::String uid, name, email; + uint64 quotaUsed = 0, quotaAllocated = 0; + + if (info.contains("user") && info.getVal("user")->isObject()) { + //"me":true, "kind":"drive#user","photoLink": "", + //"displayName":"Alexander Tkachev","emailAddress":"alexander@tkachov.ru","permissionId":"" + Common::JSONObject user = info.getVal("user")->asObject(); + uid = user.getVal("permissionId")->asString(); //not sure it's user's id, but who cares anyway? + name = user.getVal("displayName")->asString(); + email = user.getVal("emailAddress")->asString(); + } + + if (info.contains("storageQuota") && info.getVal("storageQuota")->isObject()) { + //"usageInDrive":"6332462","limit":"18253611008","usage":"6332462","usageInDriveTrash":"0" + Common::JSONObject storageQuota = info.getVal("storageQuota")->asObject(); + Common::String usage = storageQuota.getVal("usage")->asString(); + Common::String limit = storageQuota.getVal("limit")->asString(); + quotaUsed = atoull(usage); + quotaAllocated = atoull(limit); + } + + (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated))); + delete outerCallback; + } + + delete json; +} + +void GoogleDriveStorage::printJson(Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("printJson: NULL"); + return; + } + + debug("%s", json->stringify().c_str()); + delete json; +} + +void GoogleDriveStorage::fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response) { + if (!response.value) { + warning("fileInfoCallback: NULL"); + if (outerCallback) (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, 0)); + return; + } + + Common::JSONObject result = response.value->asObject(); + if (result.contains("@content.downloadUrl")) { + const char *url = result.getVal("@content.downloadUrl")->asString().c_str(); + if (outerCallback) + (*outerCallback)(Networking::NetworkReadStreamResponse( + response.request, + new Networking::NetworkReadStream(url, 0, "") + )); + } else { + warning("downloadUrl not found in passed JSON"); + debug("%s", response.value->stringify().c_str()); + if (outerCallback) (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, 0)); + } + delete response.value; +} + +Networking::Request *GoogleDriveStorage::listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + //return addRequest(new GoogleDriveListDirectoryRequest(this, path, callback, errorCallback, recursive)); + return nullptr; //TODO +} + +Networking::Request *GoogleDriveStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) { + //return addRequest(new GoogleDriveUploadRequest(this, path, contents, callback, errorCallback)); + return nullptr; //TODO +} + +Networking::Request *GoogleDriveStorage::streamFile(Common::String path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) { + /* + Common::String url = "https://api.onedrive.com/v1.0/drive/special/approot:/" + path; + Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, Networking::NetworkReadStreamResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::fileInfoCallback, outerCallback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _token); + return addRequest(request); + */ + return nullptr; //TODO +} + +void GoogleDriveStorage::fileDownloaded(BoolResponse response) { + if (response.value) debug("file downloaded!"); + else debug("download failed!"); +} + +void GoogleDriveStorage::printFiles(FileArrayResponse response) { + debug("files:"); + Common::Array<StorageFile> &files = response.value; + for (uint32 i = 0; i < files.size(); ++i) + debug("\t%s", files[i].path().c_str()); +} + +void GoogleDriveStorage::printBool(BoolResponse response) { + debug("bool: %s", response.value ? "true" : "false"); +} + +void GoogleDriveStorage::printFile(UploadResponse response) { + debug("\nuploaded file info:"); + debug("\tpath: %s", response.value.path().c_str()); + debug("\tsize: %u", response.value.size()); + debug("\ttimestamp: %u", response.value.timestamp()); +} + +void GoogleDriveStorage::printInfo(StorageInfoResponse response) { + debug("\nuser info:"); + debug("\tname: %s", response.value.name().c_str()); + debug("\temail: %s", response.value.email().c_str()); + debug("\tdisk usage: %llu/%llu", response.value.used(), response.value.available()); +} + +Networking::Request *GoogleDriveStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) errorCallback = getErrorPrintingCallback(); + //return addRequest(new GoogleDriveCreateDirectoryRequest(this, path, callback, errorCallback)); + return nullptr; //TODO +} + +Networking::Request *GoogleDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + if (!callback) callback = new Common::Callback<GoogleDriveStorage, StorageInfoResponse>(this, &GoogleDriveStorage::printInfo); + Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, StorageInfoResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::infoInnerCallback, callback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, "https://www.googleapis.com/drive/v3/about?fields=storageQuota,user"); + request->addHeader("Authorization: Bearer " + _token); + return addRequest(request); +} + +Common::String GoogleDriveStorage::savesDirectoryPath() { return "saves/"; } + +GoogleDriveStorage *GoogleDriveStorage::loadFromConfig(Common::String keyPrefix) { + loadKeyAndSecret(); + + if (!ConfMan.hasKey(keyPrefix + "access_token", "cloud")) { + warning("No access_token found"); + return 0; + } + + if (!ConfMan.hasKey(keyPrefix + "refresh_token", "cloud")) { + warning("No refresh_token found"); + return 0; + } + + Common::String accessToken = ConfMan.get(keyPrefix + "access_token", "cloud"); + Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", "cloud"); + return new GoogleDriveStorage(accessToken, refreshToken); +} + +Common::String GoogleDriveStorage::getAuthLink() { + Common::String url = "https://accounts.google.com/o/oauth2/auth"; + url += "?response_type=code"; + url += "&redirect_uri=http://localhost"; //that's for copy-pasting + //url += "&redirect_uri=http%3A%2F%2Flocalhost"; //that's "http://localhost" for automatic opening + url += "&client_id="; url += KEY; + url += "&scope=https://www.googleapis.com/auth/drive.appfolder"; //for copy-pasting + return url; +} + +void GoogleDriveStorage::authThroughConsole() { + if (!ConfMan.hasKey("GOOGLE_DRIVE_KEY", "cloud") || !ConfMan.hasKey("GOOGLE_DRIVE_SECRET", "cloud")) { + warning("No Google Drive keys available, cannot do auth"); + return; + } + + loadKeyAndSecret(); + + if (ConfMan.hasKey("googledrive_code", "cloud")) { + //phase 2: get access_token using specified code + new GoogleDriveStorage(ConfMan.get("googledrive_code", "cloud")); + return; + } + + debug("Navigate to this URL and press \"Allow\":"); + debug("%s\n", getAuthLink().c_str()); + debug("Then, add googledrive_code key in [cloud] section of configuration file. You should copy the <code> value from URL and put it as value for that key.\n"); + debug("Navigate to this URL to get more information on ScummVM's configuration files:"); + debug("http://wiki.scummvm.org/index.php/User_Manual/Configuring_ScummVM#Using_the_configuration_file_to_configure_ScummVM\n"); +} + +} // End of namespace GoogleDrive +} // End of namespace Cloud diff --git a/backends/cloud/googledrive/googledrivestorage.h b/backends/cloud/googledrive/googledrivestorage.h new file mode 100644 index 0000000000..8a82a54533 --- /dev/null +++ b/backends/cloud/googledrive/googledrivestorage.h @@ -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 2 +* 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, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVESTORAGE_H +#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVESTORAGE_H + +#include "backends/cloud/storage.h" +#include "common/callback.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace GoogleDrive { + +class GoogleDriveStorage: public Cloud::Storage { + static char *KEY, *SECRET; + + static void loadKeyAndSecret(); + + Common::String _token, _refreshToken; + + /** This private constructor is called from loadFromConfig(). */ + GoogleDriveStorage(Common::String token, Common::String refreshToken); + + /** + * This private constructor is called from authThroughConsole() (phase 2). + * It uses OAuth code flow to get tokens. + */ + GoogleDriveStorage(Common::String code); + + void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response); + void codeFlowComplete(BoolResponse response); + + /** Constructs StorageInfo based on JSON response from cloud. */ + void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json); + + void printJson(Networking::JsonResponse response); + void fileDownloaded(BoolResponse response); + void printFiles(FileArrayResponse response); + void printBool(BoolResponse response); + void printFile(UploadResponse response); + void printInfo(StorageInfoResponse response); + + void fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response); +public: + virtual ~GoogleDriveStorage(); + + /** + * 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(Common::String keyPrefix); + + /** Public Cloud API comes down there. */ + + /** Returns ListDirectoryStatus struct with list of files. */ + virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Returns UploadStatus struct with info about uploaded file. */ + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + + /** Calls the callback when finished. */ + virtual Networking::Request *remove(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) { return nullptr; } //TODO + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns the StorageInfo struct. */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath(); + + /** + * Load token and user id from configs and return GoogleDriveStorage for those. + * @return pointer to the newly created GoogleDriveStorage or 0 if some problem occured. + */ + static GoogleDriveStorage *loadFromConfig(Common::String keyPrefix); + + /** + * Returns GoogleDrive auth link. + */ + static Common::String getAuthLink(); + + /** + * Show message with GoogleDrive auth instructions. (Temporary) + */ + static void authThroughConsole(); + + /** + * Gets new access_token. If <code> passed is "", refresh_token is used. + * Use "" in order to refresh token and pass a callback, so you could + * continue your work when new token is available. + */ + void getAccessToken(BoolCallback callback, Common::String code = ""); + + Common::String accessToken() { return _token; } +}; + +} // End of namespace GoogleDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/googledrive/googledrivetokenrefresher.cpp b/backends/cloud/googledrive/googledrivetokenrefresher.cpp new file mode 100644 index 0000000000..3fae830105 --- /dev/null +++ b/backends/cloud/googledrive/googledrivetokenrefresher.cpp @@ -0,0 +1,121 @@ +/* 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 2 +* 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, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/googledrive/googledrivetokenrefresher.h" +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +namespace Cloud { +namespace GoogleDrive { + +GoogleDriveTokenRefresher::GoogleDriveTokenRefresher(GoogleDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url): + CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {} + +GoogleDriveTokenRefresher::~GoogleDriveTokenRefresher() {} + +void GoogleDriveTokenRefresher::tokenRefreshed(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, "", -1)); + return; + } + + //update headers: first change header with token, then pass those to request + for (uint32 i = 0; i < _headers.size(); ++i) { + if (_headers[i].contains("Authorization")) { + _headers[i] = "Authorization: Bearer " + _parentStorage->accessToken(); + } + } + setHeaders(_headers); + + //successfully received refreshed token, can restart the original request now + retry(0); +} + +void GoogleDriveTokenRefresher::finishSuccess(Common::JSONValue *json) { + if (!json) { + //that's probably not an error (200 OK) + CurlJsonRequest::finishSuccess(nullptr); + return; + } + + Common::JSONObject result = json->asObject(); + if (result.contains("error")) { + //new token needed => request token & then retry original request + if (_stream) { + debug("code %ld", _stream->httpResponseCode()); + } + + Common::JSONObject error = result.getVal("error")->asObject(); + bool irrecoverable = true; + + uint32 code = -1; + Common::String message; + if (error.contains("code") && error.getVal("code")->isIntegerNumber()) { + code = error.getVal("code")->asIntegerNumber(); + debug("code = %u", code); + } + + if (error.contains("message")) { + message = error.getVal("message")->asString(); + debug("message = %s", message.c_str()); + } + + if (code == 401 || message == "Invalid Credentials") + irrecoverable = false; + + if (irrecoverable) { + finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), -1)); //TODO: httpCode + delete json; + return; + } + + pause(); + delete json; + _parentStorage->getAccessToken(new Common::Callback<GoogleDriveTokenRefresher, Storage::BoolResponse>(this, &GoogleDriveTokenRefresher::tokenRefreshed)); + return; + } + + //notify user of success + CurlJsonRequest::finishSuccess(json); +} + +void GoogleDriveTokenRefresher::setHeaders(Common::Array<Common::String> &headers) { + _headers = headers; + curl_slist_free_all(_headersList); + _headersList = 0; + for (uint32 i = 0; i < headers.size(); ++i) + CurlJsonRequest::addHeader(headers[i]); +} + +void GoogleDriveTokenRefresher::addHeader(Common::String header) { + _headers.push_back(header); + CurlJsonRequest::addHeader(header); +} + +} // End of namespace GoogleDrive +} // End of namespace Cloud diff --git a/backends/cloud/googledrive/googledrivetokenrefresher.h b/backends/cloud/googledrive/googledrivetokenrefresher.h new file mode 100644 index 0000000000..3dcb56bde6 --- /dev/null +++ b/backends/cloud/googledrive/googledrivetokenrefresher.h @@ -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 2 +* 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, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVETOKENREFRESHER_H +#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVETOKENREFRESHER_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace GoogleDrive { + +class GoogleDriveStorage; + +class GoogleDriveTokenRefresher: public Networking::CurlJsonRequest { + GoogleDriveStorage *_parentStorage; + Common::Array<Common::String> _headers; + + void tokenRefreshed(Storage::BoolResponse response); + + virtual void finishSuccess(Common::JSONValue *json); +public: + GoogleDriveTokenRefresher(GoogleDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url); + virtual ~GoogleDriveTokenRefresher(); + + virtual void setHeaders(Common::Array<Common::String> &headers); + virtual void addHeader(Common::String header); +}; + +} // End of namespace GoogleDrive +} // End of namespace Cloud + +#endif diff --git a/backends/module.mk b/backends/module.mk index 40ccd17c78..8833edc9db 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -32,6 +32,8 @@ MODULE_OBJS += \ cloud/dropbox/dropboxcreatedirectoryrequest.o \ cloud/dropbox/dropboxlistdirectoryrequest.o \ cloud/dropbox/dropboxuploadrequest.o \ + cloud/googledrive/googledrivestorage.o \ + cloud/googledrive/googledrivetokenrefresher.o \ cloud/onedrive/onedrivestorage.o \ cloud/onedrive/onedrivecreatedirectoryrequest.o \ cloud/onedrive/onedrivetokenrefresher.o \ diff --git a/backends/networking/curl/curljsonrequest.cpp b/backends/networking/curl/curljsonrequest.cpp index df982bc814..46d88657d2 100644 --- a/backends/networking/curl/curljsonrequest.cpp +++ b/backends/networking/curl/curljsonrequest.cpp @@ -44,9 +44,11 @@ char *CurlJsonRequest::getPreparedContents() { //replace all "bad" bytes with '.' character byte *result = _contentsStream.getData(); uint32 size = _contentsStream.size(); - for (uint32 i = 0; i < size; ++i) - if (result[i] < 0x20 || result[i] > 0x7f) + for (uint32 i = 0; i < size; ++i) { + if (result[i] == '\n') result[i] = ' '; //yeah, kinda stupid + else if (result[i] < 0x20 || result[i] > 0x7f) result[i] = '.'; + } //make it zero-terminated string result[size - 1] = '\0'; diff --git a/common/json.cpp b/common/json.cpp index 779f99aae0..5f63d3fe1f 100644 --- a/common/json.cpp +++ b/common/json.cpp @@ -300,7 +300,7 @@ JSONValue *JSONValue::parse(const char **data) { bool neg = **data == '-'; if (neg) (*data)++; - int integer = 0; + long long int integer = 0; double number = 0.0; bool onlyInteger = true; @@ -568,7 +568,7 @@ JSONValue::JSONValue(double numberValue) { * * @param int numberValue The number to use as the value */ -JSONValue::JSONValue(int numberValue) { +JSONValue::JSONValue(long long int numberValue) { _type = JSONType_IntegerNumber; _integerValue = numberValue; } @@ -794,7 +794,7 @@ double JSONValue::asNumber() const { * * @return int Returns the number value */ -int JSONValue::asIntegerNumber() const { +long long int JSONValue::asIntegerNumber() const { return _integerValue; } diff --git a/common/json.h b/common/json.h index 35571935ee..d9bbbdb77c 100644 --- a/common/json.h +++ b/common/json.h @@ -96,7 +96,7 @@ public: JSONValue(const String &stringValue); JSONValue(bool boolValue); JSONValue(double numberValue); - JSONValue(int numberValue); + JSONValue(long long int numberValue); JSONValue(const JSONArray &arrayValue); JSONValue(const JSONObject &objectValue); JSONValue(const JSONValue &source); @@ -113,7 +113,7 @@ public: const String &asString() const; bool asBool() const; double asNumber() const; - int asIntegerNumber() const; + long long int asIntegerNumber() const; const JSONArray &asArray() const; const JSONObject &asObject() const; @@ -138,7 +138,7 @@ private: union { bool _boolValue; double _numberValue; - int _integerValue; + long long int _integerValue; String *_stringValue; JSONArray *_arrayValue; JSONObject *_objectValue; |