scummvm/backends/cloud/savessyncrequest.cpp
D G Turner 466b162996 CLOUD: Fix GCC Compilation Warning in Debug Code.
The warning emitted here was due to debug() call with an empty format
string, so this could have been fixed by replacing this with "%s", "".

However, this change should be better since it avoids the duplication
of the number of file check over several lines and reduces the string
duplication i.e. DRY improvements.
2019-08-03 14:52:57 +01:00

440 lines
16 KiB
C++

/* 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;
debug(9, "SavesSyncRequest decisions:");
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 || !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 (Common::HashMap<Common::String, bool>::iterator i = localFileNotAvailableInCloud.begin(); i != localFileNotAvailableInCloud.end(); ++i) {
if (i->_key == DefaultSaveFileManager::TIMESTAMPS_FILENAME || !CloudMan.canSyncFilename(i->_key))
continue;
if (i->_value) {
_filesToUpload.push_back(i->_key);
debug(9, "- uploading file %s, because it is not present on remote", i->_key.c_str());
}
}
debug(9, "\nSavesSyncRequest: ");
if (_filesToDownload.size() > 0) {
debug(9, "nothing to download");
} else {
debug(9, "download files:");
for (uint32 i = 0; i < _filesToDownload.size(); ++i) {
debug(9, " %s", _filesToDownload[i].name().c_str());
}
debug(9, "%s", "");
}
debug(9, "SavesSyncRequest: ");
if (_filesToUpload.size() > 0) {
debug(9, "nothing to upload");
} else {
debug(9, "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;
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.c_str());
// 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.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, 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, 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, "\nSavesSyncRequest: 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, "\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, 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