diff options
-rw-r--r-- | backends/cloud/savessyncrequest.cpp | 229 | ||||
-rw-r--r-- | backends/cloud/savessyncrequest.h | 63 | ||||
-rw-r--r-- | backends/module.mk | 1 |
3 files changed, 293 insertions, 0 deletions
diff --git a/backends/cloud/savessyncrequest.cpp b/backends/cloud/savessyncrequest.cpp new file mode 100644 index 0000000000..d48ec6ba45 --- /dev/null +++ b/backends/cloud/savessyncrequest.cpp @@ -0,0 +1,229 @@ +/* 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 "common/debug.h" +#include "common/file.h" + +namespace Cloud { + +SavesSyncRequest::SavesSyncRequest(Storage *storage, Storage::BoolCallback callback): + Request(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(); + _ignoreCallback = false; + + //load timestamps + loadTimestamps(); + + //list saves directory + _workingRequest = _storage->listDirectory("saves", new Common::Callback<SavesSyncRequest, Storage::FileArrayResponse>(this, &SavesSyncRequest::directoryListedCallback)); +} + +void SavesSyncRequest::directoryListedCallback(Storage::FileArrayResponse pair) { + if (_ignoreCallback) return; + //TODO: somehow ListDirectory requests must indicate that file array is incomplete + + const uint32 INVALID_TIMESTAMP = UINT_MAX; + + //determine which files to download and which files to upload + Common::Array<StorageFile> &remoteFiles = pair.value; + for (uint32 i = 0; i < remoteFiles.size(); ++i) { + StorageFile &file = remoteFiles[i]; + if (file.isDirectory()) continue; + Common::String name = file.name(); + if (!_localFilesTimestamps.contains(name)) + _filesToDownload.push_back(file); + else { + if (_localFilesTimestamps[name] != INVALID_TIMESTAMP) { + 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()) + _filesToDownload.push_back(file); + else + _filesToUpload.push_back(file.name()); + } + } + } + + //upload files with invalid timestamp (the ones we've added - means they might not have any remote version) + for (Common::HashMap<Common::String, uint32>::iterator i = _localFilesTimestamps.begin(); i != _localFilesTimestamps.end(); ++i) { + if (i->_value == INVALID_TIMESTAMP) + _filesToUpload.push_back(i->_key); + } + + //start downloading files + downloadNextFile(); +} + +void SavesSyncRequest::downloadNextFile() { + if (_filesToDownload.empty()) { + uploadNextFile(); + return; + } + + _currentDownloadingFile = _filesToDownload.back(); + _filesToDownload.pop_back(); + + _workingRequest = _storage->download(_currentDownloadingFile.path(), "saves/" + _currentDownloadingFile.name(), + new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::fileDownloadedCallback) + ); +} + +void SavesSyncRequest::fileDownloadedCallback(Storage::BoolResponse pair) { + if (_ignoreCallback) return; + + //stop syncing if download failed + if (!pair.value) { + finish(); + return; + } + + //update local timestamp for downloaded file + _localFilesTimestamps[_currentDownloadingFile.name()] = _currentDownloadingFile.timestamp(); + + //continue downloading files + downloadNextFile(); +} + +void SavesSyncRequest::uploadNextFile() { + if (_filesToUpload.empty()) { + finishBool(true); + return; + } + + _currentUploadingFile = _filesToUpload.back(); + _filesToUpload.pop_back(); + + _workingRequest = _storage->upload("saves/" + _currentUploadingFile, nullptr, //TODO: pass save's read stream + new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::fileUploadedCallback) + ); +} + +void SavesSyncRequest::fileUploadedCallback(Storage::BoolResponse pair) { + if (_ignoreCallback) return; + + //stop syncing if upload failed + if (!pair.value) { + finish(); + return; + } + + //TODO: update local timestamp for the uploaded file + //_localFilesTimestamps[_currentUploadingFile] = pair.request.<what?>; + + //continue uploading files + uploadNextFile(); +} + +void SavesSyncRequest::handle() {} + +void SavesSyncRequest::restart() { start(); } + +void SavesSyncRequest::finish() { finishBool(false); } + +void SavesSyncRequest::finishBool(bool success) { + Request::finish(); + + //save updated timestamps (even if Request failed, there would be only valid timestamps) + saveTimestamps(); + + if (_boolCallback) (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +void SavesSyncRequest::loadTimestamps() { + Common::File f; + if (!f.open("saves/timestamps")) + error("SavesSyncRequest: failed to open 'saves/timestamps' file to load timestamps"); + + while (!f.eos()) { + //read filename into buffer (reading until the first ' ') + Common::String buffer; + while (!f.eos()) { + byte b = f.readByte(); + if (b == ' ') break; + buffer += (char)b; + } + + //read timestamp info buffer (reading until ' ' or some line ending char) + Common::String filename = buffer; + bool lineEnded = false; + buffer = ""; + while (!f.eos()) { + byte b = f.readByte(); + if (b == ' ' || b == '\n' || b == '\r') { + lineEnded = (b == '\n'); + break; + } + buffer += (char)b; + } + + //parse timestamp + uint timestamp = atol(buffer.c_str()); + _localFilesTimestamps[filename] = timestamp; + + //read until the end of the line + if (!lineEnded) { + while (!f.eos()) { + byte b = f.readByte(); + if (b == '\n') break; + } + } + } + + f.close(); +} + +void SavesSyncRequest::saveTimestamps() { + Common::DumpFile f; + if (!f.open("saves/timestamps", true)) + error("SavesSyncRequest: failed to open 'saves/timestamps' file to save timestamps"); + Common::String data; + for (Common::HashMap<Common::String, uint32>::iterator i = _localFilesTimestamps.begin(); i != _localFilesTimestamps.end(); ++i) + data += i->_key + Common::String::format(" %u\n", i->_value); + if (f.write(data.c_str(), data.size()) != data.size()) + error("SavesSyncRequest: failed to write timestamps data into 'saves/timestamps'"); + f.close(); +} + + +} // End of namespace Cloud diff --git a/backends/cloud/savessyncrequest.h b/backends/cloud/savessyncrequest.h new file mode 100644 index 0000000000..a8c54d44ad --- /dev/null +++ b/backends/cloud/savessyncrequest.h @@ -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 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_SAVESSYNCREQUEST_H +#define BACKENDS_CLOUD_SAVESSYNCREQUEST_H + +#include "backends/networking/curl/request.h" +#include "backends/cloud/storage.h" +#include "common/hashmap.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; + + void start(); + void directoryListedCallback(Storage::FileArrayResponse pair); + void fileDownloadedCallback(Storage::BoolResponse pair); + void fileUploadedCallback(Storage::BoolResponse pair); + void downloadNextFile(); + void uploadNextFile(); + void finishBool(bool success); + void loadTimestamps(); + void saveTimestamps(); +public: + SavesSyncRequest(Storage *storage, Storage::BoolCallback callback); + virtual ~SavesSyncRequest(); + + virtual void handle(); + virtual void restart(); + virtual void finish(); +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/module.mk b/backends/module.mk index f5b1aa4ec8..a31704a655 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -26,6 +26,7 @@ MODULE_OBJS += \ cloud/storagefile.o \ cloud/downloadrequest.o \ cloud/folderdownloadrequest.o \ + cloud/savessyncrequest.o \ cloud/dropbox/dropboxstorage.o \ cloud/dropbox/dropboxlistdirectoryrequest.o \ cloud/onedrive/onedrivestorage.o \ |