ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

savessyncrequest.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "backends/cloud/savessyncrequest.h"
00024 #include "backends/cloud/cloudmanager.h"
00025 #include "backends/networking/curl/curljsonrequest.h"
00026 #include "backends/saves/default/default-saves.h"
00027 #include "common/config-manager.h"
00028 #include "common/debug.h"
00029 #include "common/file.h"
00030 #include "common/json.h"
00031 #include "common/savefile.h"
00032 #include "common/system.h"
00033 #include "gui/saveload-dialog.h"
00034 
00035 namespace Cloud {
00036 
00037 SavesSyncRequest::SavesSyncRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb):
00038     Request(nullptr, ecb), CommandSender(nullptr), _storage(storage), _boolCallback(callback),
00039     _workingRequest(nullptr), _ignoreCallback(false) {
00040     start();
00041 }
00042 
00043 SavesSyncRequest::~SavesSyncRequest() {
00044     _ignoreCallback = true;
00045     if (_workingRequest)
00046         _workingRequest->finish();
00047     delete _boolCallback;
00048 }
00049 
00050 void SavesSyncRequest::start() {
00051     //cleanup
00052     _ignoreCallback = true;
00053     if (_workingRequest)
00054         _workingRequest->finish();
00055     _currentDownloadingFile = StorageFile();
00056     _currentUploadingFile = "";
00057     _filesToDownload.clear();
00058     _filesToUpload.clear();
00059     _localFilesTimestamps.clear();
00060     _totalFilesToHandle = 0;
00061     _ignoreCallback = false;
00062 
00063     //load timestamps
00064     _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps();
00065 
00066     //list saves directory
00067     Common::String dir = _storage->savesDirectoryPath();
00068     if (dir.lastChar() == '/')
00069         dir.deleteLastChar();
00070     _workingRequest = _storage->listDirectory(
00071         dir,
00072         new Common::Callback<SavesSyncRequest, Storage::ListDirectoryResponse>(this, &SavesSyncRequest::directoryListedCallback),
00073         new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryListedErrorCallback)
00074     );
00075     if (!_workingRequest) finishError(Networking::ErrorResponse(this, "SavesSyncRequest::start: Storage couldn't create Request to list directory"));
00076 }
00077 
00078 void SavesSyncRequest::directoryListedCallback(Storage::ListDirectoryResponse response) {
00079     _workingRequest = nullptr;
00080     if (_ignoreCallback)
00081         return;
00082 
00083     if (response.request) _date = response.request->date();
00084 
00085     Common::HashMap<Common::String, bool> localFileNotAvailableInCloud;
00086     for (Common::HashMap<Common::String, uint32>::iterator i = _localFilesTimestamps.begin(); i != _localFilesTimestamps.end(); ++i) {
00087         localFileNotAvailableInCloud[i->_key] = true;
00088     }
00089 
00090     //determine which files to download and which files to upload
00091     Common::Array<StorageFile> &remoteFiles = response.value;
00092     uint64 totalSize = 0;
00093     debug(9, "SavesSyncRequest decisions:");
00094     for (uint32 i = 0; i < remoteFiles.size(); ++i) {
00095         StorageFile &file = remoteFiles[i];
00096         if (file.isDirectory())
00097             continue;
00098         totalSize += file.size();
00099         if (file.name() == DefaultSaveFileManager::TIMESTAMPS_FILENAME || !CloudMan.canSyncFilename(file.name()))
00100             continue;
00101 
00102         Common::String name = file.name();
00103         if (!_localFilesTimestamps.contains(name)) {
00104             _filesToDownload.push_back(file);
00105             debug(9, "- downloading file %s, because it is not present on local", name.c_str());
00106         } else {
00107             localFileNotAvailableInCloud[name] = false;
00108 
00109             if (_localFilesTimestamps[name] == file.timestamp())
00110                 continue;
00111 
00112             //we actually can have some files not only with timestamp < remote
00113             //but also with timestamp > remote (when we have been using ANOTHER CLOUD and then switched back)
00114             if (_localFilesTimestamps[name] > file.timestamp() || _localFilesTimestamps[name] == DefaultSaveFileManager::INVALID_TIMESTAMP)
00115                 _filesToUpload.push_back(file.name());
00116             else
00117                 _filesToDownload.push_back(file);
00118 
00119             if (_localFilesTimestamps[name] == DefaultSaveFileManager::INVALID_TIMESTAMP)
00120                 debug(9, "- uploading file %s, because it is has invalid timestamp", name.c_str());
00121             else if (_localFilesTimestamps[name] > file.timestamp())
00122                 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());
00123             else
00124                 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());
00125         }
00126     }
00127 
00128     CloudMan.setStorageUsedSpace(CloudMan.getStorageIndex(), totalSize);
00129 
00130     //upload files which are unavailable in cloud
00131     for (Common::HashMap<Common::String, bool>::iterator i = localFileNotAvailableInCloud.begin(); i != localFileNotAvailableInCloud.end(); ++i) {
00132         if (i->_key == DefaultSaveFileManager::TIMESTAMPS_FILENAME || !CloudMan.canSyncFilename(i->_key))
00133             continue;
00134         if (i->_value) {
00135             _filesToUpload.push_back(i->_key);
00136             debug(9, "- uploading file %s, because it is not present on remote", i->_key.c_str());
00137         }
00138     }
00139 
00140     debug(9, "\nSavesSyncRequest: ");
00141     if (_filesToDownload.size() > 0) {
00142         debug(9, "download files:");
00143         for (uint32 i = 0; i < _filesToDownload.size(); ++i) {
00144             debug(9, " %s", _filesToDownload[i].name().c_str());
00145         }
00146         debug(9, "%s", "");
00147     } else {
00148         debug(9, "nothing to download");
00149     }
00150     debug(9, "SavesSyncRequest: ");
00151     if (_filesToUpload.size() > 0) {
00152         debug(9, "upload files:");
00153         for (uint32 i = 0; i < _filesToUpload.size(); ++i) {
00154             debug(9, " %s", _filesToUpload[i].c_str());
00155         }
00156     } else {
00157         debug(9, "nothing to upload");
00158     }
00159     _totalFilesToHandle = _filesToDownload.size() + _filesToUpload.size();
00160 
00161     //start downloading files
00162     if (!_filesToDownload.empty()) {
00163         downloadNextFile();
00164     } else {
00165         uploadNextFile();
00166     }
00167 }
00168 
00169 void SavesSyncRequest::directoryListedErrorCallback(Networking::ErrorResponse error) {
00170     _workingRequest = nullptr;
00171     if (_ignoreCallback)
00172         return;
00173 
00174     if (error.failed) debug(9, "%s", error.response.c_str());
00175 
00176     bool irrecoverable = error.interrupted || error.failed;
00177     if (error.failed) {
00178         Common::JSONValue *value = Common::JSON::parse(error.response.c_str());
00179 
00180         // somehow OneDrive returns JSON with '.' in unexpected places, try fixing it
00181         if (!value) {
00182             Common::String fixedResponse = error.response;
00183             for (uint32 i = 0; i < fixedResponse.size(); ++i) {
00184                 if (fixedResponse[i] == '.')
00185                     fixedResponse.replace(i, 1, " ");
00186             }
00187             value = Common::JSON::parse(fixedResponse.c_str());
00188         }
00189 
00190         if (value) {
00191             if (value->isObject()) {
00192                 Common::JSONObject object = value->asObject();
00193 
00194                 //Dropbox-related error:
00195                 if (object.contains("error_summary") && object.getVal("error_summary")->isString()) {
00196                     Common::String summary = object.getVal("error_summary")->asString();
00197                     if (summary.contains("not_found")) {
00198                         irrecoverable = false;
00199                     }
00200                 }
00201 
00202                 //OneDrive-related error:
00203                 if (object.contains("error") && object.getVal("error")->isObject()) {
00204                     Common::JSONObject errorNode = object.getVal("error")->asObject();
00205                     if (Networking::CurlJsonRequest::jsonContainsString(errorNode, "code", "SavesSyncRequest")) {
00206                         Common::String code = errorNode.getVal("code")->asString();
00207                         if (code == "itemNotFound") {
00208                             irrecoverable = false;
00209                         }
00210                     }
00211                 }
00212             }
00213             delete value;
00214         }
00215 
00216         //Google Drive, Box and OneDrive-related ScummVM-based error
00217         if (error.response.contains("subdirectory not found")) {
00218             irrecoverable = false; //base "/ScummVM/" folder not found
00219         } else if (error.response.contains("no such file found in its parent directory")) {
00220             irrecoverable = false; //"Saves" folder within "/ResidualVM/" not found
00221         } else if (error.response.contains("itemNotFound") && error.response.contains("Item does not exist")) {
00222             irrecoverable = false; //"saves" folder within application folder is not found
00223         }
00224     }
00225 
00226     if (irrecoverable) {
00227         finishError(error);
00228         return;
00229     }
00230 
00231     //we're lucky - user just lacks his "/cloud/" folder - let's create one
00232     Common::String dir = _storage->savesDirectoryPath();
00233     if (dir.lastChar() == '/')
00234         dir.deleteLastChar();
00235     debug(9, "\nSavesSyncRequest: creating %s", dir.c_str());
00236     _workingRequest = _storage->createDirectory(
00237         dir,
00238         new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::directoryCreatedCallback),
00239         new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryCreatedErrorCallback)
00240     );
00241     if (!_workingRequest)
00242         finishError(Networking::ErrorResponse(this, "SavesSyncRequest::directoryListedErrorCallback: Storage couldn't create Request to create remote directory"));
00243 }
00244 
00245 void SavesSyncRequest::directoryCreatedCallback(Storage::BoolResponse response) {
00246     _workingRequest = nullptr;
00247     if (_ignoreCallback)
00248         return;
00249 
00250     //stop syncing if failed to create saves directory
00251     if (!response.value) {
00252         finishError(Networking::ErrorResponse(this, false, true, "SavesSyncRequest::directoryCreatedCallback: failed to create remote directory", -1));
00253         return;
00254     }
00255 
00256     //continue with empty files list
00257     Common::Array<StorageFile> files;
00258     directoryListedCallback(Storage::ListDirectoryResponse(response.request, files));
00259 }
00260 
00261 void SavesSyncRequest::directoryCreatedErrorCallback(Networking::ErrorResponse error) {
00262     _workingRequest = nullptr;
00263     if (_ignoreCallback)
00264         return;
00265 
00266     //stop syncing if failed to create saves directory
00267     finishError(error);
00268 }
00269 
00270 void SavesSyncRequest::downloadNextFile() {
00271     if (_filesToDownload.empty()) {
00272         _currentDownloadingFile = StorageFile("", 0, 0, false); //so getFilesToDownload() would return an empty array
00273         sendCommand(GUI::kSavesSyncEndedCmd, 0);
00274         uploadNextFile();
00275         return;
00276     }
00277 
00278     _currentDownloadingFile = _filesToDownload.back();
00279     _filesToDownload.pop_back();
00280 
00281     sendCommand(GUI::kSavesSyncProgressCmd, (int)(getDownloadingProgress() * 100));
00282 
00283     debug(9, "\nSavesSyncRequest: downloading %s (%d %%)", _currentDownloadingFile.name().c_str(), (int)(getProgress() * 100));
00284     _workingRequest = _storage->downloadById(
00285         _currentDownloadingFile.id(),
00286         DefaultSaveFileManager::concatWithSavesPath(_currentDownloadingFile.name()),
00287         new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::fileDownloadedCallback),
00288         new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileDownloadedErrorCallback)
00289     );
00290     if (!_workingRequest)
00291         finishError(Networking::ErrorResponse(this, "SavesSyncRequest::downloadNextFile: Storage couldn't create Request to download a file"));
00292 }
00293 
00294 void SavesSyncRequest::fileDownloadedCallback(Storage::BoolResponse response) {
00295     _workingRequest = nullptr;
00296     if (_ignoreCallback)
00297         return;
00298 
00299     //stop syncing if download failed
00300     if (!response.value) {
00301         //delete the incomplete file
00302         g_system->getSavefileManager()->removeSavefile(_currentDownloadingFile.name());
00303         finishError(Networking::ErrorResponse(this, false, true, "SavesSyncRequest::fileDownloadedCallback: failed to download a file", -1));
00304         return;
00305     }
00306 
00307     //update local timestamp for downloaded file
00308     _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps();
00309     _localFilesTimestamps[_currentDownloadingFile.name()] = _currentDownloadingFile.timestamp();
00310     DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps);
00311 
00312     //continue downloading files
00313     downloadNextFile();
00314 }
00315 
00316 void SavesSyncRequest::fileDownloadedErrorCallback(Networking::ErrorResponse error) {
00317     _workingRequest = nullptr;
00318     if (_ignoreCallback)
00319         return;
00320 
00321     //stop syncing if download failed
00322     finishError(error);
00323 }
00324 
00325 void SavesSyncRequest::uploadNextFile() {
00326     if (_filesToUpload.empty()) {
00327         finishSync(true);
00328         return;
00329     }
00330 
00331     _currentUploadingFile = _filesToUpload.back();
00332     _filesToUpload.pop_back();
00333 
00334     debug(9, "\nSavesSyncRequest: uploading %s (%d %%)", _currentUploadingFile.c_str(), (int)(getProgress() * 100));
00335     if (_storage->uploadStreamSupported()) {
00336         _workingRequest = _storage->upload(
00337             _storage->savesDirectoryPath() + _currentUploadingFile,
00338             g_system->getSavefileManager()->openRawFile(_currentUploadingFile),
00339             new Common::Callback<SavesSyncRequest, Storage::UploadResponse>(this, &SavesSyncRequest::fileUploadedCallback),
00340             new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback)
00341         );
00342     } else {
00343         _workingRequest = _storage->upload(
00344             _storage->savesDirectoryPath() + _currentUploadingFile,
00345             DefaultSaveFileManager::concatWithSavesPath(_currentUploadingFile),
00346             new Common::Callback<SavesSyncRequest, Storage::UploadResponse>(this, &SavesSyncRequest::fileUploadedCallback),
00347             new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback)
00348         );
00349     }
00350     if (!_workingRequest) finishError(Networking::ErrorResponse(this, "SavesSyncRequest::uploadNextFile: Storage couldn't create Request to upload a file"));
00351 }
00352 
00353 void SavesSyncRequest::fileUploadedCallback(Storage::UploadResponse response) {
00354     _workingRequest = nullptr;
00355     if (_ignoreCallback)
00356         return;
00357 
00358     //update local timestamp for the uploaded file
00359     _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps();
00360     _localFilesTimestamps[_currentUploadingFile] = response.value.timestamp();
00361     DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps);
00362 
00363     //continue uploading files
00364     uploadNextFile();
00365 }
00366 
00367 void SavesSyncRequest::fileUploadedErrorCallback(Networking::ErrorResponse error) {
00368     _workingRequest = nullptr;
00369     if (_ignoreCallback)
00370         return;
00371 
00372     //stop syncing if upload failed
00373     finishError(error);
00374 }
00375 
00376 void SavesSyncRequest::handle() {}
00377 
00378 void SavesSyncRequest::restart() { start(); }
00379 
00380 double SavesSyncRequest::getDownloadingProgress() const {
00381     if (_totalFilesToHandle == 0) {
00382         if (_state == Networking::FINISHED)
00383             return 1; //nothing to upload and download => Request ends soon
00384         return 0; //directory not listed yet
00385     }
00386 
00387     if (_totalFilesToHandle == _filesToUpload.size())
00388         return 1; //nothing to download => download complete
00389 
00390     uint32 totalFilesToDownload = _totalFilesToHandle - _filesToUpload.size();
00391     uint32 filesLeftToDownload = _filesToDownload.size() + (_currentDownloadingFile.name() != "" ? 1 : 0);
00392     return (double)(totalFilesToDownload - filesLeftToDownload) / (double)(totalFilesToDownload);
00393 }
00394 
00395 double SavesSyncRequest::getProgress() const {
00396     if (_totalFilesToHandle == 0) {
00397         if (_state == Networking::FINISHED)
00398             return 1; //nothing to upload and download => Request ends soon
00399         return 0; //directory not listed yet
00400     }
00401 
00402     return (double)(_totalFilesToHandle - _filesToDownload.size() - _filesToUpload.size()) / (double)(_totalFilesToHandle);
00403 }
00404 
00405 Common::Array<Common::String> SavesSyncRequest::getFilesToDownload() {
00406     Common::Array<Common::String> result;
00407     for (uint32 i = 0; i < _filesToDownload.size(); ++i)
00408         result.push_back(_filesToDownload[i].name());
00409     if (_currentDownloadingFile.name() != "")
00410         result.push_back(_currentDownloadingFile.name());
00411     return result;
00412 }
00413 
00414 void SavesSyncRequest::finishError(Networking::ErrorResponse error) {
00415     debug(9, "SavesSync::finishError");
00416     //if we were downloading a file - remember the name
00417     //and make the Request close() it, so we can delete it
00418     Common::String name = _currentDownloadingFile.name();
00419     if (_workingRequest) {
00420         _ignoreCallback = true;
00421         _workingRequest->finish();
00422         _workingRequest = nullptr;
00423         _ignoreCallback = false;
00424     }
00425     //unlock all the files by making getFilesToDownload() return empty array
00426     _currentDownloadingFile = StorageFile();
00427     _filesToDownload.clear();
00428     //delete the incomplete file
00429     if (name != "")
00430         g_system->getSavefileManager()->removeSavefile(name);
00431     Request::finishError(error);
00432 }
00433 
00434 void SavesSyncRequest::finishSync(bool success) {
00435     Request::finishSuccess();
00436 
00437     //update last successful sync date
00438     CloudMan.setStorageLastSync(CloudMan.getStorageIndex(), _date);
00439 
00440     if (_boolCallback)
00441         (*_boolCallback)(Storage::BoolResponse(this, success));
00442 }
00443 
00444 } // End of namespace Cloud


Generated on Sat Aug 8 2020 05:01:04 for ResidualVM by doxygen 1.7.1
curved edge   curved edge