aboutsummaryrefslogtreecommitdiff
path: root/backends/cloud/savessyncrequest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backends/cloud/savessyncrequest.cpp')
-rw-r--r--backends/cloud/savessyncrequest.cpp403
1 files changed, 403 insertions, 0 deletions
diff --git a/backends/cloud/savessyncrequest.cpp b/backends/cloud/savessyncrequest.cpp
new file mode 100644
index 0000000000..f9b16b355b
--- /dev/null
+++ b/backends/cloud/savessyncrequest.cpp
@@ -0,0 +1,403 @@
+/* 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.
+ *
+ */
+
+#include "backends/cloud/savessyncrequest.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/saves/default/default-saves.h"
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/file.h"
+#include "common/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), CommandSender(nullptr), _storage(storage), _boolCallback(callback),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ 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, Storage::ListDirectoryResponse>(this, &SavesSyncRequest::directoryListedCallback),
+ new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryListedErrorCallback)
+ );
+ if (!_workingRequest) finishError(Networking::ErrorResponse(this));
+}
+
+void SavesSyncRequest::directoryListedCallback(Storage::ListDirectoryResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ if (response.request) _date = response.request->date();
+
+ Common::HashMap<Common::String, bool> localFileNotAvailableInCloud;
+ for (Common::HashMap<Common::String, uint32>::iterator i = _localFilesTimestamps.begin(); i != _localFilesTimestamps.end(); ++i) {
+ localFileNotAvailableInCloud[i->_key] = true;
+ }
+
+ //determine which files to download and which files to upload
+ Common::Array<StorageFile> &remoteFiles = response.value;
+ uint64 totalSize = 0;
+ for (uint32 i = 0; i < remoteFiles.size(); ++i) {
+ StorageFile &file = remoteFiles[i];
+ if (file.isDirectory())
+ continue;
+ totalSize += file.size();
+ if (file.name() == DefaultSaveFileManager::TIMESTAMPS_FILENAME)
+ continue;
+
+ Common::String name = file.name();
+ if (!_localFilesTimestamps.contains(name)) {
+ _filesToDownload.push_back(file);
+ } 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);
+ }
+ }
+
+ CloudMan.setStorageUsedSpace(CloudMan.getStorageIndex(), totalSize);
+
+ //upload files which are unavailable in cloud
+ for (Common::HashMap<Common::String, bool>::iterator i = localFileNotAvailableInCloud.begin(); i != localFileNotAvailableInCloud.end(); ++i) {
+ if (i->_key == DefaultSaveFileManager::TIMESTAMPS_FILENAME)
+ continue;
+ if (i->_value)
+ _filesToUpload.push_back(i->_key);
+ }
+
+ debug(9, "\nSavesSyncRequest: download files:");
+ for (uint32 i = 0; i < _filesToDownload.size(); ++i) {
+ debug(9, "%s", _filesToDownload[i].name().c_str());
+ }
+ debug(9, "\nSavesSyncRequest: upload files:");
+ for (uint32 i = 0; i < _filesToUpload.size(); ++i) {
+ debug(9, "%s", _filesToUpload[i].c_str());
+ }
+ _totalFilesToHandle = _filesToDownload.size() + _filesToUpload.size();
+
+ //start downloading files
+ downloadNextFile();
+}
+
+void SavesSyncRequest::directoryListedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ bool irrecoverable = error.interrupted || error.failed;
+ if (error.failed) {
+ Common::JSONValue *value = Common::JSON::parse(error.response.c_str());
+ 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::CurlJsonRequest::jsonContainsString(errorNode, "code", "SavesSyncRequest")) {
+ Common::String code = errorNode.getVal("code")->asString();
+ if (code == "itemNotFound") {
+ irrecoverable = false;
+ }
+ }
+ }
+ }
+ delete value;
+ }
+
+ //Google Drive and Box-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
+ }
+ }
+
+ 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, "SavesSyncRequest: creating %s", dir.c_str());
+ _workingRequest = _storage->createDirectory(
+ dir,
+ new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::directoryCreatedCallback),
+ new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryCreatedErrorCallback)
+ );
+ if (!_workingRequest)
+ finishError(Networking::ErrorResponse(this));
+}
+
+void SavesSyncRequest::directoryCreatedCallback(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, "", -1));
+ return;
+ }
+
+ //continue with empty files list
+ Common::Array<StorageFile> files;
+ directoryListedCallback(Storage::ListDirectoryResponse(response.request, files));
+}
+
+void SavesSyncRequest::directoryCreatedErrorCallback(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
+ sendCommand(GUI::kSavesSyncEndedCmd, 0);
+ uploadNextFile();
+ return;
+ }
+
+ _currentDownloadingFile = _filesToDownload.back();
+ _filesToDownload.pop_back();
+
+ sendCommand(GUI::kSavesSyncProgressCmd, (int)(getDownloadingProgress() * 100));
+
+ debug(9, "SavesSyncRequest: downloading %s (%d %%)", _currentDownloadingFile.name().c_str(), (int)(getProgress() * 100));
+ _workingRequest = _storage->downloadById(
+ _currentDownloadingFile.id(),
+ DefaultSaveFileManager::concatWithSavesPath(_currentDownloadingFile.name()),
+ new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::fileDownloadedCallback),
+ new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileDownloadedErrorCallback)
+ );
+ if (!_workingRequest)
+ finishError(Networking::ErrorResponse(this));
+}
+
+void SavesSyncRequest::fileDownloadedCallback(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, "", -1));
+ return;
+ }
+
+ //update local timestamp for downloaded file
+ _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps();
+ _localFilesTimestamps[_currentDownloadingFile.name()] = _currentDownloadingFile.timestamp();
+ DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps);
+
+ //continue downloading files
+ downloadNextFile();
+}
+
+void SavesSyncRequest::fileDownloadedErrorCallback(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, "SavesSyncRequest: 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, Storage::UploadResponse>(this, &SavesSyncRequest::fileUploadedCallback),
+ new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback)
+ );
+ } else {
+ _workingRequest = _storage->upload(
+ _storage->savesDirectoryPath() + _currentUploadingFile,
+ DefaultSaveFileManager::concatWithSavesPath(_currentUploadingFile),
+ new Common::Callback<SavesSyncRequest, Storage::UploadResponse>(this, &SavesSyncRequest::fileUploadedCallback),
+ new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback)
+ );
+ }
+ if (!_workingRequest) finishError(Networking::ErrorResponse(this));
+}
+
+void SavesSyncRequest::fileUploadedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ //update local timestamp for the uploaded file
+ _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps();
+ _localFilesTimestamps[_currentUploadingFile] = response.value.timestamp();
+ DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps);
+
+ //continue uploading files
+ uploadNextFile();
+}
+
+void SavesSyncRequest::fileUploadedErrorCallback(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
+
+ uint32 totalFilesToDownload = _totalFilesToHandle - _filesToUpload.size();
+ uint32 filesLeftToDownload = _filesToDownload.size() + (_currentDownloadingFile.name() != "" ? 1 : 0);
+ return (double)(totalFilesToDownload - filesLeftToDownload) / (double)(totalFilesToDownload);
+}
+
+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;
+}
+
+void SavesSyncRequest::finishError(Networking::ErrorResponse error) {
+ 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
+ CloudMan.setStorageLastSync(CloudMan.getStorageIndex(), _date);
+
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+} // End of namespace Cloud