aboutsummaryrefslogtreecommitdiff
path: root/backends
diff options
context:
space:
mode:
authorEugene Sandulenko2016-08-30 13:54:12 +0200
committerGitHub2016-08-30 13:54:12 +0200
commitbfbfbd3e1a8397cc3b1c9eaff6c7754abe3ddc3d (patch)
tree68851bdc99b78b4b1902944c1df3c3ee5d4f4489 /backends
parent7df744c291e5fcf5dc7afd3b21189c2c56810f8e (diff)
parent368f664c813075f8b8cb2c572dce65f60128eda4 (diff)
downloadscummvm-rg350-bfbfbd3e1a8397cc3b1c9eaff6c7754abe3ddc3d.tar.gz
scummvm-rg350-bfbfbd3e1a8397cc3b1c9eaff6c7754abe3ddc3d.tar.bz2
scummvm-rg350-bfbfbd3e1a8397cc3b1c9eaff6c7754abe3ddc3d.zip
Merge pull request #788 from Tkachov/cloud
ALL: Add Cloud storage support
Diffstat (limited to 'backends')
-rw-r--r--backends/base-backend.cpp14
-rw-r--r--backends/base-backend.h3
-rw-r--r--backends/cloud/box/boxlistdirectorybyidrequest.cpp195
-rw-r--r--backends/cloud/box/boxlistdirectorybyidrequest.h63
-rw-r--r--backends/cloud/box/boxstorage.cpp345
-rw-r--r--backends/cloud/box/boxstorage.h116
-rw-r--r--backends/cloud/box/boxtokenrefresher.cpp137
-rw-r--r--backends/cloud/box/boxtokenrefresher.h53
-rw-r--r--backends/cloud/box/boxuploadrequest.cpp232
-rw-r--r--backends/cloud/box/boxuploadrequest.h63
-rw-r--r--backends/cloud/cloudmanager.cpp456
-rw-r--r--backends/cloud/cloudmanager.h274
-rw-r--r--backends/cloud/downloadrequest.cpp138
-rw-r--r--backends/cloud/downloadrequest.h64
-rw-r--r--backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp137
-rw-r--r--backends/cloud/dropbox/dropboxcreatedirectoryrequest.h57
-rw-r--r--backends/cloud/dropbox/dropboxinforequest.cpp192
-rw-r--r--backends/cloud/dropbox/dropboxinforequest.h56
-rw-r--r--backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp222
-rw-r--r--backends/cloud/dropbox/dropboxlistdirectoryrequest.h61
-rw-r--r--backends/cloud/dropbox/dropboxstorage.cpp198
-rw-r--r--backends/cloud/dropbox/dropboxstorage.h101
-rw-r--r--backends/cloud/dropbox/dropboxuploadrequest.cpp204
-rw-r--r--backends/cloud/dropbox/dropboxuploadrequest.h60
-rw-r--r--backends/cloud/folderdownloadrequest.cpp197
-rw-r--r--backends/cloud/folderdownloadrequest.h79
-rw-r--r--backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp163
-rw-r--r--backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h63
-rw-r--r--backends/cloud/googledrive/googledrivestorage.cpp348
-rw-r--r--backends/cloud/googledrive/googledrivestorage.h118
-rw-r--r--backends/cloud/googledrive/googledrivetokenrefresher.cpp125
-rw-r--r--backends/cloud/googledrive/googledrivetokenrefresher.h52
-rw-r--r--backends/cloud/googledrive/googledriveuploadrequest.cpp353
-rw-r--r--backends/cloud/googledrive/googledriveuploadrequest.h70
-rw-r--r--backends/cloud/id/idcreatedirectoryrequest.cpp163
-rw-r--r--backends/cloud/id/idcreatedirectoryrequest.h65
-rw-r--r--backends/cloud/id/iddownloadrequest.cpp108
-rw-r--r--backends/cloud/id/iddownloadrequest.h62
-rw-r--r--backends/cloud/id/idlistdirectoryrequest.cpp141
-rw-r--r--backends/cloud/id/idlistdirectoryrequest.h66
-rw-r--r--backends/cloud/id/idresolveidrequest.cpp136
-rw-r--r--backends/cloud/id/idresolveidrequest.h60
-rw-r--r--backends/cloud/id/idstorage.cpp109
-rw-r--r--backends/cloud/id/idstorage.h83
-rw-r--r--backends/cloud/id/idstreamfilerequest.cpp98
-rw-r--r--backends/cloud/id/idstreamfilerequest.h59
-rw-r--r--backends/cloud/iso8601.cpp100
-rw-r--r--backends/cloud/iso8601.h37
-rw-r--r--backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp150
-rw-r--r--backends/cloud/onedrive/onedrivecreatedirectoryrequest.h59
-rw-r--r--backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp193
-rw-r--r--backends/cloud/onedrive/onedrivelistdirectoryrequest.h66
-rw-r--r--backends/cloud/onedrive/onedrivestorage.cpp326
-rw-r--r--backends/cloud/onedrive/onedrivestorage.h113
-rw-r--r--backends/cloud/onedrive/onedrivetokenrefresher.cpp130
-rw-r--r--backends/cloud/onedrive/onedrivetokenrefresher.h52
-rw-r--r--backends/cloud/onedrive/onedriveuploadrequest.cpp191
-rw-r--r--backends/cloud/onedrive/onedriveuploadrequest.h61
-rw-r--r--backends/cloud/savessyncrequest.cpp403
-rw-r--r--backends/cloud/savessyncrequest.h80
-rw-r--r--backends/cloud/storage.cpp342
-rw-r--r--backends/cloud/storage.h238
-rw-r--r--backends/cloud/storagefile.cpp68
-rw-r--r--backends/cloud/storagefile.h65
-rw-r--r--backends/cloud/storageinfo.h53
-rw-r--r--backends/fs/abstract-fs.h9
-rw-r--r--backends/fs/amigaos4/amigaos4-fs.cpp5
-rw-r--r--backends/fs/amigaos4/amigaos4-fs.h1
-rw-r--r--backends/fs/chroot/chroot-fs.cpp5
-rw-r--r--backends/fs/chroot/chroot-fs.h1
-rw-r--r--backends/fs/ds/ds-fs.cpp10
-rw-r--r--backends/fs/ds/ds-fs.h2
-rw-r--r--backends/fs/n64/n64-fs.cpp5
-rw-r--r--backends/fs/n64/n64-fs.h1
-rw-r--r--backends/fs/posix/posix-fs.cpp25
-rw-r--r--backends/fs/posix/posix-fs.h1
-rw-r--r--backends/fs/ps2/ps2-fs.cpp5
-rw-r--r--backends/fs/ps2/ps2-fs.h1
-rw-r--r--backends/fs/psp/psp-fs.cpp5
-rw-r--r--backends/fs/psp/psp-fs.h1
-rw-r--r--backends/fs/symbian/symbian-fs.cpp6
-rw-r--r--backends/fs/symbian/symbian-fs.h1
-rw-r--r--backends/fs/wii/wii-fs.cpp5
-rw-r--r--backends/fs/wii/wii-fs.h1
-rw-r--r--backends/fs/windows/windows-fs.cpp32
-rw-r--r--backends/fs/windows/windows-fs.h1
-rw-r--r--backends/graphics/graphics.h4
-rw-r--r--backends/graphics/opengl/opengl-graphics.cpp25
-rw-r--r--backends/graphics/opengl/opengl-graphics.h3
-rw-r--r--backends/graphics/surfacesdl/surfacesdl-graphics.cpp257
-rw-r--r--backends/graphics/surfacesdl/surfacesdl-graphics.h20
-rw-r--r--backends/modular-backend.cpp12
-rw-r--r--backends/modular-backend.h3
-rw-r--r--backends/module.mk95
-rw-r--r--backends/networking/browser/openurl-android.cpp35
-rw-r--r--backends/networking/browser/openurl-default.cpp36
-rw-r--r--backends/networking/browser/openurl-osx.cpp49
-rw-r--r--backends/networking/browser/openurl-posix.cpp77
-rw-r--r--backends/networking/browser/openurl-windows.cpp43
-rw-r--r--backends/networking/browser/openurl.h41
-rw-r--r--backends/networking/connection/islimited-android.cpp35
-rw-r--r--backends/networking/connection/islimited-default.cpp36
-rw-r--r--backends/networking/connection/islimited.h39
-rw-r--r--backends/networking/curl/cloudicon.cpp171
-rw-r--r--backends/networking/curl/cloudicon.h70
-rw-r--r--backends/networking/curl/cloudicon_data.h111
-rw-r--r--backends/networking/curl/cloudicon_disabled_data.h117
-rw-r--r--backends/networking/curl/connectionmanager.cpp204
-rw-r--r--backends/networking/curl/connectionmanager.h132
-rw-r--r--backends/networking/curl/curljsonrequest.cpp215
-rw-r--r--backends/networking/curl/curljsonrequest.h67
-rw-r--r--backends/networking/curl/curlrequest.cpp162
-rw-r--r--backends/networking/curl/curlrequest.h100
-rw-r--r--backends/networking/curl/networkreadstream.cpp257
-rw-r--r--backends/networking/curl/networkreadstream.h142
-rw-r--r--backends/networking/curl/request.cpp74
-rw-r--r--backends/networking/curl/request.h203
-rw-r--r--backends/networking/make_archive.py37
-rw-r--r--backends/networking/sdl_net/client.cpp190
-rw-r--r--backends/networking/sdl_net/client.h125
-rw-r--r--backends/networking/sdl_net/getclienthandler.cpp162
-rw-r--r--backends/networking/sdl_net/getclienthandler.h57
-rw-r--r--backends/networking/sdl_net/handlers/basehandler.h41
-rw-r--r--backends/networking/sdl_net/handlers/createdirectoryhandler.cpp129
-rw-r--r--backends/networking/sdl_net/handlers/createdirectoryhandler.h42
-rw-r--r--backends/networking/sdl_net/handlers/downloadfilehandler.cpp88
-rw-r--r--backends/networking/sdl_net/handlers/downloadfilehandler.h40
-rw-r--r--backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp81
-rw-r--r--backends/networking/sdl_net/handlers/filesajaxpagehandler.h40
-rw-r--r--backends/networking/sdl_net/handlers/filesbasehandler.cpp87
-rw-r--r--backends/networking/sdl_net/handlers/filesbasehandler.h52
-rw-r--r--backends/networking/sdl_net/handlers/filespagehandler.cpp237
-rw-r--r--backends/networking/sdl_net/handlers/filespagehandler.h62
-rw-r--r--backends/networking/sdl_net/handlers/indexpagehandler.cpp65
-rw-r--r--backends/networking/sdl_net/handlers/indexpagehandler.h45
-rw-r--r--backends/networking/sdl_net/handlers/listajaxhandler.cpp157
-rw-r--r--backends/networking/sdl_net/handlers/listajaxhandler.h63
-rw-r--r--backends/networking/sdl_net/handlers/resourcehandler.cpp75
-rw-r--r--backends/networking/sdl_net/handlers/resourcehandler.h42
-rw-r--r--backends/networking/sdl_net/handlers/uploadfilehandler.cpp79
-rw-r--r--backends/networking/sdl_net/handlers/uploadfilehandler.h40
-rw-r--r--backends/networking/sdl_net/handlerutils.cpp200
-rw-r--r--backends/networking/sdl_net/handlerutils.h50
-rw-r--r--backends/networking/sdl_net/localwebserver.cpp446
-rw-r--r--backends/networking/sdl_net/localwebserver.h115
-rw-r--r--backends/networking/sdl_net/reader.cpp462
-rw-r--r--backends/networking/sdl_net/reader.h143
-rw-r--r--backends/networking/sdl_net/uploadfileclienthandler.cpp212
-rw-r--r--backends/networking/sdl_net/uploadfileclienthandler.h70
-rw-r--r--backends/networking/wwwroot.zipbin0 -> 242704 bytes
-rw-r--r--backends/networking/wwwroot/.files.html60
-rw-r--r--backends/networking/wwwroot/.filesAJAX.html240
-rw-r--r--backends/networking/wwwroot/.index.html18
-rw-r--r--backends/networking/wwwroot/ajax.js48
-rw-r--r--backends/networking/wwwroot/favicon.icobin0 -> 94081 bytes
-rw-r--r--backends/networking/wwwroot/icons/7z.pngbin0 -> 166 bytes
-rw-r--r--backends/networking/wwwroot/icons/dir.pngbin0 -> 150 bytes
-rw-r--r--backends/networking/wwwroot/icons/txt.pngbin0 -> 156 bytes
-rw-r--r--backends/networking/wwwroot/icons/unk.pngbin0 -> 142 bytes
-rw-r--r--backends/networking/wwwroot/icons/up.pngbin0 -> 161 bytes
-rw-r--r--backends/networking/wwwroot/icons/zip.pngbin0 -> 183 bytes
-rw-r--r--backends/networking/wwwroot/logo.pngbin0 -> 132967 bytes
-rw-r--r--backends/networking/wwwroot/style.css113
-rw-r--r--backends/platform/android/jni.cpp39
-rw-r--r--backends/platform/android/jni.h4
-rw-r--r--backends/platform/android/org/scummvm/scummvm/ScummVM.java2
-rw-r--r--backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java19
-rw-r--r--backends/platform/ds/arm9/source/gbampsave.cpp2
-rw-r--r--backends/platform/n64/framfs_save_manager.h2
-rw-r--r--backends/platform/n64/pakfs_save_manager.h2
-rw-r--r--backends/platform/ps2/savefilemgr.cpp2
-rw-r--r--backends/platform/sdl/sdl.cpp54
-rw-r--r--backends/platform/sdl/sdl.h9
-rw-r--r--backends/saves/default/default-saves.cpp166
-rw-r--r--backends/saves/default/default-saves.h22
-rw-r--r--backends/saves/savefile.cpp28
176 files changed, 16348 insertions, 60 deletions
diff --git a/backends/base-backend.cpp b/backends/base-backend.cpp
index 3e95c3e26a..dfb9e284ce 100644
--- a/backends/base-backend.cpp
+++ b/backends/base-backend.cpp
@@ -39,6 +39,20 @@ void BaseBackend::displayMessageOnOSD(const char *msg) {
dialog.runModal();
}
+void BaseBackend::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {
+ warning("BaseBackend::copyRectToOSD not implemented"); //TODO
+}
+
+void BaseBackend::clearOSD() {
+ warning("BaseBackend::clearOSD not implemented"); //TODO
+ //what should I do? Remove all TimedMessageDialogs?
+}
+
+Graphics::PixelFormat BaseBackend::getOSDFormat() {
+ warning("BaseBackend::getOSDFormat not implemented");
+ return Graphics::PixelFormat();
+}
+
void BaseBackend::initBackend() {
// Init Event manager
#ifndef DISABLE_DEFAULT_EVENT_MANAGER
diff --git a/backends/base-backend.h b/backends/base-backend.h
index 598f682b32..2394edaf38 100644
--- a/backends/base-backend.h
+++ b/backends/base-backend.h
@@ -33,6 +33,9 @@ public:
virtual void initBackend();
virtual void displayMessageOnOSD(const char *msg);
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h);
+ virtual void clearOSD();
+ virtual Graphics::PixelFormat getOSDFormat();
virtual void fillScreen(uint32 col);
};
diff --git a/backends/cloud/box/boxlistdirectorybyidrequest.cpp b/backends/cloud/box/boxlistdirectorybyidrequest.cpp
new file mode 100644
index 0000000000..c013f1eb2a
--- /dev/null
+++ b/backends/cloud/box/boxlistdirectorybyidrequest.cpp
@@ -0,0 +1,195 @@
+/* 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/box/boxlistdirectorybyidrequest.h"
+#include "backends/cloud/box/boxstorage.h"
+#include "backends/cloud/box/boxtokenrefresher.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Box {
+
+#define BOX_LIST_DIRECTORY_LIMIT 1000
+#define BOX_FOLDERS_API_LINK "https://api.box.com/2.0/folders/%s/items?offset=%u&limit=%u&fields=%s"
+
+BoxListDirectoryByIdRequest::BoxListDirectoryByIdRequest(BoxStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _requestedId(id), _storage(storage), _listDirectoryCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+BoxListDirectoryByIdRequest::~BoxListDirectoryByIdRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest) _workingRequest->finish();
+ delete _listDirectoryCallback;
+}
+
+void BoxListDirectoryByIdRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest) _workingRequest->finish();
+ _files.clear();
+ _ignoreCallback = false;
+
+ makeRequest(0);
+}
+
+void BoxListDirectoryByIdRequest::makeRequest(uint32 offset) {
+ Common::String url = Common::String::format(
+ BOX_FOLDERS_API_LINK,
+ _requestedId.c_str(),
+ offset,
+ BOX_LIST_DIRECTORY_LIMIT,
+ "id,type,name,size,modified_at"
+ );
+
+ Networking::JsonCallback callback = new Common::Callback<BoxListDirectoryByIdRequest, Networking::JsonResponse>(this, &BoxListDirectoryByIdRequest::responseCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<BoxListDirectoryByIdRequest, Networking::ErrorResponse>(this, &BoxListDirectoryByIdRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void BoxListDirectoryByIdRequest::responseCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete response.value;
+ return;
+ }
+
+ if (response.request)
+ _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject responseObject = json->asObject();
+ //debug(9, "%s", json->stringify(true).c_str());
+
+ //TODO: handle error messages passed as JSON
+ /*
+ if (responseObject.contains("error") || responseObject.contains("error_summary")) {
+ warning("Box returned error: %s", responseObject.getVal("error_summary")->asString().c_str());
+ error.failed = true;
+ error.response = json->stringify();
+ finishError(error);
+ delete json;
+ return;
+ }
+ */
+
+ //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
+ if (responseObject.contains("entries")) {
+ if (!responseObject.getVal("entries")->isArray()) {
+ error.response = Common::String::format(
+ "\"entries\" found, but that's not an array!\n%s",
+ responseObject.getVal("entries")->stringify(true).c_str()
+ );
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONArray items = responseObject.getVal("entries")->asArray();
+ for (uint32 i = 0; i < items.size(); ++i) {
+ if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "BoxListDirectoryByIdRequest")) continue;
+
+ Common::JSONObject item = items[i]->asObject();
+
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "id", "BoxListDirectoryByIdRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "name", "BoxListDirectoryByIdRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "type", "BoxListDirectoryByIdRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "modified_at", "BoxListDirectoryByIdRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxListDirectoryByIdRequest")) continue;
+
+ Common::String id = item.getVal("id")->asString();
+ Common::String name = item.getVal("name")->asString();
+ bool isDirectory = (item.getVal("type")->asString() == "folder");
+ uint32 size;
+ if (item.getVal("size")->isString()) {
+ size = item.getVal("size")->asString().asUint64();
+ } else {
+ size = item.getVal("size")->asIntegerNumber();
+ }
+ uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString());
+
+ //as we list directory by id, we can't determine full path for the file, so we leave it empty
+ _files.push_back(StorageFile(id, "", name, size, timestamp, isDirectory));
+ }
+ }
+
+ uint32 received = 0;
+ uint32 totalCount = 0;
+ if (responseObject.contains("total_count") && responseObject.getVal("total_count")->isIntegerNumber())
+ totalCount = responseObject.getVal("total_count")->asIntegerNumber();
+ if (responseObject.contains("offset") && responseObject.getVal("offset")->isIntegerNumber())
+ received = responseObject.getVal("offset")->asIntegerNumber();
+ if (responseObject.contains("limit") && responseObject.getVal("limit")->isIntegerNumber())
+ received += responseObject.getVal("limit")->asIntegerNumber();
+ bool hasMore = (received < totalCount);
+
+ if (hasMore) makeRequest(received);
+ else finishListing(_files);
+
+ delete json;
+}
+
+void BoxListDirectoryByIdRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+ if (error.request) _date = error.request->date();
+ finishError(error);
+}
+
+void BoxListDirectoryByIdRequest::handle() {}
+
+void BoxListDirectoryByIdRequest::restart() { start(); }
+
+Common::String BoxListDirectoryByIdRequest::date() const { return _date; }
+
+void BoxListDirectoryByIdRequest::finishListing(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_listDirectoryCallback) (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
+}
+
+} // End of namespace Box
+} // End of namespace Cloud
diff --git a/backends/cloud/box/boxlistdirectorybyidrequest.h b/backends/cloud/box/boxlistdirectorybyidrequest.h
new file mode 100644
index 0000000000..13f1ba056c
--- /dev/null
+++ b/backends/cloud/box/boxlistdirectorybyidrequest.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_BOX_BOXLISTDIRECTORYBYIDREQUEST_H
+#define BACKENDS_CLOUD_BOX_BOXLISTDIRECTORYBYIDREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Box {
+
+class BoxStorage;
+
+class BoxListDirectoryByIdRequest: public Networking::Request {
+ Common::String _requestedId;
+ BoxStorage *_storage;
+
+ Storage::ListDirectoryCallback _listDirectoryCallback;
+ Common::Array<StorageFile> _files;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void makeRequest(uint32 offset);
+ void responseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishListing(Common::Array<StorageFile> &files);
+public:
+ BoxListDirectoryByIdRequest(BoxStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb);
+ virtual ~BoxListDirectoryByIdRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace Box
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/box/boxstorage.cpp b/backends/cloud/box/boxstorage.cpp
new file mode 100644
index 0000000000..70864679e7
--- /dev/null
+++ b/backends/cloud/box/boxstorage.cpp
@@ -0,0 +1,345 @@
+/* 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/box/boxstorage.h"
+#include "backends/cloud/box/boxlistdirectorybyidrequest.h"
+#include "backends/cloud/box/boxtokenrefresher.h"
+#include "backends/cloud/box/boxuploadrequest.h"
+#include "backends/cloud/cloudmanager.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>
+
+#ifdef ENABLE_RELEASE
+#include "dists/clouds/cloud_keys.h"
+#endif
+
+namespace Cloud {
+namespace Box {
+
+#define BOX_OAUTH2_TOKEN "https://api.box.com/oauth2/token"
+#define BOX_API_FOLDERS "https://api.box.com/2.0/folders"
+#define BOX_API_FILES_CONTENT "https://api.box.com/2.0/files/%s/content"
+#define BOX_API_USERS_ME "https://api.box.com/2.0/users/me"
+
+char *BoxStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth
+char *BoxStorage::SECRET = nullptr;
+
+void BoxStorage::loadKeyAndSecret() {
+#ifdef ENABLE_RELEASE
+ KEY = RELEASE_BOX_KEY;
+ SECRET = RELEASE_BOX_SECRET;
+#else
+ Common::String k = ConfMan.get("BOX_KEY", ConfMan.kCloudDomain);
+ KEY = new char[k.size() + 1];
+ memcpy(KEY, k.c_str(), k.size());
+ KEY[k.size()] = 0;
+
+ k = ConfMan.get("BOX_SECRET", ConfMan.kCloudDomain);
+ SECRET = new char[k.size() + 1];
+ memcpy(SECRET, k.c_str(), k.size());
+ SECRET[k.size()] = 0;
+#endif
+}
+
+BoxStorage::BoxStorage(Common::String accessToken, Common::String refreshToken):
+ _token(accessToken), _refreshToken(refreshToken) {}
+
+BoxStorage::BoxStorage(Common::String code) {
+ getAccessToken(
+ new Common::Callback<BoxStorage, BoolResponse>(this, &BoxStorage::codeFlowComplete),
+ new Common::Callback<BoxStorage, Networking::ErrorResponse>(this, &BoxStorage::codeFlowFailed),
+ code
+ );
+}
+
+BoxStorage::~BoxStorage() {}
+
+void BoxStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) {
+ if (!KEY || !SECRET)
+ loadKeyAndSecret();
+ bool codeFlow = (code != "");
+
+ if (!codeFlow && _refreshToken == "") {
+ warning("BoxStorage: no refresh token available to get new access token.");
+ if (callback) (*callback)(BoolResponse(nullptr, false));
+ return;
+ }
+
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::tokenRefreshed, callback);
+ if (errorCallback == nullptr)
+ errorCallback = getErrorPrintingCallback();
+
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, BOX_OAUTH2_TOKEN);
+ if (codeFlow) {
+ request->addPostField("grant_type=authorization_code");
+ request->addPostField("code=" + code);
+ } else {
+ request->addPostField("grant_type=refresh_token");
+ request->addPostField("refresh_token=" + _refreshToken);
+ }
+ request->addPostField("client_id=" + Common::String(KEY));
+ request->addPostField("client_secret=" + Common::String(SECRET));
+ /*
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345");
+ } else {
+ request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code");
+ }
+ */
+ addRequest(request);
+}
+
+void BoxStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("BoxStorage: got NULL instead of JSON");
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete callback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage")) {
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete json;
+ delete callback;
+ return;
+ }
+
+ Common::JSONObject result = json->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "BoxStorage") ||
+ !Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "BoxStorage")) {
+ warning("BoxStorage: bad response, no token passed");
+ debug(9, "%s", json->stringify().c_str());
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ } else {
+ _token = result.getVal("access_token")->asString();
+ _refreshToken = result.getVal("refresh_token")->asString();
+ CloudMan.save(); //ask CloudManager to save our new refreshToken
+ if (callback)
+ (*callback)(BoolResponse(nullptr, true));
+ }
+ delete json;
+ delete callback;
+}
+
+void BoxStorage::codeFlowComplete(BoolResponse response) {
+ if (!response.value) {
+ warning("BoxStorage: failed to get access token through code flow");
+ CloudMan.removeStorage(this);
+ return;
+ }
+
+ CloudMan.replaceStorage(this, kStorageBoxId);
+ ConfMan.flushToDisk();
+}
+
+void BoxStorage::codeFlowFailed(Networking::ErrorResponse error) {
+ debug(9, "BoxStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
+ debug(9, "%s", error.response.c_str());
+ CloudMan.removeStorage(this);
+}
+
+void BoxStorage::saveConfig(Common::String keyPrefix) {
+ ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
+ ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
+}
+
+Common::String BoxStorage::name() const {
+ return "Box";
+}
+
+void BoxStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("BoxStorage::infoInnerCallback: NULL passed instead of JSON");
+ delete outerCallback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage::infoInnerCallback")) {
+ delete json;
+ delete outerCallback;
+ return;
+ }
+
+ Common::JSONObject info = json->asObject();
+
+ Common::String uid, name, email;
+ uint64 quotaUsed = 0, quotaAllocated = 0;
+
+ // can check that "type": "user"
+ // there is also "max_upload_size", "phone" and "avatar_url"
+
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "id", "BoxStorage::infoInnerCallback"))
+ uid = info.getVal("id")->asString();
+
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "name", "BoxStorage::infoInnerCallback"))
+ name = info.getVal("name")->asString();
+
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "login", "BoxStorage::infoInnerCallback"))
+ email = info.getVal("login")->asString();
+
+ if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "space_amount", "BoxStorage::infoInnerCallback"))
+ quotaAllocated = info.getVal("space_amount")->asIntegerNumber();
+
+ if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "space_used", "BoxStorage::infoInnerCallback"))
+ quotaUsed = info.getVal("space_used")->asIntegerNumber();
+
+ Common::String username = email;
+ if (username == "") username = name;
+ if (username == "") username = uid;
+ CloudMan.setStorageUsername(kStorageBoxId, username);
+
+ if (outerCallback) {
+ (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated)));
+ delete outerCallback;
+ }
+
+ delete json;
+}
+
+Networking::Request *BoxStorage::listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ if (!callback)
+ callback = getPrintFilesCallback();
+ return addRequest(new BoxListDirectoryByIdRequest(this, id, callback, errorCallback));
+}
+
+void BoxStorage::createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("BoxStorage::createDirectoryInnerCallback: NULL passed instead of JSON");
+ delete outerCallback;
+ return;
+ }
+
+ if (outerCallback) {
+ if (Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage::createDirectoryInnerCallback")) {
+ Common::JSONObject info = json->asObject();
+ (*outerCallback)(BoolResponse(nullptr, info.contains("id")));
+ } else {
+ (*outerCallback)(BoolResponse(nullptr, false));
+ }
+ delete outerCallback;
+ }
+
+ delete json;
+}
+
+Networking::Request *BoxStorage::createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+
+ Common::String url = BOX_API_FOLDERS;
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::createDirectoryInnerCallback, callback);
+ Networking::CurlJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + accessToken());
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONObject parentObject;
+ parentObject.setVal("id", new Common::JSONValue(parentId));
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("name", new Common::JSONValue(name));
+ jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject));
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ return addRequest(request);
+}
+
+Networking::Request *BoxStorage::upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ return addRequest(new BoxUploadRequest(this, remotePath, localPath, callback, errorCallback));
+}
+
+Networking::Request *BoxStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ warning("BoxStorage::upload(ReadStream) not implemented");
+ if (errorCallback)
+ (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "BoxStorage::upload(ReadStream) not implemented", -1));
+ delete callback;
+ delete errorCallback;
+ return nullptr;
+}
+
+bool BoxStorage::uploadStreamSupported() {
+ return false;
+}
+
+Networking::Request *BoxStorage::streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
+ if (callback) {
+ Common::String url = Common::String::format(BOX_API_FILES_CONTENT, id.c_str());
+ Common::String header = "Authorization: Bearer " + _token;
+ curl_slist *headersList = curl_slist_append(nullptr, header.c_str());
+ Networking::NetworkReadStream *stream = new Networking::NetworkReadStream(url.c_str(), headersList, "");
+ (*callback)(Networking::NetworkReadStreamResponse(nullptr, stream));
+ }
+ delete callback;
+ delete errorCallback;
+ return nullptr;
+}
+
+Networking::Request *BoxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, StorageInfoResponse, Networking::JsonResponse>(this, &BoxStorage::infoInnerCallback, callback);
+ Networking::CurlJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, BOX_API_USERS_ME);
+ request->addHeader("Authorization: Bearer " + _token);
+ return addRequest(request);
+}
+
+Common::String BoxStorage::savesDirectoryPath() { return "scummvm/saves/"; }
+
+BoxStorage *BoxStorage::loadFromConfig(Common::String keyPrefix) {
+ loadKeyAndSecret();
+
+ if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
+ warning("BoxStorage: no access_token found");
+ return nullptr;
+ }
+
+ if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
+ warning("BoxStorage: no refresh_token found");
+ return nullptr;
+ }
+
+ Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
+ Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
+ return new BoxStorage(accessToken, refreshToken);
+}
+
+Common::String BoxStorage::getRootDirectoryId() {
+ return "0";
+}
+
+} // End of namespace Box
+} // End of namespace Cloud
diff --git a/backends/cloud/box/boxstorage.h b/backends/cloud/box/boxstorage.h
new file mode 100644
index 0000000000..2dd516d894
--- /dev/null
+++ b/backends/cloud/box/boxstorage.h
@@ -0,0 +1,116 @@
+/* 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_BOX_BOXSTORAGE_H
+#define BACKENDS_CLOUD_BOX_BOXSTORAGE_H
+
+#include "backends/cloud/id/idstorage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Box {
+
+class BoxStorage: public Id::IdStorage {
+ static char *KEY, *SECRET;
+
+ static void loadKeyAndSecret();
+
+ Common::String _token, _refreshToken;
+
+ /** This private constructor is called from loadFromConfig(). */
+ BoxStorage(Common::String token, Common::String refreshToken);
+
+ void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response);
+ void codeFlowComplete(BoolResponse response);
+ void codeFlowFailed(Networking::ErrorResponse error);
+
+ /** Constructs StorageInfo based on JSON response from cloud. */
+ void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json);
+
+ void createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response);
+public:
+ /** This constructor uses OAuth code flow to get tokens. */
+ BoxStorage(Common::String code);
+ virtual ~BoxStorage();
+
+ /**
+ * 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);
+
+ /**
+ * Return unique storage name.
+ * @returns some unique storage name (for example, "Dropbox (user@example.com)")
+ */
+ virtual Common::String name() const;
+
+ /** Public Cloud API comes down there. */
+
+ virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns UploadStatus struct with info about uploaded file. */
+ virtual Networking::Request *upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns whether Storage supports upload(ReadStream). */
+ virtual bool uploadStreamSupported();
+
+ /** Returns pointer to Networking::NetworkReadStream. */
+ virtual Networking::Request *streamFileById(Common::String path, Networking::NetworkReadStreamCallback 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 BoxStorage for those.
+ * @return pointer to the newly created BoxStorage or 0 if some problem occured.
+ */
+ static BoxStorage *loadFromConfig(Common::String keyPrefix);
+
+ virtual Common::String getRootDirectoryId();
+
+ /**
+ * 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, Networking::ErrorCallback errorCallback = nullptr, Common::String code = "");
+
+ Common::String accessToken() const { return _token; }
+};
+
+} // End of namespace Box
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/box/boxtokenrefresher.cpp b/backends/cloud/box/boxtokenrefresher.cpp
new file mode 100644
index 0000000000..ca05eef838
--- /dev/null
+++ b/backends/cloud/box/boxtokenrefresher.cpp
@@ -0,0 +1,137 @@
+/* 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/box/boxtokenrefresher.h"
+#include "backends/cloud/box/boxstorage.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+namespace Cloud {
+namespace Box {
+
+BoxTokenRefresher::BoxTokenRefresher(BoxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url):
+ CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {}
+
+BoxTokenRefresher::~BoxTokenRefresher() {}
+
+void BoxTokenRefresher::tokenRefreshed(Storage::BoolResponse response) {
+ if (!response.value) {
+ //failed to refresh token, notify user with NULL in original callback
+ warning("BoxTokenRefresher: 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 BoxTokenRefresher::finishJson(Common::JSONValue *json) {
+ if (!json) {
+ //that's probably not an error (200 OK)
+ CurlJsonRequest::finishJson(nullptr);
+ return;
+ }
+
+ if (jsonIsObject(json, "BoxTokenRefresher")) {
+ Common::JSONObject result = json->asObject();
+ if (result.contains("type") && result.getVal("type")->isString() && result.getVal("type")->asString() == "error") {
+ //new token needed => request token & then retry original request
+ long httpCode = -1;
+ if (_stream) {
+ httpCode = _stream->httpResponseCode();
+ debug(9, "BoxTokenRefresher: code %ld", httpCode);
+ }
+
+ bool irrecoverable = true;
+
+ Common::String code, message;
+ if (jsonContainsString(result, "code", "BoxTokenRefresher")) {
+ code = result.getVal("code")->asString();
+ debug(9, "BoxTokenRefresher: code = %s", code.c_str());
+ }
+
+ if (jsonContainsString(result, "message", "BoxTokenRefresher")) {
+ message = result.getVal("message")->asString();
+ debug(9, "BoxTokenRefresher: message = %s", message.c_str());
+ }
+
+ //TODO: decide when token refreshment will help
+ //for now refreshment is used only when HTTP 401 is passed in finishError()
+ //if (code == "unauthenticated") irrecoverable = false;
+
+ if (irrecoverable) {
+ finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpCode));
+ delete json;
+ return;
+ }
+
+ pause();
+ delete json;
+ _parentStorage->getAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed));
+ return;
+ }
+ }
+
+ //notify user of success
+ CurlJsonRequest::finishJson(json);
+}
+
+void BoxTokenRefresher::finishError(Networking::ErrorResponse error) {
+ if (error.httpResponseCode == 401) { // invalid_token
+ pause();
+ _parentStorage->getAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed));
+ return;
+ }
+
+ // there are also 400 == invalid_request and 403 == insufficient_scope
+ // but TokenRefresher is there to refresh token when it's invalid only
+
+ Request::finishError(error);
+}
+
+void BoxTokenRefresher::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 BoxTokenRefresher::addHeader(Common::String header) {
+ _headers.push_back(header);
+ CurlJsonRequest::addHeader(header);
+}
+
+} // End of namespace Box
+} // End of namespace Cloud
diff --git a/backends/cloud/box/boxtokenrefresher.h b/backends/cloud/box/boxtokenrefresher.h
new file mode 100644
index 0000000000..c08e8468c3
--- /dev/null
+++ b/backends/cloud/box/boxtokenrefresher.h
@@ -0,0 +1,53 @@
+/* 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_BOX_BOXTOKENREFRESHER_H
+#define BACKENDS_CLOUD_BOX_BOXTOKENREFRESHER_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Box {
+
+class BoxStorage;
+
+class BoxTokenRefresher: public Networking::CurlJsonRequest {
+ BoxStorage *_parentStorage;
+ Common::Array<Common::String> _headers;
+
+ void tokenRefreshed(Storage::BoolResponse response);
+
+ virtual void finishJson(Common::JSONValue *json);
+ virtual void finishError(Networking::ErrorResponse error);
+public:
+ BoxTokenRefresher(BoxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url);
+ virtual ~BoxTokenRefresher();
+
+ virtual void setHeaders(Common::Array<Common::String> &headers);
+ virtual void addHeader(Common::String header);
+};
+
+} // End of namespace Box
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/box/boxuploadrequest.cpp b/backends/cloud/box/boxuploadrequest.cpp
new file mode 100644
index 0000000000..5084aa5652
--- /dev/null
+++ b/backends/cloud/box/boxuploadrequest.cpp
@@ -0,0 +1,232 @@
+/* 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/box/boxuploadrequest.h"
+#include "backends/cloud/box/boxstorage.h"
+#include "backends/cloud/box/boxtokenrefresher.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Box {
+
+#define BOX_API_FILES "https://upload.box.com/api/2.0/files"
+
+BoxUploadRequest::BoxUploadRequest(BoxStorage *storage, Common::String path, Common::String localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _localPath(localPath), _uploadCallback(callback),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+BoxUploadRequest::~BoxUploadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _uploadCallback;
+}
+
+void BoxUploadRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _resolvedId = ""; //used to update file contents
+ _parentId = ""; //used to create file within parent directory
+ _ignoreCallback = false;
+
+ resolveId();
+}
+
+void BoxUploadRequest::resolveId() {
+ //check whether such file already exists
+ Storage::UploadCallback innerCallback = new Common::Callback<BoxUploadRequest, Storage::UploadResponse>(this, &BoxUploadRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::idResolveFailedCallback);
+ _workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback);
+}
+
+void BoxUploadRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+ _resolvedId = response.value.id();
+ upload();
+}
+
+void BoxUploadRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+
+ //not resolved => error or no such file
+ if (error.response.contains("no such file found in its parent directory")) {
+ //parent's id after the '\n'
+ Common::String parentId = error.response;
+ for (uint32 i = 0; i < parentId.size(); ++i)
+ if (parentId[i] == '\n') {
+ parentId.erase(0, i + 1);
+ break;
+ }
+
+ _parentId = parentId;
+ upload();
+ return;
+ }
+
+ finishError(error);
+}
+
+void BoxUploadRequest::upload() {
+ Common::String name = _savePath;
+ for (uint32 i = name.size(); i > 0; --i) {
+ if (name[i - 1] == '/' || name[i - 1] == '\\') {
+ name.erase(0, i);
+ break;
+ }
+ }
+
+ Common::String url = BOX_API_FILES;
+ if (_resolvedId != "")
+ url += "/" + _resolvedId;
+ url += "/content";
+ Networking::JsonCallback callback = new Common::Callback<BoxUploadRequest, Networking::JsonResponse>(this, &BoxUploadRequest::uploadedCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::notUploadedCallback);
+ Networking::CurlJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+
+ Common::JSONObject jsonRequestParameters;
+ if (_resolvedId == "") {
+ Common::JSONObject parentObject;
+ parentObject.setVal("id", new Common::JSONValue(_parentId));
+ jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject));
+ jsonRequestParameters.setVal("name", new Common::JSONValue(name));
+ }
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addFormField("attributes", Common::JSON::stringify(&value));
+ request->addFormFile("file", _localPath);
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void BoxUploadRequest::uploadedCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+
+ Networking::ErrorResponse error(this, false, true, "", -1);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq) {
+ const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
+ if (stream) {
+ long code = stream->httpResponseCode();
+ error.httpResponseCode = code;
+ }
+ }
+
+ if (error.httpResponseCode != 200 && error.httpResponseCode != 201)
+ warning("BoxUploadRequest: looks like an error (bad HTTP code)");
+
+ //check JSON and show warnings if it's malformed
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject object = json->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsArray(object, "entries", "BoxUploadRequest")) {
+ Common::JSONArray entries = object.getVal("entries")->asArray();
+ if (entries.size() == 0) {
+ warning("BoxUploadRequest: 'entries' found, but it's empty");
+ } else if (!Networking::CurlJsonRequest::jsonIsObject(entries[0], "BoxUploadRequest")) {
+ warning("BoxUploadRequest: 'entries' first item is not an object");
+ } else {
+ Common::JSONObject item = entries[0]->asObject();
+
+ if (Networking::CurlJsonRequest::jsonContainsString(item, "id", "BoxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(item, "name", "BoxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(item, "type", "BoxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(item, "modified_at", "BoxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxUploadRequest")) {
+
+ //finished
+ Common::String id = item.getVal("id")->asString();
+ Common::String name = item.getVal("name")->asString();
+ bool isDirectory = (item.getVal("type")->asString() == "folder");
+ uint32 size;
+ if (item.getVal("size")->isString()) {
+ size = item.getVal("size")->asString().asUint64();
+ } else {
+ size = item.getVal("size")->asIntegerNumber();
+ }
+ uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString());
+
+ finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory));
+ delete json;
+ return;
+ }
+ }
+ }
+
+ //TODO: check errors
+ /*
+ if (object.contains("error")) {
+ warning("Box returned error: %s", json->stringify(true).c_str());
+ delete json;
+ error.response = json->stringify(true);
+ finishError(error);
+ return;
+ }
+ */
+
+ warning("BoxUploadRequest: no file info to return");
+ finishUpload(StorageFile(_savePath, 0, 0, false));
+
+ delete json;
+}
+
+void BoxUploadRequest::notUploadedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+ finishError(error);
+}
+
+void BoxUploadRequest::handle() {}
+
+void BoxUploadRequest::restart() { start(); }
+
+void BoxUploadRequest::finishUpload(StorageFile file) {
+ Request::finishSuccess();
+ if (_uploadCallback)
+ (*_uploadCallback)(Storage::UploadResponse(this, file));
+}
+
+} // End of namespace Box
+} // End of namespace Cloud
diff --git a/backends/cloud/box/boxuploadrequest.h b/backends/cloud/box/boxuploadrequest.h
new file mode 100644
index 0000000000..1bc8690210
--- /dev/null
+++ b/backends/cloud/box/boxuploadrequest.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_BOX_BOXUPLOADREQUEST_H
+#define BACKENDS_CLOUD_BOX_BOXUPLOADREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Box {
+class BoxStorage;
+
+class BoxUploadRequest: public Networking::Request {
+ BoxStorage *_storage;
+ Common::String _savePath, _localPath;
+ Storage::UploadCallback _uploadCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _resolvedId, _parentId;
+
+ void start();
+ void resolveId();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveFailedCallback(Networking::ErrorResponse error);
+ void upload();
+ void uploadedCallback(Networking::JsonResponse response);
+ void notUploadedCallback(Networking::ErrorResponse error);
+ void finishUpload(StorageFile status);
+
+public:
+ BoxUploadRequest(BoxStorage *storage, Common::String path, Common::String localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
+ virtual ~BoxUploadRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace Box
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/cloudmanager.cpp b/backends/cloud/cloudmanager.cpp
new file mode 100644
index 0000000000..826fc6103d
--- /dev/null
+++ b/backends/cloud/cloudmanager.cpp
@@ -0,0 +1,456 @@
+/* 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/cloudmanager.h"
+#include "backends/cloud/box/boxstorage.h"
+#include "backends/cloud/dropbox/dropboxstorage.h"
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/cloud/googledrive/googledrivestorage.h"
+#include "common/translation.h"
+#include "common/config-manager.h"
+#include "common/str.h"
+#ifdef USE_SDL_NET
+#include "backends/networking/sdl_net/localwebserver.h"
+#endif
+
+namespace Common {
+
+DECLARE_SINGLETON(Cloud::CloudManager);
+
+}
+
+namespace Cloud {
+
+const char *const CloudManager::kStoragePrefix = "storage_";
+
+CloudManager::CloudManager() : _currentStorageIndex(0), _activeStorage(nullptr) {}
+
+CloudManager::~CloudManager() {
+ delete _activeStorage;
+ freeStorages();
+}
+
+Common::String CloudManager::getStorageConfigName(uint32 index) const {
+ switch (index) {
+ case kStorageNoneId: return "<none>";
+ case kStorageDropboxId: return "Dropbox";
+ case kStorageOneDriveId: return "OneDrive";
+ case kStorageGoogleDriveId: return "GoogleDrive";
+ case kStorageBoxId: return "Box";
+ }
+ assert(false); // Unhandled StorageID value
+ return "";
+}
+
+void CloudManager::loadStorage() {
+ switch (_currentStorageIndex) {
+ case kStorageDropboxId:
+ _activeStorage = Dropbox::DropboxStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
+ break;
+ case kStorageOneDriveId:
+ _activeStorage = OneDrive::OneDriveStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
+ break;
+ case kStorageGoogleDriveId:
+ _activeStorage = GoogleDrive::GoogleDriveStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
+ break;
+ case kStorageBoxId:
+ _activeStorage = Box::BoxStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
+ break;
+ default:
+ _activeStorage = nullptr;
+ }
+
+ if (!_activeStorage) {
+ _currentStorageIndex = kStorageNoneId;
+ }
+}
+
+void CloudManager::init() {
+ //init configs structs
+ for (uint32 i = 0; i < kStorageTotal; ++i) {
+ Common::String name = getStorageConfigName(i);
+ StorageConfig config;
+ config.name = _(name);
+ config.username = "";
+ config.lastSyncDate = "";
+ config.usedBytes = 0;
+ if (ConfMan.hasKey(kStoragePrefix + name + "_username", ConfMan.kCloudDomain))
+ config.username = ConfMan.get(kStoragePrefix + name + "_username", ConfMan.kCloudDomain);
+ if (ConfMan.hasKey(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain))
+ config.lastSyncDate = ConfMan.get(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain);
+ if (ConfMan.hasKey(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain))
+ config.usedBytes = ConfMan.get(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain).asUint64();
+ _storages.push_back(config);
+ }
+
+ //load an active storage if there is any
+ _currentStorageIndex = kStorageNoneId;
+ if (ConfMan.hasKey("current_storage", ConfMan.kCloudDomain))
+ _currentStorageIndex = ConfMan.getInt("current_storage", ConfMan.kCloudDomain);
+
+ loadStorage();
+}
+
+void CloudManager::save() {
+ for (uint32 i = 0; i < _storages.size(); ++i) {
+ if (i == kStorageNoneId)
+ continue;
+ Common::String name = getStorageConfigName(i);
+ ConfMan.set(kStoragePrefix + name + "_username", _storages[i].username, ConfMan.kCloudDomain);
+ ConfMan.set(kStoragePrefix + name + "_lastSync", _storages[i].lastSyncDate, ConfMan.kCloudDomain);
+ ConfMan.set(kStoragePrefix + name + "_usedBytes", Common::String::format("%lu", _storages[i].usedBytes), ConfMan.kCloudDomain);
+ }
+
+ ConfMan.set("current_storage", Common::String::format("%u", _currentStorageIndex), ConfMan.kCloudDomain);
+ if (_activeStorage)
+ _activeStorage->saveConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
+ ConfMan.flushToDisk();
+}
+
+void CloudManager::replaceStorage(Storage *storage, uint32 index) {
+ freeStorages();
+ if (!storage)
+ error("CloudManager::replaceStorage: NULL storage passed");
+ if (index >= kStorageTotal)
+ error("CloudManager::replaceStorage: invalid index passed");
+ if (_activeStorage != nullptr && _activeStorage->isWorking()) {
+ warning("CloudManager::replaceStorage: replacing Storage while the other is working");
+ if (_activeStorage->isDownloading())
+ _activeStorage->cancelDownload();
+ if (_activeStorage->isSyncing())
+ _activeStorage->cancelSync();
+ removeStorage(_activeStorage);
+ } else {
+ delete _activeStorage;
+ }
+ _activeStorage = storage;
+ _currentStorageIndex = index;
+ save();
+
+ //do what should be done on first Storage connect
+ if (_activeStorage) {
+ _activeStorage->info(nullptr, nullptr); //automatically calls setStorageUsername()
+ _activeStorage->syncSaves(nullptr, nullptr);
+ }
+}
+
+void CloudManager::removeStorage(Storage *storage) {
+ // can't just delete it as it's mostly likely the one who calls the method
+ // it would be freed on freeStorages() call (on next Storage connect or replace)
+ _storagesToRemove.push_back(storage);
+}
+
+void CloudManager::freeStorages() {
+ for (uint32 i = 0; i < _storagesToRemove.size(); ++i)
+ delete _storagesToRemove[i];
+ _storagesToRemove.clear();
+}
+
+void CloudManager::passNoStorageConnected(Networking::ErrorCallback errorCallback) const {
+ if (errorCallback == nullptr)
+ return;
+ (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "No Storage connected!", -1));
+}
+
+Storage *CloudManager::getCurrentStorage() const {
+ return _activeStorage;
+}
+
+uint32 CloudManager::getStorageIndex() const {
+ return _currentStorageIndex;
+}
+
+Common::StringArray CloudManager::listStorages() const {
+ Common::StringArray result;
+ for (uint32 i = 0; i < _storages.size(); ++i) {
+ result.push_back(_storages[i].name);
+ }
+ return result;
+}
+
+bool CloudManager::switchStorage(uint32 index) {
+ if (index >= _storages.size()) {
+ warning("CloudManager::switchStorage: invalid index passed");
+ return false;
+ }
+
+ Storage *storage = getCurrentStorage();
+ if (storage && storage->isWorking()) {
+ warning("CloudManager::switchStorage: another storage is working now");
+ return false;
+ }
+
+ _currentStorageIndex = index;
+ loadStorage();
+ save();
+ return true;
+}
+
+Common::String CloudManager::getStorageUsername(uint32 index) {
+ if (index >= _storages.size())
+ return "";
+ return _storages[index].username;
+}
+
+uint64 CloudManager::getStorageUsedSpace(uint32 index) {
+ if (index >= _storages.size())
+ return 0;
+ return _storages[index].usedBytes;
+}
+
+Common::String CloudManager::getStorageLastSync(uint32 index) {
+ if (index >= _storages.size())
+ return "";
+ if (index == _currentStorageIndex && isSyncing())
+ return "";
+ return _storages[index].lastSyncDate;
+}
+
+void CloudManager::setStorageUsername(uint32 index, Common::String name) {
+ if (index >= _storages.size())
+ return;
+ _storages[index].username = name;
+ save();
+}
+
+void CloudManager::setStorageUsedSpace(uint32 index, uint64 used) {
+ if (index >= _storages.size())
+ return;
+ _storages[index].usedBytes = used;
+ save();
+}
+
+void CloudManager::setStorageLastSync(uint32 index, Common::String date) {
+ if (index >= _storages.size())
+ return;
+ _storages[index].lastSyncDate = date;
+ save();
+}
+
+void CloudManager::connectStorage(uint32 index, Common::String code) {
+ freeStorages();
+
+ Storage *storage = nullptr;
+ switch (index) {
+ case kStorageDropboxId:
+ storage = new Dropbox::DropboxStorage(code);
+ break;
+ case kStorageOneDriveId:
+ storage = new OneDrive::OneDriveStorage(code);
+ break;
+ case kStorageGoogleDriveId:
+ storage = new GoogleDrive::GoogleDriveStorage(code);
+ break;
+ case kStorageBoxId:
+ storage = new Box::BoxStorage(code);
+ break;
+ }
+ // in these constructors Storages request token using the passed code
+ // when the token is received, they call replaceStorage()
+ // or removeStorage(), if some error occurred
+ // thus, no memory leak happens
+}
+
+Networking::Request *CloudManager::listDirectory(Common::String path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
+ Storage *storage = getCurrentStorage();
+ if (storage) {
+ return storage->listDirectory(path, callback, errorCallback, recursive);
+ } else {
+ passNoStorageConnected(errorCallback);
+ delete callback;
+ delete errorCallback;
+ }
+ return nullptr;
+}
+
+Networking::Request *CloudManager::downloadFolder(Common::String remotePath, Common::String localPath, Storage::FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
+ Storage *storage = getCurrentStorage();
+ if (storage) {
+ return storage->downloadFolder(remotePath, localPath, callback, errorCallback, recursive);
+ } else {
+ passNoStorageConnected(errorCallback);
+ delete callback;
+ delete errorCallback;
+ }
+ return nullptr;
+}
+
+Networking::Request *CloudManager::info(Storage::StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
+ Storage *storage = getCurrentStorage();
+ if (storage) {
+ return storage->info(callback, errorCallback);
+ } else {
+ passNoStorageConnected(errorCallback);
+ delete callback;
+ delete errorCallback;
+ }
+ return nullptr;
+}
+
+Common::String CloudManager::savesDirectoryPath() {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->savesDirectoryPath();
+ return "";
+}
+
+SavesSyncRequest *CloudManager::syncSaves(Storage::BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ Storage *storage = getCurrentStorage();
+ if (storage) {
+ setStorageLastSync(_currentStorageIndex, "???"); //TODO get the date
+ return storage->syncSaves(callback, errorCallback);
+ } else {
+ passNoStorageConnected(errorCallback);
+ delete callback;
+ delete errorCallback;
+ }
+ return nullptr;
+}
+
+bool CloudManager::isWorking() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->isWorking();
+ return false;
+}
+
+bool CloudManager::couldUseLocalServer() {
+#ifdef USE_SDL_NET
+ return Networking::LocalWebserver::getPort() == Networking::LocalWebserver::DEFAULT_SERVER_PORT;
+#else
+ return false;
+#endif
+}
+
+///// SavesSyncRequest-related /////
+
+bool CloudManager::isSyncing() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->isSyncing();
+ return false;
+}
+
+double CloudManager::getSyncDownloadingProgress() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getSyncDownloadingProgress();
+ return 1;
+}
+
+double CloudManager::getSyncProgress() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getSyncProgress();
+ return 1;
+}
+
+Common::Array<Common::String> CloudManager::getSyncingFiles() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getSyncingFiles();
+ return Common::Array<Common::String>();
+}
+
+void CloudManager::cancelSync() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ storage->cancelSync();
+}
+
+void CloudManager::setSyncTarget(GUI::CommandReceiver *target) const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ storage->setSyncTarget(target);
+}
+
+///// DownloadFolderRequest-related /////
+
+bool CloudManager::startDownload(Common::String remotePath, Common::String localPath) const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->startDownload(remotePath, localPath);
+ return false;
+}
+
+void CloudManager::cancelDownload() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ storage->cancelDownload();
+}
+
+void CloudManager::setDownloadTarget(GUI::CommandReceiver *target) const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ storage->setDownloadTarget(target);
+}
+
+bool CloudManager::isDownloading() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->isDownloading();
+ return false;
+}
+
+double CloudManager::getDownloadingProgress() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadingProgress();
+ return 1;
+}
+
+uint64 CloudManager::getDownloadBytesNumber() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadBytesNumber();
+ return 0;
+}
+
+uint64 CloudManager::getDownloadTotalBytesNumber() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadTotalBytesNumber();
+ return 0;
+}
+
+uint64 CloudManager::getDownloadSpeed() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadSpeed();
+ return 0;
+}
+
+Common::String CloudManager::getDownloadRemoteDirectory() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadRemoteDirectory();
+ return "";
+}
+
+Common::String CloudManager::getDownloadLocalDirectory() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadLocalDirectory();
+ return "";
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/cloudmanager.h b/backends/cloud/cloudmanager.h
new file mode 100644
index 0000000000..c504ff39cb
--- /dev/null
+++ b/backends/cloud/cloudmanager.h
@@ -0,0 +1,274 @@
+/* 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 CLOUD_CLOUDMANAGER_H
+#define CLOUD_CLOUDMANAGER_H
+
+#include "backends/cloud/storage.h"
+#include "common/array.h"
+#include "common/singleton.h"
+#include "common/str-array.h"
+
+namespace GUI {
+
+class CommandReceiver;
+
+}
+
+namespace Cloud {
+
+// The actual indexes in CloudManager's array
+enum StorageID {
+ kStorageNoneId = 0,
+ kStorageDropboxId = 1,
+ kStorageOneDriveId = 2,
+ kStorageGoogleDriveId = 3,
+ kStorageBoxId = 4,
+
+ kStorageTotal
+};
+
+class CloudManager : public Common::Singleton<CloudManager> {
+ static const char *const kStoragePrefix;
+
+ struct StorageConfig {
+ Common::String name, username;
+ uint64 usedBytes;
+ Common::String lastSyncDate;
+ };
+
+ Common::Array<StorageConfig> _storages;
+ uint _currentStorageIndex;
+ Storage *_activeStorage;
+ Common::Array<Storage *> _storagesToRemove;
+
+ void loadStorage();
+
+ Common::String getStorageConfigName(uint32 index) const;
+
+ /** Frees memory used by storages which failed to connect. */
+ void freeStorages();
+
+ /** Calls the error callback with a special "no storage connected" message. */
+ void passNoStorageConnected(Networking::ErrorCallback errorCallback) const;
+
+public:
+ CloudManager();
+ virtual ~CloudManager();
+
+ /**
+ * Loads all information from configs and creates current Storage instance.
+ *
+ * @note It's called once on startup in scummvm_main().
+ */
+ void init();
+
+ /**
+ * Saves all information into configuration file.
+ */
+ void save();
+
+ /**
+ * Replace active Storage.
+ * @note this method automatically saves the changes with ConfMan.
+ *
+ * @param storage Cloud::Storage to replace active storage with.
+ * @param index one of Cloud::StorageID enum values to indicate what storage type is replaced.
+ */
+ void replaceStorage(Storage *storage, uint32 index);
+
+ /** Adds storage in the list of storages to remove later. */
+ void removeStorage(Storage *storage);
+
+ /**
+ * Returns active Storage, which could be used to interact
+ * with cloud storage.
+ *
+ * @return active Cloud::Storage or null, if there is no active Storage.
+ */
+ Cloud::Storage *getCurrentStorage() const;
+
+ /**
+ * Return active Storage's index.
+ *
+ * @return active Storage's index.
+ */
+ uint32 getStorageIndex() const;
+
+ /**
+ * Return Storages names as list.
+ *
+ * @return a list of Storages names.
+ */
+ Common::StringArray listStorages() const;
+
+ /**
+ * Changes the storage to the one with given index.
+ *
+ * @param new Storage's index.
+ */
+ bool switchStorage(uint32 index);
+
+ /**
+ * Return username used by Storage.
+ *
+ * @param Storage's index.
+ * @returns username or "" if index is invalid (no such Storage).
+ */
+ Common::String getStorageUsername(uint32 index);
+
+ /**
+ * Return space used by Storage.
+ *
+ * @param Storage's index.
+ * @returns used space in bytes or 0 if index is invalid (no such Storage).
+ */
+ uint64 getStorageUsedSpace(uint32 index);
+
+ /**
+ * Return Storage's last sync date.
+ *
+ * @param Storage's index.
+ * @returns last sync date or "" if index is invalid (no such Storage).
+ It also returns "" if there never was any sync
+ or if storage is syncing right now.
+ */
+ Common::String getStorageLastSync(uint32 index);
+
+ /**
+ * Set Storage's username.
+ * Automatically saves changes to the config.
+ *
+ * @param index Storage's index.
+ * @param name username to set
+ */
+ void setStorageUsername(uint32 index, Common::String name);
+
+ /**
+ * Set Storage's used space field.
+ * Automatically saves changes to the config.
+ *
+ * @param index Storage's index.
+ * @param used value to set
+ */
+ void setStorageUsedSpace(uint32 index, uint64 used);
+
+ /**
+ * Set Storage's last sync date.
+ * Automatically saves changes to the config.
+ *
+ * @param index Storage's index.
+ * @param date date to set
+ */
+ void setStorageLastSync(uint32 index, Common::String date);
+
+ /**
+ * Replace Storage which has given index with a
+ * storage created with given code.
+ *
+ * @param index Storage's index
+ * @param code OAuth2 code received from user
+ */
+ void connectStorage(uint32 index, Common::String code);
+
+ /** Returns ListDirectoryResponse with list of files. */
+ Networking::Request *listDirectory(Common::String path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
+
+ /** Returns Common::Array<StorageFile> with list of files, which were not downloaded. */
+ Networking::Request *downloadFolder(Common::String remotePath, Common::String localPath, Storage::FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
+
+ /** Return the StorageInfo struct. */
+ Networking::Request *info(Storage::StorageInfoCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns storage's saves directory path with the trailing slash. */
+ Common::String savesDirectoryPath();
+
+ /**
+ * Starts saves syncing process in currently active storage if there is any.
+ */
+ SavesSyncRequest *syncSaves(Cloud::Storage::BoolCallback callback = nullptr, Networking::ErrorCallback errorCallback = nullptr);
+
+ /** Returns whether there are any requests running. */
+ bool isWorking() const;
+
+ /** Returns whether LocalWebserver is available to use for auth. */
+ static bool couldUseLocalServer();
+
+ ///// SavesSyncRequest-related /////
+
+ /** Returns whether there is a SavesSyncRequest running. */
+ bool isSyncing() const;
+
+ /** Returns a number in [0, 1] range which represents current sync downloading progress (1 = complete). */
+ double getSyncDownloadingProgress() const;
+
+ /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */
+ double getSyncProgress() const;
+
+ /** Returns an array of saves names which are not yet synced (thus cannot be used). */
+ Common::Array<Common::String> getSyncingFiles() const;
+
+ /** Cancels running sync. */
+ void cancelSync() const;
+
+ /** Sets SavesSyncRequest's target to given CommandReceiver. */
+ void setSyncTarget(GUI::CommandReceiver *target) const;
+
+ ///// DownloadFolderRequest-related /////
+
+ /** Starts a folder download. */
+ bool startDownload(Common::String remotePath, Common::String localPath) const;
+
+ /** Cancels running download. */
+ void cancelDownload() const;
+
+ /** Sets FolderDownloadRequest's target to given CommandReceiver. */
+ void setDownloadTarget(GUI::CommandReceiver *target) const;
+
+ /** Returns whether there is a FolderDownloadRequest running. */
+ bool isDownloading() const;
+
+ /** Returns a number in [0, 1] range which represents current download progress (1 = complete). */
+ double getDownloadingProgress() const;
+
+ /** Returns a number of bytes that is downloaded in current download progress. */
+ uint64 getDownloadBytesNumber() const;
+
+ /** Returns a total number of bytes to be downloaded in current download progress. */
+ uint64 getDownloadTotalBytesNumber() const;
+
+ /** Returns download speed of current download progress. */
+ uint64 getDownloadSpeed() const;
+
+ /** Returns remote directory path. */
+ Common::String getDownloadRemoteDirectory() const;
+
+ /** Returns local directory path. */
+ Common::String getDownloadLocalDirectory() const;
+};
+
+/** Shortcut for accessing the connection manager. */
+#define CloudMan Cloud::CloudManager::instance()
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/downloadrequest.cpp b/backends/cloud/downloadrequest.cpp
new file mode 100644
index 0000000000..e28670fc7b
--- /dev/null
+++ b/backends/cloud/downloadrequest.cpp
@@ -0,0 +1,138 @@
+/* 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/downloadrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "common/textconsole.h"
+
+namespace Cloud {
+
+DownloadRequest::DownloadRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb, Common::String remoteFileId, Common::DumpFile *dumpFile):
+ Request(nullptr, ecb), _boolCallback(callback), _localFile(dumpFile), _remoteFileId(remoteFileId), _storage(storage),
+ _remoteFileStream(nullptr), _workingRequest(nullptr), _ignoreCallback(false), _buffer(new byte[DOWNLOAD_REQUEST_BUFFER_SIZE]) {
+ start();
+}
+
+DownloadRequest::~DownloadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+ delete _localFile;
+ delete[] _buffer;
+}
+
+void DownloadRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _remoteFileStream = nullptr;
+ //TODO: add some way to reopen DumpFile, so DownloadRequest could be restarted
+ _ignoreCallback = false;
+
+ _workingRequest = _storage->streamFileById(
+ _remoteFileId,
+ new Common::Callback<DownloadRequest, Networking::NetworkReadStreamResponse>(this, &DownloadRequest::streamCallback),
+ new Common::Callback<DownloadRequest, Networking::ErrorResponse>(this, &DownloadRequest::streamErrorCallback)
+ );
+}
+
+void DownloadRequest::streamCallback(Networking::NetworkReadStreamResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ _remoteFileStream = (Networking::NetworkReadStream *)response.value;
+}
+
+void DownloadRequest::streamErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void DownloadRequest::handle() {
+ if (!_localFile) {
+ warning("DownloadRequest: no file to write");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ if (!_localFile->isOpen()) {
+ warning("DownloadRequest: failed to open file to write");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ if (!_remoteFileStream) {
+ //waiting for callback
+ return;
+ }
+
+ uint32 readBytes = _remoteFileStream->read(_buffer, DOWNLOAD_REQUEST_BUFFER_SIZE);
+
+ if (readBytes != 0)
+ if (_localFile->write(_buffer, readBytes) != readBytes) {
+ warning("DownloadRequest: unable to write all received bytes into output file");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ if (_remoteFileStream->eos()) {
+ if (_remoteFileStream->httpResponseCode() != 200) {
+ warning("DownloadRequest: HTTP response code is not 200 OK (it's %ld)", _remoteFileStream->httpResponseCode());
+ //TODO: do something about it actually
+ // the problem is file's already downloaded, stream is over
+ // so we can't return error message anymore
+ }
+
+ finishDownload(_remoteFileStream->httpResponseCode() == 200);
+
+ _localFile->close(); //yes, I know it's closed automatically in ~DumpFile()
+ }
+}
+
+void DownloadRequest::restart() {
+ warning("DownloadRequest: can't restart as there are no means to reopen DumpFile");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ //start();
+}
+
+void DownloadRequest::finishDownload(bool success) {
+ Request::finishSuccess();
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+void DownloadRequest::finishError(Networking::ErrorResponse error) {
+ if (_localFile)
+ _localFile->close();
+ Request::finishError(error);
+}
+
+double DownloadRequest::getProgress() const {
+ if (_remoteFileStream)
+ return _remoteFileStream->getProgress();
+ return 0;
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/downloadrequest.h b/backends/cloud/downloadrequest.h
new file mode 100644
index 0000000000..7e58098849
--- /dev/null
+++ b/backends/cloud/downloadrequest.h
@@ -0,0 +1,64 @@
+/* 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_DOWNLOADREQUEST_H
+#define BACKENDS_CLOUD_DOWNLOADREQUEST_H
+
+#include "backends/networking/curl/request.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "backends/cloud/storage.h"
+#include "common/file.h"
+
+namespace Cloud {
+
+#define DOWNLOAD_REQUEST_BUFFER_SIZE 1 * 1024 * 1024
+
+class DownloadRequest: public Networking::Request {
+ Storage::BoolCallback _boolCallback;
+ Common::DumpFile *_localFile;
+ Common::String _remoteFileId;
+ Storage *_storage;
+ Networking::NetworkReadStream *_remoteFileStream;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ byte *_buffer;
+
+ void start();
+ void streamCallback(Networking::NetworkReadStreamResponse response);
+ void streamErrorCallback(Networking::ErrorResponse error);
+ void finishDownload(bool success);
+ virtual void finishError(Networking::ErrorResponse error);
+
+public:
+ DownloadRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb, Common::String remoteFileId, Common::DumpFile *dumpFile);
+ virtual ~DownloadRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp
new file mode 100644
index 0000000000..97090b44f8
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp
@@ -0,0 +1,137 @@
+/* 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/dropbox/dropboxcreatedirectoryrequest.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+#define DROPBOX_API_CREATE_FOLDER "https://api.dropboxapi.com/2/files/create_folder"
+
+DropboxCreateDirectoryRequest::DropboxCreateDirectoryRequest(Common::String token, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _token(token), _path(path), _boolCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+DropboxCreateDirectoryRequest::~DropboxCreateDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+}
+
+void DropboxCreateDirectoryRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _ignoreCallback = false;
+
+ Networking::JsonCallback innerCallback = new Common::Callback<DropboxCreateDirectoryRequest, Networking::JsonResponse>(this, &DropboxCreateDirectoryRequest::responseCallback);
+ Networking::ErrorCallback errorCallback = new Common::Callback<DropboxCreateDirectoryRequest, Networking::ErrorResponse>(this, &DropboxCreateDirectoryRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_CREATE_FOLDER);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("path", new Common::JSONValue(_path));
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void DropboxCreateDirectoryRequest::responseCallback(Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete json;
+ return;
+ }
+ if (response.request) _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject info = json->asObject();
+ if (info.contains("id")) {
+ finishCreation(true);
+ } else {
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "error_summary", "DropboxCreateDirectoryRequest")) {
+ Common::String summary = info.getVal("error_summary")->asString();
+ if (summary.contains("path") && summary.contains("conflict") && summary.contains("folder")) {
+ // existing directory - not an error for CreateDirectoryRequest
+ finishCreation(false);
+ delete json;
+ return;
+ }
+ }
+ error.response = json->stringify(true);
+ finishError(error);
+ }
+
+ delete json;
+}
+
+void DropboxCreateDirectoryRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void DropboxCreateDirectoryRequest::handle() {}
+
+void DropboxCreateDirectoryRequest::restart() { start(); }
+
+Common::String DropboxCreateDirectoryRequest::date() const { return _date; }
+
+void DropboxCreateDirectoryRequest::finishCreation(bool success) {
+ Request::finishSuccess();
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
diff --git a/backends/cloud/dropbox/dropboxcreatedirectoryrequest.h b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.h
new file mode 100644
index 0000000000..0ef6a22a04
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.h
@@ -0,0 +1,57 @@
+/* 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_DROPBOX_DROPBOXCREATEDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_DROPBOX_DROPBOXCREATEDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+class DropboxCreateDirectoryRequest: public Networking::Request {
+ Common::String _token;
+ Common::String _path;
+ Storage::BoolCallback _boolCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void responseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishCreation(bool success);
+public:
+ DropboxCreateDirectoryRequest(Common::String token, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
+ virtual ~DropboxCreateDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/dropbox/dropboxinforequest.cpp b/backends/cloud/dropbox/dropboxinforequest.cpp
new file mode 100644
index 0000000000..6cdbe3321b
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxinforequest.cpp
@@ -0,0 +1,192 @@
+/* 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/dropbox/dropboxinforequest.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+#define DROPBOX_API_GET_CURRENT_ACCOUNT "https://api.dropboxapi.com/2/users/get_current_account"
+#define DROPBOX_API_GET_SPACE_USAGE "https://api.dropboxapi.com/2/users/get_space_usage"
+
+DropboxInfoRequest::DropboxInfoRequest(Common::String token, Storage::StorageInfoCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _token(token), _infoCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+DropboxInfoRequest::~DropboxInfoRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _infoCallback;
+}
+
+void DropboxInfoRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _ignoreCallback = false;
+
+ Networking::JsonCallback innerCallback = new Common::Callback<DropboxInfoRequest, Networking::JsonResponse>(this, &DropboxInfoRequest::userResponseCallback);
+ Networking::ErrorCallback errorCallback = new Common::Callback<DropboxInfoRequest, Networking::ErrorResponse>(this, &DropboxInfoRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_GET_CURRENT_ACCOUNT);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/json");
+ request->addPostField("null"); //use POST
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void DropboxInfoRequest::userResponseCallback(Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete json;
+ return;
+ }
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ //Dropbox documentation states there are no errors for this API method
+ Common::JSONObject info = json->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "name", "DropboxInfoRequest") &&
+ Networking::CurlJsonRequest::jsonIsObject(info.getVal("name"), "DropboxInfoRequest")) {
+ Common::JSONObject nameInfo = info.getVal("name")->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsString(nameInfo, "display_name", "DropboxInfoRequest")) {
+ _name = nameInfo.getVal("display_name")->asString();
+ }
+ }
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "account_id", "DropboxInfoRequest")) {
+ _uid = info.getVal("account_id")->asString();
+ }
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "email", "DropboxInfoRequest")) {
+ _email = info.getVal("email")->asString();
+ }
+ CloudMan.setStorageUsername(kStorageDropboxId, _email);
+ delete json;
+
+ Networking::JsonCallback innerCallback = new Common::Callback<DropboxInfoRequest, Networking::JsonResponse>(this, &DropboxInfoRequest::quotaResponseCallback);
+ Networking::ErrorCallback errorCallback = new Common::Callback<DropboxInfoRequest, Networking::ErrorResponse>(this, &DropboxInfoRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_GET_SPACE_USAGE);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/json");
+ request->addPostField("null"); //use POST
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void DropboxInfoRequest::quotaResponseCallback(Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete json;
+ return;
+ }
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ //Dropbox documentation states there are no errors for this API method
+ Common::JSONObject info = json->asObject();
+
+ if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "used", "DropboxInfoRequest")) {
+ error.response = "Passed JSON misses 'used' attribute!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ uint64 used = info.getVal("used")->asIntegerNumber(), allocated = 0;
+
+ if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "allocation", "DropboxInfoRequest") &&
+ Networking::CurlJsonRequest::jsonIsObject(info.getVal("allocation"), "DropboxInfoRequest")) {
+ Common::JSONObject allocation = info.getVal("allocation")->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(allocation, "allocated", "DropboxInfoRequest")) {
+ error.response = "Passed JSON misses 'allocation/allocated' attribute!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ allocated = allocation.getVal("allocated")->asIntegerNumber();
+ }
+
+ finishInfo(StorageInfo(_uid, _name, _email, used, allocated));
+ delete json;
+}
+
+void DropboxInfoRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+ finishError(error);
+}
+
+void DropboxInfoRequest::handle() {}
+
+void DropboxInfoRequest::restart() { start(); }
+
+void DropboxInfoRequest::finishInfo(StorageInfo info) {
+ Request::finishSuccess();
+ if (_infoCallback)
+ (*_infoCallback)(Storage::StorageInfoResponse(this, info));
+}
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
diff --git a/backends/cloud/dropbox/dropboxinforequest.h b/backends/cloud/dropbox/dropboxinforequest.h
new file mode 100644
index 0000000000..68a3e135de
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxinforequest.h
@@ -0,0 +1,56 @@
+/* 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_DROPBOX_DROPBOXINFOREQUEST_H
+#define BACKENDS_CLOUD_DROPBOX_DROPBOXINFOREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+class DropboxInfoRequest: public Networking::Request {
+ Common::String _token;
+ Common::String _uid, _name, _email;
+ Storage::StorageInfoCallback _infoCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+
+ void start();
+ void userResponseCallback(Networking::JsonResponse response);
+ void quotaResponseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishInfo(StorageInfo info);
+public:
+ DropboxInfoRequest(Common::String token, Storage::StorageInfoCallback cb, Networking::ErrorCallback ecb);
+ virtual ~DropboxInfoRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp b/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp
new file mode 100644
index 0000000000..8b00f7c2bf
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp
@@ -0,0 +1,222 @@
+/* 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/dropbox/dropboxlistdirectoryrequest.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+#define DROPBOX_API_LIST_FOLDER "https://api.dropboxapi.com/2/files/list_folder"
+#define DROPBOX_API_LIST_FOLDER_CONTINUE "https://api.dropboxapi.com/2/files/list_folder/continue"
+
+DropboxListDirectoryRequest::DropboxListDirectoryRequest(Common::String token, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive):
+ Networking::Request(nullptr, ecb), _requestedPath(path), _requestedRecursive(recursive), _listDirectoryCallback(cb),
+ _token(token), _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+DropboxListDirectoryRequest::~DropboxListDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _listDirectoryCallback;
+}
+
+void DropboxListDirectoryRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _files.clear();
+ _ignoreCallback = false;
+
+ Networking::JsonCallback callback = new Common::Callback<DropboxListDirectoryRequest, Networking::JsonResponse>(this, &DropboxListDirectoryRequest::responseCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<DropboxListDirectoryRequest, Networking::ErrorResponse>(this, &DropboxListDirectoryRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, DROPBOX_API_LIST_FOLDER);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("path", new Common::JSONValue(_requestedPath));
+ jsonRequestParameters.setVal("recursive", new Common::JSONValue(_requestedRecursive));
+ jsonRequestParameters.setVal("include_media_info", new Common::JSONValue(false));
+ jsonRequestParameters.setVal("include_deleted", new Common::JSONValue(false));
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void DropboxListDirectoryRequest::responseCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+
+ if (_ignoreCallback) {
+ delete response.value;
+ return;
+ }
+
+ if (response.request)
+ _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject responseObject = json->asObject();
+
+ if (responseObject.contains("error") || responseObject.contains("error_summary")) {
+ if (responseObject.contains("error_summary") && responseObject.getVal("error_summary")->isString()) {
+ warning("Dropbox returned error: %s", responseObject.getVal("error_summary")->asString().c_str());
+ }
+ error.failed = true;
+ error.response = json->stringify();
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
+ if (responseObject.contains("entries")) {
+ if (!responseObject.getVal("entries")->isArray()) {
+ error.response = Common::String::format(
+ "\"entries\" found, but that's not an array!\n%s",
+ responseObject.getVal("entries")->stringify(true).c_str()
+ );
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONArray items = responseObject.getVal("entries")->asArray();
+ for (uint32 i = 0; i < items.size(); ++i) {
+ if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "DropboxListDirectoryRequest"))
+ continue;
+
+ Common::JSONObject item = items[i]->asObject();
+
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "path_lower", "DropboxListDirectoryRequest"))
+ continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, ".tag", "DropboxListDirectoryRequest"))
+ continue;
+
+ Common::String path = item.getVal("path_lower")->asString();
+ bool isDirectory = (item.getVal(".tag")->asString() == "folder");
+ uint32 size = 0, timestamp = 0;
+ if (!isDirectory) {
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "server_modified", "DropboxListDirectoryRequest"))
+ continue;
+ if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(item, "size", "DropboxListDirectoryRequest"))
+ continue;
+
+ size = item.getVal("size")->asIntegerNumber();
+ timestamp = ISO8601::convertToTimestamp(item.getVal("server_modified")->asString());
+ }
+ _files.push_back(StorageFile(path, size, timestamp, isDirectory));
+ }
+ }
+
+ bool hasMore = false;
+ if (responseObject.contains("has_more")) {
+ if (!responseObject.getVal("has_more")->isBool()) {
+ warning("DropboxListDirectoryRequest: \"has_more\" is not a boolean");
+ debug(9, "%s", responseObject.getVal("has_more")->stringify(true).c_str());
+ error.response = "\"has_more\" is not a boolean!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ hasMore = responseObject.getVal("has_more")->asBool();
+ }
+
+ if (hasMore) {
+ if (!Networking::CurlJsonRequest::jsonContainsString(responseObject, "cursor", "DropboxListDirectoryRequest")) {
+ error.response = "\"has_more\" found, but \"cursor\" is not (or it's not a string)!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Networking::JsonCallback callback = new Common::Callback<DropboxListDirectoryRequest, Networking::JsonResponse>(this, &DropboxListDirectoryRequest::responseCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<DropboxListDirectoryRequest, Networking::ErrorResponse>(this, &DropboxListDirectoryRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, DROPBOX_API_LIST_FOLDER_CONTINUE);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("cursor", new Common::JSONValue(responseObject.getVal("cursor")->asString()));
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ _workingRequest = ConnMan.addRequest(request);
+ } else {
+ finishListing(_files);
+ }
+
+ delete json;
+}
+
+void DropboxListDirectoryRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void DropboxListDirectoryRequest::handle() {}
+
+void DropboxListDirectoryRequest::restart() { start(); }
+
+Common::String DropboxListDirectoryRequest::date() const { return _date; }
+
+void DropboxListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_listDirectoryCallback)
+ (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
+}
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
diff --git a/backends/cloud/dropbox/dropboxlistdirectoryrequest.h b/backends/cloud/dropbox/dropboxlistdirectoryrequest.h
new file mode 100644
index 0000000000..5c0d8dfa21
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxlistdirectoryrequest.h
@@ -0,0 +1,61 @@
+/* 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_DROPBOX_DROPBOXLISTDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_DROPBOX_DROPBOXLISTDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+class DropboxListDirectoryRequest: public Networking::Request {
+ Common::String _requestedPath;
+ bool _requestedRecursive;
+
+ Storage::ListDirectoryCallback _listDirectoryCallback;
+ Common::String _token;
+ Common::Array<StorageFile> _files;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void responseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishListing(Common::Array<StorageFile> &files);
+public:
+ DropboxListDirectoryRequest(Common::String token, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false);
+ virtual ~DropboxListDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/dropbox/dropboxstorage.cpp b/backends/cloud/dropbox/dropboxstorage.cpp
new file mode 100644
index 0000000000..d12070316b
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxstorage.cpp
@@ -0,0 +1,198 @@
+/* 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/dropbox/dropboxstorage.h"
+#include "backends/cloud/dropbox/dropboxcreatedirectoryrequest.h"
+#include "backends/cloud/dropbox/dropboxinforequest.h"
+#include "backends/cloud/dropbox/dropboxlistdirectoryrequest.h"
+#include "backends/cloud/dropbox/dropboxuploadrequest.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+#ifdef ENABLE_RELEASE
+#include "dists/clouds/cloud_keys.h"
+#endif
+
+namespace Cloud {
+namespace Dropbox {
+
+#define DROPBOX_OAUTH2_TOKEN "https://api.dropboxapi.com/oauth2/token"
+#define DROPBOX_API_FILES_DOWNLOAD "https://content.dropboxapi.com/2/files/download"
+
+char *DropboxStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth
+char *DropboxStorage::SECRET = nullptr;
+
+void DropboxStorage::loadKeyAndSecret() {
+#ifdef ENABLE_RELEASE
+ KEY = RELEASE_DROPBOX_KEY;
+ SECRET = RELEASE_DROPBOX_SECRET;
+#else
+ Common::String k = ConfMan.get("DROPBOX_KEY", ConfMan.kCloudDomain);
+ KEY = new char[k.size() + 1];
+ memcpy(KEY, k.c_str(), k.size());
+ KEY[k.size()] = 0;
+
+ k = ConfMan.get("DROPBOX_SECRET", ConfMan.kCloudDomain);
+ SECRET = new char[k.size() + 1];
+ memcpy(SECRET, k.c_str(), k.size());
+ SECRET[k.size()] = 0;
+#endif
+}
+
+DropboxStorage::DropboxStorage(Common::String accessToken, Common::String userId): _token(accessToken), _uid(userId) {}
+
+DropboxStorage::DropboxStorage(Common::String code) {
+ getAccessToken(code);
+}
+
+DropboxStorage::~DropboxStorage() {}
+
+void DropboxStorage::getAccessToken(Common::String code) {
+ if (!KEY || !SECRET)
+ loadKeyAndSecret();
+ Networking::JsonCallback callback = new Common::Callback<DropboxStorage, Networking::JsonResponse>(this, &DropboxStorage::codeFlowComplete);
+ Networking::ErrorCallback errorCallback = new Common::Callback<DropboxStorage, Networking::ErrorResponse>(this, &DropboxStorage::codeFlowFailed);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, errorCallback, DROPBOX_OAUTH2_TOKEN);
+ request->addPostField("code=" + code);
+ request->addPostField("grant_type=authorization_code");
+ request->addPostField("client_id=" + Common::String(KEY));
+ request->addPostField("client_secret=" + Common::String(SECRET));
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2F");
+ } else {
+ request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code");
+ }
+ addRequest(request);
+}
+
+void DropboxStorage::codeFlowComplete(Networking::JsonResponse response) {
+ Common::JSONValue *json = (Common::JSONValue *)response.value;
+ if (json == nullptr) {
+ debug(9, "DropboxStorage::codeFlowComplete: got NULL instead of JSON!");
+ CloudMan.removeStorage(this);
+ return;
+ }
+
+ if (!json->isObject()) {
+ debug(9, "DropboxStorage::codeFlowComplete: Passed JSON is not an object!");
+ CloudMan.removeStorage(this);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject result = json->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "DropboxStorage::codeFlowComplete") ||
+ !Networking::CurlJsonRequest::jsonContainsString(result, "uid", "DropboxStorage::codeFlowComplete")) {
+ warning("DropboxStorage: bad response, no token/uid passed");
+ debug(9, "%s", json->stringify(true).c_str());
+ CloudMan.removeStorage(this);
+ } else {
+ _token = result.getVal("access_token")->asString();
+ _uid = result.getVal("uid")->asString();
+ ConfMan.removeKey("dropbox_code", ConfMan.kCloudDomain);
+ CloudMan.replaceStorage(this, kStorageDropboxId);
+ ConfMan.flushToDisk();
+ }
+
+ delete json;
+}
+
+void DropboxStorage::codeFlowFailed(Networking::ErrorResponse error) {
+ debug(9, "DropboxStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
+ debug(9, "%s", error.response.c_str());
+ CloudMan.removeStorage(this);
+}
+
+void DropboxStorage::saveConfig(Common::String keyPrefix) {
+ ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
+ ConfMan.set(keyPrefix + "user_id", _uid, ConfMan.kCloudDomain);
+}
+
+Common::String DropboxStorage::name() const {
+ return "Dropbox";
+}
+
+Networking::Request *DropboxStorage::listDirectory(Common::String path, ListDirectoryCallback outerCallback, Networking::ErrorCallback errorCallback, bool recursive) {
+ return addRequest(new DropboxListDirectoryRequest(_token, path, outerCallback, errorCallback, recursive));
+}
+
+Networking::Request *DropboxStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ return addRequest(new DropboxUploadRequest(_token, path, contents, callback, errorCallback));
+}
+
+Networking::Request *DropboxStorage::streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("path", new Common::JSONValue(path));
+ Common::JSONValue value(jsonRequestParameters);
+
+ Networking::CurlRequest *request = new Networking::CurlRequest(nullptr, nullptr, DROPBOX_API_FILES_DOWNLOAD); //TODO: is it OK to pass no callbacks?
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Dropbox-API-Arg: " + Common::JSON::stringify(&value));
+ request->addHeader("Content-Type: "); //required to be empty (as we do POST, it's usually app/form-url-encoded)
+
+ Networking::NetworkReadStreamResponse response = request->execute();
+ if (callback)
+ (*callback)(response);
+ return response.request; // no leak here, response.request == request
+}
+
+Networking::Request *DropboxStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ return addRequest(new DropboxCreateDirectoryRequest(_token, path, callback, errorCallback));
+}
+
+Networking::Request *DropboxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ return addRequest(new DropboxInfoRequest(_token, callback, errorCallback));
+}
+
+Common::String DropboxStorage::savesDirectoryPath() { return "/saves/"; }
+
+DropboxStorage *DropboxStorage::loadFromConfig(Common::String keyPrefix) {
+ loadKeyAndSecret();
+
+ if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
+ warning("DropboxStorage: no access_token found");
+ return nullptr;
+ }
+
+ if (!ConfMan.hasKey(keyPrefix + "user_id", ConfMan.kCloudDomain)) {
+ warning("DropboxStorage: no user_id found");
+ return nullptr;
+ }
+
+ Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
+ Common::String userId = ConfMan.get(keyPrefix + "user_id", ConfMan.kCloudDomain);
+
+ return new DropboxStorage(accessToken, userId);
+}
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
diff --git a/backends/cloud/dropbox/dropboxstorage.h b/backends/cloud/dropbox/dropboxstorage.h
new file mode 100644
index 0000000000..0a0043abee
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxstorage.h
@@ -0,0 +1,101 @@
+/* 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_DROPBOX_STORAGE_H
+#define BACKENDS_CLOUD_DROPBOX_STORAGE_H
+
+#include "backends/cloud/storage.h"
+#include "common/callback.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+class DropboxStorage: public Cloud::Storage {
+ static char *KEY, *SECRET;
+
+ static void loadKeyAndSecret();
+
+ Common::String _token, _uid;
+
+ /** This private constructor is called from loadFromConfig(). */
+ DropboxStorage(Common::String token, Common::String uid);
+
+ void getAccessToken(Common::String code);
+ void codeFlowComplete(Networking::JsonResponse response);
+ void codeFlowFailed(Networking::ErrorResponse error);
+
+public:
+ /** This constructor uses OAuth code flow to get tokens. */
+ DropboxStorage(Common::String code);
+ virtual ~DropboxStorage();
+
+ /**
+ * 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);
+
+ /**
+ * Return unique storage name.
+ * @returns some unique storage name (for example, "Dropbox (user@example.com)")
+ */
+ virtual Common::String name() const;
+
+ /** 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 *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** 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 DropboxStorage for those.
+ * @return pointer to the newly created DropboxStorage or 0 if some problem occured.
+ */
+ static DropboxStorage *loadFromConfig(Common::String keyPrefix);
+};
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/dropbox/dropboxuploadrequest.cpp b/backends/cloud/dropbox/dropboxuploadrequest.cpp
new file mode 100644
index 0000000000..2c9dcc4109
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxuploadrequest.cpp
@@ -0,0 +1,204 @@
+/* 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/dropbox/dropboxuploadrequest.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+#define DROPBOX_API_FILES_UPLOAD "https://content.dropboxapi.com/2/files/upload"
+#define DROPBOX_API_FILES_UPLOAD_SESSION "https://content.dropboxapi.com/2/files/upload_session/"
+
+DropboxUploadRequest::DropboxUploadRequest(Common::String token, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _token(token), _savePath(path), _contentsStream(contents), _uploadCallback(callback),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+DropboxUploadRequest::~DropboxUploadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _contentsStream;
+ delete _uploadCallback;
+}
+
+void DropboxUploadRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ if (!_contentsStream) {
+ warning("DropboxUploadRequest: cannot start because stream is invalid");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+ if (!_contentsStream->seek(0)) {
+ warning("DropboxUploadRequest: cannot restart because stream couldn't seek(0)");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+ _ignoreCallback = false;
+
+ uploadNextPart();
+}
+
+void DropboxUploadRequest::uploadNextPart() {
+ const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024;
+
+ Common::String url = DROPBOX_API_FILES_UPLOAD_SESSION;
+ Common::JSONObject jsonRequestParameters;
+
+ if (_contentsStream->pos() == 0 || _sessionId == "") {
+ if ((uint32)_contentsStream->size() <= UPLOAD_PER_ONE_REQUEST) {
+ url = DROPBOX_API_FILES_UPLOAD;
+ jsonRequestParameters.setVal("path", new Common::JSONValue(_savePath));
+ jsonRequestParameters.setVal("mode", new Common::JSONValue("overwrite"));
+ jsonRequestParameters.setVal("autorename", new Common::JSONValue(false));
+ jsonRequestParameters.setVal("mute", new Common::JSONValue(false));
+ } else {
+ url += "start";
+ jsonRequestParameters.setVal("close", new Common::JSONValue(false));
+ }
+ } else {
+ if ((uint32)(_contentsStream->size() - _contentsStream->pos()) <= UPLOAD_PER_ONE_REQUEST) {
+ url += "finish";
+ Common::JSONObject jsonCursor, jsonCommit;
+ jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId));
+ 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));
+ jsonCommit.setVal("mute", new Common::JSONValue(false));
+ jsonRequestParameters.setVal("cursor", new Common::JSONValue(jsonCursor));
+ jsonRequestParameters.setVal("commit", new Common::JSONValue(jsonCommit));
+ } else {
+ url += "append_v2";
+ Common::JSONObject jsonCursor;
+ jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId));
+ 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));
+ }
+ }
+
+ Common::JSONValue value(jsonRequestParameters);
+ Networking::JsonCallback callback = new Common::Callback<DropboxUploadRequest, Networking::JsonResponse>(this, &DropboxUploadRequest::partUploadedCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<DropboxUploadRequest, Networking::ErrorResponse>(this, &DropboxUploadRequest::partUploadedErrorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, url);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/octet-stream");
+ request->addHeader("Dropbox-API-Arg: " + Common::JSON::stringify(&value));
+
+ byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST];
+ uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST);
+ request->setBuffer(buffer, size);
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void DropboxUploadRequest::partUploadedCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::ErrorResponse error(this, false, true, "", -1);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ bool needsFinishRequest = false;
+
+ if (json->isObject()) {
+ Common::JSONObject object = json->asObject();
+
+ //debug(9, "%s", json->stringify(true).c_str());
+
+ if (object.contains("error") || object.contains("error_summary")) {
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "error_summary", "DropboxUploadRequest")) {
+ warning("Dropbox returned error: %s", object.getVal("error_summary")->asString().c_str());
+ }
+ error.response = json->stringify(true);
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "path_lower", "DropboxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(object, "server_modified", "DropboxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsIntegerNumber(object, "size", "DropboxUploadRequest")) {
+ //finished
+ Common::String path = object.getVal("path_lower")->asString();
+ uint32 size = object.getVal("size")->asIntegerNumber();
+ uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("server_modified")->asString());
+ finishUpload(StorageFile(path, size, timestamp, false));
+ return;
+ }
+
+ if (_sessionId == "") {
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "session_id", "DropboxUploadRequest"))
+ _sessionId = object.getVal("session_id")->asString();
+ needsFinishRequest = true;
+ }
+ }
+
+ if (!needsFinishRequest && (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1)) {
+ warning("DropboxUploadRequest: no file info to return");
+ finishUpload(StorageFile(_savePath, 0, 0, false));
+ } else {
+ uploadNextPart();
+ }
+
+ delete json;
+}
+
+void DropboxUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void DropboxUploadRequest::handle() {}
+
+void DropboxUploadRequest::restart() { start(); }
+
+void DropboxUploadRequest::finishUpload(StorageFile file) {
+ Request::finishSuccess();
+ if (_uploadCallback)
+ (*_uploadCallback)(Storage::UploadResponse(this, file));
+}
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
diff --git a/backends/cloud/dropbox/dropboxuploadrequest.h b/backends/cloud/dropbox/dropboxuploadrequest.h
new file mode 100644
index 0000000000..5adf5a6df2
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxuploadrequest.h
@@ -0,0 +1,60 @@
+/* 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_DROPBOX_DROPBOXUPLOADREQUEST_H
+#define BACKENDS_CLOUD_DROPBOX_DROPBOXUPLOADREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+class DropboxUploadRequest: public Networking::Request {
+ Common::String _token;
+ Common::String _savePath;
+ Common::SeekableReadStream *_contentsStream;
+ Storage::UploadCallback _uploadCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _sessionId;
+
+ void start();
+ void uploadNextPart();
+ void partUploadedCallback(Networking::JsonResponse response);
+ void partUploadedErrorCallback(Networking::ErrorResponse error);
+ void finishUpload(StorageFile status);
+
+public:
+ DropboxUploadRequest(Common::String token, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
+ virtual ~DropboxUploadRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/folderdownloadrequest.cpp b/backends/cloud/folderdownloadrequest.cpp
new file mode 100644
index 0000000000..7eeee0c6d6
--- /dev/null
+++ b/backends/cloud/folderdownloadrequest.cpp
@@ -0,0 +1,197 @@
+/* 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/folderdownloadrequest.h"
+#include "backends/cloud/downloadrequest.h"
+#include "backends/cloud/id/iddownloadrequest.h"
+#include "common/debug.h"
+#include "gui/downloaddialog.h"
+#include <backends/networking/curl/connectionmanager.h>
+
+namespace Cloud {
+
+FolderDownloadRequest::FolderDownloadRequest(Storage *storage, Storage::FileArrayCallback callback, Networking::ErrorCallback ecb, Common::String remoteDirectoryPath, Common::String localDirectoryPath, bool recursive):
+ Request(nullptr, ecb), CommandSender(nullptr), _storage(storage), _fileArrayCallback(callback),
+ _remoteDirectoryPath(remoteDirectoryPath), _localDirectoryPath(localDirectoryPath), _recursive(recursive),
+ _workingRequest(nullptr), _ignoreCallback(false), _totalFiles(0) {
+ start();
+}
+
+FolderDownloadRequest::~FolderDownloadRequest() {
+ sendCommand(GUI::kDownloadEndedCmd, 0);
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _fileArrayCallback;
+}
+
+void FolderDownloadRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _currentFile = StorageFile();
+ _pendingFiles.clear();
+ _failedFiles.clear();
+ _ignoreCallback = false;
+ _totalFiles = 0;
+ _downloadedBytes = _totalBytes = _wasDownloadedBytes = _currentDownloadSpeed = 0;
+
+ //list directory first
+ _workingRequest = _storage->listDirectory(
+ _remoteDirectoryPath,
+ new Common::Callback<FolderDownloadRequest, Storage::ListDirectoryResponse>(this, &FolderDownloadRequest::directoryListedCallback),
+ new Common::Callback<FolderDownloadRequest, Networking::ErrorResponse>(this, &FolderDownloadRequest::directoryListedErrorCallback),
+ _recursive
+ );
+}
+
+void FolderDownloadRequest::directoryListedCallback(Storage::ListDirectoryResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ _pendingFiles = response.value;
+
+ // remove all directories
+ // non-empty directories would be created by DumpFile, and empty ones are just ignored
+ for (Common::Array<StorageFile>::iterator i = _pendingFiles.begin(); i != _pendingFiles.end();)
+ if (i->isDirectory())
+ _pendingFiles.erase(i);
+ else {
+ _totalBytes += i->size();
+ ++i;
+ }
+
+ _totalFiles = _pendingFiles.size();
+ downloadNextFile();
+}
+
+void FolderDownloadRequest::directoryListedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void FolderDownloadRequest::fileDownloadedCallback(Storage::BoolResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (!response.value) _failedFiles.push_back(_currentFile);
+ _downloadedBytes += _currentFile.size();
+ downloadNextFile();
+}
+
+void FolderDownloadRequest::fileDownloadedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ fileDownloadedCallback(Storage::BoolResponse(error.request, false));
+}
+
+void FolderDownloadRequest::downloadNextFile() {
+ do {
+ if (_pendingFiles.empty()) {
+ sendCommand(GUI::kDownloadEndedCmd, 0);
+ finishDownload(_failedFiles);
+ return;
+ }
+
+ _currentFile = _pendingFiles.back();
+ _pendingFiles.pop_back();
+ } while (_currentFile.isDirectory()); // directories are actually removed earlier, in the directoryListedCallback()
+
+ sendCommand(GUI::kDownloadProgressCmd, (int)(getProgress() * 100));
+
+ Common::String remotePath = _currentFile.path();
+ Common::String localPath = remotePath;
+ if (_remoteDirectoryPath == "" || remotePath.hasPrefix(_remoteDirectoryPath)) {
+ localPath.erase(0, _remoteDirectoryPath.size());
+ if (_remoteDirectoryPath != "" && (_remoteDirectoryPath.lastChar() != '/' && _remoteDirectoryPath.lastChar() != '\\'))
+ localPath.erase(0, 1);
+ } else {
+ warning("FolderDownloadRequest: Can't process the following paths:");
+ warning("remote directory: %s", _remoteDirectoryPath.c_str());
+ warning("remote file under that directory: %s", remotePath.c_str());
+ }
+ if (_localDirectoryPath != "") {
+ if (_localDirectoryPath.lastChar() == '/' || _localDirectoryPath.lastChar() == '\\')
+ localPath = _localDirectoryPath + localPath;
+ else
+ localPath = _localDirectoryPath + "/" + localPath;
+ }
+ debug(9, "FolderDownloadRequest: %s -> %s", remotePath.c_str(), localPath.c_str());
+ _workingRequest = _storage->downloadById(
+ _currentFile.id(), localPath,
+ new Common::Callback<FolderDownloadRequest, Storage::BoolResponse>(this, &FolderDownloadRequest::fileDownloadedCallback),
+ new Common::Callback<FolderDownloadRequest, Networking::ErrorResponse>(this, &FolderDownloadRequest::fileDownloadedErrorCallback)
+ );
+}
+
+void FolderDownloadRequest::handle() {
+ uint32 microsecondsPassed = Networking::ConnectionManager::getCloudRequestsPeriodInMicroseconds();
+ uint64 currentDownloadedBytes = getDownloadedBytes();
+ uint64 downloadedThisPeriod = currentDownloadedBytes - _wasDownloadedBytes;
+ _currentDownloadSpeed = downloadedThisPeriod * (1000000L / microsecondsPassed);
+ _wasDownloadedBytes = currentDownloadedBytes;
+}
+
+void FolderDownloadRequest::restart() { start(); }
+
+void FolderDownloadRequest::finishDownload(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_fileArrayCallback)
+ (*_fileArrayCallback)(Storage::FileArrayResponse(this, files));
+}
+
+double FolderDownloadRequest::getProgress() const {
+ if (_totalFiles == 0 || _totalBytes == 0)
+ return 0;
+ return (double)getDownloadedBytes() / (double)getTotalBytesToDownload();
+}
+
+uint64 FolderDownloadRequest::getDownloadedBytes() const {
+ if (_totalFiles == 0)
+ return 0;
+
+ double currentFileProgress = 0;
+ DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest);
+ if (downloadRequest != nullptr) {
+ currentFileProgress = downloadRequest->getProgress();
+ } else {
+ Id::IdDownloadRequest *idDownloadRequest = dynamic_cast<Id::IdDownloadRequest *>(_workingRequest);
+ if (idDownloadRequest != nullptr)
+ currentFileProgress = idDownloadRequest->getProgress();
+ }
+
+ return _downloadedBytes + (uint64)(currentFileProgress * _currentFile.size());
+}
+
+uint64 FolderDownloadRequest::getTotalBytesToDownload() const {
+ return _totalBytes;
+}
+
+uint64 FolderDownloadRequest::getDownloadSpeed() const {
+ return _currentDownloadSpeed;
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/folderdownloadrequest.h b/backends/cloud/folderdownloadrequest.h
new file mode 100644
index 0000000000..08fa193e07
--- /dev/null
+++ b/backends/cloud/folderdownloadrequest.h
@@ -0,0 +1,79 @@
+/* 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_FOLDERDOWNLOADREQUEST_H
+#define BACKENDS_CLOUD_FOLDERDOWNLOADREQUEST_H
+
+#include "backends/networking/curl/request.h"
+#include "backends/cloud/storage.h"
+#include "gui/object.h"
+
+namespace Cloud {
+
+class FolderDownloadRequest: public Networking::Request, public GUI::CommandSender {
+ Storage *_storage;
+ Storage::FileArrayCallback _fileArrayCallback;
+ Common::String _remoteDirectoryPath, _localDirectoryPath;
+ bool _recursive;
+ Common::Array<StorageFile> _pendingFiles, _failedFiles;
+ StorageFile _currentFile;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ uint32 _totalFiles;
+ uint64 _downloadedBytes, _totalBytes, _wasDownloadedBytes, _currentDownloadSpeed;
+
+ void start();
+ void directoryListedCallback(Storage::ListDirectoryResponse response);
+ void directoryListedErrorCallback(Networking::ErrorResponse error);
+ void fileDownloadedCallback(Storage::BoolResponse response);
+ void fileDownloadedErrorCallback(Networking::ErrorResponse error);
+ void downloadNextFile();
+ void finishDownload(Common::Array<StorageFile> &files);
+public:
+ FolderDownloadRequest(Storage *storage, Storage::FileArrayCallback callback, Networking::ErrorCallback ecb, Common::String remoteDirectoryPath, Common::String localDirectoryPath, bool recursive);
+ virtual ~FolderDownloadRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+
+ /** Returns a number of downloaded bytes. */
+ uint64 getDownloadedBytes() const;
+
+ /** Returns a total number of bytes to download. */
+ uint64 getTotalBytesToDownload() const;
+
+ /** Returns average download speed for the last second. */
+ uint64 getDownloadSpeed() const;
+
+ /** Returns remote directory path. */
+ Common::String getRemotePath() const { return _remoteDirectoryPath; }
+
+ /** Returns local directory path. */
+ Common::String getLocalPath() const { return _localDirectoryPath; }
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp
new file mode 100644
index 0000000000..52611126a0
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp
@@ -0,0 +1,163 @@
+/* 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/googledrive/googledrivelistdirectorybyidrequest.h"
+#include "backends/cloud/googledrive/googledrivestorage.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+#include "googledrivetokenrefresher.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+
+#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/drive/v3/files?spaces=drive&fields=files%28id,mimeType,modifiedTime,name,size%29,nextPageToken&orderBy=folder,name"
+//files(id,mimeType,modifiedTime,name,size),nextPageToken
+
+GoogleDriveListDirectoryByIdRequest::GoogleDriveListDirectoryByIdRequest(GoogleDriveStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _requestedId(id), _storage(storage), _listDirectoryCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+GoogleDriveListDirectoryByIdRequest::~GoogleDriveListDirectoryByIdRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _listDirectoryCallback;
+}
+
+void GoogleDriveListDirectoryByIdRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _files.clear();
+ _ignoreCallback = false;
+
+ makeRequest("");
+}
+
+void GoogleDriveListDirectoryByIdRequest::makeRequest(Common::String pageToken) {
+ Common::String url = GOOGLEDRIVE_API_FILES;
+ if (pageToken != "")
+ url += "&pageToken=" + pageToken;
+ url += "&q=%27" + _requestedId + "%27+in+parents";
+
+ Networking::JsonCallback callback = new Common::Callback<GoogleDriveListDirectoryByIdRequest, Networking::JsonResponse>(this, &GoogleDriveListDirectoryByIdRequest::responseCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveListDirectoryByIdRequest, Networking::ErrorResponse>(this, &GoogleDriveListDirectoryByIdRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void GoogleDriveListDirectoryByIdRequest::responseCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete response.value;
+ return;
+ }
+ if (response.request)
+ _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ Common::JSONValue *json = response.value;
+ if (json) {
+ Common::JSONObject responseObject = json->asObject();
+
+ ///debug("%s", json->stringify(true).c_str());
+
+ if (responseObject.contains("error") || responseObject.contains("error_summary")) {
+ warning("GoogleDrive returned error: %s", responseObject.getVal("error_summary")->asString().c_str());
+ error.failed = true;
+ error.response = json->stringify();
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ //TODO: check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
+
+ if (responseObject.contains("files") && responseObject.getVal("files")->isArray()) {
+ Common::JSONArray items = responseObject.getVal("files")->asArray();
+ for (uint32 i = 0; i < items.size(); ++i) {
+ Common::JSONObject item = items[i]->asObject();
+ Common::String id = item.getVal("id")->asString();
+ Common::String name = item.getVal("name")->asString();
+ bool isDirectory = (item.getVal("mimeType")->asString() == "application/vnd.google-apps.folder");
+ uint32 size = 0, timestamp = 0;
+ if (item.contains("size") && item.getVal("size")->isString())
+ size = item.getVal("size")->asString().asUint64();
+ if (item.contains("modifiedTime") && item.getVal("modifiedTime")->isString())
+ timestamp = ISO8601::convertToTimestamp(item.getVal("modifiedTime")->asString());
+
+ //as we list directory by id, we can't determine full path for the file, so we leave it empty
+ _files.push_back(StorageFile(id, "", name, size, timestamp, isDirectory));
+ }
+ }
+
+ bool hasMore = (responseObject.contains("nextPageToken"));
+
+ if (hasMore) {
+ Common::String token = responseObject.getVal("nextPageToken")->asString();
+ makeRequest(token);
+ } else {
+ finishListing(_files);
+ }
+ } else {
+ warning("null, not json");
+ error.failed = true;
+ finishError(error);
+ }
+
+ delete json;
+}
+
+void GoogleDriveListDirectoryByIdRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void GoogleDriveListDirectoryByIdRequest::handle() {}
+
+void GoogleDriveListDirectoryByIdRequest::restart() { start(); }
+
+Common::String GoogleDriveListDirectoryByIdRequest::date() const { return _date; }
+
+void GoogleDriveListDirectoryByIdRequest::finishListing(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_listDirectoryCallback)
+ (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
+}
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h
new file mode 100644
index 0000000000..e94c6b1f4e
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.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_GOOGLEDRIVE_GOOGLEDRIVELISTDIRECTORYBYIDREQUEST_H
+#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVELISTDIRECTORYBYIDREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+
+class GoogleDriveStorage;
+
+class GoogleDriveListDirectoryByIdRequest: public Networking::Request {
+ Common::String _requestedId;
+ GoogleDriveStorage *_storage;
+
+ Storage::ListDirectoryCallback _listDirectoryCallback;
+ Common::Array<StorageFile> _files;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void makeRequest(Common::String pageToken);
+ void responseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishListing(Common::Array<StorageFile> &files);
+public:
+ GoogleDriveListDirectoryByIdRequest(GoogleDriveStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb);
+ virtual ~GoogleDriveListDirectoryByIdRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/googledrive/googledrivestorage.cpp b/backends/cloud/googledrive/googledrivestorage.cpp
new file mode 100644
index 0000000000..1b4b8baf56
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivestorage.cpp
@@ -0,0 +1,348 @@
+/* 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/cloud/googledrive/googledrivelistdirectorybyidrequest.h"
+#include "backends/cloud/googledrive/googledriveuploadrequest.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>
+
+#ifdef ENABLE_RELEASE
+#include "dists/clouds/cloud_keys.h"
+#endif
+
+namespace Cloud {
+namespace GoogleDrive {
+
+#define GOOGLEDRIVE_OAUTH2_TOKEN "https://accounts.google.com/o/oauth2/token"
+#define GOOGLEDRIVE_API_FILES_ALT_MEDIA "https://www.googleapis.com/drive/v3/files/%s?alt=media"
+#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/drive/v3/files"
+#define GOOGLEDRIVE_API_ABOUT "https://www.googleapis.com/drive/v3/about?fields=storageQuota,user"
+
+char *GoogleDriveStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth
+char *GoogleDriveStorage::SECRET = nullptr;
+
+void GoogleDriveStorage::loadKeyAndSecret() {
+#ifdef ENABLE_RELEASE
+ KEY = RELEASE_GOOGLE_DRIVE_KEY;
+ SECRET = RELEASE_GOOGLE_DRIVE_SECRET;
+#else
+ Common::String k = ConfMan.get("GOOGLE_DRIVE_KEY", ConfMan.kCloudDomain);
+ KEY = new char[k.size() + 1];
+ memcpy(KEY, k.c_str(), k.size());
+ KEY[k.size()] = 0;
+
+ k = ConfMan.get("GOOGLE_DRIVE_SECRET", ConfMan.kCloudDomain);
+ SECRET = new char[k.size() + 1];
+ memcpy(SECRET, k.c_str(), k.size());
+ SECRET[k.size()] = 0;
+#endif
+}
+
+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),
+ new Common::Callback<GoogleDriveStorage, Networking::ErrorResponse>(this, &GoogleDriveStorage::codeFlowFailed),
+ code
+ );
+}
+
+GoogleDriveStorage::~GoogleDriveStorage() {}
+
+void GoogleDriveStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) {
+ if (!KEY || !SECRET) loadKeyAndSecret();
+ 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);
+ if (errorCallback == nullptr)
+ errorCallback = getErrorPrintingCallback();
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, GOOGLEDRIVE_OAUTH2_TOKEN);
+ 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));
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345");
+ } else {
+ request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code");
+ }
+ 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));
+ delete callback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage")) {
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete json;
+ delete callback;
+ return;
+ }
+
+ Common::JSONObject result = json->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "GoogleDriveStorage")) {
+ warning("GoogleDriveStorage: bad response, no token passed");
+ debug(9, "%s", json->stringify().c_str());
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ } else {
+ _token = result.getVal("access_token")->asString();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "GoogleDriveStorage"))
+ warning("GoogleDriveStorage: 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;
+ delete callback;
+}
+
+void GoogleDriveStorage::codeFlowComplete(BoolResponse response) {
+ if (!response.value) {
+ warning("GoogleDriveStorage: failed to get access token through code flow");
+ CloudMan.removeStorage(this);
+ return;
+ }
+
+ ConfMan.removeKey("googledrive_code", ConfMan.kCloudDomain);
+ CloudMan.replaceStorage(this, kStorageGoogleDriveId);
+ ConfMan.flushToDisk();
+}
+
+void GoogleDriveStorage::codeFlowFailed(Networking::ErrorResponse error) {
+ debug(9, "GoogleDriveStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
+ debug(9, "%s", error.response.c_str());
+ CloudMan.removeStorage(this);
+}
+
+void GoogleDriveStorage::saveConfig(Common::String keyPrefix) {
+ ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
+ ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
+}
+
+Common::String GoogleDriveStorage::name() const {
+ return "Google Drive";
+}
+
+void GoogleDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("GoogleDriveStorage::infoInnerCallback: NULL passed instead of JSON");
+ delete outerCallback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage::infoInnerCallback")) {
+ delete json;
+ delete outerCallback;
+ return;
+ }
+
+ Common::JSONObject info = json->asObject();
+
+ Common::String uid, name, email;
+ uint64 quotaUsed = 0, quotaAllocated = 0;
+
+ if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "user", "GoogleDriveStorage::infoInnerCallback") &&
+ Networking::CurlJsonRequest::jsonIsObject(info.getVal("user"), "GoogleDriveStorage::infoInnerCallback")) {
+ //"me":true, "kind":"drive#user","photoLink": "",
+ //"displayName":"Alexander Tkachev","emailAddress":"alexander@tkachov.ru","permissionId":""
+ Common::JSONObject user = info.getVal("user")->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsString(user, "permissionId", "GoogleDriveStorage::infoInnerCallback"))
+ uid = user.getVal("permissionId")->asString(); //not sure it's user's id, but who cares anyway?
+ if (Networking::CurlJsonRequest::jsonContainsString(user, "displayName", "GoogleDriveStorage::infoInnerCallback"))
+ name = user.getVal("displayName")->asString();
+ if (Networking::CurlJsonRequest::jsonContainsString(user, "emailAddress", "GoogleDriveStorage::infoInnerCallback"))
+ email = user.getVal("emailAddress")->asString();
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "storageQuota", "GoogleDriveStorage::infoInnerCallback") &&
+ Networking::CurlJsonRequest::jsonIsObject(info.getVal("storageQuota"), "GoogleDriveStorage::infoInnerCallback")) {
+ //"usageInDrive":"6332462","limit":"18253611008","usage":"6332462","usageInDriveTrash":"0"
+ Common::JSONObject storageQuota = info.getVal("storageQuota")->asObject();
+
+ if (Networking::CurlJsonRequest::jsonContainsString(storageQuota, "usage", "GoogleDriveStorage::infoInnerCallback")) {
+ Common::String usage = storageQuota.getVal("usage")->asString();
+ quotaUsed = usage.asUint64();
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsString(storageQuota, "limit", "GoogleDriveStorage::infoInnerCallback")) {
+ Common::String limit = storageQuota.getVal("limit")->asString();
+ quotaAllocated = limit.asUint64();
+ }
+ }
+
+ CloudMan.setStorageUsername(kStorageGoogleDriveId, email);
+
+ if (outerCallback) {
+ (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated)));
+ delete outerCallback;
+ }
+
+ delete json;
+}
+
+void GoogleDriveStorage::createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("GoogleDriveStorage::createDirectoryInnerCallback: NULL passed instead of JSON");
+ delete outerCallback;
+ return;
+ }
+
+ if (outerCallback) {
+ if (Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage::createDirectoryInnerCallback")) {
+ Common::JSONObject info = json->asObject();
+ (*outerCallback)(BoolResponse(nullptr, info.contains("id")));
+ } else {
+ (*outerCallback)(BoolResponse(nullptr, false));
+ }
+ delete outerCallback;
+ }
+
+ delete json;
+}
+
+Networking::Request *GoogleDriveStorage::listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ if (!callback)
+ callback = new Common::Callback<GoogleDriveStorage, FileArrayResponse>(this, &GoogleDriveStorage::printFiles);
+ return addRequest(new GoogleDriveListDirectoryByIdRequest(this, id, callback, errorCallback));
+}
+
+Networking::Request *GoogleDriveStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ return addRequest(new GoogleDriveUploadRequest(this, path, contents, callback, errorCallback));
+}
+
+Networking::Request *GoogleDriveStorage::streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
+ if (callback) {
+ Common::String url = Common::String::format(GOOGLEDRIVE_API_FILES_ALT_MEDIA, ConnMan.urlEncode(id).c_str());
+ Common::String header = "Authorization: Bearer " + _token;
+ curl_slist *headersList = curl_slist_append(nullptr, header.c_str());
+ Networking::NetworkReadStream *stream = new Networking::NetworkReadStream(url.c_str(), headersList, "");
+ (*callback)(Networking::NetworkReadStreamResponse(nullptr, stream));
+ }
+ delete callback;
+ delete errorCallback;
+ return nullptr;
+}
+
+void GoogleDriveStorage::printInfo(StorageInfoResponse response) {
+ debug(9, "\nGoogleDriveStorage: user info:");
+ debug(9, "\tname: %s", response.value.name().c_str());
+ debug(9, "\temail: %s", response.value.email().c_str());
+ debug(9, "\tdisk usage: %lu/%lu", response.value.used(), response.value.available());
+}
+
+Networking::Request *GoogleDriveStorage::createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+
+ Common::String url = GOOGLEDRIVE_API_FILES;
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, BoolResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::createDirectoryInnerCallback, callback);
+ Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + accessToken());
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONArray parentsArray;
+ parentsArray.push_back(new Common::JSONValue(parentId));
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("mimeType", new Common::JSONValue("application/vnd.google-apps.folder"));
+ jsonRequestParameters.setVal("name", new Common::JSONValue(name));
+ jsonRequestParameters.setVal("parents", new Common::JSONValue(parentsArray));
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ return addRequest(request);
+}
+
+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, GOOGLEDRIVE_API_ABOUT);
+ request->addHeader("Authorization: Bearer " + _token);
+ return addRequest(request);
+}
+
+Common::String GoogleDriveStorage::savesDirectoryPath() { return "scummvm/saves/"; }
+
+GoogleDriveStorage *GoogleDriveStorage::loadFromConfig(Common::String keyPrefix) {
+ loadKeyAndSecret();
+
+ if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
+ warning("GoogleDriveStorage: no access_token found");
+ return nullptr;
+ }
+
+ if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
+ warning("GoogleDriveStorage: no refresh_token found");
+ return nullptr;
+ }
+
+ Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
+ Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
+ return new GoogleDriveStorage(accessToken, refreshToken);
+}
+
+Common::String GoogleDriveStorage::getRootDirectoryId() {
+ return "root";
+}
+
+} // 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..6a834c44ca
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivestorage.h
@@ -0,0 +1,118 @@
+/* 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/id/idstorage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+
+class GoogleDriveStorage: public Id::IdStorage {
+ 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);
+
+ void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response);
+ void codeFlowComplete(BoolResponse response);
+ void codeFlowFailed(Networking::ErrorResponse error);
+
+ /** Constructs StorageInfo based on JSON response from cloud. */
+ void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json);
+
+ /** Returns bool based on JSON response from cloud. */
+ void createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse json);
+
+ void printInfo(StorageInfoResponse response);
+public:
+ /** This constructor uses OAuth code flow to get tokens. */
+ GoogleDriveStorage(Common::String code);
+ 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);
+
+ /**
+ * Return unique storage name.
+ * @returns some unique storage name (for example, "Dropbox (user@example.com)")
+ */
+ virtual Common::String name() const;
+
+ /** Public Cloud API comes down there. */
+
+ /** Returns Array<StorageFile> - the list of files. */
+ virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** 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 *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, 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);
+
+ virtual Common::String getRootDirectoryId();
+
+ /**
+ * 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, Networking::ErrorCallback errorCallback = nullptr, Common::String code = "");
+
+ Common::String accessToken() const { 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..8cc492d6b4
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivetokenrefresher.cpp
@@ -0,0 +1,125 @@
+/* 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::finishJson(Common::JSONValue *json) {
+ if (!json) {
+ //that's probably not an error (200 OK)
+ CurlJsonRequest::finishJson(nullptr);
+ return;
+ }
+
+ if (jsonIsObject(json, "GoogleDriveTokenRefresher")) {
+ Common::JSONObject result = json->asObject();
+ long httpResponseCode = -1;
+ if (result.contains("error") && jsonIsObject(result.getVal("error"), "GoogleDriveTokenRefresher")) {
+ //new token needed => request token & then retry original request
+ if (_stream) {
+ httpResponseCode = _stream->httpResponseCode();
+ debug(9, "GoogleDriveTokenRefresher: code = %ld", httpResponseCode);
+ }
+
+ Common::JSONObject error = result.getVal("error")->asObject();
+ bool irrecoverable = true;
+
+ uint32 code = -1;
+ Common::String message;
+ if (jsonContainsIntegerNumber(error, "code", "GoogleDriveTokenRefresher")) {
+ code = error.getVal("code")->asIntegerNumber();
+ debug(9, "GoogleDriveTokenRefresher: code = %u", code);
+ }
+
+ if (jsonContainsString(error, "message", "GoogleDriveTokenRefresher")) {
+ message = error.getVal("message")->asString();
+ debug(9, "GoogleDriveTokenRefresher: message = %s", message.c_str());
+ }
+
+ if (code == 401 || message == "Invalid Credentials")
+ irrecoverable = false;
+
+ if (irrecoverable) {
+ finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpResponseCode));
+ delete json;
+ return;
+ }
+
+ pause();
+ delete json;
+ _parentStorage->getAccessToken(new Common::Callback<GoogleDriveTokenRefresher, Storage::BoolResponse>(this, &GoogleDriveTokenRefresher::tokenRefreshed));
+ return;
+ }
+ }
+
+ //notify user of success
+ CurlJsonRequest::finishJson(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..6cb3e41849
--- /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 finishJson(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/cloud/googledrive/googledriveuploadrequest.cpp b/backends/cloud/googledrive/googledriveuploadrequest.cpp
new file mode 100644
index 0000000000..5f61dcd2a8
--- /dev/null
+++ b/backends/cloud/googledrive/googledriveuploadrequest.cpp
@@ -0,0 +1,353 @@
+/* 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/googledrive/googledriveuploadrequest.h"
+#include "backends/cloud/googledrive/googledrivestorage.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+#include "googledrivetokenrefresher.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+
+#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/upload/drive/v3/files"
+
+GoogleDriveUploadRequest::GoogleDriveUploadRequest(GoogleDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+GoogleDriveUploadRequest::~GoogleDriveUploadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _contentsStream;
+ delete _uploadCallback;
+}
+
+void GoogleDriveUploadRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ if (_contentsStream == nullptr || !_contentsStream->seek(0)) {
+ warning("GoogleDriveUploadRequest: cannot restart because stream couldn't seek(0)");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+ _resolvedId = ""; //used to update file contents
+ _parentId = ""; //used to create file within parent directory
+ _serverReceivedBytes = 0;
+ _ignoreCallback = false;
+
+ resolveId();
+}
+
+void GoogleDriveUploadRequest::resolveId() {
+ //check whether such file already exists
+ Storage::UploadCallback innerCallback = new Common::Callback<GoogleDriveUploadRequest, Storage::UploadResponse>(this, &GoogleDriveUploadRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::idResolveFailedCallback);
+ _workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback);
+}
+
+void GoogleDriveUploadRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ _resolvedId = response.value.id();
+ startUpload();
+}
+
+void GoogleDriveUploadRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ //not resolved => error or no such file
+ if (error.response.contains("no such file found in its parent directory")) {
+ //parent's id after the '\n'
+ Common::String parentId = error.response;
+ for (uint32 i = 0; i < parentId.size(); ++i)
+ if (parentId[i] == '\n') {
+ parentId.erase(0, i + 1);
+ break;
+ }
+
+ _parentId = parentId;
+ startUpload();
+ return;
+ }
+
+ finishError(error);
+}
+
+void GoogleDriveUploadRequest::startUpload() {
+ Common::String name = _savePath;
+ for (uint32 i = name.size(); i > 0; --i) {
+ if (name[i - 1] == '/' || name[i - 1] == '\\') {
+ name.erase(0, i);
+ break;
+ }
+ }
+
+ Common::String url = GOOGLEDRIVE_API_FILES;
+ if (_resolvedId != "")
+ url += "/" + ConnMan.urlEncode(_resolvedId);
+ url += "?uploadType=resumable&fields=id,mimeType,modifiedTime,name,size";
+ Networking::JsonCallback callback = new Common::Callback<GoogleDriveUploadRequest, Networking::JsonResponse>(this, &GoogleDriveUploadRequest::startUploadCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::startUploadErrorCallback);
+ Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ request->addHeader("Content-Type: application/json");
+ if (_resolvedId != "")
+ request->usePatch();
+
+ Common::JSONObject jsonRequestParameters;
+ if (_resolvedId != "") {
+ jsonRequestParameters.setVal("id", new Common::JSONValue(_resolvedId));
+ } else {
+ Common::JSONArray parentsArray;
+ parentsArray.push_back(new Common::JSONValue(_parentId));
+ jsonRequestParameters.setVal("parents", new Common::JSONValue(parentsArray));
+ }
+ jsonRequestParameters.setVal("name", new Common::JSONValue(name));
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void GoogleDriveUploadRequest::startUploadCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::ErrorResponse error(this, false, true, "", -1);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq) {
+ const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
+ if (stream) {
+ long code = stream->httpResponseCode();
+ Common::String headers = stream->responseHeaders();
+ if (code == 200) {
+ const char *cstr = headers.c_str();
+ const char *position = strstr(cstr, "Location: ");
+
+ if (position) {
+ Common::String result = "";
+ char c;
+ for (const char *i = position + 10; c = *i, c != 0; ++i) {
+ if (c == '\n' || c == '\r')
+ break;
+ result += c;
+ }
+ _uploadUrl = result;
+ uploadNextPart();
+ return;
+ }
+ }
+
+ error.httpResponseCode = code;
+ }
+ }
+
+ Common::JSONValue *json = response.value;
+ delete json;
+
+ finishError(error);
+}
+
+void GoogleDriveUploadRequest::startUploadErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void GoogleDriveUploadRequest::uploadNextPart() {
+ const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024;
+ Common::String url = _uploadUrl;
+
+ Networking::JsonCallback callback = new Common::Callback<GoogleDriveUploadRequest, Networking::JsonResponse>(this, &GoogleDriveUploadRequest::partUploadedCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::partUploadedErrorCallback);
+ Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ request->usePut();
+
+ uint32 oldPos = _contentsStream->pos();
+ if (oldPos != _serverReceivedBytes) {
+ if (!_contentsStream->seek(_serverReceivedBytes)) {
+ warning("GoogleDriveUploadRequest: cannot upload because stream couldn't seek(%lu)", _serverReceivedBytes);
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+ oldPos = _serverReceivedBytes;
+ }
+
+ byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST];
+ uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST);
+ if (size != 0)
+ request->setBuffer(buffer, size);
+
+ if (_uploadUrl != "") {
+ if (_contentsStream->pos() == 0)
+ request->addHeader(Common::String::format("Content-Length: 0"));
+ else
+ request->addHeader(Common::String::format("Content-Range: bytes %u-%u/%u", oldPos, _contentsStream->pos() - 1, _contentsStream->size()));
+ }
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+bool GoogleDriveUploadRequest::handleHttp308(const Networking::NetworkReadStream *stream) {
+ //308 Resume Incomplete, with Range: X-Y header
+ if (!stream)
+ return false;
+ if (stream->httpResponseCode() != 308)
+ return false; //seriously
+
+ Common::String headers = stream->responseHeaders();
+ const char *cstr = headers.c_str();
+ for (int rangeTry = 0; rangeTry < 2; ++rangeTry) {
+ const char *needle = (rangeTry == 0 ? "Range: 0-" : "Range: bytes=0-");
+ uint32 needleLength = (rangeTry == 0 ? 9 : 15);
+
+ const char *position = strstr(cstr, needle); //if it lost the first part, I refuse to talk with it
+
+ if (position) {
+ Common::String result = "";
+ char c;
+ for (const char *i = position + needleLength; c = *i, c != 0; ++i) {
+ if (c == '\n' || c == '\r')
+ break;
+ result += c;
+ }
+ _serverReceivedBytes = result.asUint64() + 1;
+ uploadNextPart();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void GoogleDriveUploadRequest::partUploadedCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::ErrorResponse error(this, false, true, "", -1);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq) {
+ const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
+ if (stream) {
+ long code = stream->httpResponseCode();
+ error.httpResponseCode = code;
+ if (code == 308 && handleHttp308(stream)) {
+ delete (Common::JSONValue *)response.value;
+ return;
+ }
+ }
+ }
+
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (json->isObject()) {
+ Common::JSONObject object = json->asObject();
+
+ if (object.contains("error")) {
+ warning("GoogleDrive returned error: %s", json->stringify(true).c_str());
+ error.response = json->stringify(true);
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "id", "GoogleDriveUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(object, "name", "GoogleDriveUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(object, "mimeType", "GoogleDriveUploadRequest")) {
+ //finished
+ Common::String id = object.getVal("id")->asString();
+ Common::String name = object.getVal("name")->asString();
+ bool isDirectory = (object.getVal("mimeType")->asString() == "application/vnd.google-apps.folder");
+ uint32 size = 0, timestamp = 0;
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "size", "GoogleDriveUploadRequest", true))
+ size = object.getVal("size")->asString().asUint64();
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "modifiedTime", "GoogleDriveUploadRequest", true))
+ timestamp = ISO8601::convertToTimestamp(object.getVal("modifiedTime")->asString());
+
+ finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory));
+ return;
+ }
+ }
+
+ if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) {
+ warning("GoogleDriveUploadRequest: no file info to return");
+ finishUpload(StorageFile(_savePath, 0, 0, false));
+ } else {
+ uploadNextPart();
+ }
+
+ delete json;
+}
+
+void GoogleDriveUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)error.request;
+ if (rq) {
+ const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
+ if (stream) {
+ long code = stream->httpResponseCode();
+ if (code == 308 && handleHttp308(stream)) {
+ return;
+ }
+ }
+ }
+
+ finishError(error);
+}
+
+void GoogleDriveUploadRequest::handle() {}
+
+void GoogleDriveUploadRequest::restart() { start(); }
+
+void GoogleDriveUploadRequest::finishUpload(StorageFile file) {
+ Request::finishSuccess();
+ if (_uploadCallback)
+ (*_uploadCallback)(Storage::UploadResponse(this, file));
+}
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/googledrive/googledriveuploadrequest.h b/backends/cloud/googledrive/googledriveuploadrequest.h
new file mode 100644
index 0000000000..73acab5bbd
--- /dev/null
+++ b/backends/cloud/googledrive/googledriveuploadrequest.h
@@ -0,0 +1,70 @@
+/* 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_GOOGLEDRIVEUPLOADREQUEST_H
+#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVEUPLOADREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+class GoogleDriveStorage;
+
+class GoogleDriveUploadRequest: public Networking::Request {
+ GoogleDriveStorage *_storage;
+ Common::String _savePath;
+ Common::SeekableReadStream *_contentsStream;
+ Storage::UploadCallback _uploadCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _resolvedId, _parentId;
+ Common::String _uploadUrl;
+ uint64 _serverReceivedBytes;
+
+ void start();
+ void resolveId();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveFailedCallback(Networking::ErrorResponse error);
+ void startUpload();
+ void startUploadCallback(Networking::JsonResponse response);
+ void startUploadErrorCallback(Networking::ErrorResponse error);
+ void uploadNextPart();
+ void partUploadedCallback(Networking::JsonResponse response);
+ void partUploadedErrorCallback(Networking::ErrorResponse error);
+ bool handleHttp308(const Networking::NetworkReadStream *stream);
+ void finishUpload(StorageFile status);
+
+public:
+ GoogleDriveUploadRequest(GoogleDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
+ virtual ~GoogleDriveUploadRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/idcreatedirectoryrequest.cpp b/backends/cloud/id/idcreatedirectoryrequest.cpp
new file mode 100644
index 0000000000..37f417f806
--- /dev/null
+++ b/backends/cloud/id/idcreatedirectoryrequest.cpp
@@ -0,0 +1,163 @@
+/* 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/id/idcreatedirectoryrequest.h"
+#include "backends/cloud/id/idstorage.h"
+#include "common/debug.h"
+
+namespace Cloud {
+namespace Id {
+
+IdCreateDirectoryRequest::IdCreateDirectoryRequest(IdStorage *storage, Common::String parentPath, Common::String directoryName, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb),
+ _requestedParentPath(parentPath), _requestedDirectoryName(directoryName), _storage(storage), _boolCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+IdCreateDirectoryRequest::~IdCreateDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+}
+
+void IdCreateDirectoryRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _ignoreCallback = false;
+
+ //the only exception when we create parent folder - is when it's ScummVM/ base folder
+ Common::String prefix = _requestedParentPath;
+ if (prefix.size() > 7)
+ prefix.erase(7);
+ if (prefix.equalsIgnoreCase("ScummVM")) {
+ Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, Storage::BoolResponse>(this, &IdCreateDirectoryRequest::createdBaseDirectoryCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback);
+ _workingRequest = _storage->createDirectory("ScummVM", callback, failureCallback);
+ return;
+ }
+
+ resolveId();
+}
+
+void IdCreateDirectoryRequest::createdBaseDirectoryCallback(Storage::BoolResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (response.request)
+ _date = response.request->date();
+ resolveId();
+}
+
+void IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void IdCreateDirectoryRequest::resolveId() {
+ //check whether such folder already exists
+ Storage::UploadCallback innerCallback = new Common::Callback<IdCreateDirectoryRequest, Storage::UploadResponse>(this, &IdCreateDirectoryRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::idResolveFailedCallback);
+ Common::String path = _requestedParentPath;
+ if (_requestedParentPath != "")
+ path += "/";
+ path += _requestedDirectoryName;
+ _workingRequest = _storage->resolveFileId(path, innerCallback, innerErrorCallback);
+}
+
+void IdCreateDirectoryRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (response.request)
+ _date = response.request->date();
+
+ //resolved => folder already exists
+ finishCreation(false);
+}
+
+void IdCreateDirectoryRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+
+ //not resolved => folder not exists
+ if (error.response.contains("no such file found in its parent directory")) {
+ //parent's id after the '\n'
+ Common::String parentId = error.response;
+ for (uint32 i = 0; i < parentId.size(); ++i)
+ if (parentId[i] == '\n') {
+ parentId.erase(0, i + 1);
+ break;
+ }
+
+ Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, Storage::BoolResponse>(this, &IdCreateDirectoryRequest::createdDirectoryCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::createdDirectoryErrorCallback);
+ _workingRequest = _storage->createDirectoryWithParentId(parentId, _requestedDirectoryName, callback, failureCallback);
+ return;
+ }
+
+ finishError(error);
+}
+
+void IdCreateDirectoryRequest::createdDirectoryCallback(Storage::BoolResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (response.request)
+ _date = response.request->date();
+ finishCreation(response.value);
+}
+
+void IdCreateDirectoryRequest::createdDirectoryErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void IdCreateDirectoryRequest::handle() {}
+
+void IdCreateDirectoryRequest::restart() { start(); }
+
+Common::String IdCreateDirectoryRequest::date() const { return _date; }
+
+void IdCreateDirectoryRequest::finishCreation(bool success) {
+ Request::finishSuccess();
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/idcreatedirectoryrequest.h b/backends/cloud/id/idcreatedirectoryrequest.h
new file mode 100644
index 0000000000..241bcd30be
--- /dev/null
+++ b/backends/cloud/id/idcreatedirectoryrequest.h
@@ -0,0 +1,65 @@
+/* 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_ID_IDCREATEDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_ID_IDCREATEDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage;
+
+class IdCreateDirectoryRequest: public Networking::Request {
+ Common::String _requestedParentPath;
+ Common::String _requestedDirectoryName;
+ IdStorage *_storage;
+ Storage::BoolCallback _boolCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void createdBaseDirectoryCallback(Storage::BoolResponse response);
+ void createdBaseDirectoryErrorCallback(Networking::ErrorResponse error);
+ void resolveId();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveFailedCallback(Networking::ErrorResponse error);
+ void createdDirectoryCallback(Storage::BoolResponse response);
+ void createdDirectoryErrorCallback(Networking::ErrorResponse error);
+ void finishCreation(bool success);
+public:
+ IdCreateDirectoryRequest(IdStorage *storage, Common::String parentPath, Common::String directoryName, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
+ virtual ~IdCreateDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/iddownloadrequest.cpp b/backends/cloud/id/iddownloadrequest.cpp
new file mode 100644
index 0000000000..2532d611b8
--- /dev/null
+++ b/backends/cloud/id/iddownloadrequest.cpp
@@ -0,0 +1,108 @@
+/* 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/id/iddownloadrequest.h"
+#include "backends/cloud/id/idstorage.h"
+#include "backends/cloud/downloadrequest.h"
+
+namespace Cloud {
+namespace Id {
+
+IdDownloadRequest::IdDownloadRequest(IdStorage *storage, Common::String remotePath, Common::String localPath, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _requestedFile(remotePath), _requestedLocalFile(localPath), _storage(storage), _boolCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+IdDownloadRequest::~IdDownloadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+}
+
+void IdDownloadRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _ignoreCallback = false;
+
+ //find file's id
+ Storage::UploadCallback innerCallback = new Common::Callback<IdDownloadRequest, Storage::UploadResponse>(this, &IdDownloadRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdDownloadRequest, Networking::ErrorResponse>(this, &IdDownloadRequest::idResolveFailedCallback);
+ _workingRequest = _storage->resolveFileId(_requestedFile, innerCallback, innerErrorCallback);
+}
+
+void IdDownloadRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Storage::BoolCallback innerCallback = new Common::Callback<IdDownloadRequest, Storage::BoolResponse>(this, &IdDownloadRequest::downloadCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdDownloadRequest, Networking::ErrorResponse>(this, &IdDownloadRequest::downloadErrorCallback);
+ _workingRequest = _storage->downloadById(response.value.id(), _requestedLocalFile, innerCallback, innerErrorCallback);
+}
+
+void IdDownloadRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void IdDownloadRequest::downloadCallback(Storage::BoolResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishDownload(response.value);
+}
+
+void IdDownloadRequest::downloadErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void IdDownloadRequest::handle() {}
+
+void IdDownloadRequest::restart() { start(); }
+
+void IdDownloadRequest::finishDownload(bool success) {
+ Request::finishSuccess();
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+double IdDownloadRequest::getProgress() const {
+ DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest);
+ if (downloadRequest == nullptr)
+ return 0; // resolving id still
+
+ // id resolve is 10 % and download is the other 90 %
+ return 0.1 + 0.9 * downloadRequest->getProgress(); // downloading
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/iddownloadrequest.h b/backends/cloud/id/iddownloadrequest.h
new file mode 100644
index 0000000000..65e05c00b3
--- /dev/null
+++ b/backends/cloud/id/iddownloadrequest.h
@@ -0,0 +1,62 @@
+/* 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_ID_IDDOWNLOADREQUEST_H
+#define BACKENDS_CLOUD_ID_IDDOWNLOADREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage;
+
+class IdDownloadRequest: public Networking::Request {
+ Common::String _requestedFile, _requestedLocalFile;
+ IdStorage *_storage;
+ Storage::BoolCallback _boolCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+
+ void start();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveFailedCallback(Networking::ErrorResponse error);
+ void downloadCallback(Storage::BoolResponse response);
+ void downloadErrorCallback(Networking::ErrorResponse error);
+ void finishDownload(bool success);
+public:
+ IdDownloadRequest(IdStorage *storage, Common::String remotePath, Common::String localPath, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
+ virtual ~IdDownloadRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/idlistdirectoryrequest.cpp b/backends/cloud/id/idlistdirectoryrequest.cpp
new file mode 100644
index 0000000000..4e63709984
--- /dev/null
+++ b/backends/cloud/id/idlistdirectoryrequest.cpp
@@ -0,0 +1,141 @@
+/* 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/id/idlistdirectoryrequest.h"
+#include "backends/cloud/id/idstorage.h"
+
+namespace Cloud {
+namespace Id {
+
+IdListDirectoryRequest::IdListDirectoryRequest(IdStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive):
+ Networking::Request(nullptr, ecb),
+ _requestedPath(path), _requestedRecursive(recursive), _storage(storage), _listDirectoryCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+IdListDirectoryRequest::~IdListDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _listDirectoryCallback;
+}
+
+void IdListDirectoryRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _files.clear();
+ _directoriesQueue.clear();
+ _currentDirectory = StorageFile();
+ _ignoreCallback = false;
+
+ //find out that directory's id
+ Storage::UploadCallback innerCallback = new Common::Callback<IdListDirectoryRequest, Storage::UploadResponse>(this, &IdListDirectoryRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdListDirectoryRequest, Networking::ErrorResponse>(this, &IdListDirectoryRequest::idResolveErrorCallback);
+ _workingRequest = _storage->resolveFileId(_requestedPath, innerCallback, innerErrorCallback);
+}
+
+void IdListDirectoryRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (response.request)
+ _date = response.request->date();
+
+ StorageFile directory = response.value;
+ directory.setPath(_requestedPath);
+ _directoriesQueue.push_back(directory);
+ listNextDirectory();
+}
+
+void IdListDirectoryRequest::idResolveErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void IdListDirectoryRequest::listNextDirectory() {
+ if (_directoriesQueue.empty()) {
+ finishListing(_files);
+ return;
+ }
+
+ _currentDirectory = _directoriesQueue.back();
+ _directoriesQueue.pop_back();
+
+ Storage::FileArrayCallback callback = new Common::Callback<IdListDirectoryRequest, Storage::FileArrayResponse>(this, &IdListDirectoryRequest::listedDirectoryCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<IdListDirectoryRequest, Networking::ErrorResponse>(this, &IdListDirectoryRequest::listedDirectoryErrorCallback);
+ _workingRequest = _storage->listDirectoryById(_currentDirectory.id(), callback, failureCallback);
+}
+
+void IdListDirectoryRequest::listedDirectoryCallback(Storage::FileArrayResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (response.request)
+ _date = response.request->date();
+
+ for (uint32 i = 0; i < response.value.size(); ++i) {
+ StorageFile &file = response.value[i];
+ Common::String path = _currentDirectory.path();
+ if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\')
+ path += '/';
+ path += file.name();
+ file.setPath(path);
+ _files.push_back(file);
+ if (_requestedRecursive && file.isDirectory()) {
+ _directoriesQueue.push_back(file);
+ }
+ }
+
+ listNextDirectory();
+}
+
+void IdListDirectoryRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void IdListDirectoryRequest::handle() {}
+
+void IdListDirectoryRequest::restart() { start(); }
+
+Common::String IdListDirectoryRequest::date() const { return _date; }
+
+void IdListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_listDirectoryCallback)
+ (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/idlistdirectoryrequest.h b/backends/cloud/id/idlistdirectoryrequest.h
new file mode 100644
index 0000000000..58c5d2c864
--- /dev/null
+++ b/backends/cloud/id/idlistdirectoryrequest.h
@@ -0,0 +1,66 @@
+/* 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_ID_IDLISTDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_ID_IDLISTDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage;
+
+class IdListDirectoryRequest: public Networking::Request {
+ Common::String _requestedPath;
+ bool _requestedRecursive;
+ IdStorage *_storage;
+ Storage::ListDirectoryCallback _listDirectoryCallback;
+ Common::Array<StorageFile> _files;
+ Common::Array<StorageFile> _directoriesQueue;
+ StorageFile _currentDirectory;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveErrorCallback(Networking::ErrorResponse error);
+ void listNextDirectory();
+ void listedDirectoryCallback(Storage::FileArrayResponse response);
+ void listedDirectoryErrorCallback(Networking::ErrorResponse error);
+ void finishListing(Common::Array<StorageFile> &files);
+public:
+ IdListDirectoryRequest(IdStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false);
+ virtual ~IdListDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/idresolveidrequest.cpp b/backends/cloud/id/idresolveidrequest.cpp
new file mode 100644
index 0000000000..e8589fc204
--- /dev/null
+++ b/backends/cloud/id/idresolveidrequest.cpp
@@ -0,0 +1,136 @@
+/* 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/id/idresolveidrequest.h"
+#include "backends/cloud/id/idstorage.h"
+
+namespace Cloud {
+namespace Id {
+
+IdResolveIdRequest::IdResolveIdRequest(IdStorage *storage, Common::String path, Storage::UploadCallback cb, Networking::ErrorCallback ecb, bool recursive):
+ Networking::Request(nullptr, ecb),
+ _requestedPath(path), _storage(storage), _uploadCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+IdResolveIdRequest::~IdResolveIdRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _uploadCallback;
+}
+
+void IdResolveIdRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _currentDirectory = "";
+ _currentDirectoryId = _storage->getRootDirectoryId();
+ _ignoreCallback = false;
+
+ listNextDirectory(StorageFile(_currentDirectoryId, 0, 0, true));
+}
+
+void IdResolveIdRequest::listNextDirectory(StorageFile fileToReturn) {
+ if (_currentDirectory.equalsIgnoreCase(_requestedPath)) {
+ finishFile(fileToReturn);
+ return;
+ }
+
+ Storage::FileArrayCallback callback = new Common::Callback<IdResolveIdRequest, Storage::FileArrayResponse>(this, &IdResolveIdRequest::listedDirectoryCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<IdResolveIdRequest, Networking::ErrorResponse>(this, &IdResolveIdRequest::listedDirectoryErrorCallback);
+ _workingRequest = _storage->listDirectoryById(_currentDirectoryId, callback, failureCallback);
+}
+
+void IdResolveIdRequest::listedDirectoryCallback(Storage::FileArrayResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Common::String currentLevelName = _requestedPath;
+ ///debug(9, "'%s'", currentLevelName.c_str());
+ if (_currentDirectory.size())
+ currentLevelName.erase(0, _currentDirectory.size());
+ if (currentLevelName.size() && (currentLevelName[0] == '/' || currentLevelName[0] == '\\'))
+ currentLevelName.erase(0, 1);
+ ///debug(9, "'%s'", currentLevelName.c_str());
+ for (uint32 i = 0; i < currentLevelName.size(); ++i) {
+ if (currentLevelName[i] == '/' || currentLevelName[i] == '\\') {
+ currentLevelName.erase(i);
+ ///debug(9, "'%s'", currentLevelName.c_str());
+ break;
+ }
+ }
+
+ Common::String path = _currentDirectory;
+ if (path != "")
+ path += "/";
+ path += currentLevelName;
+ bool lastLevel = (path.equalsIgnoreCase(_requestedPath));
+
+ ///debug(9, "IdResolveIdRequest: searching for '%s' in '%s'", currentLevelName.c_str(), _currentDirectory.c_str());
+
+ Common::Array<StorageFile> &files = response.value;
+ bool found = false;
+ for (uint32 i = 0; i < files.size(); ++i) {
+ if ((files[i].isDirectory() || lastLevel) && files[i].name().equalsIgnoreCase(currentLevelName)) {
+ if (_currentDirectory != "")
+ _currentDirectory += "/";
+ _currentDirectory += files[i].name();
+ _currentDirectoryId = files[i].id();
+ ///debug(9, "IdResolveIdRequest: found it! new directory and its id: '%s', '%s'", _currentDirectory.c_str(), _currentDirectoryId.c_str());
+ listNextDirectory(files[i]);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (lastLevel)
+ finishError(Networking::ErrorResponse(this, false, true, Common::String("no such file found in its parent directory\n") + _currentDirectoryId, 404));
+ else
+ finishError(Networking::ErrorResponse(this, false, true, "subdirectory not found", 400));
+ }
+}
+
+void IdResolveIdRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void IdResolveIdRequest::handle() {}
+
+void IdResolveIdRequest::restart() { start(); }
+
+void IdResolveIdRequest::finishFile(StorageFile file) {
+ Request::finishSuccess();
+ if (_uploadCallback)
+ (*_uploadCallback)(Storage::UploadResponse(this, file));
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/idresolveidrequest.h b/backends/cloud/id/idresolveidrequest.h
new file mode 100644
index 0000000000..e735e96385
--- /dev/null
+++ b/backends/cloud/id/idresolveidrequest.h
@@ -0,0 +1,60 @@
+/* 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_ID_IDRESOLVEIDREQUEST_H
+#define BACKENDS_CLOUD_ID_IDRESOLVEIDREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage;
+
+class IdResolveIdRequest: public Networking::Request {
+ Common::String _requestedPath;
+ IdStorage *_storage;
+ Storage::UploadCallback _uploadCallback;
+ Common::String _currentDirectory;
+ Common::String _currentDirectoryId;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+
+ void start();
+ void listNextDirectory(StorageFile fileToReturn);
+ void listedDirectoryCallback(Storage::FileArrayResponse response);
+ void listedDirectoryErrorCallback(Networking::ErrorResponse error);
+ void finishFile(StorageFile file);
+public:
+ IdResolveIdRequest(IdStorage *storage, Common::String path, Storage::UploadCallback cb, Networking::ErrorCallback ecb, bool recursive = false); //TODO: why upload?
+ virtual ~IdResolveIdRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/idstorage.cpp b/backends/cloud/id/idstorage.cpp
new file mode 100644
index 0000000000..0ffa8c068c
--- /dev/null
+++ b/backends/cloud/id/idstorage.cpp
@@ -0,0 +1,109 @@
+/* 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/id/idstorage.h"
+#include "backends/cloud/id/idcreatedirectoryrequest.h"
+#include "backends/cloud/id/iddownloadrequest.h"
+#include "backends/cloud/id/idlistdirectoryrequest.h"
+#include "backends/cloud/id/idresolveidrequest.h"
+#include "backends/cloud/id/idstreamfilerequest.h"
+#include "common/debug.h"
+
+namespace Cloud {
+namespace Id {
+
+IdStorage::~IdStorage() {}
+
+void IdStorage::printFiles(FileArrayResponse response) {
+ debug(9, "IdStorage: files:");
+ Common::Array<StorageFile> &files = response.value;
+ for (uint32 i = 0; i < files.size(); ++i) {
+ debug(9, "\t%s%s", files[i].name().c_str(), files[i].isDirectory() ? " (directory)" : "");
+ debug(9, "\t%s", files[i].path().c_str());
+ debug(9, "\t%s", files[i].id().c_str());
+ debug(9, " ");
+ }
+}
+
+void IdStorage::printBool(BoolResponse response) {
+ debug(9, "IdStorage: bool: %s", response.value ? "true" : "false");
+}
+
+void IdStorage::printFile(UploadResponse response) {
+ debug(9, "\nIdStorage: uploaded file info:");
+ debug(9, "\tid: %s", response.value.path().c_str());
+ debug(9, "\tname: %s", response.value.name().c_str());
+ debug(9, "\tsize: %u", response.value.size());
+ debug(9, "\ttimestamp: %u", response.value.timestamp());
+}
+
+Storage::ListDirectoryCallback IdStorage::getPrintFilesCallback() {
+ return new Common::Callback<IdStorage, FileArrayResponse>(this, &IdStorage::printFiles);
+}
+
+Networking::Request *IdStorage::resolveFileId(Common::String path, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ if (!callback)
+ callback = new Common::Callback<IdStorage, UploadResponse>(this, &IdStorage::printFile);
+ return addRequest(new IdResolveIdRequest(this, path, callback, errorCallback));
+}
+
+Networking::Request *IdStorage::listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ if (!callback)
+ callback = new Common::Callback<IdStorage, FileArrayResponse>(this, &IdStorage::printFiles);
+ return addRequest(new IdListDirectoryRequest(this, path, callback, errorCallback, recursive));
+}
+
+Networking::Request *IdStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ if (!callback)
+ callback = new Common::Callback<IdStorage, BoolResponse>(this, &IdStorage::printBool);
+
+ //find out the parent path and directory name
+ Common::String parentPath = "", directoryName = path;
+ for (uint32 i = path.size(); i > 0; --i) {
+ if (path[i - 1] == '/' || path[i - 1] == '\\') {
+ parentPath = path;
+ parentPath.erase(i - 1);
+ directoryName.erase(0, i);
+ break;
+ }
+ }
+
+ return addRequest(new IdCreateDirectoryRequest(this, parentPath, directoryName, callback, errorCallback));
+}
+
+Networking::Request *IdStorage::streamFile(Common::String path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) {
+ return addRequest(new IdStreamFileRequest(this, path, outerCallback, errorCallback));
+}
+
+Networking::Request *IdStorage::download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ return addRequest(new IdDownloadRequest(this, remotePath, localPath, callback, errorCallback));
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/idstorage.h b/backends/cloud/id/idstorage.h
new file mode 100644
index 0000000000..7e64fd4a37
--- /dev/null
+++ b/backends/cloud/id/idstorage.h
@@ -0,0 +1,83 @@
+/* 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_ID_IDSTORAGE_H
+#define BACKENDS_CLOUD_ID_IDSTORAGE_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+/*
+ * Id::IdStorage is a special base class, which is created
+ * to simplify adding new storages which use ids instead of
+ * paths in their API.
+ *
+ * Some Requests are already implemented, and Storage based
+ * on IdStorage needs to override/implement a few basic things.
+ *
+ * For example, ListDirectoryRequest and ResolveIdRequests are
+ * based on listDirectoryById() and getRootDirectoryId() methods.
+ * Implementing these you'll get id resolving and directory
+ * listing by path.
+ */
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage: public Cloud::Storage {
+protected:
+ void printFiles(FileArrayResponse response);
+ void printBool(BoolResponse response);
+ void printFile(UploadResponse response);
+
+ ListDirectoryCallback getPrintFilesCallback();
+
+public:
+ virtual ~IdStorage();
+
+ /** Public Cloud API comes down there. */
+
+ /** Returns StorageFile with the resolved file's id. */
+ virtual Networking::Request *resolveFileId(Common::String path, UploadCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns ListDirectoryStatus struct with list of files. */
+ virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
+ virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /** Returns pointer to Networking::NetworkReadStream. */
+ virtual Networking::Request *streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ virtual Common::String getRootDirectoryId() = 0;
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/idstreamfilerequest.cpp b/backends/cloud/id/idstreamfilerequest.cpp
new file mode 100644
index 0000000000..2e68b15412
--- /dev/null
+++ b/backends/cloud/id/idstreamfilerequest.cpp
@@ -0,0 +1,98 @@
+/* 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/id/idstreamfilerequest.h"
+#include "backends/cloud/id/idstorage.h"
+
+namespace Cloud {
+namespace Id {
+
+IdStreamFileRequest::IdStreamFileRequest(IdStorage *storage, Common::String path, Networking::NetworkReadStreamCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _requestedFile(path), _storage(storage), _streamCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+IdStreamFileRequest::~IdStreamFileRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _streamCallback;
+}
+
+void IdStreamFileRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _ignoreCallback = false;
+
+ //find file's id
+ Storage::UploadCallback innerCallback = new Common::Callback<IdStreamFileRequest, Storage::UploadResponse>(this, &IdStreamFileRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdStreamFileRequest, Networking::ErrorResponse>(this, &IdStreamFileRequest::idResolveFailedCallback);
+ _workingRequest = _storage->resolveFileId(_requestedFile, innerCallback, innerErrorCallback);
+}
+
+void IdStreamFileRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::NetworkReadStreamCallback innerCallback = new Common::Callback<IdStreamFileRequest, Networking::NetworkReadStreamResponse>(this, &IdStreamFileRequest::streamFileCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdStreamFileRequest, Networking::ErrorResponse>(this, &IdStreamFileRequest::streamFileErrorCallback);
+ _workingRequest = _storage->streamFileById(response.value.id(), innerCallback, innerErrorCallback);
+}
+
+void IdStreamFileRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void IdStreamFileRequest::streamFileCallback(Networking::NetworkReadStreamResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishStream(response.value);
+}
+
+void IdStreamFileRequest::streamFileErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void IdStreamFileRequest::handle() {}
+
+void IdStreamFileRequest::restart() { start(); }
+
+void IdStreamFileRequest::finishStream(Networking::NetworkReadStream *stream) {
+ Request::finishSuccess();
+ if (_streamCallback)
+ (*_streamCallback)(Networking::NetworkReadStreamResponse(this, stream));
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/idstreamfilerequest.h b/backends/cloud/id/idstreamfilerequest.h
new file mode 100644
index 0000000000..20d7c0c25b
--- /dev/null
+++ b/backends/cloud/id/idstreamfilerequest.h
@@ -0,0 +1,59 @@
+/* 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_ID_IDSTREAMFILEREQUEST_H
+#define BACKENDS_CLOUD_ID_IDSTREAMFILEREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage;
+
+class IdStreamFileRequest: public Networking::Request {
+ Common::String _requestedFile;
+ IdStorage *_storage;
+ Networking::NetworkReadStreamCallback _streamCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+
+ void start();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveFailedCallback(Networking::ErrorResponse error);
+ void streamFileCallback(Networking::NetworkReadStreamResponse response);
+ void streamFileErrorCallback(Networking::ErrorResponse error);
+ void finishStream(Networking::NetworkReadStream *stream);
+public:
+ IdStreamFileRequest(IdStorage *storage, Common::String path, Networking::NetworkReadStreamCallback cb, Networking::ErrorCallback ecb);
+ virtual ~IdStreamFileRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/iso8601.cpp b/backends/cloud/iso8601.cpp
new file mode 100644
index 0000000000..177ef67f11
--- /dev/null
+++ b/backends/cloud/iso8601.cpp
@@ -0,0 +1,100 @@
+/* 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/iso8601.h"
+#include "common/str.h"
+
+namespace {
+
+Common::String getSubstring(const Common::String &s, uint32 beginning, uint32 ending) {
+ //beginning inclusive, ending exclusive
+ Common::String result = s;
+ result.erase(ending);
+ result.erase(0, beginning);
+ return result;
+}
+
+int find(const char *cstr, uint32 startPosition, char needle) {
+ const char *res = strchr(cstr + startPosition, needle);
+ if (res == nullptr)
+ return -1;
+ return res - cstr;
+}
+
+}
+
+namespace Cloud {
+namespace ISO8601 {
+
+uint32 convertToTimestamp(const Common::String &iso8601Date) {
+ //2015-05-12T15:50:38Z
+ const char *cstr = iso8601Date.c_str();
+ int firstHyphen = find(cstr, 0, '-');
+ int secondHyphen = find(cstr, firstHyphen + 1, '-');
+ int tSeparator = find(cstr, secondHyphen + 1, 'T');
+ int firstColon = find(cstr, tSeparator + 1, ':');
+ int secondColon = find(cstr, firstColon + 1, ':');
+ int zSeparator = find(cstr, secondColon + 1, 'Z');
+ if (zSeparator == -1)
+ zSeparator = find(cstr, secondColon + 1, '-'); // Box's RFC 3339
+ //now note '+1' which means if there ever was '-1' result of find(), we still did a valid find() from 0th char
+
+ Common::String year = getSubstring(iso8601Date, 0, firstHyphen);
+ Common::String month = getSubstring(iso8601Date, firstHyphen + 1, secondHyphen);
+ Common::String day = getSubstring(iso8601Date, secondHyphen + 1, tSeparator);
+ Common::String hour = getSubstring(iso8601Date, tSeparator + 1, firstColon);
+ Common::String minute = getSubstring(iso8601Date, firstColon + 1, secondColon);
+ Common::String second = getSubstring(iso8601Date, secondColon + 1, zSeparator);
+ //now note only 'ending' argument was not '+1' (which means I could've make that function such that -1 means 'until the end')
+
+ int Y = atoi(year.c_str());
+ int M = atoi(month.c_str());
+ int D = atoi(day.c_str());
+ int h = atoi(hour.c_str());
+ int m = atoi(minute.c_str());
+ int s = atoi(second.c_str());
+
+ //ok, now I compose a timestamp based on my basic perception of time/date
+ //yeah, I know about leap years and leap seconds and all, but still we don't care there
+
+ uint32 days = D - 1;
+ for (int i = 1970; i < Y; ++i)
+ if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0))
+ days += 366;
+ else
+ days += 365;
+
+ int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ for (int i = 1; i < M; ++i) {
+ days += mdays[i - 1];
+ if (i == 2)
+ if ((Y % 4 == 0 && Y % 100 != 0) || (Y % 400 == 0))
+ days += 1;
+ }
+
+ uint32 hours = days * 24 + h;
+ uint32 minutes = hours * 60 + m;
+ return minutes * 60 + s;
+}
+
+} // End of namespace ISO8601
+} // End of namespace Cloud
diff --git a/backends/cloud/iso8601.h b/backends/cloud/iso8601.h
new file mode 100644
index 0000000000..cdd817bc07
--- /dev/null
+++ b/backends/cloud/iso8601.h
@@ -0,0 +1,37 @@
+/* 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_ISO8601_H
+#define BACKENDS_CLOUD_ISO8601_H
+
+#include "common/str.h"
+
+namespace Cloud {
+namespace ISO8601 {
+
+/** Returns timestamp corresponding to given ISO 8601 date */
+uint32 convertToTimestamp(const Common::String &iso8601Date);
+
+} // End of namespace ISO8601
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp
new file mode 100644
index 0000000000..fc7e4f58b0
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp
@@ -0,0 +1,150 @@
+/* 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/onedrive/onedrivecreatedirectoryrequest.h"
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+#define ONEDRIVE_API_SPECIAL_APPROOT "https://api.onedrive.com/v1.0/drive/special/approot"
+
+OneDriveCreateDirectoryRequest::OneDriveCreateDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _storage(storage), _path(path), _boolCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+OneDriveCreateDirectoryRequest::~OneDriveCreateDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+}
+
+void OneDriveCreateDirectoryRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _ignoreCallback = false;
+
+ Common::String name = _path, parent = _path;
+ if (name.size() != 0) {
+ uint32 i = name.size() - 1;
+ while (true) {
+ parent.deleteLastChar();
+ if (name[i] == '/' || name[i] == '\\') {
+ name.erase(0, i + 1);
+ break;
+ }
+ if (i == 0)
+ break;
+ --i;
+ }
+ }
+
+ Common::String url = ONEDRIVE_API_SPECIAL_APPROOT;
+ if (parent != "")
+ url += ":/" + ConnMan.urlEncode(parent) + ":";
+ url += "/children";
+ Networking::JsonCallback innerCallback = new Common::Callback<OneDriveCreateDirectoryRequest, Networking::JsonResponse>(this, &OneDriveCreateDirectoryRequest::responseCallback);
+ Networking::ErrorCallback errorCallback = new Common::Callback<OneDriveCreateDirectoryRequest, Networking::ErrorResponse>(this, &OneDriveCreateDirectoryRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, innerCallback, errorCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("name", new Common::JSONValue(name));
+ jsonRequestParameters.setVal("folder", new Common::JSONValue(Common::JSONObject()));
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void OneDriveCreateDirectoryRequest::responseCallback(Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete json;
+ return;
+ }
+ if (response.request)
+ _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject info = json->asObject();
+ if (info.contains("id")) {
+ finishCreation(true);
+ } else {
+ error.response = json->stringify(true);
+ finishError(error);
+ }
+
+ delete json;
+}
+
+void OneDriveCreateDirectoryRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void OneDriveCreateDirectoryRequest::handle() {}
+
+void OneDriveCreateDirectoryRequest::restart() { start(); }
+
+Common::String OneDriveCreateDirectoryRequest::date() const { return _date; }
+
+void OneDriveCreateDirectoryRequest::finishCreation(bool success) {
+ Request::finishSuccess();
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h
new file mode 100644
index 0000000000..acaca2bf00
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h
@@ -0,0 +1,59 @@
+/* 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_ONEDRIVE_ONEDRIVECREATEDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVECREATEDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+class OneDriveStorage;
+
+class OneDriveCreateDirectoryRequest: public Networking::Request {
+ OneDriveStorage *_storage;
+ Common::String _path;
+ Storage::BoolCallback _boolCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void responseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishCreation(bool success);
+public:
+ OneDriveCreateDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
+ virtual ~OneDriveCreateDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp b/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp
new file mode 100644
index 0000000000..a247a9f234
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp
@@ -0,0 +1,193 @@
+/* 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/onedrive/onedrivelistdirectoryrequest.h"
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+#define ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/children"
+
+OneDriveListDirectoryRequest::OneDriveListDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive):
+ Networking::Request(nullptr, ecb),
+ _requestedPath(path), _requestedRecursive(recursive), _storage(storage), _listDirectoryCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+OneDriveListDirectoryRequest::~OneDriveListDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _listDirectoryCallback;
+}
+
+void OneDriveListDirectoryRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _files.clear();
+ _directoriesQueue.clear();
+ _currentDirectory = "";
+ _ignoreCallback = false;
+
+ _directoriesQueue.push_back(_requestedPath);
+ listNextDirectory();
+}
+
+void OneDriveListDirectoryRequest::listNextDirectory() {
+ if (_directoriesQueue.empty()) {
+ finishListing(_files);
+ return;
+ }
+
+ _currentDirectory = _directoriesQueue.back();
+ _directoriesQueue.pop_back();
+
+ if (_currentDirectory != "" && _currentDirectory.lastChar() != '/' && _currentDirectory.lastChar() != '\\')
+ _currentDirectory += '/';
+
+ Common::String dir = _currentDirectory;
+ dir.deleteLastChar();
+ Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN, ConnMan.urlEncode(dir).c_str());
+ makeRequest(url);
+}
+
+void OneDriveListDirectoryRequest::makeRequest(Common::String url) {
+ Networking::JsonCallback callback = new Common::Callback<OneDriveListDirectoryRequest, Networking::JsonResponse>(this, &OneDriveListDirectoryRequest::listedDirectoryCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveListDirectoryRequest, Networking::ErrorResponse>(this, &OneDriveListDirectoryRequest::listedDirectoryErrorCallback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void OneDriveListDirectoryRequest::listedDirectoryCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ Common::JSONValue *json = response.value;
+
+ if (_ignoreCallback) {
+ delete json;
+ return;
+ }
+
+ if (response.request)
+ _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject object = json->asObject();
+
+ //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
+ if (!Networking::CurlJsonRequest::jsonContainsArray(object, "value", "OneDriveListDirectoryRequest")) {
+ error.response = "\"value\" not found or that's not an array!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONArray items = object.getVal("value")->asArray();
+ for (uint32 i = 0; i < items.size(); ++i) {
+ if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "OneDriveListDirectoryRequest")) continue;
+
+ Common::JSONObject item = items[i]->asObject();
+
+ if (!Networking::CurlJsonRequest::jsonContainsAttribute(item, "folder", "OneDriveListDirectoryRequest", true)) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "name", "OneDriveListDirectoryRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(item, "size", "OneDriveListDirectoryRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "lastModifiedDateTime", "OneDriveListDirectoryRequest")) continue;
+
+ Common::String path = _currentDirectory + item.getVal("name")->asString();
+ bool isDirectory = item.contains("folder");
+ uint32 size = item.getVal("size")->asIntegerNumber();
+ uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("lastModifiedDateTime")->asString());
+
+ StorageFile file(path, size, timestamp, isDirectory);
+ _files.push_back(file);
+ if (_requestedRecursive && file.isDirectory()) {
+ _directoriesQueue.push_back(file.path());
+ }
+ }
+
+ bool hasMore = object.contains("@odata.nextLink");
+ if (hasMore) {
+ if (!Networking::CurlJsonRequest::jsonContainsString(object, "@odata.nextLink", "OneDriveListDirectoryRequest")) {
+ error.response = "\"@odata.nextLink\" is not a string!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ makeRequest(object.getVal("@odata.nextLink")->asString());
+ } else {
+ listNextDirectory();
+ }
+
+ delete json;
+}
+
+void OneDriveListDirectoryRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void OneDriveListDirectoryRequest::handle() {}
+
+void OneDriveListDirectoryRequest::restart() { start(); }
+
+Common::String OneDriveListDirectoryRequest::date() const { return _date; }
+
+void OneDriveListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_listDirectoryCallback)
+ (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
+}
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/onedrive/onedrivelistdirectoryrequest.h b/backends/cloud/onedrive/onedrivelistdirectoryrequest.h
new file mode 100644
index 0000000000..eb510ab257
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivelistdirectoryrequest.h
@@ -0,0 +1,66 @@
+/* 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_ONEDRIVE_ONEDRIVELISTDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVELISTDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+class OneDriveStorage;
+
+class OneDriveListDirectoryRequest: public Networking::Request {
+ Common::String _requestedPath;
+ bool _requestedRecursive;
+ OneDriveStorage *_storage;
+ Storage::ListDirectoryCallback _listDirectoryCallback;
+ Common::Array<StorageFile> _files;
+ Common::Array<Common::String> _directoriesQueue;
+ Common::String _currentDirectory;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void listNextDirectory();
+ void listedDirectoryCallback(Networking::JsonResponse response);
+ void listedDirectoryErrorCallback(Networking::ErrorResponse error);
+ void makeRequest(Common::String url);
+ void finishListing(Common::Array<StorageFile> &files);
+public:
+ OneDriveListDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false);
+ virtual ~OneDriveListDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/onedrive/onedrivestorage.cpp b/backends/cloud/onedrive/onedrivestorage.cpp
new file mode 100644
index 0000000000..4b70bb73b9
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivestorage.cpp
@@ -0,0 +1,326 @@
+/* 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/onedrive/onedrivestorage.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/cloud/onedrive/onedrivecreatedirectoryrequest.h"
+#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
+#include "backends/cloud/onedrive/onedrivelistdirectoryrequest.h"
+#include "backends/cloud/onedrive/onedriveuploadrequest.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>
+
+#ifdef ENABLE_RELEASE
+#include "dists/clouds/cloud_keys.h"
+#endif
+
+namespace Cloud {
+namespace OneDrive {
+
+#define ONEDRIVE_OAUTH2_TOKEN "https://login.live.com/oauth20_token.srf"
+#define ONEDRIVE_API_SPECIAL_APPROOT_ID "https://api.onedrive.com/v1.0/drive/special/approot:/"
+#define ONEDRIVE_API_SPECIAL_APPROOT "https://api.onedrive.com/v1.0/drive/special/approot"
+
+char *OneDriveStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth
+char *OneDriveStorage::SECRET = nullptr;
+
+void OneDriveStorage::loadKeyAndSecret() {
+#ifdef ENABLE_RELEASE
+ KEY = RELEASE_ONEDRIVE_KEY;
+ SECRET = RELEASE_ONEDRIVE_SECRET;
+#else
+ Common::String k = ConfMan.get("ONEDRIVE_KEY", ConfMan.kCloudDomain);
+ KEY = new char[k.size() + 1];
+ memcpy(KEY, k.c_str(), k.size());
+ KEY[k.size()] = 0;
+
+ k = ConfMan.get("ONEDRIVE_SECRET", ConfMan.kCloudDomain);
+ SECRET = new char[k.size() + 1];
+ memcpy(SECRET, k.c_str(), k.size());
+ SECRET[k.size()] = 0;
+#endif
+}
+
+OneDriveStorage::OneDriveStorage(Common::String accessToken, Common::String userId, Common::String refreshToken):
+ _token(accessToken), _uid(userId), _refreshToken(refreshToken) {}
+
+OneDriveStorage::OneDriveStorage(Common::String code) {
+ getAccessToken(
+ new Common::Callback<OneDriveStorage, BoolResponse>(this, &OneDriveStorage::codeFlowComplete),
+ new Common::Callback<OneDriveStorage, Networking::ErrorResponse>(this, &OneDriveStorage::codeFlowFailed),
+ code
+ );
+}
+
+OneDriveStorage::~OneDriveStorage() {}
+
+void OneDriveStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) {
+ if (!KEY || !SECRET)
+ loadKeyAndSecret();
+ bool codeFlow = (code != "");
+
+ if (!codeFlow && _refreshToken == "") {
+ warning("OneDriveStorage: no refresh token available to get new access token.");
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ return;
+ }
+
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, BoolResponse, Networking::JsonResponse>(this, &OneDriveStorage::tokenRefreshed, callback);
+ if (errorCallback == nullptr)
+ errorCallback = getErrorPrintingCallback();
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, ONEDRIVE_OAUTH2_TOKEN);
+ 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));
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2F");
+ } else {
+ request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code");
+ }
+ addRequest(request);
+}
+
+void OneDriveStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("OneDriveStorage: got NULL instead of JSON");
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete callback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage")) {
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete json;
+ delete callback;
+ return;
+ }
+
+ Common::JSONObject result = json->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "OneDriveStorage") ||
+ !Networking::CurlJsonRequest::jsonContainsString(result, "user_id", "OneDriveStorage") ||
+ !Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "OneDriveStorage")) {
+ warning("OneDriveStorage: bad response, no token or user_id passed");
+ debug(9, "%s", json->stringify().c_str());
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ } else {
+ _token = result.getVal("access_token")->asString();
+ _uid = result.getVal("user_id")->asString();
+ _refreshToken = result.getVal("refresh_token")->asString();
+ CloudMan.save(); //ask CloudManager to save our new refreshToken
+ if (callback)
+ (*callback)(BoolResponse(nullptr, true));
+ }
+ delete json;
+ delete callback;
+}
+
+void OneDriveStorage::codeFlowComplete(BoolResponse response) {
+ if (!response.value) {
+ warning("OneDriveStorage: failed to get access token through code flow");
+ CloudMan.removeStorage(this);
+ return;
+ }
+
+ ConfMan.removeKey("onedrive_code", ConfMan.kCloudDomain);
+ CloudMan.replaceStorage(this, kStorageOneDriveId);
+ ConfMan.flushToDisk();
+}
+
+void OneDriveStorage::codeFlowFailed(Networking::ErrorResponse error) {
+ debug(9, "OneDriveStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
+ debug(9, "%s", error.response.c_str());
+ CloudMan.removeStorage(this);
+}
+
+void OneDriveStorage::saveConfig(Common::String keyPrefix) {
+ ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
+ ConfMan.set(keyPrefix + "user_id", _uid, ConfMan.kCloudDomain);
+ ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
+}
+
+Common::String OneDriveStorage::name() const {
+ return "OneDrive";
+}
+
+void OneDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("OneDriveStorage::infoInnerCallback: NULL passed instead of JSON");
+ delete outerCallback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage::infoInnerCallback")) {
+ delete json;
+ delete outerCallback;
+ return;
+ }
+
+ Common::JSONObject info = json->asObject();
+
+ Common::String uid, name, email;
+ uint64 quotaUsed = 0, quotaAllocated = 26843545600L; // 25 GB, because I actually don't know any way to find out the real one
+
+ if (Networking::CurlJsonRequest::jsonContainsObject(info, "createdBy", "OneDriveStorage::infoInnerCallback")) {
+ Common::JSONObject createdBy = info.getVal("createdBy")->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsObject(createdBy, "user", "OneDriveStorage::infoInnerCallback")) {
+ Common::JSONObject user = createdBy.getVal("user")->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsString(user, "id", "OneDriveStorage::infoInnerCallback"))
+ uid = user.getVal("id")->asString();
+ if (Networking::CurlJsonRequest::jsonContainsString(user, "displayName", "OneDriveStorage::infoInnerCallback"))
+ name = user.getVal("displayName")->asString();
+ }
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "size", "OneDriveStorage::infoInnerCallback")) {
+ quotaUsed = info.getVal("size")->asIntegerNumber();
+ }
+
+ Common::String username = email;
+ if (username == "")
+ username = name;
+ if (username == "")
+ username = uid;
+ CloudMan.setStorageUsername(kStorageOneDriveId, username);
+
+ if (outerCallback) {
+ (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated)));
+ delete outerCallback;
+ }
+
+ delete json;
+}
+
+void OneDriveStorage::fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("OneDriveStorage::fileInfoCallback: NULL passed instead of JSON");
+ if (outerCallback)
+ (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr));
+ delete outerCallback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage::fileInfoCallback")) {
+ if (outerCallback)
+ (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr));
+ delete json;
+ delete outerCallback;
+ return;
+ }
+
+ Common::JSONObject result = response.value->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "@content.downloadUrl", "OneDriveStorage::fileInfoCallback")) {
+ warning("OneDriveStorage: downloadUrl not found in passed JSON");
+ debug(9, "%s", response.value->stringify().c_str());
+ if (outerCallback)
+ (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr));
+ delete json;
+ delete outerCallback;
+ return;
+ }
+
+ const char *url = result.getVal("@content.downloadUrl")->asString().c_str();
+ if (outerCallback)
+ (*outerCallback)(Networking::NetworkReadStreamResponse(
+ response.request,
+ new Networking::NetworkReadStream(url, nullptr, "")
+ ));
+
+ delete json;
+ delete outerCallback;
+}
+
+Networking::Request *OneDriveStorage::listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
+ return addRequest(new OneDriveListDirectoryRequest(this, path, callback, errorCallback, recursive));
+}
+
+Networking::Request *OneDriveStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ return addRequest(new OneDriveUploadRequest(this, path, contents, callback, errorCallback));
+}
+
+Networking::Request *OneDriveStorage::streamFileById(Common::String path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) {
+ Common::String url = ONEDRIVE_API_SPECIAL_APPROOT_ID + ConnMan.urlEncode(path);
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, Networking::NetworkReadStreamResponse, Networking::JsonResponse>(this, &OneDriveStorage::fileInfoCallback, outerCallback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _token);
+ return addRequest(request);
+}
+
+Networking::Request *OneDriveStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ return addRequest(new OneDriveCreateDirectoryRequest(this, path, callback, errorCallback));
+}
+
+Networking::Request *OneDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, StorageInfoResponse, Networking::JsonResponse>(this, &OneDriveStorage::infoInnerCallback, callback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(this, innerCallback, errorCallback, ONEDRIVE_API_SPECIAL_APPROOT);
+ request->addHeader("Authorization: bearer " + _token);
+ return addRequest(request);
+}
+
+Common::String OneDriveStorage::savesDirectoryPath() { return "saves/"; }
+
+OneDriveStorage *OneDriveStorage::loadFromConfig(Common::String keyPrefix) {
+ loadKeyAndSecret();
+
+ if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
+ warning("OneDriveStorage: no access_token found");
+ return nullptr;
+ }
+
+ if (!ConfMan.hasKey(keyPrefix + "user_id", ConfMan.kCloudDomain)) {
+ warning("OneDriveStorage: no user_id found");
+ return nullptr;
+ }
+
+ if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
+ warning("OneDriveStorage: no refresh_token found");
+ return nullptr;
+ }
+
+ Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
+ Common::String userId = ConfMan.get(keyPrefix + "user_id", ConfMan.kCloudDomain);
+ Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
+ return new OneDriveStorage(accessToken, userId, refreshToken);
+}
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/onedrive/onedrivestorage.h b/backends/cloud/onedrive/onedrivestorage.h
new file mode 100644
index 0000000000..59c61074e3
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivestorage.h
@@ -0,0 +1,113 @@
+/* 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_ONEDRIVE_ONEDRIVESTORAGE_H
+#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+class OneDriveStorage: public Cloud::Storage {
+ static char *KEY, *SECRET;
+
+ static void loadKeyAndSecret();
+
+ Common::String _token, _uid, _refreshToken;
+
+ /** This private constructor is called from loadFromConfig(). */
+ OneDriveStorage(Common::String token, Common::String uid, Common::String refreshToken);
+
+ void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response);
+ void codeFlowComplete(BoolResponse response);
+ void codeFlowFailed(Networking::ErrorResponse error);
+
+ /** Constructs StorageInfo based on JSON response from cloud. */
+ void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json);
+
+ void fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response);
+public:
+ /** This constructor uses OAuth code flow to get tokens. */
+ OneDriveStorage(Common::String code);
+ virtual ~OneDriveStorage();
+
+ /**
+ * 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);
+
+ /**
+ * Return unique storage name.
+ * @returns some unique storage name (for example, "Dropbox (user@example.com)")
+ */
+ virtual Common::String name() const;
+
+ /** 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 *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** 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 OneDriveStorage for those.
+ * @return pointer to the newly created OneDriveStorage or 0 if some problem occured.
+ */
+ static OneDriveStorage *loadFromConfig(Common::String keyPrefix);
+
+ /**
+ * 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, Networking::ErrorCallback errorCallback = nullptr, Common::String code = "");
+
+ Common::String accessToken() const { return _token; }
+};
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/onedrive/onedrivetokenrefresher.cpp b/backends/cloud/onedrive/onedrivetokenrefresher.cpp
new file mode 100644
index 0000000000..ce7895f41c
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivetokenrefresher.cpp
@@ -0,0 +1,130 @@
+/* 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/onedrive/onedrivetokenrefresher.h"
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+namespace Cloud {
+namespace OneDrive {
+
+OneDriveTokenRefresher::OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url):
+ CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {}
+
+OneDriveTokenRefresher::~OneDriveTokenRefresher() {}
+
+void OneDriveTokenRefresher::tokenRefreshed(Storage::BoolResponse response) {
+ if (!response.value) {
+ //failed to refresh token, notify user with NULL in original callback
+ warning("OneDriveTokenRefresher: 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 OneDriveTokenRefresher::finishJson(Common::JSONValue *json) {
+ if (!json) {
+ //that's probably not an error (200 OK)
+ CurlJsonRequest::finishJson(nullptr);
+ return;
+ }
+
+ if (jsonIsObject(json, "OneDriveTokenRefresher")) {
+ Common::JSONObject result = json->asObject();
+ long httpResponseCode = -1;
+ if (result.contains("error") && jsonIsObject(result.getVal("error"), "OneDriveTokenRefresher")) {
+ //new token needed => request token & then retry original request
+ if (_stream) {
+ httpResponseCode = _stream->httpResponseCode();
+ debug(9, "OneDriveTokenRefresher: code = %ld", httpResponseCode);
+ }
+
+ Common::JSONObject error = result.getVal("error")->asObject();
+ bool irrecoverable = true;
+
+ Common::String code, message;
+ if (jsonContainsString(error, "code", "OneDriveTokenRefresher")) {
+ code = error.getVal("code")->asString();
+ debug(9, "OneDriveTokenRefresher: code = %s", code.c_str());
+ }
+
+ if (jsonContainsString(error, "message", "OneDriveTokenRefresher")) {
+ message = error.getVal("message")->asString();
+ debug(9, "OneDriveTokenRefresher: message = %s", message.c_str());
+ }
+
+ //determine whether token refreshing would help in this situation
+ if (code == "itemNotFound") {
+ if (message.contains("application ID"))
+ irrecoverable = false;
+ }
+
+ if (code == "unauthenticated")
+ irrecoverable = false;
+
+ if (irrecoverable) {
+ finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpResponseCode));
+ delete json;
+ return;
+ }
+
+ pause();
+ delete json;
+ _parentStorage->getAccessToken(new Common::Callback<OneDriveTokenRefresher, Storage::BoolResponse>(this, &OneDriveTokenRefresher::tokenRefreshed));
+ return;
+ }
+ }
+
+ //notify user of success
+ CurlJsonRequest::finishJson(json);
+}
+
+void OneDriveTokenRefresher::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 OneDriveTokenRefresher::addHeader(Common::String header) {
+ _headers.push_back(header);
+ CurlJsonRequest::addHeader(header);
+}
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/onedrive/onedrivetokenrefresher.h b/backends/cloud/onedrive/onedrivetokenrefresher.h
new file mode 100644
index 0000000000..77e34d4e03
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivetokenrefresher.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_ONEDRIVE_ONEDRIVETOKENREFRESHER_H
+#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVETOKENREFRESHER_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+class OneDriveStorage;
+
+class OneDriveTokenRefresher: public Networking::CurlJsonRequest {
+ OneDriveStorage *_parentStorage;
+ Common::Array<Common::String> _headers;
+
+ void tokenRefreshed(Storage::BoolResponse response);
+
+ virtual void finishJson(Common::JSONValue *json);
+public:
+ OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url);
+ virtual ~OneDriveTokenRefresher();
+
+ virtual void setHeaders(Common::Array<Common::String> &headers);
+ virtual void addHeader(Common::String header);
+};
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/onedrive/onedriveuploadrequest.cpp b/backends/cloud/onedrive/onedriveuploadrequest.cpp
new file mode 100644
index 0000000000..41e6e2a37b
--- /dev/null
+++ b/backends/cloud/onedrive/onedriveuploadrequest.cpp
@@ -0,0 +1,191 @@
+/* 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/onedrive/onedriveuploadrequest.h"
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+#include "onedrivetokenrefresher.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+#define ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/upload.createSession"
+#define ONEDRIVE_API_SPECIAL_APPROOT_CONTENT "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/content"
+
+OneDriveUploadRequest::OneDriveUploadRequest(OneDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+OneDriveUploadRequest::~OneDriveUploadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _contentsStream;
+ delete _uploadCallback;
+}
+
+void OneDriveUploadRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ if (_contentsStream == nullptr) {
+ warning("OneDriveUploadRequest: cannot restart because no stream given");
+ finishError(Networking::ErrorResponse(this, false, true, "No stream given", -1));
+ return;
+ }
+ if (!_contentsStream->seek(0)) {
+ warning("OneDriveUploadRequest: cannot restart because stream couldn't seek(0)");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+ _ignoreCallback = false;
+
+ uploadNextPart();
+}
+
+void OneDriveUploadRequest::uploadNextPart() {
+ const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024;
+
+ if (_uploadUrl == "" && (uint32)_contentsStream->size() > UPLOAD_PER_ONE_REQUEST) {
+ Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD, ConnMan.urlEncode(_savePath).c_str()); //folder must exist
+ Networking::JsonCallback callback = new Common::Callback<OneDriveUploadRequest, Networking::JsonResponse>(this, &OneDriveUploadRequest::partUploadedCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveUploadRequest, Networking::ErrorResponse>(this, &OneDriveUploadRequest::partUploadedErrorCallback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ request->setBuffer(new byte[1], 0); //use POST
+ _workingRequest = ConnMan.addRequest(request);
+ return;
+ }
+
+ Common::String url;
+ if (_uploadUrl == "") {
+ url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CONTENT, ConnMan.urlEncode(_savePath).c_str());
+ } else {
+ url = _uploadUrl;
+ }
+
+ Networking::JsonCallback callback = new Common::Callback<OneDriveUploadRequest, Networking::JsonResponse>(this, &OneDriveUploadRequest::partUploadedCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveUploadRequest, Networking::ErrorResponse>(this, &OneDriveUploadRequest::partUploadedErrorCallback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ request->usePut();
+
+ uint32 oldPos = _contentsStream->pos();
+
+ byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST];
+ uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST);
+ request->setBuffer(buffer, size);
+
+ if (_uploadUrl != "") {
+ request->addHeader(Common::String::format("Content-Range: bytes %u-%u/%u", oldPos, _contentsStream->pos() - 1, _contentsStream->size()));
+ } else if (_contentsStream->size() == 0) {
+ warning("\"Sorry, OneDrive can't upload empty files\"");
+ finishUpload(StorageFile(_savePath, 0, 0, false));
+ delete request;
+ return;
+ }
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void OneDriveUploadRequest::partUploadedCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::ErrorResponse error(this, false, true, "", -1);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (json->isObject()) {
+ Common::JSONObject object = json->asObject();
+
+ if (object.contains("error")) {
+ warning("OneDriveUploadRequest: error: %s", json->stringify(true).c_str());
+ error.response = json->stringify(true);
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "id", "OneDriveUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(object, "name", "OneDriveUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsIntegerNumber(object, "size", "OneDriveUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(object, "lastModifiedDateTime", "OneDriveUploadRequest")) {
+ //finished
+ Common::String path = _savePath;
+ uint32 size = object.getVal("size")->asIntegerNumber();
+ uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("lastModifiedDateTime")->asString());
+ finishUpload(StorageFile(path, size, timestamp, false));
+ return;
+ }
+
+ if (_uploadUrl == "") {
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "uploadUrl", "OneDriveUploadRequest"))
+ _uploadUrl = object.getVal("uploadUrl")->asString();
+ }
+ }
+
+ if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) {
+ warning("OneDriveUploadRequest: no file info to return");
+ finishUpload(StorageFile(_savePath, 0, 0, false));
+ } else {
+ uploadNextPart();
+ }
+
+ delete json;
+}
+
+void OneDriveUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void OneDriveUploadRequest::handle() {}
+
+void OneDriveUploadRequest::restart() { start(); }
+
+void OneDriveUploadRequest::finishUpload(StorageFile file) {
+ Request::finishSuccess();
+ if (_uploadCallback)
+ (*_uploadCallback)(Storage::UploadResponse(this, file));
+}
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/onedrive/onedriveuploadrequest.h b/backends/cloud/onedrive/onedriveuploadrequest.h
new file mode 100644
index 0000000000..4e2cb24a7f
--- /dev/null
+++ b/backends/cloud/onedrive/onedriveuploadrequest.h
@@ -0,0 +1,61 @@
+/* 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_ONEDRIVE_ONEDRIVEUPLOADREQUEST_H
+#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVEUPLOADREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace OneDrive {
+class OneDriveStorage;
+
+class OneDriveUploadRequest: public Networking::Request {
+ OneDriveStorage *_storage;
+ Common::String _savePath;
+ Common::SeekableReadStream *_contentsStream;
+ Storage::UploadCallback _uploadCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _uploadUrl;
+
+ void start();
+ void uploadNextPart();
+ void partUploadedCallback(Networking::JsonResponse response);
+ void partUploadedErrorCallback(Networking::ErrorResponse error);
+ void finishUpload(StorageFile status);
+
+public:
+ OneDriveUploadRequest(OneDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
+ virtual ~OneDriveUploadRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/savessyncrequest.cpp b/backends/cloud/savessyncrequest.cpp
new file mode 100644
index 0000000000..fff46c3a83
--- /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
diff --git a/backends/cloud/savessyncrequest.h b/backends/cloud/savessyncrequest.h
new file mode 100644
index 0000000000..e891e93969
--- /dev/null
+++ b/backends/cloud/savessyncrequest.h
@@ -0,0 +1,80 @@
+/* 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"
+#include "common/hash-str.h"
+#include "gui/object.h"
+
+namespace Cloud {
+
+class SavesSyncRequest: public Networking::Request, public GUI::CommandSender {
+ 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;
+ uint32 _totalFilesToHandle;
+ Common::String _date;
+
+ void start();
+ void directoryListedCallback(Storage::ListDirectoryResponse response);
+ void directoryListedErrorCallback(Networking::ErrorResponse error);
+ void directoryCreatedCallback(Storage::BoolResponse response);
+ void directoryCreatedErrorCallback(Networking::ErrorResponse error);
+ void fileDownloadedCallback(Storage::BoolResponse response);
+ void fileDownloadedErrorCallback(Networking::ErrorResponse error);
+ void fileUploadedCallback(Storage::UploadResponse response);
+ void fileUploadedErrorCallback(Networking::ErrorResponse error);
+ void downloadNextFile();
+ void uploadNextFile();
+ virtual void finishError(Networking::ErrorResponse error);
+ void finishSync(bool success);
+
+public:
+ SavesSyncRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb);
+ virtual ~SavesSyncRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getDownloadingProgress() const;
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+
+ /** Returns an array of saves names which are not downloaded yet. */
+ Common::Array<Common::String> getFilesToDownload();
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/storage.cpp b/backends/cloud/storage.cpp
new file mode 100644
index 0000000000..910d80d153
--- /dev/null
+++ b/backends/cloud/storage.cpp
@@ -0,0 +1,342 @@
+/* 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/storage.h"
+#include "backends/cloud/downloadrequest.h"
+#include "backends/cloud/folderdownloadrequest.h"
+#include "backends/cloud/savessyncrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "common/debug.h"
+#include "common/file.h"
+#include <common/translation.h>
+
+namespace Cloud {
+
+Storage::Storage():
+ _runningRequestsCount(0), _savesSyncRequest(nullptr), _syncRestartRequestsed(false),
+ _downloadFolderRequest(nullptr) {}
+
+Storage::~Storage() {}
+
+Networking::ErrorCallback Storage::getErrorPrintingCallback() {
+ return new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::printErrorResponse);
+}
+
+void Storage::printErrorResponse(Networking::ErrorResponse error) {
+ debug(9, "Storage: error response (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
+ debug(9, "%s", error.response.c_str());
+}
+
+Networking::Request *Storage::addRequest(Networking::Request *request) {
+ _runningRequestsMutex.lock();
+ ++_runningRequestsCount;
+ if (_runningRequestsCount == 1)
+ debug(9, "Storage is working now");
+ _runningRequestsMutex.unlock();
+ return ConnMan.addRequest(request, new Common::Callback<Storage, Networking::Request *>(this, &Storage::requestFinishedCallback));
+}
+
+void Storage::requestFinishedCallback(Networking::Request *invalidRequestPointer) {
+ bool restartSync = false;
+
+ _runningRequestsMutex.lock();
+ if (invalidRequestPointer == _savesSyncRequest)
+ _savesSyncRequest = nullptr;
+ --_runningRequestsCount;
+ if (_syncRestartRequestsed)
+ restartSync = true;
+ if (_runningRequestsCount == 0 && !restartSync)
+ debug(9, "Storage is not working now");
+ _runningRequestsMutex.unlock();
+
+ if (restartSync)
+ syncSaves(nullptr, nullptr);
+}
+
+Networking::Request *Storage::upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback) errorCallback = getErrorPrintingCallback();
+
+ Common::File *f = new Common::File();
+ if (!f->open(localPath)) {
+ warning("Storage: unable to open file to upload from");
+ if (errorCallback)
+ (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "", -1));
+ delete errorCallback;
+ delete callback;
+ delete f;
+ return nullptr;
+ }
+
+ return upload(remotePath, f, callback, errorCallback);
+}
+
+bool Storage::uploadStreamSupported() {
+ return true;
+}
+
+Networking::Request *Storage::streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
+ //most Storages use paths instead of ids, so this should work
+ return streamFileById(path, callback, errorCallback);
+}
+
+Networking::Request *Storage::download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ //most Storages use paths instead of ids, so this should work
+ return downloadById(remotePath, localPath, callback, errorCallback);
+}
+
+Networking::Request *Storage::downloadById(Common::String remoteId, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback) errorCallback = getErrorPrintingCallback();
+
+ Common::DumpFile *f = new Common::DumpFile();
+ if (!f->open(localPath, true)) {
+ warning("Storage: unable to open file to download into");
+ if (errorCallback) (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "", -1));
+ delete errorCallback;
+ delete callback;
+ delete f;
+ return nullptr;
+ }
+
+ return addRequest(new DownloadRequest(this, callback, errorCallback, remoteId, f));
+}
+
+Networking::Request *Storage::downloadFolder(Common::String remotePath, Common::String localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ return addRequest(new FolderDownloadRequest(this, callback, errorCallback, remotePath, localPath, recursive));
+}
+
+SavesSyncRequest *Storage::syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest) {
+ warning("Storage::syncSaves: there is a sync in progress already");
+ _syncRestartRequestsed = true;
+ _runningRequestsMutex.unlock();
+ return _savesSyncRequest;
+ }
+ if (!callback)
+ callback = new Common::Callback<Storage, BoolResponse>(this, &Storage::savesSyncDefaultCallback);
+ if (!errorCallback)
+ errorCallback = new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::savesSyncDefaultErrorCallback);
+ _savesSyncRequest = new SavesSyncRequest(this, callback, errorCallback);
+ _syncRestartRequestsed = false;
+ _runningRequestsMutex.unlock();
+ return (SavesSyncRequest *)addRequest(_savesSyncRequest); //who knows what that ConnMan could return in the future
+}
+
+bool Storage::isWorking() {
+ _runningRequestsMutex.lock();
+ bool working = _runningRequestsCount > 0;
+ _runningRequestsMutex.unlock();
+ return working;
+}
+
+///// SavesSyncRequest-related /////
+
+bool Storage::isSyncing() {
+ _runningRequestsMutex.lock();
+ bool syncing = _savesSyncRequest != nullptr;
+ _runningRequestsMutex.unlock();
+ return syncing;
+}
+
+double Storage::getSyncDownloadingProgress() {
+ double result = 1;
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest)
+ result = _savesSyncRequest->getDownloadingProgress();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+double Storage::getSyncProgress() {
+ double result = 1;
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest)
+ result = _savesSyncRequest->getProgress();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+Common::Array<Common::String> Storage::getSyncingFiles() {
+ Common::Array<Common::String> result;
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest)
+ result = _savesSyncRequest->getFilesToDownload();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+void Storage::cancelSync() {
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest)
+ _savesSyncRequest->finish();
+ _runningRequestsMutex.unlock();
+}
+
+void Storage::setSyncTarget(GUI::CommandReceiver *target) {
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest)
+ _savesSyncRequest->setTarget(target);
+ _runningRequestsMutex.unlock();
+}
+
+void Storage::savesSyncDefaultCallback(BoolResponse response) {
+ _runningRequestsMutex.lock();
+ _savesSyncRequest = nullptr;
+ _runningRequestsMutex.unlock();
+
+ if (!response.value)
+ warning("SavesSyncRequest called success callback with `false` argument");
+ g_system->displayMessageOnOSD(_("Saves sync complete."));
+}
+
+void Storage::savesSyncDefaultErrorCallback(Networking::ErrorResponse error) {
+ _runningRequestsMutex.lock();
+ _savesSyncRequest = nullptr;
+ _runningRequestsMutex.unlock();
+
+ printErrorResponse(error);
+
+ if (error.interrupted)
+ g_system->displayMessageOnOSD(_("Saves sync was cancelled."));
+ else
+ g_system->displayMessageOnOSD(_("Saves sync failed.\nCheck your Internet connection."));
+}
+
+///// DownloadFolderRequest-related /////
+
+bool Storage::startDownload(Common::String remotePath, Common::String localPath) {
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest) {
+ warning("Storage::startDownload: there is a download in progress already");
+ _runningRequestsMutex.unlock();
+ return false;
+ }
+ _downloadFolderRequest = (FolderDownloadRequest *)downloadFolder(
+ remotePath, localPath,
+ new Common::Callback<Storage, FileArrayResponse>(this, &Storage::directoryDownloadedCallback),
+ new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::directoryDownloadedErrorCallback),
+ true
+ );
+ _runningRequestsMutex.unlock();
+ return true;
+}
+
+void Storage::cancelDownload() {
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ _downloadFolderRequest->finish();
+ _runningRequestsMutex.unlock();
+}
+
+void Storage::setDownloadTarget(GUI::CommandReceiver *target) {
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ _downloadFolderRequest->setTarget(target);
+ _runningRequestsMutex.unlock();
+}
+
+bool Storage::isDownloading() {
+ _runningRequestsMutex.lock();
+ bool syncing = _downloadFolderRequest != nullptr;
+ _runningRequestsMutex.unlock();
+ return syncing;
+}
+
+double Storage::getDownloadingProgress() {
+ double result = 1;
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getProgress();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+uint64 Storage::getDownloadBytesNumber() {
+ uint64 result = 0;
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getDownloadedBytes();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+uint64 Storage::getDownloadTotalBytesNumber() {
+ uint64 result = 0;
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getTotalBytesToDownload();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+uint64 Storage::getDownloadSpeed() {
+ uint64 result = 0;
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getDownloadSpeed();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+Common::String Storage::getDownloadRemoteDirectory() {
+ Common::String result = "";
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getRemotePath();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+Common::String Storage::getDownloadLocalDirectory() {
+ Common::String result = "";
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getLocalPath();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+void Storage::directoryDownloadedCallback(FileArrayResponse response) {
+ _runningRequestsMutex.lock();
+ _downloadFolderRequest = nullptr;
+ _runningRequestsMutex.unlock();
+
+ Common::String message;
+ if (response.value.size()) {
+ message = Common::String::format(_("Download complete.\nFailed to download %u files."), response.value.size());
+ } else {
+ message = _("Download complete.");
+ }
+ g_system->displayMessageOnOSD(message.c_str());
+}
+
+void Storage::directoryDownloadedErrorCallback(Networking::ErrorResponse error) {
+ _runningRequestsMutex.lock();
+ _downloadFolderRequest = nullptr;
+ _runningRequestsMutex.unlock();
+
+ g_system->displayMessageOnOSD(_("Download failed."));
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/storage.h b/backends/cloud/storage.h
new file mode 100644
index 0000000000..6a5765f13a
--- /dev/null
+++ b/backends/cloud/storage.h
@@ -0,0 +1,238 @@
+/* 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_STORAGE_H
+#define BACKENDS_CLOUD_STORAGE_H
+
+#include "backends/cloud/storagefile.h"
+#include "backends/cloud/storageinfo.h"
+#include "backends/networking/curl/request.h"
+#include "backends/networking/curl/curlrequest.h"
+#include "common/array.h"
+#include "common/callback.h"
+#include "common/mutex.h"
+#include "common/stream.h"
+#include "common/str.h"
+
+namespace GUI {
+
+class CommandReceiver;
+
+}
+
+namespace Cloud {
+
+class SavesSyncRequest;
+class FolderDownloadRequest;
+
+class Storage {
+public:
+ typedef Networking::Response<Common::Array<StorageFile>&> FileArrayResponse;
+ typedef Networking::Response<StorageInfo> StorageInfoResponse;
+ typedef Networking::Response<bool> BoolResponse;
+ typedef Networking::Response<StorageFile> UploadResponse;
+ typedef Networking::Response<Common::Array<StorageFile> &> ListDirectoryResponse;
+
+ typedef Common::BaseCallback<FileArrayResponse> *FileArrayCallback;
+ typedef Common::BaseCallback<StorageInfoResponse> *StorageInfoCallback;
+ typedef Common::BaseCallback<BoolResponse> *BoolCallback;
+ typedef Common::BaseCallback<UploadResponse> *UploadCallback;
+ typedef Common::BaseCallback<ListDirectoryResponse> *ListDirectoryCallback;
+
+protected:
+ /** Keeps track of running requests. */
+ uint32 _runningRequestsCount;
+ Common::Mutex _runningRequestsMutex;
+
+ /** SavesSyncRequest-related */
+ SavesSyncRequest *_savesSyncRequest;
+ bool _syncRestartRequestsed;
+
+ /** FolderDownloadRequest-related */
+ FolderDownloadRequest *_downloadFolderRequest;
+
+ /** Returns default error callback (printErrorResponse). */
+ virtual Networking::ErrorCallback getErrorPrintingCallback();
+
+ /** Prints ErrorResponse contents with debug(). */
+ virtual void printErrorResponse(Networking::ErrorResponse error);
+
+ /**
+ * Adds request to the ConnMan, but also increases _runningRequestsCount.
+ * This method should be used by Storage implementations instead of
+ * direct ConnMan.addRequest() call.
+ *
+ * @return the same Request pointer, just as a shortcut
+ */
+ virtual Networking::Request *addRequest(Networking::Request *request);
+
+ /**
+ * Decreases _runningRequestCount. It's called from ConnMan automatically.
+ * Passed pointer is dangling, but one can use the address to determine
+ * some special Requests (which addresses were remembered somewhere).
+ */
+ virtual void requestFinishedCallback(Networking::Request *invalidRequestPointer);
+
+public:
+ Storage();
+ virtual ~Storage();
+
+ /**
+ * 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) = 0;
+
+ /**
+ * Return unique storage name.
+ * @returns some unique storage name (for example, "Dropbox (user@example.com)")
+ */
+ virtual Common::String name() const = 0;
+
+ /**
+ * Public Cloud API comes down there.
+ *
+ * All Cloud API methods return Networking::Request *, which
+ * might be used to control request. All methods also accept
+ * a callback, which is called, when request is complete.
+ */
+
+ /** Returns ListDirectoryResponse with list of files. */
+ virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false) = 0;
+
+ /** Returns StorageFile with info about uploaded file. */
+ virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) = 0;
+ virtual Networking::Request *upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns whether Storage supports upload(ReadStream). */
+ virtual bool uploadStreamSupported();
+
+ /** Returns pointer to Networking::NetworkReadStream. */
+ virtual Networking::Request *streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *downloadById(Common::String remoteId, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns Common::Array<StorageFile> with list of files, which were not downloaded. */
+ virtual Networking::Request *downloadFolder(Common::String remotePath, Common::String localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
+
+ /** Calls the callback when finished. */
+ virtual SavesSyncRequest *syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /**
+ * Returns the StorageInfo struct via <callback>.
+ * Calls the <errorCallback> if failed to get information.
+ *
+ * @note on success Storage should also call
+ * CloudMan.setStorageUsername().
+ */
+ virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /** Returns storage's saves directory path with the trailing slash. */
+ virtual Common::String savesDirectoryPath() = 0;
+
+ /** Returns whether there are any requests running. */
+ virtual bool isWorking();
+
+ ///// SavesSyncRequest-related /////
+
+ /** Returns whether there is a SavesSyncRequest running. */
+ virtual bool isSyncing();
+
+ /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */
+ virtual double getSyncDownloadingProgress();
+
+ /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */
+ virtual double getSyncProgress();
+
+ /** Returns an array of saves names which are not yet synced (thus cannot be used). */
+ virtual Common::Array<Common::String> getSyncingFiles();
+
+ /** Cancels running sync. */
+ virtual void cancelSync();
+
+ /** Sets SavesSyncRequest's target to given CommandReceiver. */
+ virtual void setSyncTarget(GUI::CommandReceiver *target);
+
+protected:
+ /** Finishes the sync. Shows an OSD message. */
+ virtual void savesSyncDefaultCallback(BoolResponse response);
+
+ /** Finishes the sync. Shows an OSD message. */
+ virtual void savesSyncDefaultErrorCallback(Networking::ErrorResponse error);
+
+public:
+ ///// DownloadFolderRequest-related /////
+
+ /** Starts a folder download. */
+ virtual bool startDownload(Common::String remotePath, Common::String localPath);
+
+ /** Cancels running download. */
+ virtual void cancelDownload();
+
+ /** Sets FolderDownloadRequest's target to given CommandReceiver. */
+ virtual void setDownloadTarget(GUI::CommandReceiver *target);
+
+ /** Returns whether there is a FolderDownloadRequest running. */
+ virtual bool isDownloading();
+
+ /** Returns a number in [0, 1] range which represents current download progress (1 = complete). */
+ virtual double getDownloadingProgress();
+
+ /** Returns a number of bytes that is downloaded in current download progress. */
+ virtual uint64 getDownloadBytesNumber();
+
+ /** Returns a total number of bytes to be downloaded in current download progress. */
+ virtual uint64 getDownloadTotalBytesNumber();
+
+ /** Returns download speed of current download progress. */
+ virtual uint64 getDownloadSpeed();
+
+ /** Returns remote directory path. */
+ virtual Common::String getDownloadRemoteDirectory();
+
+ /** Returns local directory path. */
+ virtual Common::String getDownloadLocalDirectory();
+
+protected:
+ /** Finishes the download. Shows an OSD message. */
+ virtual void directoryDownloadedCallback(FileArrayResponse response);
+
+ /** Finishes the download. Shows an OSD message. */
+ virtual void directoryDownloadedErrorCallback(Networking::ErrorResponse error);
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/storagefile.cpp b/backends/cloud/storagefile.cpp
new file mode 100644
index 0000000000..62d492292d
--- /dev/null
+++ b/backends/cloud/storagefile.cpp
@@ -0,0 +1,68 @@
+/* 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/storagefile.h"
+
+namespace Cloud {
+
+StorageFile::StorageFile() {
+ _id = "";
+ _path = "";
+ _name = "";
+ _size = 0;
+ _timestamp = 0;
+ _isDirectory = false;
+}
+
+StorageFile::StorageFile(Common::String pth, uint32 sz, uint32 ts, bool dir) {
+ _id = pth;
+ _path = pth;
+
+ _name = pth;
+ if (_name.size() != 0) {
+ uint32 i = _name.size() - 1;
+ while (true) {
+ if (_name[i] == '/' || _name[i] == '\\') {
+ _name.erase(0, i + 1);
+ break;
+ }
+ if (i == 0)
+ break;
+ --i;
+ }
+ }
+
+ _size = sz;
+ _timestamp = ts;
+ _isDirectory = dir;
+}
+
+StorageFile::StorageFile(Common::String id, Common::String path, Common::String name, uint32 sz, uint32 ts, bool dir) {
+ _id = id;
+ _path = path;
+ _name = name;
+ _size = sz;
+ _timestamp = ts;
+ _isDirectory = dir;
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/storagefile.h b/backends/cloud/storagefile.h
new file mode 100644
index 0000000000..1183524f48
--- /dev/null
+++ b/backends/cloud/storagefile.h
@@ -0,0 +1,65 @@
+/* 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_STORAGEFILE_H
+#define BACKENDS_CLOUD_STORAGEFILE_H
+
+#include "common/str.h"
+
+namespace Cloud {
+
+/**
+ * StorageFile represents a file storaged on remote cloud storage.
+ * It contains basic information about a file, and might be used
+ * when listing directories or syncing files.
+ *
+ * Some storages (Google Drive, for example) don't have an actual
+ * path notation to address files. Instead, they are using ids.
+ * As resolving id by path is not a fast operation, it's required
+ * to use ids if they are known, but user-friendly paths are
+ * necessary too, because these are used by Requests.
+ *
+ * If storage supports path notation, id would actually contain path.
+ */
+class StorageFile {
+ Common::String _id, _path, _name;
+ uint32 _size, _timestamp;
+ bool _isDirectory;
+
+public:
+ StorageFile(); //invalid empty file
+ StorageFile(Common::String pth, uint32 sz, uint32 ts, bool dir);
+ StorageFile(Common::String id, Common::String path, Common::String name, uint32 sz, uint32 ts, bool dir);
+
+ Common::String id() const { return _id; }
+ Common::String path() const { return _path; }
+ Common::String name() const { return _name; }
+ uint32 size() const { return _size; }
+ uint32 timestamp() const { return _timestamp; }
+ bool isDirectory() const { return _isDirectory; }
+
+ void setPath(Common::String path_) { _path = path_; }
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/storageinfo.h b/backends/cloud/storageinfo.h
new file mode 100644
index 0000000000..f1fb540974
--- /dev/null
+++ b/backends/cloud/storageinfo.h
@@ -0,0 +1,53 @@
+/* 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_STORAGEINFO_H
+#define BACKENDS_CLOUD_STORAGEINFO_H
+
+#include "common/str.h"
+
+namespace Cloud {
+
+/**
+* StorageInfo contains information about remote cloud storage.
+* It's disk quota usage, owner name, and such.
+*/
+
+class StorageInfo {
+ Common::String _uid, _name, _email;
+ uint64 _usedBytes, _allocatedBytes;
+
+public:
+ StorageInfo(Common::String uid_, Common::String name_, Common::String email_, uint64 used_, uint64 allocated):
+ _uid(uid_), _name(name_), _email(email_), _usedBytes(used_), _allocatedBytes(allocated) {}
+
+ Common::String uid() const { return _uid; }
+ Common::String name() const { return _name; }
+ Common::String email() const { return _email; }
+ uint64 used() const { return _usedBytes; }
+ uint64 available() const { return _allocatedBytes; }
+
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/fs/abstract-fs.h b/backends/fs/abstract-fs.h
index dcfdc08975..24286b649d 100644
--- a/backends/fs/abstract-fs.h
+++ b/backends/fs/abstract-fs.h
@@ -191,6 +191,15 @@ public:
* @return pointer to the stream object, 0 in case of a failure
*/
virtual Common::WriteStream *createWriteStream() = 0;
+
+ /**
+ * Creates a file referred by this node.
+ *
+ * @param isDirectory true if created file must be a directory
+ *
+ * @return true if file is created successfully
+ */
+ virtual bool create(bool isDirectory) = 0;
};
diff --git a/backends/fs/amigaos4/amigaos4-fs.cpp b/backends/fs/amigaos4/amigaos4-fs.cpp
index 6d5b099736..24a8fb98ad 100644
--- a/backends/fs/amigaos4/amigaos4-fs.cpp
+++ b/backends/fs/amigaos4/amigaos4-fs.cpp
@@ -443,4 +443,9 @@ Common::WriteStream *AmigaOSFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
+bool AmigaOSFilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif //defined(__amigaos4__)
diff --git a/backends/fs/amigaos4/amigaos4-fs.h b/backends/fs/amigaos4/amigaos4-fs.h
index 408354888e..3ed45d3f77 100644
--- a/backends/fs/amigaos4/amigaos4-fs.h
+++ b/backends/fs/amigaos4/amigaos4-fs.h
@@ -116,6 +116,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
diff --git a/backends/fs/chroot/chroot-fs.cpp b/backends/fs/chroot/chroot-fs.cpp
index 2cbb4af9d6..f5f7c84570 100644
--- a/backends/fs/chroot/chroot-fs.cpp
+++ b/backends/fs/chroot/chroot-fs.cpp
@@ -108,6 +108,11 @@ Common::WriteStream *ChRootFilesystemNode::createWriteStream() {
return _realNode->createWriteStream();
}
+bool ChRootFilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
Common::String ChRootFilesystemNode::addPathComponent(const Common::String &path, const Common::String &component) {
const char sep = '/';
if (path.lastChar() == sep && component.firstChar() == sep) {
diff --git a/backends/fs/chroot/chroot-fs.h b/backends/fs/chroot/chroot-fs.h
index 9ff913be31..e0ecc1c47e 100644
--- a/backends/fs/chroot/chroot-fs.h
+++ b/backends/fs/chroot/chroot-fs.h
@@ -49,6 +49,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
private:
static Common::String addPathComponent(const Common::String &path, const Common::String &component);
diff --git a/backends/fs/ds/ds-fs.cpp b/backends/fs/ds/ds-fs.cpp
index 3782caf432..83b1295838 100644
--- a/backends/fs/ds/ds-fs.cpp
+++ b/backends/fs/ds/ds-fs.cpp
@@ -211,6 +211,11 @@ Common::WriteStream *DSFileSystemNode::createWriteStream() {
return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE);
}
+bool DSFileSystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
//////////////////////////////////////////////////////////////////////////
// GBAMPFileSystemNode - File system using GBA Movie Player and CF card //
//////////////////////////////////////////////////////////////////////////
@@ -393,6 +398,11 @@ Common::WriteStream *GBAMPFileSystemNode::createWriteStream() {
return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE);
}
+bool GBAMPFileSystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
DSFileStream::DSFileStream(void *handle) : _handle(handle) {
diff --git a/backends/fs/ds/ds-fs.h b/backends/fs/ds/ds-fs.h
index b9ccfcbe9c..939d1a1555 100644
--- a/backends/fs/ds/ds-fs.h
+++ b/backends/fs/ds/ds-fs.h
@@ -91,6 +91,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
/**
* Returns the zip file this node points to.
@@ -156,6 +157,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
struct fileHandle {
diff --git a/backends/fs/n64/n64-fs.cpp b/backends/fs/n64/n64-fs.cpp
index fe37dad467..d568500d30 100644
--- a/backends/fs/n64/n64-fs.cpp
+++ b/backends/fs/n64/n64-fs.cpp
@@ -160,4 +160,9 @@ Common::WriteStream *N64FilesystemNode::createWriteStream() {
return RomfsStream::makeFromPath(getPath(), true);
}
+bool N64FilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif //#ifdef __N64__
diff --git a/backends/fs/n64/n64-fs.h b/backends/fs/n64/n64-fs.h
index d503a6601e..d520ad5be6 100644
--- a/backends/fs/n64/n64-fs.h
+++ b/backends/fs/n64/n64-fs.h
@@ -73,6 +73,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
#endif
diff --git a/backends/fs/posix/posix-fs.cpp b/backends/fs/posix/posix-fs.cpp
index 1a6c4e40a5..b38a07637c 100644
--- a/backends/fs/posix/posix-fs.cpp
+++ b/backends/fs/posix/posix-fs.cpp
@@ -39,6 +39,7 @@
#include <dirent.h>
#include <stdio.h>
#include <errno.h>
+#include <fcntl.h>
#ifdef __OS2__
#define INCL_DOS
@@ -252,6 +253,30 @@ Common::WriteStream *POSIXFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
+bool POSIXFilesystemNode::create(bool isDirectory) {
+ bool success;
+
+ if (isDirectory) {
+ success = mkdir(_path.c_str(), 0755) == 0;
+ } else {
+ success = creat(_path.c_str(), 0755) != -1;
+ }
+
+ if (success) {
+ setFlags();
+ if (_isValid) {
+ if (_isDirectory != isDirectory) warning("failed to create %s: got %s", isDirectory ? "directory" : "file", _isDirectory ? "directory" : "file");
+ return _isDirectory == isDirectory;
+ }
+
+ warning("POSIXFilesystemNode: %s() was a success, but stat indicates there is no such %s",
+ isDirectory ? "mkdir" : "creat", isDirectory ? "directory" : "file");
+ return false;
+ }
+
+ return false;
+}
+
namespace Posix {
bool assureDirectoryExists(const Common::String &dir, const char *prefix) {
diff --git a/backends/fs/posix/posix-fs.h b/backends/fs/posix/posix-fs.h
index 0703ac5bf5..4ebce7e9d9 100644
--- a/backends/fs/posix/posix-fs.h
+++ b/backends/fs/posix/posix-fs.h
@@ -73,6 +73,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
private:
/**
diff --git a/backends/fs/ps2/ps2-fs.cpp b/backends/fs/ps2/ps2-fs.cpp
index 9b6e1270f1..8391e063a0 100644
--- a/backends/fs/ps2/ps2-fs.cpp
+++ b/backends/fs/ps2/ps2-fs.cpp
@@ -441,4 +441,9 @@ Common::WriteStream *Ps2FilesystemNode::createWriteStream() {
return PS2FileStream::makeFromPath(getPath(), true);
}
+bool Ps2FilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif
diff --git a/backends/fs/ps2/ps2-fs.h b/backends/fs/ps2/ps2-fs.h
index 63b866ba5b..c9da434535 100644
--- a/backends/fs/ps2/ps2-fs.h
+++ b/backends/fs/ps2/ps2-fs.h
@@ -96,6 +96,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
int getDev() { return 0; }
};
diff --git a/backends/fs/psp/psp-fs.cpp b/backends/fs/psp/psp-fs.cpp
index e8aad9fa98..6bd5e93435 100644
--- a/backends/fs/psp/psp-fs.cpp
+++ b/backends/fs/psp/psp-fs.cpp
@@ -239,4 +239,9 @@ Common::WriteStream *PSPFilesystemNode::createWriteStream() {
return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE);
}
+bool PSPFilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif //#ifdef __PSP__
diff --git a/backends/fs/psp/psp-fs.h b/backends/fs/psp/psp-fs.h
index 1bb4543d19..648544650b 100644
--- a/backends/fs/psp/psp-fs.h
+++ b/backends/fs/psp/psp-fs.h
@@ -65,6 +65,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
#endif
diff --git a/backends/fs/symbian/symbian-fs.cpp b/backends/fs/symbian/symbian-fs.cpp
index 8fbc3a402a..e4163caa33 100644
--- a/backends/fs/symbian/symbian-fs.cpp
+++ b/backends/fs/symbian/symbian-fs.cpp
@@ -231,4 +231,10 @@ Common::SeekableReadStream *SymbianFilesystemNode::createReadStream() {
Common::WriteStream *SymbianFilesystemNode::createWriteStream() {
return SymbianStdioStream::makeFromPath(getPath(), true);
}
+
+bool SymbianFilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif //#if defined(__SYMBIAN32__)
diff --git a/backends/fs/symbian/symbian-fs.h b/backends/fs/symbian/symbian-fs.h
index 339e998a28..1327f9f2e9 100644
--- a/backends/fs/symbian/symbian-fs.h
+++ b/backends/fs/symbian/symbian-fs.h
@@ -66,6 +66,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
#endif
diff --git a/backends/fs/wii/wii-fs.cpp b/backends/fs/wii/wii-fs.cpp
index 43f4f592b7..46041bf499 100644
--- a/backends/fs/wii/wii-fs.cpp
+++ b/backends/fs/wii/wii-fs.cpp
@@ -213,4 +213,9 @@ Common::WriteStream *WiiFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
+bool WiiFilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif //#if defined(__WII__)
diff --git a/backends/fs/wii/wii-fs.h b/backends/fs/wii/wii-fs.h
index c77c543dae..affb765884 100644
--- a/backends/fs/wii/wii-fs.h
+++ b/backends/fs/wii/wii-fs.h
@@ -69,6 +69,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
#endif
diff --git a/backends/fs/windows/windows-fs.cpp b/backends/fs/windows/windows-fs.cpp
index 49549b83cb..b43686f911 100644
--- a/backends/fs/windows/windows-fs.cpp
+++ b/backends/fs/windows/windows-fs.cpp
@@ -244,4 +244,36 @@ Common::WriteStream *WindowsFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
+bool WindowsFilesystemNode::create(bool isDirectory) {
+ bool success;
+
+ if (isDirectory) {
+ success = CreateDirectory(toUnicode(_path.c_str()), NULL) != 0;
+ } else {
+ success = CreateFile(toUnicode(_path.c_str()), GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) != INVALID_HANDLE_VALUE;
+ }
+
+ if (success) {
+ //this piece is copied from constructor, it checks that file exists and detects whether it's a directory
+ DWORD fileAttribs = GetFileAttributes(toUnicode(_path.c_str()));
+ if (fileAttribs != INVALID_FILE_ATTRIBUTES) {
+ _isDirectory = ((fileAttribs & FILE_ATTRIBUTE_DIRECTORY) != 0);
+ _isValid = true;
+ // Add a trailing slash, if necessary.
+ if (_isDirectory && _path.lastChar() != '\\') {
+ _path += '\\';
+ }
+
+ if (_isDirectory != isDirectory) warning("failed to create %s: got %s", isDirectory ? "directory" : "file", _isDirectory ? "directory" : "file");
+ return _isDirectory == isDirectory;
+ }
+
+ warning("WindowsFilesystemNode: Create%s() was a success, but GetFileAttributes() indicates there is no such %s",
+ isDirectory ? "Directory" : "File", isDirectory ? "directory" : "file");
+ return false;
+ }
+
+ return false;
+}
+
#endif //#ifdef WIN32
diff --git a/backends/fs/windows/windows-fs.h b/backends/fs/windows/windows-fs.h
index d06044603a..7c9f2c1e1a 100644
--- a/backends/fs/windows/windows-fs.h
+++ b/backends/fs/windows/windows-fs.h
@@ -88,6 +88,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
private:
/**
diff --git a/backends/graphics/graphics.h b/backends/graphics/graphics.h
index 3671b9f0b9..921dfca61c 100644
--- a/backends/graphics/graphics.h
+++ b/backends/graphics/graphics.h
@@ -84,6 +84,10 @@ public:
virtual void setCursorPalette(const byte *colors, uint start, uint num) = 0;
virtual void displayMessageOnOSD(const char *msg) {}
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {}
+ virtual void clearOSD() {}
+ virtual Graphics::PixelFormat getOSDFormat() { return Graphics::PixelFormat(); }
+
// Graphics::PaletteManager interface
//virtual void setPalette(const byte *colors, uint start, uint num) = 0;
diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp
index 4d6a00a3b3..c491b03f1f 100644
--- a/backends/graphics/opengl/opengl-graphics.cpp
+++ b/backends/graphics/opengl/opengl-graphics.cpp
@@ -751,6 +751,31 @@ void OpenGLGraphicsManager::displayMessageOnOSD(const char *msg) {
#endif
}
+void OpenGLGraphicsManager::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {
+#ifdef USE_OSD
+ warning("implement copyRectToOSD"); //TODO
+#endif
+}
+
+void OpenGLGraphicsManager::clearOSD() {
+#ifdef USE_OSD
+ // HACK: Actually no client code should use graphics functions from
+ // another thread. But the MT-32 emulator still does, thus we need to
+ // make sure this doesn't happen while a updateScreen call is done.
+ Common::StackLock lock(_osdMutex);
+
+ Graphics::Surface *dst = _osd->getSurface();
+ _osd->fill(0);
+ _osd->flagDirty();
+
+ // Init the OSD display parameters.
+ _osdAlpha = kOSDInitialAlpha;
+ _osdFadeStartTime = g_system->getMillis() + kOSDFadeOutDelay;
+#endif
+}
+
+Graphics::PixelFormat OpenGLGraphicsManager::getOSDFormat() { return Graphics::PixelFormat(); } //TODO
+
void OpenGLGraphicsManager::setPalette(const byte *colors, uint start, uint num) {
assert(_gameScreen->hasPalette());
diff --git a/backends/graphics/opengl/opengl-graphics.h b/backends/graphics/opengl/opengl-graphics.h
index 35435c156e..55d2c5c826 100644
--- a/backends/graphics/opengl/opengl-graphics.h
+++ b/backends/graphics/opengl/opengl-graphics.h
@@ -115,6 +115,9 @@ public:
virtual void setCursorPalette(const byte *colors, uint start, uint num);
virtual void displayMessageOnOSD(const char *msg);
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h);
+ virtual void clearOSD();
+ virtual Graphics::PixelFormat getOSDFormat();
// PaletteManager interface
virtual void setPalette(const byte *colors, uint start, uint num);
diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
index 5b591e77ff..b4b0d33b85 100644
--- a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
+++ b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
@@ -121,7 +121,7 @@ SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSou
:
SdlGraphicsManager(sdlEventSource, window),
#ifdef USE_OSD
- _osdSurface(0), _osdAlpha(SDL_ALPHA_TRANSPARENT), _osdFadeStartTime(0),
+ _osdSurface(0), _osdMessageSurface(nullptr), _osdMessageAlpha(SDL_ALPHA_TRANSPARENT), _osdMessageFadeStartTime(0),
#endif
_hwscreen(0),
#if SDL_VERSION_ATLEAST(2, 0, 0)
@@ -883,13 +883,16 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() {
_osdSurface = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA,
_hwscreen->w,
_hwscreen->h,
- 16,
- _hwscreen->format->Rmask,
- _hwscreen->format->Gmask,
- _hwscreen->format->Bmask,
- _hwscreen->format->Amask);
+ 32,
+ 0xFF000000,
+ 0x00FF0000,
+ 0x0000FF00,
+ 0x000000FF
+ );
if (_osdSurface == NULL)
error("allocating _osdSurface failed");
+
+ _osdFormat = getSurfaceFormat(_osdSurface);
SDL_SetColorKey(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, kOSDColorKey);
#endif
@@ -946,6 +949,11 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() {
SDL_FreeSurface(_osdSurface);
_osdSurface = NULL;
}
+
+ if (_osdMessageSurface) {
+ SDL_FreeSurface(_osdMessageSurface);
+ _osdMessageSurface = NULL;
+ }
#endif
DestroyScalers();
@@ -1059,21 +1067,33 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
#ifdef USE_OSD
// OSD visible (i.e. non-transparent)?
- if (_osdAlpha != SDL_ALPHA_TRANSPARENT) {
+ if (_osdMessageAlpha != SDL_ALPHA_TRANSPARENT) {
// Updated alpha value
- const int diff = SDL_GetTicks() - _osdFadeStartTime;
+ const int diff = SDL_GetTicks() - _osdMessageFadeStartTime;
if (diff > 0) {
if (diff >= kOSDFadeOutDuration) {
// Back to full transparency
- _osdAlpha = SDL_ALPHA_TRANSPARENT;
+ _osdMessageAlpha = SDL_ALPHA_TRANSPARENT;
} else {
// Do a linear fade out...
const int startAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100;
- _osdAlpha = startAlpha + diff * (SDL_ALPHA_TRANSPARENT - startAlpha) / kOSDFadeOutDuration;
+ _osdMessageAlpha = startAlpha + diff * (SDL_ALPHA_TRANSPARENT - startAlpha) / kOSDFadeOutDuration;
}
- SDL_SetAlpha(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, _osdAlpha);
_forceFull = true;
}
+
+ if (_osdMessageAlpha == SDL_ALPHA_TRANSPARENT) {
+ removeOSDMessage();
+ } else {
+ if (_osdMessageSurface && _osdSurface) {
+ SDL_Rect dstRect;
+ dstRect.x = (_osdSurface->w - _osdMessageSurface->w) / 2;
+ dstRect.y = (_osdSurface->h - _osdMessageSurface->h) / 2;
+ dstRect.w = _osdMessageSurface->w;
+ dstRect.h = _osdMessageSurface->h;
+ blitOSDMessage(dstRect);
+ }
+ }
}
#endif
@@ -1179,9 +1199,7 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
drawMouse();
#ifdef USE_OSD
- if (_osdAlpha != SDL_ALPHA_TRANSPARENT) {
- SDL_BlitSurface(_osdSurface, 0, _hwscreen, 0);
- }
+ SDL_BlitSurface(_osdSurface, 0, _hwscreen, 0);
#endif
#ifdef USE_SDL_DEBUG_FOCUSRECT
@@ -2121,26 +2139,11 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const char *msg) {
Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends
- uint i;
-
- // Lock the OSD surface for drawing
- if (SDL_LockSurface(_osdSurface))
- error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
-
- Graphics::Surface dst;
- dst.init(_osdSurface->w, _osdSurface->h, _osdSurface->pitch, _osdSurface->pixels,
- Graphics::PixelFormat(_osdSurface->format->BytesPerPixel,
- 8 - _osdSurface->format->Rloss, 8 - _osdSurface->format->Gloss,
- 8 - _osdSurface->format->Bloss, 8 - _osdSurface->format->Aloss,
- _osdSurface->format->Rshift, _osdSurface->format->Gshift,
- _osdSurface->format->Bshift, _osdSurface->format->Ashift));
+ removeOSDMessage();
// The font we are going to use:
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
- // Clear everything with the "transparent" color, i.e. the colorkey
- SDL_FillRect(_osdSurface, 0, kOSDColorKey);
-
// Split the message into separate lines.
Common::Array<Common::String> lines;
const char *ptr;
@@ -2159,44 +2162,184 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const char *msg) {
const int lineHeight = font->getFontHeight() + 2 * lineSpacing;
int width = 0;
int height = lineHeight * lines.size() + 2 * vOffset;
+ uint i;
for (i = 0; i < lines.size(); i++) {
width = MAX(width, font->getStringWidth(lines[i]) + 14);
}
// Clip the rect
- if (width > dst.w)
- width = dst.w;
- if (height > dst.h)
- height = dst.h;
+ if (width > _osdSurface->w)
+ width = _osdSurface->w;
+ if (height > _osdSurface->h)
+ height = _osdSurface->h;
+
+ _osdMessageSurface = SDL_CreateRGBSurface(
+ SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA,
+ width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF
+ );
+
+ // Lock the surface
+ if (SDL_LockSurface(_osdMessageSurface))
+ error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
// Draw a dark gray rect
- // TODO: Rounded corners ? Border?
- SDL_Rect osdRect;
- osdRect.x = (dst.w - width) / 2;
- osdRect.y = (dst.h - height) / 2;
- osdRect.w = width;
- osdRect.h = height;
- SDL_FillRect(_osdSurface, &osdRect, SDL_MapRGB(_osdSurface->format, 64, 64, 64));
+ // TODO: Rounded corners ? Border?
+ SDL_FillRect(_osdMessageSurface, nullptr, SDL_MapRGB(_osdMessageSurface->format, 64, 64, 64));
+
+ Graphics::Surface dst;
+ dst.init(_osdMessageSurface->w, _osdMessageSurface->h, _osdMessageSurface->pitch, _osdMessageSurface->pixels,
+ Graphics::PixelFormat(_osdMessageSurface->format->BytesPerPixel,
+ 8 - _osdMessageSurface->format->Rloss, 8 - _osdMessageSurface->format->Gloss,
+ 8 - _osdMessageSurface->format->Bloss, 8 - _osdMessageSurface->format->Aloss,
+ _osdMessageSurface->format->Rshift, _osdMessageSurface->format->Gshift,
+ _osdMessageSurface->format->Bshift, _osdMessageSurface->format->Ashift));
// Render the message, centered, and in white
for (i = 0; i < lines.size(); i++) {
font->drawString(&dst, lines[i],
- osdRect.x, osdRect.y + i * lineHeight + vOffset + lineSpacing, osdRect.w,
- SDL_MapRGB(_osdSurface->format, 255, 255, 255),
- Graphics::kTextAlignCenter);
+ 0, 0 + i * lineHeight + vOffset + lineSpacing, width,
+ SDL_MapRGB(_osdMessageSurface->format, 255, 255, 255),
+ Graphics::kTextAlignCenter);
+ }
+
+ // Finished drawing, so unlock the OSD message surface
+ SDL_UnlockSurface(_osdMessageSurface);
+
+ // Init the OSD display parameters, and the fade out
+ _osdMessageAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100;
+ _osdMessageFadeStartTime = SDL_GetTicks() + kOSDFadeOutDelay;
+
+ // Ensure a full redraw takes place next time the screen is updated
+ _forceFull = true;
+}
+
+void SurfaceSdlGraphicsManager::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {
+ assert(_transactionMode == kTransactionNone);
+ assert(buf);
+
+ Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends
+
+ // Lock the OSD surface for drawing
+ if (SDL_LockSurface(_osdSurface))
+ error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
+
+ // Mark that area as "dirty"
+ addDirtyRect(x, y, w, h, true);
+
+#ifdef USE_RGB_COLOR
+ byte *dst = (byte *)_osdSurface->pixels + y * _osdSurface->pitch + x * _osdSurface->format->BytesPerPixel;
+ if (_videoMode.screenWidth == w && pitch == _osdSurface->pitch) {
+ memcpy(dst, buf, h*pitch);
+ } else {
+ const byte *src = (const byte *)buf;
+ do {
+ memcpy(dst, src, w * _osdSurface->format->BytesPerPixel);
+ src += pitch;
+ dst += _osdSurface->pitch;
+ } while (--h);
}
+#else
+ byte *dst = (byte *)_osdSurface->pixels + y * _osdSurface->pitch + x;
+ if (_osdSurface->pitch == pitch && pitch == w) {
+ memcpy(dst, buf, h*w);
+ } else {
+ const byte *src = (const byte *)buf;
+ do {
+ memcpy(dst, src, w);
+ src += pitch;
+ dst += _osdSurface->pitch;
+ } while (--h);
+ }
+#endif
// Finished drawing, so unlock the OSD surface again
SDL_UnlockSurface(_osdSurface);
+}
- // Init the OSD display parameters, and the fade out
- _osdAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100;
- _osdFadeStartTime = SDL_GetTicks() + kOSDFadeOutDelay;
- SDL_SetAlpha(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, _osdAlpha);
+void SurfaceSdlGraphicsManager::clearOSD() {
+ assert(_transactionMode == kTransactionNone);
+
+ Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends
+
+ // Lock the OSD surface for drawing
+ if (SDL_LockSurface(_osdSurface))
+ error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
+
+ // Clear everything with the "transparent" color, i.e. the colorkey
+ SDL_FillRect(_osdSurface, 0, kOSDColorKey);
+
+ // Finished drawing, so unlock the OSD surface again
+ SDL_UnlockSurface(_osdSurface);
+
+ // Remove OSD message as well
+ removeOSDMessage();
// Ensure a full redraw takes place next time the screen is updated
_forceFull = true;
}
+
+void SurfaceSdlGraphicsManager::removeOSDMessage() {
+ // Lock the OSD surface for drawing
+ if (SDL_LockSurface(_osdSurface))
+ error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
+
+ //remove previous message
+ if (_osdMessageSurface) {
+ SDL_Rect osdRect;
+ osdRect.x = (_osdSurface->w - _osdMessageSurface->w) / 2;
+ osdRect.y = (_osdSurface->h - _osdMessageSurface->h) / 2;
+ osdRect.w = _osdMessageSurface->w;
+ osdRect.h = _osdMessageSurface->h;
+ SDL_FillRect(_osdSurface, &osdRect, kOSDColorKey);
+ SDL_FreeSurface(_osdMessageSurface);
+ }
+
+ _osdMessageSurface = NULL;
+ _osdMessageAlpha = SDL_ALPHA_TRANSPARENT;
+
+ // Finished drawing, so unlock the OSD surface again
+ SDL_UnlockSurface(_osdSurface);
+}
+
+void SurfaceSdlGraphicsManager::blitOSDMessage(SDL_Rect dstRect) {
+ SDL_Surface *src = _osdMessageSurface;
+ SDL_Surface *dst = _osdSurface;
+ Graphics::PixelFormat srcFormat = getSurfaceFormat(src);
+ Graphics::PixelFormat dstFormat = _osdFormat;
+ for (int y = 0; y < dstRect.h; y++) {
+ const byte *srcRow = (const byte *)((const byte *)(src->pixels) + y * src->pitch); //src (x, y) == (0, 0)
+ byte *dstRow = (byte *)((const byte *)(dst->pixels) + (dstRect.y + y) * dst->pitch + dstRect.x * dstFormat.bytesPerPixel);
+
+ for (int x = 0; x < dstRect.w; x++) {
+ uint32 srcColor;
+ if (dst->format->BytesPerPixel == 2)
+ srcColor = READ_UINT16(srcRow);
+ else if (dst->format->BytesPerPixel == 3)
+ srcColor = READ_UINT24(srcRow);
+ else
+ srcColor = READ_UINT32(srcRow);
+
+ srcRow += srcFormat.bytesPerPixel;
+
+ // Convert that color to the new format
+ byte r, g, b, a;
+ srcFormat.colorToARGB(srcColor, a, r, g, b);
+ a = _osdMessageAlpha; //this is the important line, because apart from that this is plain surface copying
+ uint32 color = dstFormat.ARGBToColor(a, r, g, b);
+
+ if (dstFormat.bytesPerPixel == 2)
+ *((uint16 *)dstRow) = color;
+ else
+ *((uint32 *)dstRow) = color;
+
+ dstRow += dstFormat.bytesPerPixel;
+ }
+ }
+}
+
+Graphics::PixelFormat SurfaceSdlGraphicsManager::getOSDFormat() {
+ return _osdFormat;
+}
#endif
bool SurfaceSdlGraphicsManager::handleScalerHotkeys(Common::KeyCode key) {
@@ -2492,4 +2635,22 @@ void SurfaceSdlGraphicsManager::SDL_UpdateRects(SDL_Surface *screen, int numrect
}
#endif // SDL_VERSION_ATLEAST(2, 0, 0)
+Graphics::PixelFormat SurfaceSdlGraphicsManager::getSurfaceFormat(SDL_Surface *surface) {
+ Graphics::PixelFormat format;
+ if (surface) {
+ format.bytesPerPixel = surface->format->BytesPerPixel;
+
+ format.rLoss = surface->format->Rloss;
+ format.gLoss = surface->format->Gloss;
+ format.bLoss = surface->format->Bloss;
+ format.aLoss = surface->format->Aloss;
+
+ format.rShift = surface->format->Rshift;
+ format.gShift = surface->format->Gshift;
+ format.bShift = surface->format->Bshift;
+ format.aShift = surface->format->Ashift;
+ }
+ return format;
+}
+
#endif
diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.h b/backends/graphics/surfacesdl/surfacesdl-graphics.h
index 25d6ff041c..d8f826aca0 100644
--- a/backends/graphics/surfacesdl/surfacesdl-graphics.h
+++ b/backends/graphics/surfacesdl/surfacesdl-graphics.h
@@ -145,6 +145,9 @@ public:
#ifdef USE_OSD
virtual void displayMessageOnOSD(const char *msg);
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h);
+ virtual void clearOSD();
+ virtual Graphics::PixelFormat getOSDFormat();
#endif
// Override from Common::EventObserver
@@ -160,12 +163,14 @@ public:
protected:
#ifdef USE_OSD
- /** Surface containing the OSD message */
+ /** Surface containing the OSD */
SDL_Surface *_osdSurface;
- /** Transparency level of the OSD */
- uint8 _osdAlpha;
+ /** Surface containing the OSD message */
+ SDL_Surface *_osdMessageSurface;
+ /** Transparency level of the OSD message */
+ uint8 _osdMessageAlpha;
/** When to start the fade out */
- uint32 _osdFadeStartTime;
+ uint32 _osdMessageFadeStartTime;
/** Enum with OSD options */
enum {
kOSDFadeOutDelay = 2 * 1000, /** < Delay before the OSD is faded out (in milliseconds) */
@@ -173,6 +178,11 @@ protected:
kOSDColorKey = 1, /** < Transparent color key */
kOSDInitialAlpha = 80 /** < Initial alpha level, in percent */
};
+ /** OSD pixel format */
+ Graphics::PixelFormat _osdFormat;
+
+ void removeOSDMessage();
+ void blitOSDMessage(SDL_Rect dstRect);
#endif
/** Hardware screen */
@@ -358,6 +368,8 @@ protected:
Common::Rect _focusRect;
#endif
+ static Graphics::PixelFormat getSurfaceFormat(SDL_Surface *surface);
+
virtual void addDirtyRect(int x, int y, int w, int h, bool realCoordinates = false);
virtual void drawMouse();
diff --git a/backends/modular-backend.cpp b/backends/modular-backend.cpp
index d8be9ca7ed..e1bdf15571 100644
--- a/backends/modular-backend.cpp
+++ b/backends/modular-backend.cpp
@@ -241,6 +241,18 @@ void ModularBackend::displayMessageOnOSD(const char *msg) {
_graphicsManager->displayMessageOnOSD(msg);
}
+void ModularBackend::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {
+ _graphicsManager->copyRectToOSD(buf, pitch, x, y, w, h);
+}
+
+void ModularBackend::clearOSD() {
+ _graphicsManager->clearOSD();
+}
+
+Graphics::PixelFormat ModularBackend::getOSDFormat() {
+ return _graphicsManager->getOSDFormat();
+}
+
void ModularBackend::quit() {
exit(0);
}
diff --git a/backends/modular-backend.h b/backends/modular-backend.h
index 20e8b7357d..9cde27915f 100644
--- a/backends/modular-backend.h
+++ b/backends/modular-backend.h
@@ -127,6 +127,9 @@ public:
virtual void quit();
virtual void displayMessageOnOSD(const char *msg);
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h);
+ virtual void clearOSD();
+ virtual Graphics::PixelFormat getOSDFormat();
//@}
diff --git a/backends/module.mk b/backends/module.mk
index 4c1ca42f06..c402a10a90 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -19,6 +19,65 @@ MODULE_OBJS := \
saves/default/default-saves.o \
timer/default/default-timer.o
+ifdef USE_LIBCURL
+MODULE_OBJS += \
+ cloud/cloudmanager.o \
+ cloud/iso8601.o \
+ cloud/storage.o \
+ cloud/storagefile.o \
+ cloud/downloadrequest.o \
+ cloud/folderdownloadrequest.o \
+ cloud/savessyncrequest.o \
+ cloud/box/boxstorage.o \
+ cloud/box/boxlistdirectorybyidrequest.o \
+ cloud/box/boxtokenrefresher.o \
+ cloud/box/boxuploadrequest.o \
+ cloud/dropbox/dropboxstorage.o \
+ cloud/dropbox/dropboxcreatedirectoryrequest.o \
+ cloud/dropbox/dropboxinforequest.o \
+ cloud/dropbox/dropboxlistdirectoryrequest.o \
+ cloud/dropbox/dropboxuploadrequest.o \
+ cloud/googledrive/googledrivelistdirectorybyidrequest.o \
+ cloud/googledrive/googledrivestorage.o \
+ cloud/googledrive/googledrivetokenrefresher.o \
+ cloud/googledrive/googledriveuploadrequest.o \
+ cloud/id/idstorage.o \
+ cloud/id/idcreatedirectoryrequest.o \
+ cloud/id/iddownloadrequest.o \
+ cloud/id/idlistdirectoryrequest.o \
+ cloud/id/idresolveidrequest.o \
+ cloud/id/idstreamfilerequest.o \
+ cloud/onedrive/onedrivestorage.o \
+ cloud/onedrive/onedrivecreatedirectoryrequest.o \
+ cloud/onedrive/onedrivetokenrefresher.o \
+ cloud/onedrive/onedrivelistdirectoryrequest.o \
+ cloud/onedrive/onedriveuploadrequest.o \
+ networking/curl/connectionmanager.o \
+ networking/curl/networkreadstream.o \
+ networking/curl/cloudicon.o \
+ networking/curl/curlrequest.o \
+ networking/curl/curljsonrequest.o \
+ networking/curl/request.o
+endif
+
+ifdef USE_SDL_NET
+MODULE_OBJS += \
+ networking/sdl_net/client.o \
+ networking/sdl_net/getclienthandler.o \
+ networking/sdl_net/handlers/createdirectoryhandler.o \
+ networking/sdl_net/handlers/downloadfilehandler.o \
+ networking/sdl_net/handlers/filesajaxpagehandler.o \
+ networking/sdl_net/handlers/filesbasehandler.o \
+ networking/sdl_net/handlers/filespagehandler.o \
+ networking/sdl_net/handlers/indexpagehandler.o \
+ networking/sdl_net/handlers/listajaxhandler.o \
+ networking/sdl_net/handlers/resourcehandler.o \
+ networking/sdl_net/handlers/uploadfilehandler.o \
+ networking/sdl_net/handlerutils.o \
+ networking/sdl_net/localwebserver.o \
+ networking/sdl_net/reader.o \
+ networking/sdl_net/uploadfileclienthandler.o
+endif
ifdef USE_ELF_LOADER
MODULE_OBJS += \
@@ -91,6 +150,42 @@ MODULE_OBJS += \
endif
endif
+# openUrl
+ifeq ($(BACKEND),android)
+MODULE_OBJS += \
+ networking/browser/openurl-android.o
+else
+ifdef MACOSX
+MODULE_OBJS += \
+ networking/browser/openurl-osx.o
+else
+ifdef WIN32
+MODULE_OBJS += \
+ networking/browser/openurl-windows.o
+else
+ ifdef POSIX
+ MODULE_OBJS += \
+ networking/browser/openurl-posix.o
+ else
+ # create_project doesn't know something about `else`
+ ifndef WIN32
+ MODULE_OBJS += \
+ networking/browser/openurl-default.o
+ endif
+ endif
+endif
+endif
+endif
+
+# Connection::isLimited
+ifeq ($(BACKEND),android)
+MODULE_OBJS += \
+ networking/connection/islimited-android.o
+else
+MODULE_OBJS += \
+ networking/connection/islimited-default.o
+endif
+
ifdef POSIX
MODULE_OBJS += \
fs/posix/posix-fs.o \
diff --git a/backends/networking/browser/openurl-android.cpp b/backends/networking/browser/openurl-android.cpp
new file mode 100644
index 0000000000..64e683238b
--- /dev/null
+++ b/backends/networking/browser/openurl-android.cpp
@@ -0,0 +1,35 @@
+/* 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/networking/browser/openurl.h"
+#include "backends/platform/android/jni.h"
+
+namespace Networking {
+namespace Browser {
+
+bool openUrl(const Common::String &url) {
+ return JNI::openUrl(url.c_str());
+}
+
+} // End of namespace Browser
+} // End of namespace Networking
+
diff --git a/backends/networking/browser/openurl-default.cpp b/backends/networking/browser/openurl-default.cpp
new file mode 100644
index 0000000000..c430953196
--- /dev/null
+++ b/backends/networking/browser/openurl-default.cpp
@@ -0,0 +1,36 @@
+/* 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/networking/browser/openurl.h"
+#include "common/textconsole.h"
+
+namespace Networking {
+namespace Browser {
+
+bool openUrl(const Common::String &url) {
+ warning("Networking::Browser::openUrl(): not implemented");
+ return false;
+}
+
+} // End of namespace Browser
+} // End of namespace Networking
+
diff --git a/backends/networking/browser/openurl-osx.cpp b/backends/networking/browser/openurl-osx.cpp
new file mode 100644
index 0000000000..8d786d7fd2
--- /dev/null
+++ b/backends/networking/browser/openurl-osx.cpp
@@ -0,0 +1,49 @@
+/* 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/networking/browser/openurl.h"
+#include <CoreFoundation/CFBundle.h>
+#include <ApplicationServices/ApplicationServices.h>
+
+namespace Networking {
+namespace Browser {
+
+using namespace std;
+
+bool openUrl(const Common::String &url) {
+ CFURLRef urlRef = CFURLCreateWithBytes (
+ NULL,
+ (UInt8*)url.c_str(),
+ url.size(),
+ kCFStringEncodingASCII,
+ NULL
+ );
+ int result = LSOpenCFURLRef(urlRef, 0);
+ CFRelease(urlRef);
+ return result == 0;
+}
+
+} // End of namespace Browser
+} // End of namespace Networking
+
diff --git a/backends/networking/browser/openurl-posix.cpp b/backends/networking/browser/openurl-posix.cpp
new file mode 100644
index 0000000000..429a379fcf
--- /dev/null
+++ b/backends/networking/browser/openurl-posix.cpp
@@ -0,0 +1,77 @@
+/* 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/networking/browser/openurl.h"
+#include "common/textconsole.h"
+#include <stdlib.h>
+
+namespace Networking {
+namespace Browser {
+
+namespace {
+bool launch(const Common::String client, const Common::String &url) {
+ // FIXME: system's input must be heavily escaped
+ // well, when url's specified by user
+ // it's OK now (urls are hardcoded somewhere in GUI)
+ Common::String cmd = client + " " + url;
+ return (system(cmd.c_str()) != -1);
+}
+}
+
+bool openUrl(const Common::String &url) {
+ // inspired by Qt's "qdesktopservices_x11.cpp"
+
+ // try "standards"
+ if (launch("xdg-open", url))
+ return true;
+ if (launch(getenv("DEFAULT_BROWSER"), url))
+ return true;
+ if (launch(getenv("BROWSER"), url))
+ return true;
+
+ // try desktop environment specific tools
+ if (launch("gnome-open", url)) // gnome
+ return true;
+ if (launch("kfmclient openURL", url)) // kde
+ return true;
+ if (launch("exo-open", url)) // xfce
+ return true;
+
+ // try browser names
+ if (launch("firefox", url))
+ return true;
+ if (launch("mozilla", url))
+ return true;
+ if (launch("netscape", url))
+ return true;
+ if (launch("opera", url))
+ return true;
+
+ warning("Networking::Browser::openUrl() (POSIX) failed to open URL");
+ return false;
+}
+
+} // End of namespace Browser
+} // End of namespace Networking
+
diff --git a/backends/networking/browser/openurl-windows.cpp b/backends/networking/browser/openurl-windows.cpp
new file mode 100644
index 0000000000..53d76f076b
--- /dev/null
+++ b/backends/networking/browser/openurl-windows.cpp
@@ -0,0 +1,43 @@
+/* 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/networking/browser/openurl.h"
+#include "common/textconsole.h"
+#include <windows.h>
+#include <Shellapi.h>
+
+namespace Networking {
+namespace Browser {
+
+bool openUrl(const Common::String &url) {
+ const uint64 result = (uint64)ShellExecute(0, 0, /*(wchar_t*)nativeFilePath.utf16()*/url.c_str(), 0, 0, SW_SHOWNORMAL);
+ // ShellExecute returns a value greater than 32 if successful
+ if (result <= 32) {
+ warning("ShellExecute failed: error = %u", result);
+ return false;
+ }
+ return true;
+}
+
+} // End of namespace Browser
+} // End of namespace Networking
+
diff --git a/backends/networking/browser/openurl.h b/backends/networking/browser/openurl.h
new file mode 100644
index 0000000000..15b4bf385b
--- /dev/null
+++ b/backends/networking/browser/openurl.h
@@ -0,0 +1,41 @@
+/* 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 NETWORKING_BROWSER_OPENURL_H
+#define NETWORKING_BROWSER_OPENURL_H
+
+#include "common/str.h"
+
+namespace Networking {
+namespace Browser {
+
+/**
+ * Opens URL in default browser (if available on the target system).
+ *
+ * Returns true on success.
+ */
+bool openUrl(const Common::String &url);
+
+} // End of namespace Browser
+} // End of namespace Networking
+
+#endif /*NETWORKING_BROWSER_OPENURL_H*/
diff --git a/backends/networking/connection/islimited-android.cpp b/backends/networking/connection/islimited-android.cpp
new file mode 100644
index 0000000000..8989f218ec
--- /dev/null
+++ b/backends/networking/connection/islimited-android.cpp
@@ -0,0 +1,35 @@
+/* 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/networking/connection/islimited.h"
+#include "backends/platform/android/jni.h"
+
+namespace Networking {
+namespace Connection {
+
+bool isLimited() {
+ return JNI::isConnectionLimited();
+}
+
+} // End of namespace Connection
+} // End of namespace Networking
+
diff --git a/backends/networking/connection/islimited-default.cpp b/backends/networking/connection/islimited-default.cpp
new file mode 100644
index 0000000000..a993077fff
--- /dev/null
+++ b/backends/networking/connection/islimited-default.cpp
@@ -0,0 +1,36 @@
+/* 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/networking/connection/islimited.h"
+#include "common/textconsole.h"
+
+namespace Networking {
+namespace Connection {
+
+bool isLimited() {
+ warning("Networking::Connection::isLimited(): not limited by default");
+ return false;
+}
+
+} // End of namespace Connection
+} // End of namespace Networking
+
diff --git a/backends/networking/connection/islimited.h b/backends/networking/connection/islimited.h
new file mode 100644
index 0000000000..b23d31d157
--- /dev/null
+++ b/backends/networking/connection/islimited.h
@@ -0,0 +1,39 @@
+/* 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 NETWORKING_CONNECTION_ISLIMITED_H
+#define NETWORKING_CONNECTION_ISLIMITED_H
+
+namespace Networking {
+namespace Connection {
+
+/**
+* Returns whether connection's limited (if available on the target system).
+*
+* Returns true if connection seems limited.
+*/
+bool isLimited();
+
+} // End of namespace Connection
+} // End of namespace Networking
+
+#endif /*NETWORKING_CONNECTION_ISLIMITED_H*/
diff --git a/backends/networking/curl/cloudicon.cpp b/backends/networking/curl/cloudicon.cpp
new file mode 100644
index 0000000000..1c1ecf2f85
--- /dev/null
+++ b/backends/networking/curl/cloudicon.cpp
@@ -0,0 +1,171 @@
+/* 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/networking/curl/cloudicon.h"
+#include "backends/cloud/cloudmanager.h"
+#include "common/memstream.h"
+#include "gui/ThemeEngine.h"
+#include "gui/gui-manager.h"
+#include "image/png.h"
+
+namespace Networking {
+
+const float CloudIcon::ALPHA_STEP = 0.025;
+const float CloudIcon::ALPHA_MAX = 1;
+const float CloudIcon::ALPHA_MIN = 0.6;
+
+CloudIcon::CloudIcon():
+ _wasVisible(false), _iconsInited(false), _showingDisabled(false),
+ _currentAlpha(0), _alphaRising(true), _disabledFrames(0) {
+ initIcons();
+}
+
+CloudIcon::~CloudIcon() {}
+
+bool CloudIcon::draw() {
+ bool stop = false;
+ initIcons();
+
+ if (CloudMan.isWorking() || _disabledFrames > 0) {
+ if (g_system) {
+ if (!_wasVisible) {
+ g_system->clearOSD();
+ _wasVisible = true;
+ }
+ --_disabledFrames;
+ if (_alphaRising) {
+ if (_currentAlpha < ALPHA_MIN)
+ _currentAlpha += 5 * ALPHA_STEP;
+ else
+ _currentAlpha += ALPHA_STEP;
+ if (_currentAlpha > ALPHA_MAX) {
+ _currentAlpha = ALPHA_MAX;
+ _alphaRising = false;
+ }
+ } else {
+ _currentAlpha -= ALPHA_STEP;
+ if (_currentAlpha < ALPHA_MIN) {
+ _currentAlpha = ALPHA_MIN;
+ _alphaRising = true;
+ }
+ }
+ } else {
+ _wasVisible = false;
+ }
+ } else {
+ _wasVisible = false;
+ _currentAlpha -= 5 * ALPHA_STEP;
+ if (_currentAlpha <= 0) {
+ _currentAlpha = 0;
+ stop = true;
+ }
+ }
+
+ if (g_system) {
+ Graphics::TransparentSurface *surface = &_icon;
+ makeAlphaIcon((_showingDisabled ? _disabledIcon : _icon), _currentAlpha);
+ if (_alphaIcon.getPixels())
+ surface = &_alphaIcon;
+ if (surface && surface->getPixels()) {
+ int x = g_system->getOverlayWidth() - surface->w - 10, y = 10;
+ g_system->copyRectToOSD(surface->getPixels(), surface->pitch, x, y, surface->w, surface->h);
+ }
+ }
+
+ if (stop)
+ _showingDisabled = false;
+ return stop;
+}
+
+void CloudIcon::showDisabled() {
+ _showingDisabled = true;
+ _disabledFrames = 20 * 3; //3 seconds 20 fps
+}
+
+#include "backends/networking/curl/cloudicon_data.h"
+#include "backends/networking/curl/cloudicon_disabled_data.h"
+
+void CloudIcon::initIcons() {
+ if (_iconsInited)
+ return;
+ loadIcon(_icon, cloudicon_data, ARRAYSIZE(cloudicon_data));
+ loadIcon(_disabledIcon, cloudicon_disabled_data, ARRAYSIZE(cloudicon_disabled_data));
+ _iconsInited = true;
+}
+
+void CloudIcon::loadIcon(Graphics::TransparentSurface &icon, byte *data, uint32 size) {
+ Image::PNGDecoder decoder;
+ Common::MemoryReadStream stream(data, size);
+ if (!decoder.loadStream(stream))
+ error("CloudIcon::loadIcon: error decoding PNG");
+
+ Graphics::TransparentSurface *s = new Graphics::TransparentSurface(*decoder.getSurface(), true);
+ if (s) {
+ Graphics::PixelFormat f = g_system->getOSDFormat();
+ if (f != s->format) {
+ Graphics::TransparentSurface *s2 = s->convertTo(f);
+ if (s2)
+ icon.copyFrom(*s2);
+ else
+ warning("CloudIcon::loadIcon: failed converting TransparentSurface");
+ delete s2;
+ } else {
+ icon.copyFrom(*s);
+ }
+ delete s;
+ } else {
+ warning("CloudIcon::loadIcon: failed reading TransparentSurface from PNGDecoder");
+ }
+}
+
+void CloudIcon::makeAlphaIcon(Graphics::TransparentSurface &icon, float alpha) {
+ _alphaIcon.copyFrom(icon);
+
+ byte *pixels = (byte *)_alphaIcon.getPixels();
+ for (int y = 0; y < _alphaIcon.h; y++) {
+ byte *row = pixels + y * _alphaIcon.pitch;
+ for (int x = 0; x < _alphaIcon.w; x++) {
+ uint32 srcColor;
+ if (_alphaIcon.format.bytesPerPixel == 2)
+ srcColor = READ_UINT16(row);
+ else if (_alphaIcon.format.bytesPerPixel == 3)
+ srcColor = READ_UINT24(row);
+ else
+ srcColor = READ_UINT32(row);
+
+ // Update color's alpha
+ byte r, g, b, a;
+ _alphaIcon.format.colorToARGB(srcColor, a, r, g, b);
+ a = (byte)(a * alpha);
+ uint32 color = _alphaIcon.format.ARGBToColor(a, r, g, b);
+
+ if (_alphaIcon.format.bytesPerPixel == 2)
+ *((uint16 *)row) = color;
+ else
+ *((uint32 *)row) = color;
+
+ row += _alphaIcon.format.bytesPerPixel;
+ }
+ }
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/cloudicon.h b/backends/networking/curl/cloudicon.h
new file mode 100644
index 0000000000..7e4d7387a3
--- /dev/null
+++ b/backends/networking/curl/cloudicon.h
@@ -0,0 +1,70 @@
+/* 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_NETWORKING_CURL_CLOUDICON_H
+#define BACKENDS_NETWORKING_CURL_CLOUDICON_H
+
+#include "graphics/transparent_surface.h"
+
+namespace Networking {
+
+class CloudIcon {
+ static const float ALPHA_STEP, ALPHA_MAX, ALPHA_MIN;
+
+ bool _wasVisible, _iconsInited, _showingDisabled;
+ Graphics::TransparentSurface _icon, _disabledIcon, _alphaIcon;
+ float _currentAlpha;
+ bool _alphaRising;
+ int _disabledFrames;
+
+ void initIcons();
+ void loadIcon(Graphics::TransparentSurface &icon, byte *data, uint32 size);
+ void makeAlphaIcon(Graphics::TransparentSurface &icon, float alpha);
+
+public:
+ CloudIcon();
+ ~CloudIcon();
+
+ /**
+ * This method is called from ConnectionManager every time
+ * its own timer calls the handle() method. The primary
+ * responsibility of this draw() method is to draw cloud icon
+ * on ScummVM's OSD when current cloud Storage is working.
+ *
+ * As we don't want ConnectionManager to work when no
+ * Requests are running, we'd like to stop the timer. But then
+ * this icon wouldn't have time to disappear smoothly. So,
+ * in order to do that, ConnectionManager stop its timer
+ * only when this draw() method returns true, indicating that
+ * the CloudIcon has disappeared and the timer could be stopped.
+ *
+ * @return true if ConnMan's timer could be stopped.
+ */
+ bool draw();
+
+ /** Draw a "cloud disabled" icon instead of "cloud syncing" one. */
+ void showDisabled();
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/cloudicon_data.h b/backends/networking/curl/cloudicon_data.h
new file mode 100644
index 0000000000..21d88182a3
--- /dev/null
+++ b/backends/networking/curl/cloudicon_data.h
@@ -0,0 +1,111 @@
+/* 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.
+ *
+ */
+
+// This is a PNG file dumped into array.
+// $ recode data..d1 <dists/cloudicon.png >cloudicon_data.h
+// The tool is from https://github.com/pinard/Recode
+
+byte cloudicon_data[] = {
+ 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68,
+ 82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 6, 0, 0, 0, 115,
+ 122, 122, 244, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8, 8, 8,
+ 124, 8, 100, 136, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11,
+ 18, 0, 0, 11, 18, 1, 210, 221, 126, 252, 0, 0, 0, 22, 116,
+ 69, 88, 116, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109,
+ 101, 0, 48, 54, 47, 48, 51, 47, 49, 54, 159, 192, 233, 192, 0,
+ 0, 0, 28, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101,
+ 0, 65, 100, 111, 98, 101, 32, 70, 105, 114, 101, 119, 111, 114, 107,
+ 115, 32, 67, 83, 54, 232, 188, 178, 140, 0, 0, 4, 50, 73, 68,
+ 65, 84, 88, 133, 197, 151, 109, 104, 150, 101, 20, 199, 127, 247, 227,
+ 179, 105, 51, 23, 65, 181, 150, 224, 154, 214, 132, 194, 249, 33, 165,
+ 22, 189, 231, 194, 210, 250, 16, 171, 180, 55, 42, 152, 68, 65, 100,
+ 52, 233, 5, 146, 144, 144, 26, 249, 169, 62, 164, 80, 89, 152, 25,
+ 18, 226, 42, 49, 87, 88, 180, 94, 96, 96, 246, 234, 180, 70, 50,
+ 66, 214, 55, 247, 22, 133, 247, 255, 244, 225, 58, 247, 158, 107, 143,
+ 219, 243, 60, 186, 192, 3, 135, 251, 220, 215, 117, 223, 231, 127, 238,
+ 235, 252, 239, 235, 58, 39, 105, 250, 206, 56, 147, 146, 55, 85, 252,
+ 108, 13, 112, 59, 176, 12, 88, 2, 204, 7, 230, 248, 220, 48, 208,
+ 15, 244, 2, 221, 64, 23, 48, 86, 137, 211, 228, 146, 158, 178, 43,
+ 80, 7, 172, 3, 218, 129, 179, 43, 241, 9, 140, 0, 155, 129, 87,
+ 128, 193, 146, 15, 47, 248, 178, 100, 0, 237, 64, 39, 112, 14, 96,
+ 174, 20, 217, 153, 228, 162, 0, 50, 25, 2, 58, 128, 45, 83, 1,
+ 228, 53, 121, 10, 170, 129, 183, 128, 213, 126, 47, 215, 49, 224, 39,
+ 224, 19, 160, 7, 56, 2, 204, 0, 154, 128, 27, 128, 229, 110, 215,
+ 120, 64, 181, 132, 149, 184, 30, 120, 4, 248, 183, 24, 40, 185, 248,
+ 243, 147, 86, 160, 154, 144, 195, 91, 252, 43, 5, 140, 2, 31, 2,
+ 27, 129, 195, 83, 125, 141, 75, 19, 240, 2, 129, 47, 179, 61, 144,
+ 4, 216, 7, 172, 44, 14, 34, 105, 232, 62, 41, 128, 109, 192, 189,
+ 14, 126, 2, 24, 0, 30, 3, 246, 150, 1, 46, 150, 54, 15, 184,
+ 1, 200, 251, 216, 123, 192, 253, 19, 2, 152, 183, 119, 66, 0, 237,
+ 192, 27, 110, 159, 0, 250, 128, 187, 128, 67, 167, 8, 158, 201, 98,
+ 224, 3, 160, 209, 131, 72, 128, 53, 68, 156, 200, 153, 192, 181, 206,
+ 68, 167, 219, 50, 49, 96, 226, 78, 19, 135, 162, 103, 138, 117, 169,
+ 137, 46, 19, 163, 38, 82, 19, 63, 152, 120, 220, 196, 12, 159, 63,
+ 104, 98, 149, 137, 99, 238, 211, 28, 163, 46, 243, 145, 147, 192, 117,
+ 157, 68, 173, 219, 195, 18, 143, 74, 28, 137, 230, 139, 181, 77, 162,
+ 71, 98, 165, 68, 141, 68, 78, 98, 145, 196, 107, 18, 59, 252, 30,
+ 137, 3, 18, 207, 72, 140, 249, 125, 173, 99, 33, 65, 114, 209, 110,
+ 131, 192, 218, 65, 2, 105, 4, 108, 245, 165, 74, 125, 165, 206, 5,
+ 214, 2, 151, 3, 7, 128, 119, 128, 95, 253, 189, 169, 228, 105, 224,
+ 85, 183, 171, 128, 29, 192, 29, 4, 82, 142, 18, 246, 151, 177, 164,
+ 126, 151, 1, 220, 3, 188, 79, 32, 222, 40, 97, 167, 235, 243, 151,
+ 207, 2, 190, 5, 154, 35, 231, 223, 0, 45, 101, 242, 127, 12, 152,
+ 75, 97, 191, 88, 12, 124, 237, 254, 18, 96, 21, 176, 35, 227, 192,
+ 50, 207, 15, 38, 126, 49, 113, 56, 202, 243, 221, 38, 154, 139, 114,
+ 223, 82, 130, 23, 153, 214, 155, 88, 20, 221, 255, 104, 226, 104, 116,
+ 223, 106, 42, 144, 112, 169, 137, 196, 131, 248, 56, 10, 166, 222, 196,
+ 141, 21, 128, 77, 165, 223, 155, 120, 42, 34, 246, 158, 200, 247, 18,
+ 19, 228, 21, 178, 60, 223, 151, 41, 1, 190, 112, 251, 60, 224, 171,
+ 104, 238, 116, 36, 33, 240, 224, 32, 240, 25, 176, 31, 120, 194, 199,
+ 27, 161, 112, 26, 102, 167, 154, 1, 127, 186, 253, 98, 9, 240, 81,
+ 2, 97, 43, 149, 231, 61, 128, 129, 104, 108, 78, 28, 64, 44, 25,
+ 105, 218, 74, 56, 156, 13, 252, 76, 248, 43, 42, 145, 140, 176, 249,
+ 226, 137, 188, 133, 20, 12, 123, 68, 9, 225, 171, 127, 7, 46, 40,
+ 227, 180, 82, 112, 128, 153, 126, 189, 148, 194, 105, 57, 12, 5, 18,
+ 246, 71, 68, 185, 214, 237, 191, 166, 65, 190, 98, 237, 243, 235, 205,
+ 209, 88, 127, 188, 19, 246, 250, 53, 39, 177, 194, 237, 157, 37, 118,
+ 193, 83, 213, 183, 37, 102, 73, 92, 39, 145, 196, 152, 57, 75, 193,
+ 82, 246, 249, 21, 75, 89, 104, 41, 205, 150, 178, 222, 82, 250, 163,
+ 241, 211, 213, 253, 150, 178, 201, 82, 174, 180, 148, 185, 150, 146, 248,
+ 120, 183, 165, 133, 20, 116, 153, 24, 113, 123, 150, 137, 103, 77, 28,
+ 55, 113, 141, 137, 173, 38, 134, 60, 61, 167, 178, 236, 3, 38, 54,
+ 152, 184, 213, 255, 253, 39, 221, 55, 142, 213, 101, 42, 252, 5, 99,
+ 132, 202, 101, 45, 97, 175, 94, 14, 172, 0, 118, 1, 15, 185, 78,
+ 71, 86, 19, 138, 217, 196, 117, 179, 99, 146, 204, 126, 125, 188, 30,
+ 168, 35, 236, 255, 181, 132, 3, 233, 15, 224, 54, 202, 87, 64, 229,
+ 164, 5, 216, 9, 92, 232, 224, 67, 192, 66, 188, 88, 205, 69, 185,
+ 26, 180, 148, 14, 207, 81, 206, 82, 230, 89, 202, 110, 75, 185, 108,
+ 26, 249, 191, 202, 82, 222, 181, 148, 243, 163, 220, 119, 56, 22, 150,
+ 78, 172, 7, 144, 216, 34, 177, 205, 237, 188, 196, 2, 137, 61, 18,
+ 15, 158, 6, 243, 31, 144, 216, 37, 209, 224, 190, 18, 137, 237, 142,
+ 49, 254, 92, 50, 115, 211, 164, 69, 233, 71, 64, 43, 140, 23, 165,
+ 127, 19, 26, 142, 231, 8, 117, 192, 84, 82, 69, 56, 118, 215, 3,
+ 55, 17, 54, 160, 172, 40, 253, 148, 80, 168, 78, 44, 74, 171, 59,
+ 39, 237, 11, 170, 129, 55, 129, 251, 40, 108, 205, 2, 254, 1, 126,
+ 243, 96, 122, 129, 163, 62, 223, 0, 92, 237, 160, 141, 17, 112, 38,
+ 219, 129, 135, 139, 193, 1, 146, 170, 151, 43, 106, 76, 106, 139, 198,
+ 229, 192, 73, 20, 96, 12, 152, 177, 253, 56, 101, 26, 147, 36, 191,
+ 177, 226, 214, 108, 13, 133, 214, 172, 220, 75, 35, 14, 90, 190, 53,
+ 203, 189, 84, 113, 119, 156, 53, 167, 173, 192, 21, 252, 95, 205, 105,
+ 178, 225, 204, 182, 231, 255, 1, 200, 91, 112, 221, 160, 249, 68, 42,
+ 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
+};
diff --git a/backends/networking/curl/cloudicon_disabled_data.h b/backends/networking/curl/cloudicon_disabled_data.h
new file mode 100644
index 0000000000..4340a8a37c
--- /dev/null
+++ b/backends/networking/curl/cloudicon_disabled_data.h
@@ -0,0 +1,117 @@
+/* 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.
+ *
+ */
+
+// This is a PNG file dumped into array.
+// $ recode data..d1 <dists/cloudicon_disabled.png >cloudicon_disabled_data.h
+// The tool is from https://github.com/pinard/Recode
+
+byte cloudicon_disabled_data[] = {
+ 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68,
+ 82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 6, 0, 0, 0, 115,
+ 122, 122, 244, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8, 8, 8,
+ 124, 8, 100, 136, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11,
+ 18, 0, 0, 11, 18, 1, 210, 221, 126, 252, 0, 0, 0, 22, 116,
+ 69, 88, 116, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109,
+ 101, 0, 48, 54, 47, 48, 51, 47, 49, 54, 159, 192, 233, 192, 0,
+ 0, 0, 28, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101,
+ 0, 65, 100, 111, 98, 101, 32, 70, 105, 114, 101, 119, 111, 114, 107,
+ 115, 32, 67, 83, 54, 232, 188, 178, 140, 0, 0, 4, 139, 73, 68,
+ 65, 84, 88, 133, 197, 215, 91, 168, 86, 69, 20, 7, 240, 223, 254,
+ 244, 120, 236, 120, 57, 26, 94, 10, 31, 34, 35, 11, 36, 203, 74,
+ 212, 160, 204, 212, 110, 166, 248, 208, 205, 172, 151, 144, 136, 158, 82,
+ 80, 168, 32, 233, 165, 160, 32, 130, 236, 165, 34, 204, 74, 18, 52,
+ 36, 169, 232, 34, 24, 221, 243, 218, 133, 74, 77, 195, 44, 200, 91,
+ 122, 188, 156, 172, 102, 159, 221, 195, 204, 246, 219, 126, 231, 226, 17,
+ 138, 22, 12, 123, 246, 236, 153, 245, 95, 107, 205, 127, 205, 172, 157,
+ 21, 254, 95, 233, 27, 122, 63, 183, 5, 179, 48, 13, 87, 98, 52,
+ 6, 167, 111, 71, 176, 11, 27, 177, 14, 107, 209, 222, 27, 165, 217,
+ 137, 211, 207, 25, 137, 197, 152, 143, 65, 189, 209, 137, 163, 120, 1,
+ 79, 98, 111, 143, 147, 143, 247, 172, 108, 62, 158, 194, 16, 20, 169,
+ 105, 232, 151, 82, 171, 24, 80, 74, 27, 22, 225, 197, 110, 13, 104,
+ 235, 122, 188, 31, 94, 194, 93, 21, 192, 14, 49, 172, 223, 226, 109,
+ 124, 130, 29, 232, 131, 49, 184, 22, 55, 166, 126, 75, 131, 65, 175,
+ 225, 94, 252, 213, 201, 128, 131, 93, 131, 191, 137, 27, 42, 192, 199,
+ 241, 6, 158, 192, 246, 238, 188, 73, 50, 6, 143, 98, 78, 50, 164,
+ 140, 200, 123, 34, 135, 78, 49, 34, 219, 215, 89, 193, 171, 152, 151,
+ 192, 3, 246, 224, 1, 188, 123, 26, 224, 170, 92, 134, 143, 49, 160,
+ 50, 86, 96, 5, 238, 174, 78, 108, 204, 130, 249, 98, 216, 75, 240,
+ 109, 184, 13, 63, 156, 1, 248, 68, 44, 21, 189, 47, 165, 67, 140,
+ 196, 60, 172, 87, 225, 68, 182, 167, 62, 105, 100, 2, 106, 77, 11,
+ 118, 139, 123, 186, 163, 59, 164, 53, 76, 16, 195, 125, 29, 250, 15,
+ 227, 167, 73, 12, 237, 27, 73, 91, 75, 142, 28, 21, 185, 51, 60,
+ 141, 181, 225, 98, 41, 59, 106, 33, 185, 26, 88, 28, 104, 77, 253,
+ 163, 129, 251, 3, 59, 42, 223, 79, 105, 171, 184, 53, 240, 73, 224,
+ 150, 64, 203, 32, 106, 99, 185, 160, 224, 236, 16, 245, 118, 4, 54,
+ 5, 166, 4, 22, 6, 218, 211, 218, 214, 132, 37, 32, 75, 238, 181,
+ 224, 55, 12, 76, 222, 191, 140, 251, 144, 39, 79, 135, 98, 1, 198,
+ 98, 11, 150, 227, 251, 50, 204, 67, 113, 121, 90, 156, 57, 185, 127,
+ 135, 154, 184, 9, 95, 160, 9, 43, 49, 59, 69, 225, 24, 206, 65,
+ 123, 150, 54, 247, 14, 188, 158, 214, 30, 23, 79, 186, 109, 9, 252,
+ 44, 124, 142, 113, 149, 232, 127, 134, 201, 196, 88, 79, 66, 115, 5,
+ 252, 24, 182, 114, 224, 32, 35, 230, 212, 207, 139, 75, 241, 169, 168,
+ 47, 195, 157, 88, 89, 110, 193, 180, 64, 145, 250, 223, 5, 182, 87,
+ 194, 125, 123, 96, 92, 195, 22, 76, 14, 201, 227, 177, 226, 65, 144,
+ 227, 111, 28, 198, 151, 113, 131, 135, 5, 46, 169, 172, 249, 38, 176,
+ 187, 242, 62, 35, 160, 150, 199, 197, 19, 114, 178, 156, 34, 231, 173,
+ 244, 180, 154, 115, 115, 166, 166, 57, 167, 180, 193, 34, 3, 7, 165,
+ 61, 11, 137, 105, 27, 112, 160, 62, 111, 235, 106, 22, 166, 126, 71,
+ 206, 59, 165, 238, 156, 43, 114, 100, 155, 98, 120, 218, 146, 206, 2,
+ 83, 241, 225, 26, 134, 165, 253, 27, 173, 65, 134, 138, 137, 222, 154,
+ 222, 139, 228, 249, 87, 233, 217, 133, 76, 159, 19, 47, 169, 89, 226,
+ 129, 214, 71, 188, 192, 134, 148, 231, 64, 121, 171, 21, 248, 85, 244,
+ 232, 177, 30, 192, 59, 90, 82, 6, 193, 9, 108, 234, 30, 28, 30,
+ 9, 209, 128, 74, 214, 71, 204, 190, 121, 231, 201, 5, 228, 220, 218,
+ 248, 97, 136, 200, 246, 102, 106, 29, 234, 73, 190, 5, 135, 186, 7,
+ 135, 201, 9, 167, 111, 227, 135, 50, 2, 71, 146, 69, 153, 232, 245,
+ 206, 192, 136, 234, 196, 86, 49, 13, 154, 162, 113, 39, 211, 101, 51,
+ 126, 239, 25, 28, 154, 19, 206, 133, 234, 119, 195, 17, 234, 36, 220,
+ 85, 33, 202, 213, 169, 191, 175, 36, 92, 139, 152, 106, 3, 212, 9,
+ 119, 76, 100, 251, 126, 157, 9, 218, 69, 219, 150, 158, 211, 42, 99,
+ 187, 114, 245, 147, 112, 83, 122, 214, 2, 51, 83, 127, 85, 16, 89,
+ 62, 94, 60, 61, 202, 20, 58, 36, 38, 244, 126, 93, 159, 146, 93,
+ 180, 101, 129, 254, 129, 107, 2, 89, 26, 219, 24, 42, 6, 188, 95,
+ 153, 124, 81, 202, 251, 37, 3, 249, 101, 188, 120, 114, 148, 223, 219,
+ 197, 212, 56, 208, 123, 240, 245, 129, 167, 3, 19, 3, 163, 42, 6,
+ 172, 11, 234, 36, 92, 43, 242, 105, 32, 250, 227, 161, 89, 44, 45,
+ 98, 196, 139, 14, 178, 146, 112, 27, 210, 179, 23, 178, 7, 203, 240,
+ 248, 156, 152, 251, 15, 38, 221, 146, 138, 181, 212, 73, 216, 46, 214,
+ 112, 11, 82, 180, 103, 138, 71, 237, 40, 145, 52, 29, 216, 221, 194,
+ 220, 41, 49, 0, 103, 36, 129, 185, 152, 158, 116, 101, 9, 171, 29,
+ 178, 85, 245, 121, 213, 235, 184, 90, 215, 229, 248, 89, 172, 112, 190,
+ 62, 83, 112, 209, 145, 85, 226, 229, 147, 105, 188, 142, 43, 172, 220,
+ 155, 179, 40, 29, 201, 85, 6, 239, 204, 153, 155, 243, 117, 47, 216,
+ 222, 216, 38, 229, 188, 146, 51, 188, 162, 119, 81, 194, 82, 205, 130,
+ 178, 189, 24, 120, 173, 97, 108, 80, 34, 102, 111, 73, 87, 182, 123,
+ 2, 107, 2, 231, 133, 184, 213, 89, 96, 69, 194, 56, 57, 47, 91,
+ 222, 57, 100, 253, 68, 130, 92, 175, 94, 148, 254, 129, 15, 240, 176,
+ 88, 7, 116, 39, 77, 226, 181, 187, 68, 172, 146, 154, 69, 78, 101,
+ 98, 77, 57, 91, 99, 81, 250, 82, 215, 138, 202, 178, 188, 44, 78,
+ 37, 67, 254, 196, 143, 201, 152, 141, 98, 217, 86, 224, 60, 92, 149,
+ 64, 207, 175, 0, 151, 178, 66, 119, 101, 249, 243, 61, 184, 163, 254,
+ 99, 210, 218, 48, 94, 94, 5, 101, 13, 162, 1, 176, 100, 251, 97,
+ 167, 249, 49, 233, 115, 179, 250, 111, 78, 23, 109, 115, 193, 178, 130,
+ 62, 5, 151, 20, 52, 23, 241, 76, 200, 10, 106, 149, 103, 173, 50,
+ 158, 21, 28, 45, 120, 174, 96, 110, 193, 71, 61, 232, 151, 61, 219,
+ 115, 4, 170, 82, 254, 156, 206, 16, 47, 197, 127, 231, 231, 244, 153,
+ 222, 27, 240, 159, 200, 63, 153, 185, 24, 191, 162, 246, 71, 153, 0,
+ 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
+};
diff --git a/backends/networking/curl/connectionmanager.cpp b/backends/networking/curl/connectionmanager.cpp
new file mode 100644
index 0000000000..f3dc91ad60
--- /dev/null
+++ b/backends/networking/curl/connectionmanager.cpp
@@ -0,0 +1,204 @@
+/* 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/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/system.h"
+#include "common/timer.h"
+#include <curl/curl.h>
+
+namespace Common {
+
+DECLARE_SINGLETON(Networking::ConnectionManager);
+
+}
+
+namespace Networking {
+
+ConnectionManager::ConnectionManager(): _multi(0), _timerStarted(false), _frame(0) {
+ curl_global_init(CURL_GLOBAL_ALL);
+ _multi = curl_multi_init();
+}
+
+ConnectionManager::~ConnectionManager() {
+ stopTimer();
+
+ //terminate all requests
+ _handleMutex.lock();
+ for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end(); ++i) {
+ Request *request = i->request;
+ RequestCallback callback = i->onDeleteCallback;
+ if (request)
+ request->finish();
+ delete request;
+ if (callback)
+ (*callback)(request);
+ }
+ _requests.clear();
+
+ //cleanup
+ curl_multi_cleanup(_multi);
+ curl_global_cleanup();
+ _multi = nullptr;
+ _handleMutex.unlock();
+}
+
+void ConnectionManager::registerEasyHandle(CURL *easy) const {
+ curl_multi_add_handle(_multi, easy);
+}
+
+Request *ConnectionManager::addRequest(Request *request, RequestCallback callback) {
+ _addedRequestsMutex.lock();
+ _addedRequests.push_back(RequestWithCallback(request, callback));
+ if (!_timerStarted)
+ startTimer();
+ _addedRequestsMutex.unlock();
+ return request;
+}
+
+void ConnectionManager::showCloudDisabledIcon() {
+ _icon.showDisabled();
+ startTimer();
+}
+
+Common::String ConnectionManager::urlEncode(Common::String s) const {
+ if (!_multi)
+ return "";
+ char *output = curl_easy_escape(_multi, s.c_str(), s.size());
+ if (output) {
+ Common::String result = output;
+ curl_free(output);
+ return result;
+ }
+ return "";
+}
+
+uint32 ConnectionManager::getCloudRequestsPeriodInMicroseconds() {
+ return TIMER_INTERVAL * CLOUD_PERIOD;
+}
+
+//private goes here:
+
+void connectionsThread(void *ignored) {
+ ConnMan.handle();
+}
+
+void ConnectionManager::startTimer(int interval) {
+ Common::TimerManager *manager = g_system->getTimerManager();
+ if (manager->installTimerProc(connectionsThread, interval, 0, "Networking::ConnectionManager's Timer")) {
+ _timerStarted = true;
+ } else {
+ warning("Failed to install Networking::ConnectionManager's timer");
+ }
+}
+
+void ConnectionManager::stopTimer() {
+ debug(9, "timer stopped");
+ Common::TimerManager *manager = g_system->getTimerManager();
+ manager->removeTimerProc(connectionsThread);
+ _timerStarted = false;
+}
+
+bool ConnectionManager::hasAddedRequests() {
+ _addedRequestsMutex.lock();
+ bool hasNewRequests = !_addedRequests.empty();
+ _addedRequestsMutex.unlock();
+ return hasNewRequests;
+}
+
+void ConnectionManager::handle() {
+ //lock mutex here (in case another handle() would be called before this one ends)
+ _handleMutex.lock();
+ ++_frame;
+ if (_frame % CLOUD_PERIOD == 0)
+ interateRequests();
+ if (_frame % CURL_PERIOD == 0)
+ processTransfers();
+
+ if (_icon.draw() && _requests.empty() && !hasAddedRequests())
+ stopTimer();
+ _handleMutex.unlock();
+}
+
+void ConnectionManager::interateRequests() {
+ //add new requests
+ _addedRequestsMutex.lock();
+ for (Common::Array<RequestWithCallback>::iterator i = _addedRequests.begin(); i != _addedRequests.end(); ++i) {
+ _requests.push_back(*i);
+ }
+ _addedRequests.clear();
+ _addedRequestsMutex.unlock();
+
+ //call handle() of all running requests (so they can do their work)
+ debug(9, "handling %d request(s)", _requests.size());
+ for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end();) {
+ Request *request = i->request;
+ if (request) {
+ if (request->state() == PROCESSING)
+ request->handle();
+ else if (request->state() == RETRY)
+ request->handleRetry();
+ }
+
+ if (!request || request->state() == FINISHED) {
+ delete (i->request);
+ if (i->onDeleteCallback)
+ (*i->onDeleteCallback)(i->request); //that's not a mistake (we're passing an address and that method knows there is no object anymore)
+ _requests.erase(i);
+ continue;
+ }
+
+ ++i;
+ }
+}
+
+void ConnectionManager::processTransfers() {
+ if (!_multi) return;
+
+ //check libcurl's transfers and notify requests of messages from queue (transfer completion or failure)
+ int transfersRunning;
+ curl_multi_perform(_multi, &transfersRunning);
+
+ int messagesInQueue;
+ CURLMsg *curlMsg;
+ while ((curlMsg = curl_multi_info_read(_multi, &messagesInQueue))) {
+ CURL *easyHandle = curlMsg->easy_handle;
+
+ NetworkReadStream *stream;
+ curl_easy_getinfo(easyHandle, CURLINFO_PRIVATE, &stream);
+ if (stream)
+ stream->finished();
+
+ if (curlMsg->msg == CURLMSG_DONE) {
+ debug(9, "ConnectionManager: SUCCESS (%d - %s)", curlMsg->data.result, curl_easy_strerror(curlMsg->data.result));
+ } else {
+ warning("ConnectionManager: FAILURE (CURLMsg (%d))", curlMsg->msg);
+ }
+
+ curl_multi_remove_handle(_multi, easyHandle);
+ }
+}
+
+} // End of namespace Cloud
diff --git a/backends/networking/curl/connectionmanager.h b/backends/networking/curl/connectionmanager.h
new file mode 100644
index 0000000000..826bef6d36
--- /dev/null
+++ b/backends/networking/curl/connectionmanager.h
@@ -0,0 +1,132 @@
+/* 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_NETWORKING_CURL_CONNECTIONMANAGER_H
+#define BACKENDS_NETWORKING_CURL_CONNECTIONMANAGER_H
+
+#include "backends/networking/curl/cloudicon.h"
+#include "backends/networking/curl/request.h"
+#include "common/str.h"
+#include "common/singleton.h"
+#include "common/hashmap.h"
+#include "common/mutex.h"
+
+typedef void CURL;
+typedef void CURLM;
+struct curl_slist;
+
+namespace Networking {
+
+class NetworkReadStream;
+
+class ConnectionManager : public Common::Singleton<ConnectionManager> {
+ static const uint32 FRAMES_PER_SECOND = 20;
+ static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND;
+ static const uint32 CLOUD_PERIOD = 20; //every 20th frame
+ static const uint32 CURL_PERIOD = 1; //every frame
+
+ friend void connectionsThread(void *); //calls handle()
+
+ typedef Common::BaseCallback<Request *> *RequestCallback;
+
+ /**
+ * RequestWithCallback is used by ConnectionManager to
+ * storage the Request and a callback which should be
+ * called on Request delete.
+ *
+ * Usually one won't need to pass such callback, but
+ * in some cases you'd like to know whether Request is
+ * still running.
+ *
+ * For example, Cloud::Storage is keeping track of how
+ * many Requests are running, and thus it needs to know
+ * that Request was destroyed to decrease its counter.
+ *
+ * onDeleteCallback is called with *invalid* pointer.
+ * ConnectionManager deletes Request first and then passes
+ * the pointer to the callback. One may use the address
+ * to find it in own HashMap or Array and remove it.
+ * So, again, this pointer is for information only. One
+ * cannot use it.
+ */
+ struct RequestWithCallback {
+ Request *request;
+ RequestCallback onDeleteCallback;
+
+ RequestWithCallback(Request *rq = nullptr, RequestCallback cb = nullptr): request(rq), onDeleteCallback(cb) {}
+ };
+
+ CURLM *_multi;
+ bool _timerStarted;
+ Common::Array<RequestWithCallback> _requests, _addedRequests;
+ Common::Mutex _handleMutex, _addedRequestsMutex;
+ CloudIcon _icon;
+ uint32 _frame;
+
+ void startTimer(int interval = TIMER_INTERVAL);
+ void stopTimer();
+ void handle();
+ void interateRequests();
+ void processTransfers();
+ bool hasAddedRequests();
+
+public:
+ ConnectionManager();
+ virtual ~ConnectionManager();
+
+ /**
+ * All libcurl transfers are going through this ConnectionManager.
+ * So, if you want to start any libcurl transfer, you must create
+ * an easy handle and register it using this method.
+ */
+ void registerEasyHandle(CURL *easy) const;
+
+ /**
+ * Use this method to add new Request into manager's queue.
+ * Manager will periodically call handle() method of these
+ * Requests until they set their state to FINISHED.
+ *
+ * If Request's state is RETRY, handleRetry() is called instead.
+ *
+ * The passed callback would be called after Request is deleted.
+ *
+ * @note This method starts the timer if it's not started yet.
+ *
+ * @return the same Request pointer, just as a shortcut
+ */
+ Request *addRequest(Request *request, RequestCallback callback = nullptr);
+
+ /** Shows a "cloud disabled" icon for a three seconds. */
+ void showCloudDisabledIcon();
+
+ /** Return URL-encoded version of given string. */
+ Common::String urlEncode(Common::String s) const;
+
+ static uint32 getCloudRequestsPeriodInMicroseconds();
+};
+
+/** Shortcut for accessing the connection manager. */
+#define ConnMan Networking::ConnectionManager::instance()
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/curljsonrequest.cpp b/backends/networking/curl/curljsonrequest.cpp
new file mode 100644
index 0000000000..1899cbd913
--- /dev/null
+++ b/backends/networking/curl/curljsonrequest.cpp
@@ -0,0 +1,215 @@
+/* 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/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+namespace Networking {
+
+CurlJsonRequest::CurlJsonRequest(JsonCallback cb, ErrorCallback ecb, Common::String url) :
+ CurlRequest(nullptr, ecb, url), _jsonCallback(cb), _contentsStream(DisposeAfterUse::YES),
+ _buffer(new byte[CURL_JSON_REQUEST_BUFFER_SIZE]) {}
+
+CurlJsonRequest::~CurlJsonRequest() {
+ delete _jsonCallback;
+ delete[] _buffer;
+}
+
+char *CurlJsonRequest::getPreparedContents() {
+ //write one more byte in the end
+ byte zero[1] = {0};
+ _contentsStream.write(zero, 1);
+
+ //replace all "bad" bytes with '.' character
+ byte *result = _contentsStream.getData();
+ uint32 size = _contentsStream.size();
+ 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';
+
+ return (char *)result;
+}
+
+void CurlJsonRequest::handle() {
+ if (!_stream) _stream = makeStream();
+
+ if (_stream) {
+ uint32 readBytes = _stream->read(_buffer, CURL_JSON_REQUEST_BUFFER_SIZE);
+ if (readBytes != 0)
+ if (_contentsStream.write(_buffer, readBytes) != readBytes)
+ warning("CurlJsonRequest: unable to write all the bytes into MemoryWriteStreamDynamic");
+
+ if (_stream->eos()) {
+ char *contents = getPreparedContents();
+ Common::JSONValue *json = Common::JSON::parse(contents);
+ if (json) {
+ finishJson(json); //it's JSON even if's not 200 OK? That's fine!..
+ } else {
+ if (_stream->httpResponseCode() == 200) //no JSON, but 200 OK? That's fine!..
+ finishJson(nullptr);
+ else
+ finishError(ErrorResponse(this, false, true, contents, _stream->httpResponseCode()));
+ }
+ }
+ }
+}
+
+void CurlJsonRequest::restart() {
+ if (_stream)
+ delete _stream;
+ _stream = nullptr;
+ _contentsStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
+ //with no stream available next handle() will create another one
+}
+
+void CurlJsonRequest::finishJson(Common::JSONValue *json) {
+ Request::finishSuccess();
+ if (_jsonCallback)
+ (*_jsonCallback)(JsonResponse(this, json)); //potential memory leak, free it in your callbacks!
+ else
+ delete json;
+}
+
+bool CurlJsonRequest::jsonIsObject(Common::JSONValue *item, const char *warningPrefix) {
+ if (item == nullptr) {
+ warning("%s: passed item is NULL", warningPrefix);
+ return false;
+ }
+
+ if (item->isObject()) return true;
+
+ warning("%s: passed item is not an object", warningPrefix);
+ debug(9, "%s", item->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsObject(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isObject()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not an object", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsString(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isString()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not a string", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isIntegerNumber()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not an integer", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsArray(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isArray()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not an array", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsStringOrIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isString() || item.getVal(key)->isIntegerNumber()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is neither a string or an integer", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsAttribute(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ return true;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/curljsonrequest.h b/backends/networking/curl/curljsonrequest.h
new file mode 100644
index 0000000000..edd523015a
--- /dev/null
+++ b/backends/networking/curl/curljsonrequest.h
@@ -0,0 +1,67 @@
+/* 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_NETWORKING_CURL_CURLJSONREQUEST_H
+#define BACKENDS_NETWORKING_CURL_CURLJSONREQUEST_H
+
+#include "backends/networking/curl/curlrequest.h"
+#include "common/memstream.h"
+#include "common/json.h"
+
+namespace Networking {
+
+typedef Response<Common::JSONValue *> JsonResponse;
+typedef Common::BaseCallback<JsonResponse> *JsonCallback;
+
+#define CURL_JSON_REQUEST_BUFFER_SIZE 512 * 1024
+
+class CurlJsonRequest: public CurlRequest {
+protected:
+ JsonCallback _jsonCallback;
+ Common::MemoryWriteStreamDynamic _contentsStream;
+ byte *_buffer;
+
+ /** Prepares raw bytes from _contentsStream to be parsed with Common::JSON::parse(). */
+ char *getPreparedContents();
+
+ /** Sets FINISHED state and passes the JSONValue * into user's callback in JsonResponse. */
+ virtual void finishJson(Common::JSONValue *json);
+
+public:
+ CurlJsonRequest(JsonCallback cb, ErrorCallback ecb, Common::String url);
+ virtual ~CurlJsonRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ static bool jsonIsObject(Common::JSONValue *item, const char *warningPrefix);
+ static bool jsonContainsObject(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsString(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsArray(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsStringOrIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsAttribute(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/curlrequest.cpp b/backends/networking/curl/curlrequest.cpp
new file mode 100644
index 0000000000..64fa347023
--- /dev/null
+++ b/backends/networking/curl/curlrequest.cpp
@@ -0,0 +1,162 @@
+/* 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/networking/curl/curlrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/textconsole.h"
+#include <curl/curl.h>
+
+namespace Networking {
+
+CurlRequest::CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url):
+ Request(cb, ecb), _url(url), _stream(nullptr), _headersList(nullptr), _bytesBuffer(nullptr),
+ _bytesBufferSize(0), _uploading(false), _usingPatch(false) {}
+
+CurlRequest::~CurlRequest() {
+ delete _stream;
+ delete _bytesBuffer;
+}
+
+NetworkReadStream *CurlRequest::makeStream() {
+ if (_bytesBuffer)
+ return new NetworkReadStream(_url.c_str(), _headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true);
+ if (!_formFields.empty() || !_formFiles.empty())
+ return new NetworkReadStream(_url.c_str(), _headersList, _formFields, _formFiles);
+ return new NetworkReadStream(_url.c_str(), _headersList, _postFields, _uploading, _usingPatch);
+}
+
+void CurlRequest::handle() {
+ if (!_stream) _stream = makeStream();
+
+ if (_stream && _stream->eos()) {
+ if (_stream->httpResponseCode() != 200) {
+ warning("CurlRequest: HTTP response code is not 200 OK (it's %ld)", _stream->httpResponseCode());
+ ErrorResponse error(this, false, true, "", _stream->httpResponseCode());
+ finishError(error);
+ return;
+ }
+
+ finishSuccess(); //note that this Request doesn't call its callback on success (that's because it has nothing to return)
+ }
+}
+
+void CurlRequest::restart() {
+ if (_stream)
+ delete _stream;
+ _stream = nullptr;
+ //with no stream available next handle() will create another one
+}
+
+Common::String CurlRequest::date() const {
+ if (_stream) {
+ Common::String headers = _stream->responseHeaders();
+ const char *cstr = headers.c_str();
+ const char *position = strstr(cstr, "Date: ");
+
+ if (position) {
+ Common::String result = "";
+ char c;
+ for (const char *i = position + 6; c = *i, c != 0; ++i) {
+ if (c == '\n' || c == '\r')
+ break;
+ result += c;
+ }
+ return result;
+ }
+ }
+ return "";
+}
+
+void CurlRequest::setHeaders(Common::Array<Common::String> &headers) {
+ curl_slist_free_all(_headersList);
+ _headersList = nullptr;
+ for (uint32 i = 0; i < headers.size(); ++i)
+ addHeader(headers[i]);
+}
+
+void CurlRequest::addHeader(Common::String header) {
+ _headersList = curl_slist_append(_headersList, header.c_str());
+}
+
+void CurlRequest::addPostField(Common::String keyValuePair) {
+ if (_bytesBuffer)
+ warning("CurlRequest: added POST fields would be ignored, because there is buffer present");
+
+ if (!_formFields.empty() || !_formFiles.empty())
+ warning("CurlRequest: added POST fields would be ignored, because there are form fields/files present");
+
+ if (_postFields == "")
+ _postFields = keyValuePair;
+ else
+ _postFields += "&" + keyValuePair;
+}
+
+void CurlRequest::addFormField(Common::String name, Common::String value) {
+ if (_bytesBuffer)
+ warning("CurlRequest: added POST form fields would be ignored, because there is buffer present");
+
+ if (_formFields.contains(name))
+ warning("CurlRequest: form field '%s' already had a value", name.c_str());
+
+ _formFields[name] = value;
+}
+
+void CurlRequest::addFormFile(Common::String name, Common::String filename) {
+ if (_bytesBuffer)
+ warning("CurlRequest: added POST form files would be ignored, because there is buffer present");
+
+ if (_formFields.contains(name))
+ warning("CurlRequest: form file field '%s' already had a value", name.c_str());
+
+ _formFiles[name] = filename;
+}
+
+void CurlRequest::setBuffer(byte *buffer, uint32 size) {
+ if (_postFields != "")
+ warning("CurlRequest: added POST fields would be ignored, because buffer added");
+
+ if (_bytesBuffer)
+ delete _bytesBuffer;
+
+ _bytesBuffer = buffer;
+ _bytesBufferSize = size;
+}
+
+void CurlRequest::usePut() { _uploading = true; }
+
+void CurlRequest::usePatch() { _usingPatch = true; }
+
+NetworkReadStreamResponse CurlRequest::execute() {
+ if (!_stream) {
+ _stream = makeStream();
+ ConnMan.addRequest(this);
+ }
+
+ return NetworkReadStreamResponse(this, _stream);
+}
+
+const NetworkReadStream *CurlRequest::getNetworkReadStream() const { return _stream; }
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/curlrequest.h b/backends/networking/curl/curlrequest.h
new file mode 100644
index 0000000000..6ce94f8983
--- /dev/null
+++ b/backends/networking/curl/curlrequest.h
@@ -0,0 +1,100 @@
+/* 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_NETWORKING_CURL_CURLREQUEST_H
+#define BACKENDS_NETWORKING_CURL_CURLREQUEST_H
+
+#include "backends/networking/curl/request.h"
+#include "common/str.h"
+#include "common/array.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+
+struct curl_slist;
+
+namespace Networking {
+
+class NetworkReadStream;
+
+typedef Response<NetworkReadStream *> NetworkReadStreamResponse;
+typedef Common::BaseCallback<NetworkReadStreamResponse> *NetworkReadStreamCallback;
+
+class CurlRequest: public Request {
+protected:
+ Common::String _url;
+ NetworkReadStream *_stream;
+ curl_slist *_headersList;
+ Common::String _postFields;
+ Common::HashMap<Common::String, Common::String> _formFields;
+ Common::HashMap<Common::String, Common::String> _formFiles;
+ byte *_bytesBuffer;
+ uint32 _bytesBufferSize;
+ bool _uploading; //using PUT method
+ bool _usingPatch; //using PATCH method
+
+ virtual NetworkReadStream *makeStream();
+
+public:
+ CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url);
+ virtual ~CurlRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+
+ /** Replaces all headers with the passed array of headers. */
+ virtual void setHeaders(Common::Array<Common::String> &headers);
+
+ /** Adds a header into headers list. */
+ virtual void addHeader(Common::String header);
+
+ /** Adds a post field (key=value pair). */
+ virtual void addPostField(Common::String field);
+
+ /** Adds a form/multipart field (name, value). */
+ virtual void addFormField(Common::String name, Common::String value);
+
+ /** Adds a form/multipart file (field name, file name). */
+ virtual void addFormFile(Common::String name, Common::String filename);
+
+ /** Sets bytes buffer. */
+ virtual void setBuffer(byte *buffer, uint32 size);
+
+ /** Remembers to use PUT method when it would create NetworkReadStream. */
+ virtual void usePut();
+
+ /** Remembers to use PATCH method when it would create NetworkReadStream. */
+ virtual void usePatch();
+
+ /**
+ * Starts this Request with ConnMan.
+ * @return its NetworkReadStream in NetworkReadStreamResponse.
+ */
+ virtual NetworkReadStreamResponse execute();
+
+ /** Returns Request's NetworkReadStream. */
+ const NetworkReadStream *getNetworkReadStream() const;
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/networkreadstream.cpp b/backends/networking/curl/networkreadstream.cpp
new file mode 100644
index 0000000000..032aef87be
--- /dev/null
+++ b/backends/networking/curl/networkreadstream.cpp
@@ -0,0 +1,257 @@
+/* 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/networking/curl/networkreadstream.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "base/version.h"
+#include <curl/curl.h>
+
+namespace Networking {
+
+static size_t curlDataCallback(char *d, size_t n, size_t l, void *p) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ return stream->write(d, n * l);
+ return 0;
+}
+
+static size_t curlReadDataCallback(char *d, size_t n, size_t l, void *p) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ return stream->fillWithSendingContents(d, n * l);
+ return 0;
+}
+
+static size_t curlHeadersCallback(char *d, size_t n, size_t l, void *p) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ return stream->addResponseHeaders(d, n * l);
+ return 0;
+}
+
+static int curlProgressCallback(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ stream->setProgress(dlnow, dltotal);
+ return 0;
+}
+
+static int curlProgressCallbackOlder(void *p, double dltotal, double dlnow, double ultotal, double ulnow) {
+ // for libcurl older than 7.32.0 (CURLOPT_PROGRESSFUNCTION)
+ return curlProgressCallback(p, (curl_off_t)dltotal, (curl_off_t)dlnow, (curl_off_t)ultotal, (curl_off_t)ulnow);
+}
+
+void NetworkReadStream::init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
+ _eos = _requestComplete = false;
+ _sendingContentsBuffer = nullptr;
+ _sendingContentsSize = _sendingContentsPos = 0;
+ _progressDownloaded = _progressTotal = 0;
+
+ _easy = curl_easy_init();
+ curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback);
+ curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us
+ curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete
+ curl_easy_setopt(_easy, CURLOPT_HEADER, 0L);
+ curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this);
+ curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback);
+ curl_easy_setopt(_easy, CURLOPT_URL, url);
+ curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L);
+ curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on
+ curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList);
+ curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion);
+ curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
+#if LIBCURL_VERSION_NUM >= 0x072000
+ // CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0
+ // CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used
+ curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback);
+ curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this);
+#endif
+ if (uploading) {
+ curl_easy_setopt(_easy, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt(_easy, CURLOPT_READDATA, this);
+ curl_easy_setopt(_easy, CURLOPT_READFUNCTION, curlReadDataCallback);
+ _sendingContentsBuffer = buffer;
+ _sendingContentsSize = bufferSize;
+ } else if (usingPatch) {
+ curl_easy_setopt(_easy, CURLOPT_CUSTOMREQUEST, "PATCH");
+ } else {
+ if (post || bufferSize != 0) {
+ curl_easy_setopt(_easy, CURLOPT_POSTFIELDSIZE, bufferSize);
+ curl_easy_setopt(_easy, CURLOPT_COPYPOSTFIELDS, buffer);
+ }
+ }
+ ConnMan.registerEasyHandle(_easy);
+}
+
+void NetworkReadStream::init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) {
+ _eos = _requestComplete = false;
+ _sendingContentsBuffer = nullptr;
+ _sendingContentsSize = _sendingContentsPos = 0;
+ _progressDownloaded = _progressTotal = 0;
+
+ _easy = curl_easy_init();
+ curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback);
+ curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us
+ curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete
+ curl_easy_setopt(_easy, CURLOPT_HEADER, 0L);
+ curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this);
+ curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback);
+ curl_easy_setopt(_easy, CURLOPT_URL, url);
+ curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L);
+ curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on
+ curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList);
+ curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion);
+ curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
+#if LIBCURL_VERSION_NUM >= 0x072000
+ // CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0
+ // CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used
+ curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback);
+ curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this);
+#endif
+
+ // set POST multipart upload form fields/files
+ struct curl_httppost *formpost = nullptr;
+ struct curl_httppost *lastptr = nullptr;
+
+ for (Common::HashMap<Common::String, Common::String>::iterator i = formFields.begin(); i != formFields.end(); ++i) {
+ CURLFORMcode code = curl_formadd(
+ &formpost,
+ &lastptr,
+ CURLFORM_COPYNAME, i->_key.c_str(),
+ CURLFORM_COPYCONTENTS, i->_value.c_str(),
+ CURLFORM_END
+ );
+
+ if (code != CURL_FORMADD_OK)
+ warning("NetworkReadStream: field curl_formadd('%s') failed", i->_key.c_str());
+ }
+
+ for (Common::HashMap<Common::String, Common::String>::iterator i = formFiles.begin(); i != formFiles.end(); ++i) {
+ CURLFORMcode code = curl_formadd(
+ &formpost,
+ &lastptr,
+ CURLFORM_COPYNAME, i->_key.c_str(),
+ CURLFORM_FILE, i->_value.c_str(),
+ CURLFORM_END
+ );
+
+ if (code != CURL_FORMADD_OK)
+ warning("NetworkReadStream: file curl_formadd('%s') failed", i->_key.c_str());
+ }
+
+ curl_easy_setopt(_easy, CURLOPT_HTTPPOST, formpost);
+
+ ConnMan.registerEasyHandle(_easy);
+}
+
+NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading, bool usingPatch) {
+ init(url, headersList, (const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
+}
+
+NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) {
+ init(url, headersList, formFields, formFiles);
+}
+
+NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
+ init(url, headersList, buffer, bufferSize, uploading, usingPatch, post);
+}
+
+NetworkReadStream::~NetworkReadStream() {
+ if (_easy)
+ curl_easy_cleanup(_easy);
+}
+
+bool NetworkReadStream::eos() const {
+ return _eos;
+}
+
+uint32 NetworkReadStream::read(void *dataPtr, uint32 dataSize) {
+ uint32 actuallyRead = MemoryReadWriteStream::read(dataPtr, dataSize);
+
+ if (actuallyRead == 0) {
+ if (_requestComplete)
+ _eos = true;
+ return 0;
+ }
+
+ return actuallyRead;
+}
+
+void NetworkReadStream::finished() {
+ _requestComplete = true;
+}
+
+long NetworkReadStream::httpResponseCode() const {
+ long responseCode = -1;
+ if (_easy)
+ curl_easy_getinfo(_easy, CURLINFO_RESPONSE_CODE, &responseCode);
+ return responseCode;
+}
+
+Common::String NetworkReadStream::currentLocation() const {
+ Common::String result = "";
+ if (_easy) {
+ char *pointer;
+ curl_easy_getinfo(_easy, CURLINFO_EFFECTIVE_URL, &pointer);
+ result = Common::String(pointer);
+ }
+ return result;
+}
+
+Common::String NetworkReadStream::responseHeaders() const {
+ return _responseHeaders;
+}
+
+uint32 NetworkReadStream::fillWithSendingContents(char *bufferToFill, uint32 maxSize) {
+ uint32 size = _sendingContentsSize - _sendingContentsPos;
+ if (size > maxSize)
+ size = maxSize;
+ for (uint32 i = 0; i < size; ++i) {
+ bufferToFill[i] = _sendingContentsBuffer[_sendingContentsPos + i];
+ }
+ _sendingContentsPos += size;
+ return size;
+}
+
+uint32 NetworkReadStream::addResponseHeaders(char *buffer, uint32 size) {
+ _responseHeaders += Common::String(buffer, size);
+ return size;
+}
+
+double NetworkReadStream::getProgress() const {
+ if (_progressTotal < 1)
+ return 0;
+ return (double)_progressDownloaded / (double)_progressTotal;
+}
+
+void NetworkReadStream::setProgress(uint64 downloaded, uint64 total) {
+ _progressDownloaded = downloaded;
+ _progressTotal = total;
+}
+
+} // End of namespace Cloud
diff --git a/backends/networking/curl/networkreadstream.h b/backends/networking/curl/networkreadstream.h
new file mode 100644
index 0000000000..2be6d591cb
--- /dev/null
+++ b/backends/networking/curl/networkreadstream.h
@@ -0,0 +1,142 @@
+/* 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_NETWORKING_CURL_NETWORKREADSTREAM_H
+#define BACKENDS_NETWORKING_CURL_NETWORKREADSTREAM_H
+
+#include "common/memstream.h"
+#include "common/stream.h"
+#include "common/str.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+
+typedef void CURL;
+struct curl_slist;
+
+namespace Networking {
+
+class NetworkReadStream: public Common::MemoryReadWriteStream {
+ CURL *_easy;
+ bool _eos, _requestComplete;
+ const byte *_sendingContentsBuffer;
+ uint32 _sendingContentsSize;
+ uint32 _sendingContentsPos;
+ Common::String _responseHeaders;
+ uint64 _progressDownloaded, _progressTotal;
+ void init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
+ void init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles);
+
+public:
+ /** Send <postFields>, using POST by default. */
+ NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading = false, bool usingPatch = false);
+ /** Send <formFields>, <formFiles>, using POST multipart/form. */
+ NetworkReadStream(
+ const char *url, curl_slist *headersList,
+ Common::HashMap<Common::String, Common::String> formFields,
+ Common::HashMap<Common::String, Common::String> formFiles);
+ /** Send <buffer, using POST by default. */
+ NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true);
+ virtual ~NetworkReadStream();
+
+ /**
+ * Returns true if a read failed because the stream end has been reached.
+ * This flag is cleared by clearErr().
+ * For a SeekableReadStream, it is also cleared by a successful seek.
+ *
+ * @note The semantics of any implementation of this method are
+ * supposed to match those of ISO C feof(). In particular, in a stream
+ * with N bytes, reading exactly N bytes from the start should *not*
+ * set eos; only reading *beyond* the available data should set it.
+ */
+ virtual bool eos() const;
+
+ /**
+ * Read data from the stream. Subclasses must implement this
+ * method; all other read methods are implemented using it.
+ *
+ * @note The semantics of any implementation of this method are
+ * supposed to match those of ISO C fread(), in particular where
+ * it concerns setting error and end of file/stream flags.
+ *
+ * @param dataPtr pointer to a buffer into which the data is read
+ * @param dataSize number of bytes to be read
+ * @return the number of bytes which were actually read.
+ */
+ virtual uint32 read(void *dataPtr, uint32 dataSize);
+
+ /**
+ * This method is called by ConnectionManager to indicate
+ * that transfer is finished.
+ *
+ * @note It's called on failure too.
+ */
+ void finished();
+
+ /**
+ * Returns HTTP response code from inner CURL handle.
+ * It returns -1 to indicate there is no inner handle.
+ *
+ * @note This method should be called when eos() == true.
+ */
+ long httpResponseCode() const;
+
+ /**
+ * Return current location URL from inner CURL handle.
+ * "" is returned to indicate there is no inner handle.
+ *
+ * @note This method should be called when eos() == true.
+ */
+ Common::String currentLocation() const;
+
+ /**
+ * Return response headers.
+ *
+ * @note This method should be called when eos() == true.
+ */
+ Common::String responseHeaders() const;
+
+ /**
+ * Fills the passed buffer with _sendingContentsBuffer contents.
+ * It works similarly to read(), expect it's not for reading
+ * Stream's contents, but for sending our own data to the server.
+ *
+ * @returns how many bytes were actually read (filled in)
+ */
+ uint32 fillWithSendingContents(char *bufferToFill, uint32 maxSize);
+
+ /**
+ * Remembers headers returned to CURL in server's response.
+ *
+ * @returns how many bytes were actually read
+ */
+ uint32 addResponseHeaders(char *buffer, uint32 size);
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+
+ /** Used in curl progress callback to pass current downloaded/total values. */
+ void setProgress(uint64 downloaded, uint64 total);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/request.cpp b/backends/networking/curl/request.cpp
new file mode 100644
index 0000000000..30af48a478
--- /dev/null
+++ b/backends/networking/curl/request.cpp
@@ -0,0 +1,74 @@
+/* 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/networking/curl/request.h"
+
+namespace Networking {
+
+ErrorResponse::ErrorResponse(Request *rq):
+ request(rq), interrupted(false), failed(true), response(""), httpResponseCode(-1) {}
+
+ErrorResponse::ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode):
+ request(rq), interrupted(interrupt), failed(failure), response(resp), httpResponseCode(httpCode) {}
+
+Request::Request(DataCallback cb, ErrorCallback ecb):
+ _callback(cb), _errorCallback(ecb), _state(PROCESSING), _retryInSeconds(0) {}
+
+Request::~Request() {
+ delete _callback;
+ delete _errorCallback;
+}
+
+void Request::handleRetry() {
+ if (_retryInSeconds > 0) {
+ --_retryInSeconds;
+ } else {
+ _state = PROCESSING;
+ restart();
+ }
+}
+
+void Request::pause() { _state = PAUSED; }
+
+void Request::finish() {
+ ErrorResponse error(this, true, false, "", -1);
+ finishError(error);
+}
+
+void Request::retry(uint32 seconds) {
+ _state = RETRY;
+ _retryInSeconds = seconds;
+}
+
+RequestState Request::state() const { return _state; }
+
+Common::String Request::date() const { return ""; }
+
+void Request::finishError(ErrorResponse error) {
+ _state = FINISHED;
+ if (_errorCallback)
+ (*_errorCallback)(error);
+}
+
+void Request::finishSuccess() { _state = FINISHED; }
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/request.h b/backends/networking/curl/request.h
new file mode 100644
index 0000000000..9b366ea40c
--- /dev/null
+++ b/backends/networking/curl/request.h
@@ -0,0 +1,203 @@
+/* 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_NETWORKING_CURL_REQUEST_H
+#define BACKENDS_NETWORKING_CURL_REQUEST_H
+
+#include "common/callback.h"
+#include "common/scummsys.h"
+#include "common/str.h"
+
+namespace Networking {
+
+class Request;
+
+/**
+ * Response<T> is a struct to be returned from Request
+ * to user's callbacks. It's a type safe way to indicate
+ * which "return value" Request has and user awaits.
+ *
+ * It just keeps a Request pointer together with
+ * some T value (which might be a pointer, a reference
+ * or a plain type (copied by value)).
+ *
+ * To make it more convenient, typedefs are used.
+ * For example, Response<void *> is called DataResponse
+ * and corresponding callback pointer is DataCallback.
+ */
+
+template<typename T> struct Response {
+ Request *request;
+ T value;
+
+ Response(Request *rq, T v) : request(rq), value(v) {}
+};
+
+/**
+ * ErrorResponse is a struct to be returned from Request
+ * to user's failure callbacks.
+ *
+ * It keeps a Request pointer together with some useful
+ * information fields, which would explain why failure
+ * callback was called.
+ *
+ * <interrupted> flag is set when Request was interrupted,
+ * i.e. finished by user with finish() call.
+ *
+ * <failed> flag is set when Request has failed because of
+ * some error (bad server response, for example).
+ *
+ * <response> contains server's original response.
+ *
+ * <httpResponseCode> contains server's HTTP response code.
+ */
+
+struct ErrorResponse {
+ Request *request;
+ bool interrupted;
+ bool failed;
+ Common::String response;
+ long httpResponseCode;
+
+ ErrorResponse(Request *rq);
+ ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode);
+};
+
+typedef Response<void *> DataReponse;
+typedef Common::BaseCallback<DataReponse> *DataCallback;
+typedef Common::BaseCallback<ErrorResponse> *ErrorCallback;
+
+/**
+ * RequestState is used to indicate current Request state.
+ * ConnectionManager uses it to decide what to do with the Request.
+ *
+ * PROCESSING state indicates that Request is working.
+ * ConnectionManager calls handle() method of Requests in that state.
+ *
+ * PAUSED state indicates that Request is not working.
+ * ConnectionManager keeps Requests in that state and doesn't call any methods of those.
+ *
+ * RETRY state indicates that Request must restart after a few seconds.
+ * ConnectionManager calls handleRetry() method of Requests in that state.
+ * Default handleRetry() implementation decreases _retryInSeconds value
+ * until it reaches zero. When it does, Request's restart() method is called.
+ *
+ * FINISHED state indicates that Request did the work and might be deleted.
+ * ConnectionManager deletes Requests in that state.
+ * After this state is set, but before ConnectionManager deletes the Request,
+ * Request calls user's callback. User can ask Request to change its state
+ * by calling retry() or pause() methods and Request won't be deleted.
+ *
+ * Request get a success and failure callbacks. Request must call one
+ * (and only one!) of these callbacks when it sets FINISHED state.
+ */
+enum RequestState {
+ PROCESSING,
+ PAUSED,
+ RETRY,
+ FINISHED
+};
+
+class Request {
+protected:
+ /**
+ * Callback, which should be called when Request is finished.
+ * That's the way Requests pass the result to the code which asked to create this request.
+ *
+ * @note some Requests use their own callbacks to return something but void *.
+ * @note callback must be called in finish() or similar method.
+ */
+ DataCallback _callback;
+
+ /**
+ * Callback, which should be called when Request is failed/interrupted.
+ * That's the way Requests pass error information to the code which asked to create this request.
+ * @note callback must be called in finish() or similar method.
+ */
+ ErrorCallback _errorCallback;
+
+ /**
+ * Request state, which is used by ConnectionManager to determine
+ * whether request might be deleted or it's still working.
+ *
+ * State might be changed from outside with finish(), pause() or
+ * retry() methods. Override these if you want to react to these
+ * changes correctly.
+ */
+ RequestState _state;
+
+ /** In RETRY state this indicates whether it's time to call restart(). */
+ uint32 _retryInSeconds;
+
+ /** Sets FINISHED state and calls the _errorCallback with given error. */
+ virtual void finishError(ErrorResponse error);
+
+ /** Sets FINISHED state. Implementations might extend it if needed. */
+ virtual void finishSuccess();
+
+public:
+ Request(DataCallback cb, ErrorCallback ecb);
+ virtual ~Request();
+
+ /** Method, which does actual work. Depends on what this Request is doing. */
+ virtual void handle() = 0;
+
+ /** Method, which is called by ConnectionManager when Request's state is RETRY. */
+ virtual void handleRetry();
+
+ /** Method, which is used to restart the Request. */
+ virtual void restart() = 0;
+
+ /** Method, which is called to pause the Request. */
+ virtual void pause();
+
+ /**
+ * Method, which is called to *interrupt* the Request.
+ * When it's called, Request must stop its work and
+ * call the failure callback to notify user.
+ */
+ virtual void finish();
+
+ /** Method, which is called to retry the Request. */
+ virtual void retry(uint32 seconds);
+
+ /** Returns Request's current state. */
+ RequestState state() const;
+
+ /**
+ * Return date this Request received from server.
+ * It could be extracted from "Date" header,
+ * which is kept in NetworkReadStream.
+ *
+ * @note not all Requests do that, so "" is returned
+ * to indicate the date is unknown. That's also true
+ * if no server response available or no "Date" header
+ * was passed.
+ *
+ * @returns date from "Date" response header.
+ */
+ virtual Common::String date() const;
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/make_archive.py b/backends/networking/make_archive.py
new file mode 100644
index 0000000000..64d314bedd
--- /dev/null
+++ b/backends/networking/make_archive.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# encoding: utf-8
+import sys
+import re
+import os
+import zipfile
+
+ARCHIVE_FILE_EXTENSIONS = ('.html', '.css', '.js', '.ico', '.png')
+
+def buildArchive(archiveName):
+ if not os.path.isdir(archiveName):
+ print ("Invalid archive name: " + archiveName)
+ return
+
+ zf = zipfile.ZipFile(archiveName + ".zip", 'w')
+
+ print ("Building '" + archiveName + "' archive:")
+ os.chdir(archiveName)
+
+ directories = ['.', './icons']
+ for d in directories:
+ filenames = os.listdir(d)
+ filenames.sort()
+ for filename in filenames:
+ if os.path.isfile(d + '/' + filename) and filename.endswith(ARCHIVE_FILE_EXTENSIONS):
+ zf.write(d + '/' + filename, d + '/' + filename)
+ print (" Adding file: " + d + '/' + filename)
+
+ os.chdir('../')
+
+ zf.close()
+
+def main():
+ buildArchive("wwwroot")
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/backends/networking/sdl_net/client.cpp b/backends/networking/sdl_net/client.cpp
new file mode 100644
index 0000000000..dab38ba5c0
--- /dev/null
+++ b/backends/networking/sdl_net/client.cpp
@@ -0,0 +1,190 @@
+/* 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/networking/sdl_net/client.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/memstream.h"
+#include <SDL/SDL_net.h>
+
+namespace Networking {
+
+Client::Client():
+ _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr),
+ _previousHandler(nullptr), _stream(nullptr), _buffer(new byte[CLIENT_BUFFER_SIZE]) {}
+
+Client::Client(SDLNet_SocketSet set, TCPsocket socket):
+ _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr),
+ _previousHandler(nullptr), _stream(nullptr), _buffer(new byte[CLIENT_BUFFER_SIZE]) {
+ open(set, socket);
+}
+
+Client::~Client() {
+ close();
+ delete[] _buffer;
+}
+
+void Client::open(SDLNet_SocketSet set, TCPsocket socket) {
+ if (_state != INVALID)
+ close();
+ _state = READING_HEADERS;
+ _socket = socket;
+ _set = set;
+ Reader cleanReader;
+ _reader = cleanReader;
+ if (_handler)
+ delete _handler;
+ _handler = nullptr;
+ if (_previousHandler)
+ delete _previousHandler;
+ _previousHandler = nullptr;
+ _stream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
+ if (set) {
+ int numused = SDLNet_TCP_AddSocket(set, socket);
+ if (numused == -1) {
+ error("Client: SDLNet_AddSocket: %s\n", SDLNet_GetError());
+ }
+ }
+}
+
+bool Client::readMoreIfNeeded() {
+ if (_stream == nullptr)
+ return false; //nothing to read into
+ if (_stream->size() - _stream->pos() > 0)
+ return true; //not needed, some data left in the stream
+ if (!_socket)
+ return false;
+ if (!SDLNet_SocketReady(_socket))
+ return false;
+
+ int bytes = SDLNet_TCP_Recv(_socket, _buffer, CLIENT_BUFFER_SIZE);
+ if (bytes <= 0) {
+ warning("Client::readMoreIfNeeded: recv fail");
+ close();
+ return false;
+ }
+
+ if (_stream->write(_buffer, bytes) != bytes) {
+ warning("Client::readMoreIfNeeded: failed to write() into MemoryReadWriteStream");
+ close();
+ return false;
+ }
+
+ return true;
+}
+
+void Client::readHeaders() {
+ if (!readMoreIfNeeded())
+ return;
+ _reader.setContent(_stream);
+ if (_reader.readFirstHeaders())
+ _state = (_reader.badRequest() ? BAD_REQUEST : READ_HEADERS);
+}
+
+bool Client::readContent(Common::WriteStream *stream) {
+ if (!readMoreIfNeeded())
+ return false;
+ _reader.setContent(_stream);
+ return _reader.readFirstContent(stream);
+}
+
+bool Client::readBlockHeaders(Common::WriteStream *stream) {
+ if (!readMoreIfNeeded())
+ return false;
+ _reader.setContent(_stream);
+ return _reader.readBlockHeaders(stream);
+}
+
+bool Client::readBlockContent(Common::WriteStream *stream) {
+ if (!readMoreIfNeeded())
+ return false;
+ _reader.setContent(_stream);
+ return _reader.readBlockContent(stream);
+}
+
+void Client::setHandler(ClientHandler *handler) {
+ if (_handler) {
+ if (_previousHandler)
+ delete _previousHandler;
+ _previousHandler = _handler; //can't just delete it, as setHandler() could've been called by handler itself
+ }
+ _state = BEING_HANDLED;
+ _handler = handler;
+}
+
+void Client::handle() {
+ if (_state != BEING_HANDLED)
+ warning("handle() called in a wrong Client's state");
+ if (!_handler)
+ warning("Client doesn't have handler to be handled by");
+ if (_handler)
+ _handler->handle(this);
+}
+
+void Client::close() {
+ if (_set) {
+ if (_socket) {
+ int numused = SDLNet_TCP_DelSocket(_set, _socket);
+ if (numused == -1)
+ error("Client: SDLNet_DelSocket: %s\n", SDLNet_GetError());
+ }
+ _set = nullptr;
+ }
+
+ if (_socket) {
+ SDLNet_TCP_Close(_socket);
+ _socket = nullptr;
+ }
+
+ if (_stream) {
+ delete _stream;
+ _stream = nullptr;
+ }
+
+ _state = INVALID;
+}
+
+
+ClientState Client::state() const { return _state; }
+
+Common::String Client::headers() const { return _reader.headers(); }
+
+Common::String Client::method() const { return _reader.method(); }
+
+Common::String Client::path() const { return _reader.path(); }
+
+Common::String Client::query() const { return _reader.query(); }
+
+Common::String Client::queryParameter(Common::String name) const { return _reader.queryParameter(name); }
+
+Common::String Client::anchor() const { return _reader.anchor(); }
+
+bool Client::noMoreContent() const { return _reader.noMoreContent(); }
+
+bool Client::socketIsReady() { return SDLNet_SocketReady(_socket); }
+
+int Client::recv(void *data, int maxlen) { return SDLNet_TCP_Recv(_socket, data, maxlen); }
+
+int Client::send(void *data, int len) { return SDLNet_TCP_Send(_socket, data, len); }
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/client.h b/backends/networking/sdl_net/client.h
new file mode 100644
index 0000000000..134c1be05d
--- /dev/null
+++ b/backends/networking/sdl_net/client.h
@@ -0,0 +1,125 @@
+/* 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_NETWORKING_SDL_NET_CLIENT_H
+#define BACKENDS_NETWORKING_SDL_NET_CLIENT_H
+
+#include "backends/networking/sdl_net/reader.h"
+#include "common/str.h"
+
+namespace Common {
+class MemoryReadWriteStream;
+}
+
+typedef struct _SDLNet_SocketSet *SDLNet_SocketSet;
+typedef struct _TCPsocket *TCPsocket;
+
+namespace Networking {
+
+enum ClientState {
+ INVALID,
+ READING_HEADERS,
+ READ_HEADERS,
+ BAD_REQUEST,
+ BEING_HANDLED
+};
+
+class Client;
+
+#define CLIENT_BUFFER_SIZE 1 * 1024 * 1024
+
+class ClientHandler {
+public:
+ virtual ~ClientHandler() {};
+ virtual void handle(Client *client) = 0;
+};
+
+/**
+ * Client class represents one client's HTTP request
+ * to the LocalWebserver.
+ *
+ * While in READING_HEADERS state, it's kept in LocalWebserver.
+ * Client must read the headers and decide whether it's
+ * READ_HEADERS (could be handled) or BAD_REQUEST (failed).
+ *
+ * If it's READ_HEADERS, LocalWebserver searches for a corresponding
+ * BaseHandler. These classes use the information from headers -
+ * like method, path, GET parameters - to build the response
+ * for this client's request. When they do, they call setHandler()
+ * and pass a special ClientHandler. Client becomes BEING_HANDLED.
+ *
+ * While in that state, LocalWebserver calls Client's handle() and
+ * it's passed to ClientHandler. The latter does the job: it commands
+ * Client to read or write bytes with its socket or calls
+ * readContent() methods, so Client reads the request through Reader.
+ */
+
+class Client {
+ ClientState _state;
+ SDLNet_SocketSet _set;
+ TCPsocket _socket;
+ Reader _reader;
+ ClientHandler *_handler, *_previousHandler;
+ Common::MemoryReadWriteStream *_stream;
+ byte *_buffer;
+
+ bool readMoreIfNeeded();
+
+public:
+ Client();
+ Client(SDLNet_SocketSet set, TCPsocket socket);
+ virtual ~Client();
+
+ void open(SDLNet_SocketSet set, TCPsocket socket);
+ void readHeaders();
+ bool readContent(Common::WriteStream *stream);
+ bool readBlockHeaders(Common::WriteStream *stream);
+ bool readBlockContent(Common::WriteStream *stream);
+ void setHandler(ClientHandler *handler);
+ void handle();
+ void close();
+
+ ClientState state() const;
+ Common::String headers() const;
+ Common::String method() const;
+ Common::String path() const;
+ Common::String query() const;
+ Common::String queryParameter(Common::String name) const;
+ Common::String anchor() const;
+
+ bool noMoreContent() const;
+
+ /**
+ * Return SDLNet_SocketReady(_socket).
+ *
+ * It's "ready" when it has something
+ * to read (recv()). You can send()
+ * when this is false.
+ */
+ bool socketIsReady();
+ int recv(void *data, int maxlen);
+ int send(void *data, int len);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/getclienthandler.cpp b/backends/networking/sdl_net/getclienthandler.cpp
new file mode 100644
index 0000000000..1c4f5db8a8
--- /dev/null
+++ b/backends/networking/sdl_net/getclienthandler.cpp
@@ -0,0 +1,162 @@
+/* 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/networking/sdl_net/getclienthandler.h"
+#include "common/textconsole.h"
+
+namespace Networking {
+
+GetClientHandler::GetClientHandler(Common::SeekableReadStream *stream):
+ _responseCode(200), _headersPrepared(false),
+ _stream(stream), _buffer(new byte[CLIENT_HANDLER_BUFFER_SIZE]) {}
+
+GetClientHandler::~GetClientHandler() {
+ delete _stream;
+ delete[] _buffer;
+}
+
+const char *GetClientHandler::responseMessage(long responseCode) {
+ switch (responseCode) {
+ case 100: return "Continue";
+ case 101: return "Switching Protocols";
+ case 102: return "Processing";
+
+ case 200: return "OK";
+ case 201: return "Created";
+ case 202: return "Accepted";
+ case 203: return "Non-Authoritative Information";
+ case 204: return "No Content";
+ case 205: return "Reset Content";
+ case 206: return "Partial Content";
+ case 207: return "Multi-Status";
+ case 226: return "IM Used";
+
+ case 300: return "Multiple Choices";
+ case 301: return "Moved Permanently";
+ case 302: return "Moved Temporarily"; //case 302: return "Found";
+ case 303: return "See Other";
+ case 304: return "Not Modified";
+ case 305: return "Use Proxy";
+ case 306: return "RESERVED";
+ case 307: return "Temporary Redirect";
+
+ case 400: return "Bad Request";
+ case 401: return "Unauthorized";
+ case 402: return "Payment Required";
+ case 403: return "Forbidden";
+ case 404: return "Not Found";
+ case 405: return "Method Not Allowed";
+ case 406: return "Not Acceptable";
+ case 407: return "Proxy Authentication Required";
+ case 408: return "Request Timeout";
+ case 409: return "Conflict";
+ case 410: return "Gone";
+ case 411: return "Length Required";
+ case 412: return "Precondition Failed";
+ case 413: return "Request Entity Too Large";
+ case 414: return "Request-URI Too Large";
+ case 415: return "Unsupported Media Type";
+ case 416: return "Requested Range Not Satisfiable";
+ case 417: return "Expectation Failed";
+ case 422: return "Unprocessable Entity";
+ case 423: return "Locked";
+ case 424: return "Failed Dependency";
+ case 425: return "Unordered Collection";
+ case 426: return "Upgrade Required";
+ case 428: return "Precondition Required";
+ case 429: return "Too Many Requests";
+ case 431: return "Request Header Fields Too Large";
+ case 434: return "Requested Host Unavailable";
+ case 449: return "Retry With";
+ case 451: return "Unavailable For Legal Reasons";
+
+ case 500: return "Internal Server Error";
+ case 501: return "Not Implemented";
+ case 502: return "Bad Gateway";
+ case 503: return "Service Unavailable";
+ case 504: return "Gateway Timeout";
+ case 505: return "HTTP Version Not Supported";
+ case 506: return "Variant Also Negotiates";
+ case 507: return "Insufficient Storage";
+ case 508: return "Loop Detected";
+ case 509: return "Bandwidth Limit Exceeded";
+ case 510: return "Not Extended";
+ case 511: return "Network Authentication Required";
+ }
+ return "Unknown";
+}
+
+void GetClientHandler::prepareHeaders() {
+ if (!_specialHeaders.contains("Content-Type"))
+ setHeader("Content-Type", "text/html");
+
+ if (!_specialHeaders.contains("Content-Length") && _stream)
+ setHeader("Content-Length", Common::String::format("%u", _stream->size()));
+
+ _headers = Common::String::format("HTTP/1.1 %ld %s\r\n", _responseCode, responseMessage(_responseCode));
+ for (Common::HashMap<Common::String, Common::String>::iterator i = _specialHeaders.begin(); i != _specialHeaders.end(); ++i)
+ _headers += i->_key + ": " + i->_value + "\r\n";
+ _headers += "\r\n";
+
+ _headersPrepared = true;
+}
+
+void GetClientHandler::handle(Client *client) {
+ if (!client)
+ return;
+ if (!_headersPrepared)
+ prepareHeaders();
+
+ uint32 readBytes;
+
+ // send headers first
+ if (_headers.size() > 0) {
+ readBytes = _headers.size();
+ if (readBytes > CLIENT_HANDLER_BUFFER_SIZE)
+ readBytes = CLIENT_HANDLER_BUFFER_SIZE;
+ memcpy(_buffer, _headers.c_str(), readBytes);
+ _headers.erase(0, readBytes);
+ } else {
+ if (!_stream) {
+ client->close();
+ return;
+ }
+
+ readBytes = _stream->read(_buffer, CLIENT_HANDLER_BUFFER_SIZE);
+ }
+
+ if (readBytes != 0)
+ if (client->send(_buffer, readBytes) != readBytes) {
+ warning("GetClientHandler: unable to send all bytes to the client");
+ client->close();
+ return;
+ }
+
+ // we're done here!
+ if (_stream->eos())
+ client->close();
+}
+
+void GetClientHandler::setHeader(Common::String name, Common::String value) { _specialHeaders[name] = value; }
+void GetClientHandler::setResponseCode(long code) { _responseCode = code; }
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/getclienthandler.h b/backends/networking/sdl_net/getclienthandler.h
new file mode 100644
index 0000000000..3486ceef8a
--- /dev/null
+++ b/backends/networking/sdl_net/getclienthandler.h
@@ -0,0 +1,57 @@
+/* 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_NETWORKING_SDL_NET_GETCLIENTHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_GETCLIENTHANDLER_H
+
+#include "backends/networking/sdl_net/client.h"
+#include "common/hashmap.h"
+#include "common/stream.h"
+#include "common/hash-str.h"
+
+namespace Networking {
+
+#define CLIENT_HANDLER_BUFFER_SIZE 1 * 1024 * 1024
+
+class GetClientHandler: public ClientHandler {
+ Common::HashMap<Common::String, Common::String> _specialHeaders;
+ long _responseCode;
+ bool _headersPrepared;
+ Common::String _headers;
+ Common::SeekableReadStream *_stream;
+ byte *_buffer;
+
+ static const char *responseMessage(long responseCode);
+ void prepareHeaders();
+
+public:
+ GetClientHandler(Common::SeekableReadStream *stream);
+ virtual ~GetClientHandler();
+
+ virtual void handle(Client *client);
+ void setHeader(Common::String name, Common::String value);
+ void setResponseCode(long code);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/basehandler.h b/backends/networking/sdl_net/handlers/basehandler.h
new file mode 100644
index 0000000000..dec5e955bd
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/basehandler.h
@@ -0,0 +1,41 @@
+/* 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_NETWORKING_SDL_NET_BASEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_BASEHANDLER_H
+
+#include "backends/networking/sdl_net/client.h"
+
+namespace Networking {
+
+class BaseHandler {
+public:
+ BaseHandler() {}
+ virtual ~BaseHandler() {}
+
+ virtual void handle(Client &) = 0;
+ virtual bool minimalModeSupported() { return false; }
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp b/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp
new file mode 100644
index 0000000000..284bf16651
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp
@@ -0,0 +1,129 @@
+/* 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/networking/sdl_net/handlers/createdirectoryhandler.h"
+#include "backends/fs/fs-factory.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/translation.h"
+#include <common/callback.h>
+
+namespace Networking {
+
+CreateDirectoryHandler::CreateDirectoryHandler() {}
+
+CreateDirectoryHandler::~CreateDirectoryHandler() {}
+
+void CreateDirectoryHandler::handleError(Client &client, Common::String message) const {
+ if (client.queryParameter("answer_json") == "true")
+ setJsonResponseHandler(client, "error", message);
+ else
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, message);
+}
+
+void CreateDirectoryHandler::setJsonResponseHandler(Client &client, Common::String type, Common::String message) const {
+ Common::JSONObject response;
+ response.setVal("type", new Common::JSONValue(type));
+ response.setVal("message", new Common::JSONValue(message));
+
+ Common::JSONValue json = response;
+ LocalWebserver::setClientGetHandler(client, json.stringify(true));
+}
+
+/// public
+
+void CreateDirectoryHandler::handle(Client &client) {
+ Common::String path = client.queryParameter("path");
+ Common::String name = client.queryParameter("directory_name");
+
+ // check that <path> is not an absolute root
+ if (path == "" || path == "/") {
+ handleError(client, _("Can't create directory here!"));
+ return;
+ }
+
+ // check that <path> contains no '../'
+ if (HandlerUtils::hasForbiddenCombinations(path)) {
+ handleError(client, _("Invalid path!"));
+ return;
+ }
+
+ // transform virtual path to actual file system one
+ Common::String prefixToRemove = "", prefixToAdd = "";
+ if (!transformPath(path, prefixToRemove, prefixToAdd) || path.empty()) {
+ handleError(client, _("Invalid path!"));
+ return;
+ }
+
+ // check that <path> exists, is directory and isn't forbidden
+ AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path);
+ if (!HandlerUtils::permittedPath(node->getPath())) {
+ handleError(client, _("Invalid path!"));
+ return;
+ }
+ if (!node->exists()) {
+ handleError(client, _("Parent directory doesn't exists!"));
+ return;
+ }
+ if (!node->isDirectory()) {
+ handleError(client, _("Can't create a directory within a file!"));
+ return;
+ }
+
+ // check that <directory_name> doesn't exist or is directory
+ if (path.lastChar() != '/' && path.lastChar() != '\\')
+ path += '/';
+ node = g_system->getFilesystemFactory()->makeFileNodePath(path + name);
+ if (node->exists()) {
+ if (!node->isDirectory()) {
+ handleError(client, _("There is a file with that name in the parent directory!"));
+ return;
+ }
+ } else {
+ // create the <directory_name> in <path>
+ if (!node->create(true)) {
+ handleError(client, _("Failed to create the directory!"));
+ return;
+ }
+ }
+
+ // if json requested, respond with it
+ if (client.queryParameter("answer_json") == "true") {
+ setJsonResponseHandler(client, "success", _("Directory created successfully!"));
+ return;
+ }
+
+ // set redirect on success
+ HandlerUtils::setMessageHandler(
+ client,
+ Common::String::format(
+ "%s<br/><a href=\"files?path=%s\">%s</a>",
+ _("Directory created successfully!"),
+ client.queryParameter("path").c_str(),
+ _("Back to parent directory")
+ ),
+ (client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") +
+ LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path"))
+ );
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/createdirectoryhandler.h b/backends/networking/sdl_net/handlers/createdirectoryhandler.h
new file mode 100644
index 0000000000..2a18d5c4aa
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/createdirectoryhandler.h
@@ -0,0 +1,42 @@
+/* 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_NETWORKING_SDL_NET_CREATEDIRECTORYHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_CREATEDIRECTORYHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+
+namespace Networking {
+
+class CreateDirectoryHandler: public FilesBaseHandler {
+ void handleError(Client &client, Common::String message) const;
+ void setJsonResponseHandler(Client &client, Common::String type, Common::String message) const;
+public:
+ CreateDirectoryHandler();
+ virtual ~CreateDirectoryHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/downloadfilehandler.cpp b/backends/networking/sdl_net/handlers/downloadfilehandler.cpp
new file mode 100644
index 0000000000..9e212b1a6c
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/downloadfilehandler.cpp
@@ -0,0 +1,88 @@
+/* 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/networking/sdl_net/handlers/downloadfilehandler.h"
+#include "backends/fs/fs-factory.h"
+#include "backends/networking/sdl_net/getclienthandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+DownloadFileHandler::DownloadFileHandler() {}
+
+DownloadFileHandler::~DownloadFileHandler() {}
+
+/// public
+
+void DownloadFileHandler::handle(Client &client) {
+ Common::String path = client.queryParameter("path");
+
+ // check that <path> is not an absolute root
+ if (path == "" || path == "/") {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // check that <path> contains no '../'
+ if (HandlerUtils::hasForbiddenCombinations(path)) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // transform virtual path to actual file system one
+ Common::String prefixToRemove = "", prefixToAdd = "";
+ if (!transformPath(path, prefixToRemove, prefixToAdd, false) || path.empty()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // check that <path> exists, is directory and isn't forbidden
+ AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path);
+ if (!HandlerUtils::permittedPath(node->getPath())) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+ if (!node->exists()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The file doesn't exist!"));
+ return;
+ }
+ if (node->isDirectory()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't download a directory!"));
+ return;
+ }
+ Common::SeekableReadStream *stream = node->createReadStream();
+ if (stream == nullptr) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Failed to read the file!"));
+ return;
+ }
+
+ GetClientHandler *handler = new GetClientHandler(stream);
+ handler->setResponseCode(200);
+ handler->setHeader("Content-Type", "application/force-download");
+ handler->setHeader("Content-Disposition", "attachment; filename=\"" + node->getDisplayName() + "\"");
+ handler->setHeader("Content-Transfer-Encoding", "binary");
+ client.setHandler(handler);
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/downloadfilehandler.h b/backends/networking/sdl_net/handlers/downloadfilehandler.h
new file mode 100644
index 0000000000..5fa5e5d55a
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/downloadfilehandler.h
@@ -0,0 +1,40 @@
+/* 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_NETWORKING_SDL_NET_DOWNLOADFILEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_DOWNLOADFILEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+
+namespace Networking {
+
+class DownloadFileHandler: public FilesBaseHandler {
+public:
+ DownloadFileHandler();
+ virtual ~DownloadFileHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp b/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp
new file mode 100644
index 0000000000..8c5ee29b70
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp
@@ -0,0 +1,81 @@
+/* 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/networking/sdl_net/handlers/filesajaxpagehandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+#define FILES_PAGE_NAME ".filesAJAX.html"
+
+FilesAjaxPageHandler::FilesAjaxPageHandler() {}
+
+FilesAjaxPageHandler::~FilesAjaxPageHandler() {}
+
+namespace {
+
+Common::String encodeDoubleQuotesAndSlashes(Common::String s) {
+ Common::String result = "";
+ for (uint32 i = 0; i < s.size(); ++i)
+ if (s[i] == '"') {
+ result += "\\\"";
+ } else if (s[i] == '\\') {
+ result += "\\\\";
+ } else {
+ result += s[i];
+ }
+ return result;
+}
+
+}
+
+/// public
+
+void FilesAjaxPageHandler::handle(Client &client) {
+ // load stylish response page from the archive
+ Common::SeekableReadStream *const stream = HandlerUtils::getArchiveFile(FILES_PAGE_NAME);
+ if (stream == nullptr) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The page is not available without the resources."));
+ return;
+ }
+
+ Common::String response = HandlerUtils::readEverythingFromStream(stream);
+ Common::String path = client.queryParameter("path");
+
+ //these occur twice:
+ replace(response, "{create_directory_button}", _("Create directory"));
+ replace(response, "{create_directory_button}", _("Create directory"));
+ replace(response, "{upload_files_button}", _("Upload files")); //tab
+ replace(response, "{upload_file_button}", _("Upload files")); //button in the tab
+ replace(response, "{create_directory_desc}", _("Type new directory name:"));
+ replace(response, "{upload_file_desc}", _("Select a file to upload:"));
+ replace(response, "{or_upload_directory_desc}", _("Or select a directory (works in Chrome only):"));
+ replace(response, "{index_of}", _("Index of "));
+ replace(response, "{loading}", _("Loading..."));
+ replace(response, "{error}", _("Error occurred"));
+ replace(response, "{start_path}", encodeDoubleQuotesAndSlashes(path));
+ LocalWebserver::setClientGetHandler(client, response);
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/filesajaxpagehandler.h b/backends/networking/sdl_net/handlers/filesajaxpagehandler.h
new file mode 100644
index 0000000000..1d9b125c2e
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filesajaxpagehandler.h
@@ -0,0 +1,40 @@
+/* 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_NETWORKING_SDL_NET_FILESAJAXPAGEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_FILESAJAXPAGEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+
+namespace Networking {
+
+class FilesAjaxPageHandler: public FilesBaseHandler {
+public:
+ FilesAjaxPageHandler();
+ virtual ~FilesAjaxPageHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/filesbasehandler.cpp b/backends/networking/sdl_net/handlers/filesbasehandler.cpp
new file mode 100644
index 0000000000..135e0fb100
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filesbasehandler.cpp
@@ -0,0 +1,87 @@
+/* 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/networking/sdl_net/handlers/filesbasehandler.h"
+#include "backends/saves/default/default-saves.h"
+#include "common/config-manager.h"
+#include "common/system.h"
+
+namespace Networking {
+
+FilesBaseHandler::FilesBaseHandler() {}
+
+FilesBaseHandler::~FilesBaseHandler() {}
+
+Common::String FilesBaseHandler::parentPath(Common::String path) {
+ if (path.size() && (path.lastChar() == '/' || path.lastChar() == '\\')) path.deleteLastChar();
+ if (!path.empty()) {
+ for (int i = path.size() - 1; i >= 0; --i)
+ if (i == 0 || path[i] == '/' || path[i] == '\\') {
+ path.erase(i);
+ break;
+ }
+ }
+ if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\')
+ path += '/';
+ return path;
+}
+
+bool FilesBaseHandler::transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd, bool isDirectory) {
+ // <path> is not empty, but could lack the trailing slash
+ if (isDirectory && path.lastChar() != '/' && path.lastChar() != '\\')
+ path += '/';
+
+ if (path.hasPrefix("/root") && ConfMan.hasKey("rootpath", "cloud")) {
+ prefixToAdd = "/root/";
+ prefixToRemove = ConfMan.get("rootpath", "cloud");
+ if (prefixToRemove.size() && prefixToRemove.lastChar() != '/' && prefixToRemove.lastChar() != '\\')
+ prefixToRemove += '/';
+ if (prefixToRemove == "/") prefixToRemove = "";
+ path.erase(0, 5);
+ if (path.size() && (path[0] == '/' || path[0] == '\\'))
+ path.deleteChar(0); // if that was "/root/ab/c", it becomes "/ab/c", but we need "ab/c"
+ path = prefixToRemove + path;
+ if (path == "")
+ path = "/"; // absolute root is '/'
+ return true;
+ }
+
+ if (path.hasPrefix("/saves")) {
+ prefixToAdd = "/saves/";
+
+ // determine savepath (prefix to remove)
+ DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
+ prefixToRemove = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath"));
+ if (prefixToRemove.size() && prefixToRemove.lastChar() != '/' && prefixToRemove.lastChar() != '\\')
+ prefixToRemove += '/';
+
+ path.erase(0, 6);
+ if (path.size() && (path[0] == '/' || path[0] == '\\'))
+ path.deleteChar(0);
+ path = prefixToRemove + path;
+ return true;
+ }
+
+ return false;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/filesbasehandler.h b/backends/networking/sdl_net/handlers/filesbasehandler.h
new file mode 100644
index 0000000000..1c7f4dd799
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filesbasehandler.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_NETWORKING_SDL_NET_FILESBASEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_FILESBASEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/basehandler.h"
+
+namespace Networking {
+
+class FilesBaseHandler: public BaseHandler {
+protected:
+ Common::String parentPath(Common::String path);
+
+ /**
+ * Transforms virtual <path> into actual file system path.
+ *
+ * Fills prefixes with actual file system prefix ("to remove")
+ * and virtual path prefix ("to add").
+ *
+ * Returns true on success.
+ */
+ bool transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd, bool isDirectory = true);
+public:
+ FilesBaseHandler();
+ virtual ~FilesBaseHandler();
+
+ virtual void handle(Client &client) = 0;
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/filespagehandler.cpp b/backends/networking/sdl_net/handlers/filespagehandler.cpp
new file mode 100644
index 0000000000..e117b4922c
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filespagehandler.cpp
@@ -0,0 +1,237 @@
+/* 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/networking/sdl_net/handlers/filespagehandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/config-manager.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+#define INDEX_PAGE_NAME ".index.html"
+#define FILES_PAGE_NAME ".files.html"
+
+FilesPageHandler::FilesPageHandler() {}
+
+FilesPageHandler::~FilesPageHandler() {}
+
+namespace {
+Common::String encodeDoubleQuotes(Common::String s) {
+ Common::String result = "";
+ for (uint32 i = 0; i < s.size(); ++i)
+ if (s[i] == '"') {
+ result += "\\\"";
+ } else {
+ result += s[i];
+ }
+ return result;
+}
+
+Common::String encodeHtmlEntities(Common::String s) {
+ Common::String result = "";
+ for (uint32 i = 0; i < s.size(); ++i)
+ if (s[i] == '<')
+ result += "&lt;";
+ else if (s[i] == '>')
+ result += "&gt;";
+ else if (s[i] == '&')
+ result += "&amp;";
+ else if (s[i] > 0x7F)
+ result += Common::String::format("&#%d;", (int)s[i]);
+ else result += s[i];
+ return result;
+}
+
+Common::String getDisplayPath(Common::String s) {
+ Common::String result = "";
+ for (uint32 i = 0; i < s.size(); ++i)
+ if (s[i] == '\\')
+ result += '/';
+ else
+ result += s[i];
+ if (result == "")
+ return "/";
+ return result;
+}
+}
+
+bool FilesPageHandler::listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate) {
+ if (path == "" || path == "/") {
+ if (ConfMan.hasKey("rootpath", "cloud"))
+ addItem(content, itemTemplate, IT_DIRECTORY, "/root/", _("File system root"));
+ addItem(content, itemTemplate, IT_DIRECTORY, "/saves/", _("Saved games"));
+ return true;
+ }
+
+ if (HandlerUtils::hasForbiddenCombinations(path))
+ return false;
+
+ Common::String prefixToRemove = "", prefixToAdd = "";
+ if (!transformPath(path, prefixToRemove, prefixToAdd))
+ return false;
+
+ Common::FSNode node = Common::FSNode(path);
+ if (path == "/")
+ node = node.getParent(); // absolute root
+
+ if (!HandlerUtils::permittedPath(node.getPath()))
+ return false;
+
+ if (!node.isDirectory())
+ return false;
+
+ // list directory
+ Common::FSList _nodeContent;
+ if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files
+ _nodeContent.clear();
+ else
+ Common::sort(_nodeContent.begin(), _nodeContent.end());
+
+ // add parent directory link
+ {
+ Common::String filePath = path;
+ if (filePath.hasPrefix(prefixToRemove))
+ filePath.erase(0, prefixToRemove.size());
+ if (filePath == "" || filePath == "/" || filePath == "\\")
+ filePath = "/";
+ else
+ filePath = parentPath(prefixToAdd + filePath);
+ addItem(content, itemTemplate, IT_PARENT_DIRECTORY, filePath, _("Parent directory"));
+ }
+
+ // fill the content
+ for (Common::FSList::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) {
+ Common::String name = i->getDisplayName();
+ if (i->isDirectory())
+ name += "/";
+
+ Common::String filePath = i->getPath();
+ if (filePath.hasPrefix(prefixToRemove))
+ filePath.erase(0, prefixToRemove.size());
+ filePath = prefixToAdd + filePath;
+
+ addItem(content, itemTemplate, detectType(i->isDirectory(), name), filePath, name);
+ }
+
+ return true;
+}
+
+FilesPageHandler::ItemType FilesPageHandler::detectType(bool isDirectory, const Common::String &name) {
+ if (isDirectory)
+ return IT_DIRECTORY;
+ if (name.hasSuffix(".txt"))
+ return IT_TXT;
+ if (name.hasSuffix(".zip"))
+ return IT_ZIP;
+ if (name.hasSuffix(".7z"))
+ return IT_7Z;
+ return IT_UNKNOWN;
+}
+
+void FilesPageHandler::addItem(Common::String &content, const Common::String &itemTemplate, ItemType itemType, Common::String path, Common::String name, Common::String size) const {
+ Common::String item = itemTemplate, icon;
+ bool isDirectory = (itemType == IT_DIRECTORY || itemType == IT_PARENT_DIRECTORY);
+ switch (itemType) {
+ case IT_DIRECTORY:
+ icon = "dir.png";
+ break;
+ case IT_PARENT_DIRECTORY:
+ icon = "up.png";
+ break;
+ case IT_TXT:
+ icon = "txt.png";
+ break;
+ case IT_ZIP:
+ icon = "zip.png";
+ break;
+ case IT_7Z:
+ icon = "7z.png";
+ break;
+ default:
+ icon = "unk.png";
+ }
+ replace(item, "{icon}", icon);
+ replace(item, "{link}", (isDirectory ? "files?path=" : "download?path=") + LocalWebserver::urlEncodeQueryParameterValue(path));
+ replace(item, "{name}", encodeHtmlEntities(name));
+ replace(item, "{size}", size);
+ content += item;
+}
+
+/// public
+
+void FilesPageHandler::handle(Client &client) {
+ Common::String response =
+ "<html>" \
+ "<head><title>ScummVM</title></head>" \
+ "<body>" \
+ "<p>{create_directory_desc}</p>" \
+ "<form action=\"create\">" \
+ "<input type=\"hidden\" name=\"path\" value=\"{path}\"/>" \
+ "<input type=\"text\" name=\"directory_name\" value=\"\"/>" \
+ "<input type=\"submit\" value=\"{create_directory_button}\"/>" \
+ "</form>" \
+ "<hr/>" \
+ "<p>{upload_file_desc}</p>" \
+ "<form action=\"upload?path={path}\" method=\"post\" enctype=\"multipart/form-data\">" \
+ "<input type=\"file\" name=\"upload_file-f\" allowdirs multiple/>" \
+ "<span>{or_upload_directory_desc}</span>" \
+ "<input type=\"file\" name=\"upload_file-d\" directory webkitdirectory multiple/>" \
+ "<input type=\"submit\" value=\"{upload_file_button}\"/>" \
+ "</form>"
+ "<hr/>" \
+ "<h1>{index_of_directory}</h1>" \
+ "<table>{content}</table>" \
+ "</body>" \
+ "</html>";
+ Common::String itemTemplate = "<tr><td><img src=\"icons/{icon}\"/></td><td><a href=\"{link}\">{name}</a></td><td>{size}</td></tr>\n"; //TODO: load this template too?
+
+ // load stylish response page from the archive
+ Common::SeekableReadStream *const stream = HandlerUtils::getArchiveFile(FILES_PAGE_NAME);
+ if (stream)
+ response = HandlerUtils::readEverythingFromStream(stream);
+
+ Common::String path = client.queryParameter("path");
+ Common::String content = "";
+
+ // show an error message if failed to list directory
+ if (!listDirectory(path, content, itemTemplate)) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("ScummVM couldn't list the directory you specified."));
+ return;
+ }
+
+ //these occur twice:
+ replace(response, "{create_directory_button}", _("Create directory"));
+ replace(response, "{create_directory_button}", _("Create directory"));
+ replace(response, "{path}", encodeDoubleQuotes(client.queryParameter("path")));
+ replace(response, "{path}", encodeDoubleQuotes(client.queryParameter("path")));
+ replace(response, "{upload_files_button}", _("Upload files")); //tab
+ replace(response, "{upload_file_button}", _("Upload files")); //button in the tab
+ replace(response, "{create_directory_desc}", _("Type new directory name:"));
+ replace(response, "{upload_file_desc}", _("Select a file to upload:"));
+ replace(response, "{or_upload_directory_desc}", _("Or select a directory (works in Chrome only):"));
+ replace(response, "{index_of_directory}", Common::String::format(_("Index of %s"), encodeHtmlEntities(getDisplayPath(client.queryParameter("path"))).c_str()));
+ replace(response, "{content}", content);
+ LocalWebserver::setClientGetHandler(client, response);
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/filespagehandler.h b/backends/networking/sdl_net/handlers/filespagehandler.h
new file mode 100644
index 0000000000..e404036cb6
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filespagehandler.h
@@ -0,0 +1,62 @@
+/* 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_NETWORKING_SDL_NET_FILESPAGEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_FILESPAGEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+
+namespace Networking {
+
+class FilesPageHandler: public FilesBaseHandler {
+ enum ItemType {
+ IT_DIRECTORY,
+ IT_PARENT_DIRECTORY,
+ IT_TXT,
+ IT_ZIP,
+ IT_7Z,
+ IT_UNKNOWN
+ };
+
+ /**
+ * Lists the directory <path>.
+ *
+ * Returns true on success.
+ */
+ bool listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate);
+
+ /** Helper method for detecting items' type. */
+ static ItemType detectType(bool isDirectory, const Common::String &name);
+
+ /** Helper method for adding items into the files list. */
+ void addItem(Common::String &content, const Common::String &itemTemplate, ItemType itemType, Common::String path, Common::String name, Common::String size = "") const;
+
+public:
+ FilesPageHandler();
+ virtual ~FilesPageHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/indexpagehandler.cpp b/backends/networking/sdl_net/handlers/indexpagehandler.cpp
new file mode 100644
index 0000000000..985bd6635e
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/indexpagehandler.cpp
@@ -0,0 +1,65 @@
+/* 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/networking/sdl_net/handlers/indexpagehandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/translation.h"
+#include "gui/storagewizarddialog.h"
+
+namespace Networking {
+
+IndexPageHandler::IndexPageHandler(): CommandSender(nullptr) {}
+
+IndexPageHandler::~IndexPageHandler() {}
+
+/// public
+
+Common::String IndexPageHandler::code() const { return _code; }
+
+void IndexPageHandler::handle(Client &client) {
+ Common::String code = client.queryParameter("code");
+
+ if (code == "") {
+ // redirect to "/filesAJAX"
+ HandlerUtils::setMessageHandler(
+ client,
+ Common::String::format(
+ "%s<br/><a href=\"files\">%s</a>",
+ _("This is a local webserver index page."),
+ _("Open Files manager")
+ ),
+ "/filesAJAX"
+ );
+ return;
+ }
+
+ _code = code;
+ sendCommand(GUI::kStorageCodePassedCmd, 0);
+ HandlerUtils::setMessageHandler(client, _("ScummVM got the code and already connects to your cloud storage!"));
+}
+
+bool IndexPageHandler::minimalModeSupported() {
+ return true;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/indexpagehandler.h b/backends/networking/sdl_net/handlers/indexpagehandler.h
new file mode 100644
index 0000000000..8065954b27
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/indexpagehandler.h
@@ -0,0 +1,45 @@
+/* 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_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/basehandler.h"
+#include "gui/object.h"
+
+namespace Networking {
+class LocalWebserver;
+
+class IndexPageHandler: public BaseHandler, public GUI::CommandSender {
+ Common::String _code;
+public:
+ IndexPageHandler();
+ virtual ~IndexPageHandler();
+
+ Common::String code() const;
+ virtual void handle(Client &client);
+ virtual bool minimalModeSupported();
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/listajaxhandler.cpp b/backends/networking/sdl_net/handlers/listajaxhandler.cpp
new file mode 100644
index 0000000000..f94b674a3c
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/listajaxhandler.cpp
@@ -0,0 +1,157 @@
+/* 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/networking/sdl_net/handlers/listajaxhandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/config-manager.h"
+#include "common/json.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+ListAjaxHandler::ListAjaxHandler() {}
+
+ListAjaxHandler::~ListAjaxHandler() {}
+
+Common::JSONObject ListAjaxHandler::listDirectory(Common::String path) {
+ Common::JSONArray itemsList;
+ Common::JSONObject errorResult;
+ Common::JSONObject successResult;
+ successResult.setVal("type", new Common::JSONValue("success"));
+ errorResult.setVal("type", new Common::JSONValue("error"));
+
+ if (path == "" || path == "/") {
+ if (ConfMan.hasKey("rootpath", "cloud"))
+ addItem(itemsList, IT_DIRECTORY, "/root/", _("File system root"));
+ addItem(itemsList, IT_DIRECTORY, "/saves/", _("Saved games"));
+ successResult.setVal("items", new Common::JSONValue(itemsList));
+ return successResult;
+ }
+
+ if (HandlerUtils::hasForbiddenCombinations(path))
+ return errorResult;
+
+ Common::String prefixToRemove = "", prefixToAdd = "";
+ if (!transformPath(path, prefixToRemove, prefixToAdd))
+ return errorResult;
+
+ Common::FSNode node = Common::FSNode(path);
+ if (path == "/")
+ node = node.getParent(); // absolute root
+
+ if (!HandlerUtils::permittedPath(node.getPath()))
+ return errorResult;
+
+ if (!node.isDirectory())
+ return errorResult;
+
+ // list directory
+ Common::FSList _nodeContent;
+ if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files
+ _nodeContent.clear();
+ else
+ Common::sort(_nodeContent.begin(), _nodeContent.end());
+
+ // add parent directory link
+ {
+ Common::String filePath = path;
+ if (filePath.hasPrefix(prefixToRemove))
+ filePath.erase(0, prefixToRemove.size());
+ if (filePath == "" || filePath == "/" || filePath == "\\")
+ filePath = "/";
+ else
+ filePath = parentPath(prefixToAdd + filePath);
+ addItem(itemsList, IT_PARENT_DIRECTORY, filePath, _("Parent directory"));
+ }
+
+ // fill the content
+ for (Common::FSList::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) {
+ Common::String name = i->getDisplayName();
+ if (i->isDirectory()) name += "/";
+
+ Common::String filePath = i->getPath();
+ if (filePath.hasPrefix(prefixToRemove))
+ filePath.erase(0, prefixToRemove.size());
+ filePath = prefixToAdd + filePath;
+
+ addItem(itemsList, detectType(i->isDirectory(), name), filePath, name);
+ }
+
+ successResult.setVal("items", new Common::JSONValue(itemsList));
+ return successResult;
+}
+
+ListAjaxHandler::ItemType ListAjaxHandler::detectType(bool isDirectory, const Common::String &name) {
+ if (isDirectory)
+ return IT_DIRECTORY;
+ if (name.hasSuffix(".txt"))
+ return IT_TXT;
+ if (name.hasSuffix(".zip"))
+ return IT_ZIP;
+ if (name.hasSuffix(".7z"))
+ return IT_7Z;
+ return IT_UNKNOWN;
+}
+
+void ListAjaxHandler::addItem(Common::JSONArray &responseItemsList, ItemType itemType, Common::String path, Common::String name, Common::String size) {
+ Common::String icon;
+ bool isDirectory = (itemType == IT_DIRECTORY || itemType == IT_PARENT_DIRECTORY);
+ switch (itemType) {
+ case IT_DIRECTORY:
+ icon = "dir.png";
+ break;
+ case IT_PARENT_DIRECTORY:
+ icon = "up.png";
+ break;
+ case IT_TXT:
+ icon = "txt.png";
+ break;
+ case IT_ZIP:
+ icon = "zip.png";
+ break;
+ case IT_7Z:
+ icon = "7z.png";
+ break;
+ default:
+ icon = "unk.png";
+ }
+
+ Common::JSONObject item;
+ item.setVal("name", new Common::JSONValue(name));
+ item.setVal("path", new Common::JSONValue(path));
+ item.setVal("isDirectory", new Common::JSONValue(isDirectory));
+ item.setVal("size", new Common::JSONValue(size));
+ item.setVal("icon", new Common::JSONValue(icon));
+ responseItemsList.push_back(new Common::JSONValue(item));
+}
+
+/// public
+
+void ListAjaxHandler::handle(Client &client) {
+ Common::String path = client.queryParameter("path");
+ Common::JSONValue jsonResponse = listDirectory(path);
+ Common::String response = jsonResponse.stringify(true);
+ LocalWebserver::setClientGetHandler(client, response);
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/listajaxhandler.h b/backends/networking/sdl_net/handlers/listajaxhandler.h
new file mode 100644
index 0000000000..40840ad6c3
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/listajaxhandler.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_NETWORKING_SDL_NET_LISTAJAXHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_LISTAJAXHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+#include "common/json.h"
+
+namespace Networking {
+
+class ListAjaxHandler: public FilesBaseHandler {
+ enum ItemType {
+ IT_DIRECTORY,
+ IT_PARENT_DIRECTORY,
+ IT_TXT,
+ IT_ZIP,
+ IT_7Z,
+ IT_UNKNOWN
+ };
+
+ /**
+ * Lists the directory <path>.
+ *
+ * Returns JSON with either listed directory or error response.
+ */
+ Common::JSONObject listDirectory(Common::String path);
+
+ /** Helper method for detecting items' type. */
+ static ItemType detectType(bool isDirectory, const Common::String &name);
+
+ /** Helper method for adding items into the files list. */
+ static void addItem(Common::JSONArray &responseItemsList, ItemType itemType, Common::String path, Common::String name, Common::String size = "");
+
+public:
+ ListAjaxHandler();
+ virtual ~ListAjaxHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/resourcehandler.cpp b/backends/networking/sdl_net/handlers/resourcehandler.cpp
new file mode 100644
index 0000000000..631eb63351
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/resourcehandler.cpp
@@ -0,0 +1,75 @@
+/* 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/networking/sdl_net/handlers/resourcehandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+
+namespace Networking {
+
+ResourceHandler::ResourceHandler() {}
+
+ResourceHandler::~ResourceHandler() {}
+
+const char *ResourceHandler::determineMimeType(Common::String &filename) {
+ // text
+ if (filename.hasSuffix(".html")) return "text/html";
+ if (filename.hasSuffix(".css")) return "text/css";
+ if (filename.hasSuffix(".txt")) return "text/plain";
+ if (filename.hasSuffix(".js")) return "application/javascript";
+
+ // images
+ if (filename.hasSuffix(".jpeg") || filename.hasSuffix(".jpg") || filename.hasSuffix(".jpe")) return "image/jpeg";
+ if (filename.hasSuffix(".gif")) return "image/gif";
+ if (filename.hasSuffix(".png")) return "image/png";
+ if (filename.hasSuffix(".svg")) return "image/svg+xml";
+ if (filename.hasSuffix(".tiff")) return "image/tiff";
+ if (filename.hasSuffix(".ico")) return "image/vnd.microsoft.icon";
+ if (filename.hasSuffix(".wbmp")) return "image/vnd.wap.wbmp";
+
+ if (filename.hasSuffix(".zip")) return "application/zip";
+ return "application/octet-stream";
+}
+
+/// public
+
+void ResourceHandler::handle(Client &client) {
+ Common::String filename = client.path();
+ filename.deleteChar(0);
+
+ // if archive hidden file is requested, ignore
+ if (filename.size() && filename[0] == '.')
+ return;
+
+ // if file not found, don't set handler either
+ Common::SeekableReadStream *file = HandlerUtils::getArchiveFile(filename);
+ if (file == nullptr)
+ return;
+
+ LocalWebserver::setClientGetHandler(client, file, 200, determineMimeType(filename));
+}
+
+bool ResourceHandler::minimalModeSupported() {
+ return true;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/resourcehandler.h b/backends/networking/sdl_net/handlers/resourcehandler.h
new file mode 100644
index 0000000000..2ec4c5bb19
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/resourcehandler.h
@@ -0,0 +1,42 @@
+/* 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_NETWORKING_SDL_NET_RESOURCEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_RESOURCEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/basehandler.h"
+
+namespace Networking {
+
+class ResourceHandler: public BaseHandler {
+ static const char *determineMimeType(Common::String &filename);
+public:
+ ResourceHandler();
+ virtual ~ResourceHandler();
+
+ virtual void handle(Client &client);
+ virtual bool minimalModeSupported();
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/uploadfilehandler.cpp b/backends/networking/sdl_net/handlers/uploadfilehandler.cpp
new file mode 100644
index 0000000000..a0e992c25e
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/uploadfilehandler.cpp
@@ -0,0 +1,79 @@
+/* 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/networking/sdl_net/handlers/uploadfilehandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/uploadfileclienthandler.h"
+#include "backends/fs/fs-factory.h"
+#include "common/system.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+UploadFileHandler::UploadFileHandler() {}
+
+UploadFileHandler::~UploadFileHandler() {}
+
+/// public
+
+void UploadFileHandler::handle(Client &client) {
+ Common::String path = client.queryParameter("path");
+
+ // check that <path> is not an absolute root
+ if (path == "" || path == "/" || path == "\\") {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // check that <path> contains no '../'
+ if (HandlerUtils::hasForbiddenCombinations(path)) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // transform virtual path to actual file system one
+ Common::String prefixToRemove = "", prefixToAdd = "";
+ if (!transformPath(path, prefixToRemove, prefixToAdd, false) || path.empty()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // check that <path> exists, is directory and isn't forbidden
+ AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path);
+ if (!HandlerUtils::permittedPath(node->getPath())) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+ if (!node->exists()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The parent directory doesn't exist!"));
+ return;
+ }
+ if (!node->isDirectory()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't upload into a file!"));
+ return;
+ }
+
+ // if all OK, set special handler
+ client.setHandler(new UploadFileClientHandler(path));
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/uploadfilehandler.h b/backends/networking/sdl_net/handlers/uploadfilehandler.h
new file mode 100644
index 0000000000..cbff215156
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/uploadfilehandler.h
@@ -0,0 +1,40 @@
+/* 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_NETWORKING_SDL_NET_UPLOADFILEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_UPLOADFILEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+
+namespace Networking {
+
+class UploadFileHandler: public FilesBaseHandler {
+public:
+ UploadFileHandler();
+ virtual ~UploadFileHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlerutils.cpp b/backends/networking/sdl_net/handlerutils.cpp
new file mode 100644
index 0000000000..dc21ab5ce1
--- /dev/null
+++ b/backends/networking/sdl_net/handlerutils.cpp
@@ -0,0 +1,200 @@
+/* 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/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "backends/saves/default/default-saves.h"
+#include "common/archive.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/translation.h"
+#include "common/unzip.h"
+
+namespace Networking {
+
+#define ARCHIVE_NAME "wwwroot.zip"
+
+#define INDEX_PAGE_NAME ".index.html"
+
+Common::Archive *HandlerUtils::getZipArchive() {
+ // first search in themepath
+ if (ConfMan.hasKey("themepath")) {
+ const Common::FSNode &node = Common::FSNode(ConfMan.get("themepath"));
+ if (!node.exists() || !node.isReadable() || !node.isDirectory())
+ return nullptr;
+
+ Common::FSNode fileNode = node.getChild(ARCHIVE_NAME);
+ if (fileNode.exists() && fileNode.isReadable() && !fileNode.isDirectory()) {
+ Common::SeekableReadStream *const stream = fileNode.createReadStream();
+ Common::Archive *zipArchive = Common::makeZipArchive(stream);
+ if (zipArchive)
+ return zipArchive;
+ }
+ }
+
+ // then use SearchMan to find it
+ Common::ArchiveMemberList fileList;
+ SearchMan.listMatchingMembers(fileList, ARCHIVE_NAME);
+ for (Common::ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) {
+ Common::ArchiveMember const &m = **it;
+ Common::SeekableReadStream *const stream = m.createReadStream();
+ Common::Archive *zipArchive = Common::makeZipArchive(stream);
+ if (zipArchive)
+ return zipArchive;
+ }
+
+ return nullptr;
+}
+
+Common::ArchiveMemberList HandlerUtils::listArchive() {
+ Common::ArchiveMemberList resultList;
+ Common::Archive *zipArchive = getZipArchive();
+ if (zipArchive) {
+ zipArchive->listMembers(resultList);
+ delete zipArchive;
+ }
+ return resultList;
+}
+
+Common::SeekableReadStream *HandlerUtils::getArchiveFile(Common::String name) {
+ Common::SeekableReadStream *result = nullptr;
+ Common::Archive *zipArchive = getZipArchive();
+ if (zipArchive) {
+ const Common::ArchiveMemberPtr ptr = zipArchive->getMember(name);
+ if (ptr.get() == nullptr)
+ return nullptr;
+ result = ptr->createReadStream();
+ delete zipArchive;
+ }
+ return result;
+}
+
+Common::String HandlerUtils::readEverythingFromStream(Common::SeekableReadStream *const stream) {
+ Common::String result;
+ char buf[1024];
+ uint32 readBytes;
+ while (!stream->eos()) {
+ readBytes = stream->read(buf, 1024);
+ result += Common::String(buf, readBytes);
+ }
+ return result;
+}
+
+Common::String HandlerUtils::normalizePath(const Common::String &path) {
+ Common::String normalized;
+ bool slash = false;
+ for (uint32 i = 0; i < path.size(); ++i) {
+ char c = path[i];
+ if (c == '\\' || c == '/') {
+ slash = true;
+ continue;
+ }
+
+ if (slash) {
+ normalized += '/';
+ slash = false;
+ }
+
+ if ('A' <= c && c <= 'Z') {
+ normalized += c - 'A' + 'a';
+ } else {
+ normalized += c;
+ }
+ }
+ if (slash) normalized += '/';
+ return normalized;
+}
+
+bool HandlerUtils::hasForbiddenCombinations(const Common::String &path) {
+ return (path.contains("/../") || path.contains("\\..\\") || path.contains("\\../") || path.contains("/..\\"));
+}
+
+bool HandlerUtils::isBlacklisted(const Common::String &path) {
+ const char *blacklist[] = {
+ "/etc",
+ "/bin",
+ "c:/windows" // just saying: I know guys who install windows on another drives
+ };
+
+ // normalize path
+ Common::String normalized = normalizePath(path);
+
+ uint32 size = sizeof(blacklist) / sizeof(const char *);
+ for (uint32 i = 0; i < size; ++i)
+ if (normalized.hasPrefix(blacklist[i]))
+ return true;
+
+ return false;
+}
+
+bool HandlerUtils::hasPermittedPrefix(const Common::String &path) {
+ // normalize path
+ Common::String normalized = normalizePath(path);
+
+ // prefix for /root/
+ Common::String prefix;
+ if (ConfMan.hasKey("rootpath", "cloud")) {
+ prefix = normalizePath(ConfMan.get("rootpath", "cloud"));
+ if (prefix == "/" || normalized.hasPrefix(prefix))
+ return true;
+ }
+
+ // prefix for /saves/
+ DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
+ prefix = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath"));
+ return (normalized.hasPrefix(normalizePath(prefix)));
+}
+
+bool HandlerUtils::permittedPath(const Common::String path) {
+ return hasPermittedPrefix(path) && !isBlacklisted(path);
+}
+
+void HandlerUtils::setMessageHandler(Client &client, Common::String message, Common::String redirectTo) {
+ Common::String response = "<html><head><title>ScummVM</title></head><body>{message}</body></html>";
+
+ // load stylish response page from the archive
+ Common::SeekableReadStream *const stream = getArchiveFile(INDEX_PAGE_NAME);
+ if (stream)
+ response = readEverythingFromStream(stream);
+
+ replace(response, "{message}", message);
+ if (redirectTo.empty())
+ LocalWebserver::setClientGetHandler(client, response);
+ else
+ LocalWebserver::setClientRedirectHandler(client, response, redirectTo);
+}
+
+void HandlerUtils::setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo) {
+ setMessageHandler(
+ client,
+ Common::String::format(
+ "%s<br/><a href=\"files%s?path=%s\">%s</a>",
+ message.c_str(),
+ client.queryParameter("ajax") == "true" ? "AJAX" : "",
+ "%2F", //that's encoded "/"
+ _("Back to the files manager")
+ ),
+ redirectTo
+ );
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlerutils.h b/backends/networking/sdl_net/handlerutils.h
new file mode 100644
index 0000000000..4c2eff49b6
--- /dev/null
+++ b/backends/networking/sdl_net/handlerutils.h
@@ -0,0 +1,50 @@
+/* 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_NETWORKING_SDL_NET_HANDLERUTILS_H
+#define BACKENDS_NETWORKING_SDL_NET_HANDLERUTILS_H
+
+#include "backends/networking/sdl_net/client.h"
+#include "common/archive.h"
+
+namespace Networking {
+
+class HandlerUtils {
+public:
+ static Common::Archive *getZipArchive();
+ static Common::ArchiveMemberList listArchive();
+ static Common::SeekableReadStream *getArchiveFile(Common::String name);
+ static Common::String readEverythingFromStream(Common::SeekableReadStream *const stream);
+
+ static Common::String normalizePath(const Common::String &path);
+ static bool hasForbiddenCombinations(const Common::String &path);
+ static bool isBlacklisted(const Common::String &path);
+ static bool hasPermittedPrefix(const Common::String &path);
+ static bool permittedPath(const Common::String path);
+
+ static void setMessageHandler(Client &client, Common::String message, Common::String redirectTo = "");
+ static void setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo = "");
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/localwebserver.cpp b/backends/networking/sdl_net/localwebserver.cpp
new file mode 100644
index 0000000000..6557c7a88d
--- /dev/null
+++ b/backends/networking/sdl_net/localwebserver.cpp
@@ -0,0 +1,446 @@
+/* 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/networking/sdl_net/localwebserver.h"
+#include "backends/networking/sdl_net/getclienthandler.h"
+#include "common/memstream.h"
+#include "common/str.h"
+#include "common/system.h"
+#include "common/timer.h"
+#include "common/translation.h"
+#include <SDL/SDL_net.h>
+#include <common/config-manager.h>
+
+#ifdef POSIX
+#include <sys/types.h>
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
+namespace Common {
+class MemoryReadWriteStream;
+
+DECLARE_SINGLETON(Networking::LocalWebserver);
+
+}
+
+namespace Networking {
+
+LocalWebserver::LocalWebserver(): _set(nullptr), _serverSocket(nullptr), _timerStarted(false),
+ _stopOnIdle(false), _minimalMode(false), _clients(0), _idlingFrames(0), _serverPort(DEFAULT_SERVER_PORT) {
+ addPathHandler("/", &_indexPageHandler);
+ addPathHandler("/files", &_filesPageHandler);
+ addPathHandler("/create", &_createDirectoryHandler);
+ addPathHandler("/download", &_downloadFileHandler);
+ addPathHandler("/upload", &_uploadFileHandler);
+ addPathHandler("/list", &_listAjaxHandler);
+ addPathHandler("/filesAJAX", &_filesAjaxPageHandler);
+ _defaultHandler = &_resourceHandler;
+}
+
+LocalWebserver::~LocalWebserver() {
+ stop();
+}
+
+void localWebserverTimer(void *ignored) {
+ LocalServer.handle();
+}
+
+void LocalWebserver::startTimer(int interval) {
+ Common::TimerManager *manager = g_system->getTimerManager();
+ if (manager->installTimerProc(localWebserverTimer, interval, 0, "Networking::LocalWebserver's Timer")) {
+ _timerStarted = true;
+ } else {
+ warning("Failed to install Networking::LocalWebserver's timer");
+ }
+}
+
+void LocalWebserver::stopTimer() {
+ Common::TimerManager *manager = g_system->getTimerManager();
+ manager->removeTimerProc(localWebserverTimer);
+ _timerStarted = false;
+}
+
+void LocalWebserver::start(bool useMinimalMode) {
+ _handleMutex.lock();
+ _serverPort = getPort();
+ _stopOnIdle = false;
+ if (_timerStarted) {
+ _handleMutex.unlock();
+ return;
+ }
+ _minimalMode = useMinimalMode;
+ startTimer();
+
+ // Create a listening TCP socket
+ IPaddress ip;
+ if (SDLNet_ResolveHost(&ip, NULL, _serverPort) == -1) {
+ error("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
+ }
+
+ resolveAddress(&ip);
+
+ _serverSocket = SDLNet_TCP_Open(&ip);
+ if (!_serverSocket) {
+ warning("LocalWebserver: SDLNet_TCP_Open: %s", SDLNet_GetError());
+ stopTimer();
+ g_system->displayMessageOnOSD(_("Failed to start local webserver.\nCheck whether selected port is not used by another application and try again."));
+ _handleMutex.unlock();
+ return;
+ }
+
+ // Create a socket set
+ _set = SDLNet_AllocSocketSet(MAX_CONNECTIONS + 1); //one more for our server socket
+ if (!_set) {
+ error("LocalWebserver: SDLNet_AllocSocketSet: %s\n", SDLNet_GetError());
+ }
+
+ int numused = SDLNet_TCP_AddSocket(_set, _serverSocket);
+ if (numused == -1) {
+ error("LocalWebserver: SDLNet_AddSocket: %s\n", SDLNet_GetError());
+ }
+ _handleMutex.unlock();
+}
+
+void LocalWebserver::stop() {
+ _handleMutex.lock();
+ if (_timerStarted)
+ stopTimer();
+
+ if (_serverSocket) {
+ SDLNet_TCP_Close(_serverSocket);
+ _serverSocket = nullptr;
+ }
+
+ for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
+ _client[i].close();
+
+ _clients = 0;
+
+ if (_set) {
+ SDLNet_FreeSocketSet(_set);
+ _set = nullptr;
+ }
+ _handleMutex.unlock();
+}
+
+void LocalWebserver::stopOnIdle() { _stopOnIdle = true; }
+
+void LocalWebserver::addPathHandler(Common::String path, BaseHandler *handler) {
+ if (_pathHandlers.contains(path))
+ warning("LocalWebserver::addPathHandler: path already had a handler");
+ _pathHandlers[path] = handler;
+}
+
+Common::String LocalWebserver::getAddress() { return _address; }
+
+IndexPageHandler &LocalWebserver::indexPageHandler() { return _indexPageHandler; }
+
+bool LocalWebserver::isRunning() {
+ bool result = false;
+ _handleMutex.lock();
+ result = _timerStarted;
+ _handleMutex.unlock();
+ return result;
+}
+
+uint32 LocalWebserver::getPort() {
+#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+ if (ConfMan.hasKey("local_server_port"))
+ return ConfMan.getInt("local_server_port");
+#endif
+ return DEFAULT_SERVER_PORT;
+}
+
+void LocalWebserver::handle() {
+ _handleMutex.lock();
+ int numready = SDLNet_CheckSockets(_set, 0);
+ if (numready == -1) {
+ error("LocalWebserver: SDLNet_CheckSockets: %s\n", SDLNet_GetError());
+ } else if (numready) {
+ acceptClient();
+ }
+
+ for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
+ handleClient(i);
+
+ _clients = 0;
+ for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
+ if (_client[i].state() != INVALID)
+ ++_clients;
+
+ if (_clients == 0)
+ ++_idlingFrames;
+ else
+ _idlingFrames = 0;
+
+ if (_idlingFrames > FRAMES_PER_SECOND && _stopOnIdle) {
+ _handleMutex.unlock();
+ stop();
+ return;
+ }
+
+ _handleMutex.unlock();
+}
+
+void LocalWebserver::handleClient(uint32 i) {
+ switch (_client[i].state()) {
+ case INVALID:
+ return;
+ case READING_HEADERS:
+ _client[i].readHeaders();
+ break;
+ case READ_HEADERS: {
+ // decide what to do next with that client
+ // check whether we know a handler for such URL
+ BaseHandler *handler = nullptr;
+ if (_pathHandlers.contains(_client[i].path())) {
+ handler = _pathHandlers[_client[i].path()];
+ } else {
+ // try default handler
+ handler = _defaultHandler;
+ }
+
+ // if server's in "minimal mode", only handlers which support it are used
+ if (handler && (!_minimalMode || handler->minimalModeSupported()))
+ handler->handle(_client[i]);
+
+ if (_client[i].state() == BEING_HANDLED || _client[i].state() == INVALID)
+ break;
+
+ // if no handler, answer with default BAD REQUEST
+ // fallthrough
+ }
+
+ case BAD_REQUEST:
+ setClientGetHandler(_client[i], "<html><head><title>ScummVM - Bad Request</title></head><body>BAD REQUEST</body></html>", 400);
+ break;
+ case BEING_HANDLED:
+ _client[i].handle();
+ break;
+ }
+}
+
+void LocalWebserver::acceptClient() {
+ if (!SDLNet_SocketReady(_serverSocket))
+ return;
+
+ TCPsocket client = SDLNet_TCP_Accept(_serverSocket);
+ if (!client)
+ return;
+
+ if (_clients == MAX_CONNECTIONS) { //drop the connection
+ SDLNet_TCP_Close(client);
+ return;
+ }
+
+ ++_clients;
+ for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
+ if (_client[i].state() == INVALID) {
+ _client[i].open(_set, client);
+ break;
+ }
+}
+
+void LocalWebserver::resolveAddress(void *ipAddress) {
+ IPaddress *ip = (IPaddress *)ipAddress;
+
+ // not resolved
+ _address = Common::String::format("http://127.0.0.1:%u/ (unresolved)", _serverPort);
+
+ // default way (might work everywhere, surely works on Windows)
+ const char *name = SDLNet_ResolveIP(ip);
+ if (name == NULL) {
+ warning("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
+ } else {
+ IPaddress localIp;
+ if (SDLNet_ResolveHost(&localIp, name, _serverPort) == -1) {
+ warning("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
+ } else {
+ _address = Common::String::format(
+ "http://%u.%u.%u.%u:%u/",
+ localIp.host & 0xFF, (localIp.host >> 8) & 0xFF, (localIp.host >> 16) & 0xFF, (localIp.host >> 24) & 0xFF,
+ _serverPort
+ );
+ }
+ }
+
+ // check that our trick worked
+ if (_address.contains("/127.0.0.1:") || _address.contains("localhost") || _address.contains("/0.0.0.0:"))
+ warning("LocalWebserver: Failed to resolve IP with the default way");
+ else
+ return;
+
+ // if not - try platform-specific
+#ifdef POSIX
+ struct ifaddrs *ifAddrStruct = NULL;
+ void *tmpAddrPtr = NULL;
+
+ getifaddrs(&ifAddrStruct);
+
+ for (struct ifaddrs *i = ifAddrStruct; i != NULL; i = i->ifa_next) {
+ if (!i->ifa_addr) {
+ continue;
+ }
+
+ Common::String addr;
+
+ // IPv4
+ if (i->ifa_addr->sa_family == AF_INET) {
+ tmpAddrPtr = &((struct sockaddr_in *)i->ifa_addr)->sin_addr;
+ char addressBuffer[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
+ debug(9, "%s IP Address %s", i->ifa_name, addressBuffer);
+ addr = addressBuffer;
+ }
+
+ // IPv6
+ /*
+ if (i->ifa_addr->sa_family == AF_INET6) {
+ tmpAddrPtr = &((struct sockaddr_in6 *)i->ifa_addr)->sin6_addr;
+ char addressBuffer[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
+ debug(9, "%s IP Address %s", i->ifa_name, addressBuffer);
+ addr = addressBuffer;
+ }
+ */
+
+ if (addr.empty())
+ continue;
+
+ // ignored IPv4 addresses
+ if (addr.equals("127.0.0.1") || addr.equals("0.0.0.0") || addr.equals("localhost"))
+ continue;
+
+ // ignored IPv6 addresses
+ /*
+ if (addr.equals("::1"))
+ continue;
+ */
+
+ // use the address found
+ _address = "http://" + addr + Common::String::format(":%u/", _serverPort);
+ }
+
+ if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
+#endif
+}
+
+void LocalWebserver::setClientGetHandler(Client &client, Common::String response, long code, const char *mimeType) {
+ byte *data = new byte[response.size()];
+ memcpy(data, response.c_str(), response.size());
+ Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
+ setClientGetHandler(client, stream, code, mimeType);
+}
+
+void LocalWebserver::setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code, const char *mimeType) {
+ GetClientHandler *handler = new GetClientHandler(responseStream);
+ handler->setResponseCode(code);
+ if (mimeType)
+ handler->setHeader("Content-Type", mimeType);
+ client.setHandler(handler);
+}
+
+void LocalWebserver::setClientRedirectHandler(Client &client, Common::String response, Common::String location, const char *mimeType) {
+ byte *data = new byte[response.size()];
+ memcpy(data, response.c_str(), response.size());
+ Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
+ setClientRedirectHandler(client, stream, location, mimeType);
+}
+
+void LocalWebserver::setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, Common::String location, const char *mimeType) {
+ GetClientHandler *handler = new GetClientHandler(responseStream);
+ handler->setResponseCode(302); //redirect
+ handler->setHeader("Location", location);
+ if (mimeType)
+ handler->setHeader("Content-Type", mimeType);
+ client.setHandler(handler);
+}
+
+namespace {
+int hexDigit(char c) {
+ if ('0' <= c && c <= '9') return c - '0';
+ if ('A' <= c && c <= 'F') return c - 'A' + 10;
+ if ('a' <= c && c <= 'f') return c - 'a' + 10;
+ return -1;
+}
+}
+
+Common::String LocalWebserver::urlDecode(Common::String value) {
+ Common::String result = "";
+ uint32 size = value.size();
+ for (uint32 i = 0; i < size; ++i) {
+ if (value[i] == '+') {
+ result += ' ';
+ continue;
+ }
+
+ if (value[i] == '%' && i + 2 < size) {
+ int d1 = hexDigit(value[i + 1]);
+ int d2 = hexDigit(value[i + 2]);
+ if (0 <= d1 && d1 < 16 && 0 <= d2 && d2 < 16) {
+ result += (char)(d1 * 16 + d2);
+ i = i + 2;
+ continue;
+ }
+ }
+
+ result += value[i];
+ }
+ return result;
+}
+
+namespace {
+bool isQueryUnreserved(char c) {
+ return (
+ ('0' <= c && c <= '9') ||
+ ('A' <= c && c <= 'Z') ||
+ ('a' <= c && c <= 'z') ||
+ c == '-' || c == '_' || c == '.' || c == '!' ||
+ c == '~' || c == '*' || c == '\'' || c == '(' || c == ')'
+ );
+}
+}
+
+Common::String LocalWebserver::urlEncodeQueryParameterValue(Common::String value) {
+ //OK chars = alphanum | "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+ //reserved for query are ";", "/", "?", ":", "@", "&", "=", "+", ","
+ //that means these must be encoded too or otherwise they could malform the query
+ Common::String result = "";
+ char hexChar[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+ for (uint32 i = 0; i < value.size(); ++i) {
+ char c = value[i];
+ if (isQueryUnreserved(c))
+ result += c;
+ else {
+ result += '%';
+ result += hexChar[(c >> 4) & 0xF];
+ result += hexChar[c & 0xF];
+ }
+ }
+ return result;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/localwebserver.h b/backends/networking/sdl_net/localwebserver.h
new file mode 100644
index 0000000000..c6cf8485c3
--- /dev/null
+++ b/backends/networking/sdl_net/localwebserver.h
@@ -0,0 +1,115 @@
+/* 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_NETWORKING_SDL_NET_LOCALWEBSERVER_H
+#define BACKENDS_NETWORKING_SDL_NET_LOCALWEBSERVER_H
+
+#include "backends/networking/sdl_net/client.h"
+#include "backends/networking/sdl_net/handlers/basehandler.h"
+#include "backends/networking/sdl_net/handlers/createdirectoryhandler.h"
+#include "backends/networking/sdl_net/handlers/downloadfilehandler.h"
+#include "backends/networking/sdl_net/handlers/filesajaxpagehandler.h"
+#include "backends/networking/sdl_net/handlers/filespagehandler.h"
+#include "backends/networking/sdl_net/handlers/indexpagehandler.h"
+#include "backends/networking/sdl_net/handlers/listajaxhandler.h"
+#include "backends/networking/sdl_net/handlers/resourcehandler.h"
+#include "backends/networking/sdl_net/handlers/uploadfilehandler.h"
+#include "common/hash-str.h"
+#include "common/mutex.h"
+#include "common/singleton.h"
+#include "common/scummsys.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+typedef struct _SDLNet_SocketSet *SDLNet_SocketSet;
+typedef struct _TCPsocket *TCPsocket;
+
+namespace Networking {
+
+#define NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+
+class LocalWebserver : public Common::Singleton<LocalWebserver> {
+ static const uint32 FRAMES_PER_SECOND = 20;
+ static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND;
+ static const uint32 MAX_CONNECTIONS = 10;
+
+ friend void localWebserverTimer(void *); //calls handle()
+
+ SDLNet_SocketSet _set;
+ TCPsocket _serverSocket;
+ Client _client[MAX_CONNECTIONS];
+ int _clients;
+ bool _timerStarted, _stopOnIdle, _minimalMode;
+ Common::HashMap<Common::String, BaseHandler*> _pathHandlers;
+ BaseHandler *_defaultHandler;
+ IndexPageHandler _indexPageHandler;
+ FilesPageHandler _filesPageHandler;
+ CreateDirectoryHandler _createDirectoryHandler;
+ DownloadFileHandler _downloadFileHandler;
+ UploadFileHandler _uploadFileHandler;
+ ListAjaxHandler _listAjaxHandler;
+ FilesAjaxPageHandler _filesAjaxPageHandler;
+ ResourceHandler _resourceHandler;
+ uint32 _idlingFrames;
+ Common::Mutex _handleMutex;
+ Common::String _address;
+ uint32 _serverPort;
+
+ void startTimer(int interval = TIMER_INTERVAL);
+ void stopTimer();
+ void handle();
+ void handleClient(uint32 i);
+ void acceptClient();
+ void resolveAddress(void *ipAddress);
+ void addPathHandler(Common::String path, BaseHandler *handler);
+
+public:
+ static const uint32 DEFAULT_SERVER_PORT = 12345;
+
+ LocalWebserver();
+ virtual ~LocalWebserver();
+
+ void start(bool useMinimalMode = false);
+ void stop();
+ void stopOnIdle();
+
+ Common::String getAddress();
+ IndexPageHandler &indexPageHandler();
+ bool isRunning();
+ static uint32 getPort();
+
+ static void setClientGetHandler(Client &client, Common::String response, long code = 200, const char *mimeType = nullptr);
+ static void setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code = 200, const char *mimeType = nullptr);
+ static void setClientRedirectHandler(Client &client, Common::String response, Common::String location, const char *mimeType = nullptr);
+ static void setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, Common::String location, const char *mimeType = nullptr);
+ static Common::String urlDecode(Common::String value);
+ static Common::String urlEncodeQueryParameterValue(Common::String value);
+};
+
+/** Shortcut for accessing the local webserver. */
+#define LocalServer Networking::LocalWebserver::instance()
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/reader.cpp b/backends/networking/sdl_net/reader.cpp
new file mode 100644
index 0000000000..8f3199f51c
--- /dev/null
+++ b/backends/networking/sdl_net/reader.cpp
@@ -0,0 +1,462 @@
+/* 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/networking/sdl_net/reader.h"
+#include "backends/fs/fs-factory.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/memstream.h"
+#include "common/stream.h"
+
+namespace Networking {
+
+Reader::Reader() {
+ _state = RS_NONE;
+ _content = nullptr;
+ _bytesLeft = 0;
+
+ _window = nullptr;
+ _windowUsed = 0;
+ _windowSize = 0;
+
+ _headersStream = nullptr;
+ _firstBlock = true;
+
+ _contentLength = 0;
+ _availableBytes = 0;
+ _isBadRequest = false;
+ _allContentRead = false;
+}
+
+Reader::~Reader() {
+ cleanup();
+}
+
+Reader &Reader::operator=(Reader &r) {
+ if (this == &r)
+ return *this;
+ cleanup();
+
+ _state = r._state;
+ _content = r._content;
+ _bytesLeft = r._bytesLeft;
+ r._state = RS_NONE;
+
+ _window = r._window;
+ _windowUsed = r._windowUsed;
+ _windowSize = r._windowSize;
+ r._window = nullptr;
+
+ _headersStream = r._headersStream;
+ r._headersStream = nullptr;
+
+ _headers = r._headers;
+ _method = r._method;
+ _path = r._path;
+ _query = r._query;
+ _anchor = r._anchor;
+ _queryParameters = r._queryParameters;
+ _contentLength = r._contentLength;
+ _boundary = r._boundary;
+ _availableBytes = r._availableBytes;
+ _firstBlock = r._firstBlock;
+ _isBadRequest = r._isBadRequest;
+ _allContentRead = r._allContentRead;
+
+ return *this;
+}
+
+void Reader::cleanup() {
+ //_content is not to be freed, it's not owned by Reader
+
+ if (_headersStream != nullptr)
+ delete _headersStream;
+
+ if (_window != nullptr)
+ freeWindow();
+}
+
+bool Reader::readAndHandleFirstHeaders() {
+ Common::String boundary = "\r\n\r\n";
+ if (_window == nullptr) {
+ makeWindow(boundary.size());
+ }
+ if (_headersStream == nullptr) {
+ _headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
+ }
+
+ while (readOneByteInStream(_headersStream, boundary)) {
+ if (_headersStream->size() > SUSPICIOUS_HEADERS_SIZE) {
+ _isBadRequest = true;
+ return true;
+ }
+ if (!bytesLeft())
+ return false;
+ }
+ handleFirstHeaders(_headersStream);
+
+ freeWindow();
+ _state = RS_READING_CONTENT;
+ return true;
+}
+
+bool Reader::readBlockHeadersIntoStream(Common::WriteStream *stream) {
+ Common::String boundary = "\r\n\r\n";
+ if (_window == nullptr) makeWindow(boundary.size());
+
+ while (readOneByteInStream(stream, boundary)) {
+ if (!bytesLeft())
+ return false;
+ }
+ if (stream) stream->flush();
+
+ freeWindow();
+ _state = RS_READING_CONTENT;
+ return true;
+}
+
+namespace {
+void readFromThatUntilLineEnd(const char *cstr, Common::String needle, Common::String &result) {
+ const char *position = strstr(cstr, needle.c_str());
+
+ if (position) {
+ char c;
+ for (const char *i = position + needle.size(); c = *i, c != 0; ++i) {
+ if (c == '\n' || c == '\r')
+ break;
+ result += c;
+ }
+ }
+}
+}
+
+void Reader::handleFirstHeaders(Common::MemoryReadWriteStream *headersStream) {
+ if (!_boundary.empty()) {
+ warning("Reader: handleFirstHeaders() called when first headers were already handled");
+ return;
+ }
+
+ //parse method, path, query, fragment
+ _headers = readEverythingFromMemoryStream(headersStream);
+ parseFirstLine(_headers);
+
+ //find boundary
+ _boundary = "";
+ readFromThatUntilLineEnd(_headers.c_str(), "boundary=", _boundary);
+
+ //find content length
+ Common::String contentLength = "";
+ readFromThatUntilLineEnd(_headers.c_str(), "Content-Length: ", contentLength);
+ _contentLength = contentLength.asUint64();
+ _availableBytes = _contentLength;
+}
+
+void Reader::parseFirstLine(const Common::String &headers) {
+ uint32 headersSize = headers.size();
+ bool bad = false;
+
+ if (headersSize > 0) {
+ const char *cstr = headers.c_str();
+ const char *position = strstr(cstr, "\r\n");
+ if (position) { //we have at least one line - and we want the first one
+ //"<METHOD> <path> HTTP/<VERSION>\r\n"
+ Common::String method, path, http, buf;
+ uint32 length = position - cstr;
+ if (headersSize > length)
+ headersSize = length;
+ for (uint32 i = 0; i < headersSize; ++i) {
+ if (headers[i] != ' ')
+ buf += headers[i];
+ if (headers[i] == ' ' || i == headersSize - 1) {
+ if (method == "") {
+ method = buf;
+ } else if (path == "") {
+ path = buf;
+ } else if (http == "") {
+ http = buf;
+ } else {
+ bad = true;
+ break;
+ }
+ buf = "";
+ }
+ }
+
+ //check that method is supported
+ if (method != "GET" && method != "PUT" && method != "POST")
+ bad = true;
+
+ //check that HTTP/<VERSION> is OK
+ if (!http.hasPrefix("HTTP/"))
+ bad = true;
+
+ _method = method;
+ parsePathQueryAndAnchor(path);
+ }
+ }
+
+ if (bad) _isBadRequest = true;
+}
+
+void Reader::parsePathQueryAndAnchor(Common::String path) {
+ //<path>[?query][#anchor]
+ bool readingPath = true;
+ bool readingQuery = false;
+ _path = "";
+ _query = "";
+ _anchor = "";
+ for (uint32 i = 0; i < path.size(); ++i) {
+ if (readingPath) {
+ if (path[i] == '?') {
+ readingPath = false;
+ readingQuery = true;
+ } else {
+ _path += path[i];
+ }
+ } else if (readingQuery) {
+ if (path[i] == '#') {
+ readingQuery = false;
+ } else {
+ _query += path[i];
+ }
+ } else {
+ _anchor += path[i];
+ }
+ }
+
+ parseQueryParameters();
+}
+
+void Reader::parseQueryParameters() {
+ Common::String key = "";
+ Common::String value = "";
+ bool readingKey = true;
+ for (uint32 i = 0; i < _query.size(); ++i) {
+ if (readingKey) {
+ if (_query[i] == '=') {
+ readingKey = false;
+ value = "";
+ } else {
+ key += _query[i];
+ }
+ } else {
+ if (_query[i] == '&') {
+ if (_queryParameters.contains(key))
+ warning("Reader: query parameter \"%s\" is already set!", key.c_str());
+ else
+ _queryParameters[key] = LocalWebserver::urlDecode(value);
+ readingKey = true;
+ key = "";
+ } else {
+ value += _query[i];
+ }
+ }
+ }
+
+ if (!key.empty()) {
+ if (_queryParameters.contains(key))
+ warning("Reader: query parameter \"%s\" is already set!", key.c_str());
+ else
+ _queryParameters[key] = LocalWebserver::urlDecode(value);
+ }
+}
+
+bool Reader::readContentIntoStream(Common::WriteStream *stream) {
+ Common::String boundary = "--" + _boundary;
+ if (!_firstBlock)
+ boundary = "\r\n" + boundary;
+ if (_boundary.empty())
+ boundary = "\r\n";
+ if (_window == nullptr)
+ makeWindow(boundary.size());
+
+ while (readOneByteInStream(stream, boundary)) {
+ if (!bytesLeft())
+ return false;
+ }
+
+ _firstBlock = false;
+ if (stream)
+ stream->flush();
+
+ freeWindow();
+ _state = RS_READING_HEADERS;
+ return true;
+}
+
+void Reader::makeWindow(uint32 size) {
+ freeWindow();
+
+ _window = new byte[size];
+ _windowUsed = 0;
+ _windowSize = size;
+}
+
+void Reader::freeWindow() {
+ delete[] _window;
+ _window = nullptr;
+ _windowUsed = _windowSize = 0;
+}
+
+namespace {
+bool windowEqualsString(const byte *window, uint32 windowSize, const Common::String &boundary) {
+ if (boundary.size() != windowSize)
+ return false;
+
+ for (uint32 i = 0; i < windowSize; ++i) {
+ if (window[i] != boundary[i])
+ return false;
+ }
+
+ return true;
+}
+}
+
+bool Reader::readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary) {
+ byte b = readOne();
+ _window[_windowUsed++] = b;
+ if (_windowUsed < _windowSize)
+ return true;
+
+ //when window is filled, check whether that's the boundary
+ if (windowEqualsString(_window, _windowSize, boundary))
+ return false;
+
+ //if not, add the first byte of the window to the string
+ if (stream)
+ stream->writeByte(_window[0]);
+ for (uint32 i = 1; i < _windowSize; ++i)
+ _window[i - 1] = _window[i];
+ --_windowUsed;
+ return true;
+}
+
+byte Reader::readOne() {
+ byte b;
+ _content->read(&b, 1);
+ --_availableBytes;
+ --_bytesLeft;
+ return b;
+}
+
+/// public
+
+bool Reader::readFirstHeaders() {
+ if (_state == RS_NONE)
+ _state = RS_READING_HEADERS;
+
+ if (!bytesLeft())
+ return false;
+
+ if (_state == RS_READING_HEADERS)
+ return readAndHandleFirstHeaders();
+
+ warning("Reader::readFirstHeaders(): bad state");
+ return false;
+}
+
+bool Reader::readFirstContent(Common::WriteStream *stream) {
+ if (_state != RS_READING_CONTENT) {
+ warning("Reader::readFirstContent(): bad state");
+ return false;
+ }
+
+ // no difference, actually
+ return readBlockContent(stream);
+}
+
+bool Reader::readBlockHeaders(Common::WriteStream *stream) {
+ if (_state != RS_READING_HEADERS) {
+ warning("Reader::readBlockHeaders(): bad state");
+ return false;
+ }
+
+ if (!bytesLeft())
+ return false;
+
+ return readBlockHeadersIntoStream(stream);
+}
+
+bool Reader::readBlockContent(Common::WriteStream *stream) {
+ if (_state != RS_READING_CONTENT) {
+ warning("Reader::readBlockContent(): bad state");
+ return false;
+ }
+
+ if (!bytesLeft())
+ return false;
+
+ if (!readContentIntoStream(stream))
+ return false;
+
+ if (_availableBytes >= 2) {
+ Common::String bts;
+ bts += readOne();
+ bts += readOne();
+ if (bts == "--")
+ _allContentRead = true;
+ else if (bts != "\r\n")
+ warning("Reader: strange bytes: \"%s\"", bts.c_str());
+ } else {
+ warning("Reader: strange ending");
+ _allContentRead = true;
+ }
+
+ return true;
+}
+
+uint32 Reader::bytesLeft() const { return _bytesLeft; }
+
+void Reader::setContent(Common::MemoryReadWriteStream *stream) {
+ _content = stream;
+ _bytesLeft = stream->size() - stream->pos();
+}
+
+bool Reader::badRequest() const { return _isBadRequest; }
+
+bool Reader::noMoreContent() const { return _allContentRead; }
+
+Common::String Reader::headers() const { return _headers; }
+
+Common::String Reader::method() const { return _method; }
+
+Common::String Reader::path() const { return _path; }
+
+Common::String Reader::query() const { return _query; }
+
+Common::String Reader::queryParameter(Common::String name) const { return _queryParameters[name]; }
+
+Common::String Reader::anchor() const { return _anchor; }
+
+Common::String Reader::readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream) {
+ Common::String result;
+ char buf[1024];
+ uint32 readBytes;
+ while (true) {
+ readBytes = stream->read(buf, 1024);
+ if (readBytes == 0)
+ break;
+ result += Common::String(buf, readBytes);
+ }
+ return result;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/reader.h b/backends/networking/sdl_net/reader.h
new file mode 100644
index 0000000000..16d62a27eb
--- /dev/null
+++ b/backends/networking/sdl_net/reader.h
@@ -0,0 +1,143 @@
+/* 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_NETWORKING_SDL_NET_READER_H
+#define BACKENDS_NETWORKING_SDL_NET_READER_H
+
+#include "common/str.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+
+namespace Common {
+class MemoryReadWriteStream;
+class WriteStream;
+}
+
+namespace Networking {
+
+enum ReaderState {
+ RS_NONE,
+ RS_READING_HEADERS,
+ RS_READING_CONTENT
+};
+
+/**
+ * This is a helper class for Client.
+ *
+ * It parses HTTP request and finds headers
+ * and content. It also supports POST form/multipart.
+ *
+ * One might pass the request even byte by byte,
+ * Reader will always be able to continue from the
+ * state it stopped on.
+ *
+ * Main headers/content must be read with
+ * readFirstHeaders() and readFirstContent() methods.
+ * Further headers/content blocks (POST form/multipart)
+ * must be read with readBlockHeaders() and readBlockContent().
+ *
+ * Main headers and parsed URL components could be accessed
+ * with special methods after reading.
+ *
+ * To use the object, call setContent() and then one of those
+ * reading methods. It would return whether reading is over
+ * or not. If reading is over, content stream still could
+ * contain bytes to read with other methods.
+ *
+ * If reading is not over, Reader awaits you to call the
+ * same reading method when you'd get more content.
+ *
+ * If it's over, you should check whether Reader awaits
+ * more content with noMoreContent() and call the other
+ * reading method, if it is. When headers are read, one
+ * must read contents, and vice versa.
+ */
+
+class Reader {
+ ReaderState _state;
+ Common::MemoryReadWriteStream *_content;
+ uint32 _bytesLeft;
+
+ byte *_window;
+ uint32 _windowUsed, _windowSize;
+
+ Common::MemoryReadWriteStream *_headersStream;
+
+ Common::String _headers;
+ Common::String _method, _path, _query, _anchor;
+ Common::HashMap<Common::String, Common::String> _queryParameters;
+ uint32 _contentLength;
+ Common::String _boundary;
+ uint32 _availableBytes;
+ bool _firstBlock;
+ bool _isBadRequest;
+ bool _allContentRead;
+
+ void cleanup();
+
+ bool readAndHandleFirstHeaders(); //true when ended reading
+ bool readBlockHeadersIntoStream(Common::WriteStream *stream); //true when ended reading
+ bool readContentIntoStream(Common::WriteStream *stream); //true when ended reading
+
+ void handleFirstHeaders(Common::MemoryReadWriteStream *headers);
+ void parseFirstLine(const Common::String &headers);
+ void parsePathQueryAndAnchor(Common::String path);
+ void parseQueryParameters();
+
+ void makeWindow(uint32 size);
+ void freeWindow();
+ bool readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary);
+
+ byte readOne();
+ uint32 bytesLeft() const;
+
+public:
+ static const uint32 SUSPICIOUS_HEADERS_SIZE = 1024 * 1024; // 1 MB is really a lot
+
+ Reader();
+ ~Reader();
+
+ Reader &operator=(Reader &r);
+
+ bool readFirstHeaders(); //true when ended reading
+ bool readFirstContent(Common::WriteStream *stream); //true when ended reading
+ bool readBlockHeaders(Common::WriteStream *stream); //true when ended reading
+ bool readBlockContent(Common::WriteStream *stream); //true when ended reading
+
+ void setContent(Common::MemoryReadWriteStream *stream);
+
+ bool badRequest() const;
+ bool noMoreContent() const;
+
+ Common::String headers() const;
+ Common::String method() const;
+ Common::String path() const;
+ Common::String query() const;
+ Common::String queryParameter(Common::String name) const;
+ Common::String anchor() const;
+
+ static Common::String readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/uploadfileclienthandler.cpp b/backends/networking/sdl_net/uploadfileclienthandler.cpp
new file mode 100644
index 0000000000..ebf341682c
--- /dev/null
+++ b/backends/networking/sdl_net/uploadfileclienthandler.cpp
@@ -0,0 +1,212 @@
+/* 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/networking/sdl_net/uploadfileclienthandler.h"
+#include "backends/fs/fs-factory.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "backends/networking/sdl_net/reader.h"
+#include "common/file.h"
+#include "common/memstream.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+UploadFileClientHandler::UploadFileClientHandler(Common::String parentDirectoryPath):
+ _state(UFH_READING_CONTENT), _headersStream(nullptr), _contentStream(nullptr),
+ _parentDirectoryPath(parentDirectoryPath), _uploadedFiles(0) {}
+
+UploadFileClientHandler::~UploadFileClientHandler() {
+ delete _headersStream;
+ delete _contentStream;
+}
+
+void UploadFileClientHandler::handle(Client *client) {
+ if (client == nullptr) {
+ warning("UploadFileClientHandler::handle(): empty client pointer");
+ return;
+ }
+
+ while (true) {
+ switch (_state) {
+ case UFH_READING_CONTENT:
+ if (client->readContent(nullptr)) {
+ _state = UFH_READING_BLOCK_HEADERS;
+ continue;
+ }
+ break;
+
+ case UFH_READING_BLOCK_HEADERS:
+ if (_headersStream == nullptr)
+ _headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
+
+ if (client->readBlockHeaders(_headersStream)) {
+ handleBlockHeaders(client);
+ continue;
+ }
+
+ // fail on suspicious headers
+ if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) {
+ setErrorMessageHandler(*client, _("Invalid request: headers are too long!"));
+ }
+ break;
+
+ case UFH_READING_BLOCK_CONTENT:
+ // _contentStream is created by handleBlockHeaders() if needed
+
+ if (client->readBlockContent(_contentStream)) {
+ handleBlockContent(client);
+ continue;
+ }
+ break;
+
+ case UFH_ERROR:
+ case UFH_STOP:
+ return;
+ }
+
+ break;
+ }
+}
+
+namespace {
+void readFromThatUntilDoubleQuote(const char *cstr, Common::String needle, Common::String &result) {
+ const char *position = strstr(cstr, needle.c_str());
+
+ if (position) {
+ char c;
+ for (const char *i = position + needle.size(); c = *i, c != 0; ++i) {
+ if (c == '"')
+ break;
+ result += c;
+ }
+ }
+}
+}
+
+void UploadFileClientHandler::handleBlockHeaders(Client *client) {
+ _state = UFH_READING_BLOCK_CONTENT;
+
+ // fail on suspicious headers
+ if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) {
+ setErrorMessageHandler(*client, _("Invalid request: headers are too long!"));
+ }
+
+ // search for "upload_file" field
+ Common::String headers = Reader::readEverythingFromMemoryStream(_headersStream);
+ Common::String fieldName = "";
+ readFromThatUntilDoubleQuote(headers.c_str(), "name=\"", fieldName);
+ if (!fieldName.hasPrefix("upload_file"))
+ return;
+
+ Common::String filename = "";
+ readFromThatUntilDoubleQuote(headers.c_str(), "filename=\"", filename);
+
+ // skip block if <filename> is empty
+ if (filename.empty())
+ return;
+
+ if (HandlerUtils::hasForbiddenCombinations(filename))
+ return;
+
+ // check that <path>/<filename> doesn't exist
+ Common::String path = _parentDirectoryPath;
+ if (path.lastChar() != '/' && path.lastChar() != '\\')
+ path += '/';
+ AbstractFSNode *originalNode = g_system->getFilesystemFactory()->makeFileNodePath(path + filename);
+ if (!HandlerUtils::permittedPath(originalNode->getPath())) {
+ setErrorMessageHandler(*client, _("Invalid path!"));
+ return;
+ }
+ if (originalNode->exists()) {
+ setErrorMessageHandler(*client, _("There is a file with that name in the parent directory!"));
+ return;
+ }
+
+ // remove previous stream (if there is one)
+ if (_contentStream) {
+ delete _contentStream;
+ _contentStream = nullptr;
+ }
+
+ // create file stream (and necessary subdirectories)
+ Common::DumpFile *f = new Common::DumpFile();
+ if (!f->open(originalNode->getPath(), true)) {
+ delete f;
+ setErrorMessageHandler(*client, _("Failed to upload the file!"));
+ return;
+ }
+
+ _contentStream = f;
+}
+
+void UploadFileClientHandler::handleBlockContent(Client *client) {
+ _state = UFH_READING_BLOCK_HEADERS;
+
+ // if previous block headers were file-related and created a stream
+ if (_contentStream) {
+ _contentStream->flush();
+ ++_uploadedFiles;
+
+ delete _contentStream;
+ _contentStream = nullptr;
+
+ if (client->noMoreContent()) {
+ // success - redirect back to directory listing
+ setSuccessHandler(*client);
+ return;
+ }
+ }
+
+ // no more content avaiable
+ if (client->noMoreContent()) {
+ // if no file field was found - failure
+ if (_uploadedFiles == 0) {
+ setErrorMessageHandler(*client, _("No file was passed!"));
+ } else {
+ setSuccessHandler(*client);
+ }
+ }
+}
+
+void UploadFileClientHandler::setErrorMessageHandler(Client &client, Common::String message) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, message);
+ _state = UFH_ERROR;
+}
+
+void UploadFileClientHandler::setSuccessHandler(Client &client) {
+ // success - redirect back to directory listing
+ HandlerUtils::setMessageHandler(
+ client,
+ Common::String::format(
+ "%s<br/><a href=\"files?path=%s\">%s</a>",
+ _("Uploaded successfully!"),
+ client.queryParameter("path").c_str(),
+ _("Back to parent directory")
+ ),
+ (client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") +
+ LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path"))
+ );
+ _state = UFH_STOP;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/uploadfileclienthandler.h b/backends/networking/sdl_net/uploadfileclienthandler.h
new file mode 100644
index 0000000000..b6481cf18f
--- /dev/null
+++ b/backends/networking/sdl_net/uploadfileclienthandler.h
@@ -0,0 +1,70 @@
+/* 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_NETWORKING_SDL_NET_UPLOADFILECLIENTHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_UPLOADFILECLIENTHANDLER_H
+
+#include "backends/networking/sdl_net/client.h"
+#include "common/stream.h"
+
+namespace Networking {
+
+enum UploadFileHandlerState {
+ UFH_READING_CONTENT,
+ UFH_READING_BLOCK_HEADERS,
+ UFH_READING_BLOCK_CONTENT,
+ UFH_ERROR,
+ UFH_STOP
+};
+
+/**
+ * This class handles POST form/multipart upload.
+ *
+ * handleBlockHeaders() looks for filename and, if it's found,
+ * handleBlockContent() saves content into the file with such name.
+ *
+ * If no file found or other error occurs, it sets
+ * default error message handler.
+ */
+
+class UploadFileClientHandler: public ClientHandler {
+ UploadFileHandlerState _state;
+ Common::MemoryReadWriteStream *_headersStream;
+ Common::WriteStream *_contentStream;
+ Common::String _parentDirectoryPath;
+ uint32 _uploadedFiles;
+
+ void handleBlockHeaders(Client *client);
+ void handleBlockContent(Client *client);
+ void setErrorMessageHandler(Client &client, Common::String message);
+ void setSuccessHandler(Client &client);
+
+public:
+ UploadFileClientHandler(Common::String parentDirectoryPath);
+ virtual ~UploadFileClientHandler();
+
+ virtual void handle(Client *client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/wwwroot.zip b/backends/networking/wwwroot.zip
new file mode 100644
index 0000000000..b767d7c5d7
--- /dev/null
+++ b/backends/networking/wwwroot.zip
Binary files differ
diff --git a/backends/networking/wwwroot/.files.html b/backends/networking/wwwroot/.files.html
new file mode 100644
index 0000000000..f05c0113f8
--- /dev/null
+++ b/backends/networking/wwwroot/.files.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<html>
+ <head>
+ <title>ScummVM</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" type="text/css" href="style.css"/>
+ </head>
+ <body>
+ <div class="container">
+ <div class='header'>
+ <center><img src="logo.png"/></center>
+ </div>
+ <div class="controls">
+ <table class="buttons"><tr>
+ <td><a href="javascript:show('create_directory');">{create_directory_button}</a></td>
+ <td><a href="javascript:show('upload_file');">{upload_files_button}</a></td>
+ </tr></table>
+ <div id="create_directory" class="modal">
+ <p>{create_directory_desc}</p>
+ <form action="create">
+ <input type="hidden" name="path" value="{path}"/>
+ <input type="text" name="directory_name" value=""/>
+ <input type="submit" value="{create_directory_button}"/>
+ </form>
+ </div>
+ <div id="upload_file" class="modal">
+ <p>{upload_file_desc}</p>
+ <form action="upload?path={path}" method="post" enctype="multipart/form-data">
+ <!-- we don't need "[]" in the name, as our webserver is not using PHP -->
+ <!-- "allowdirs" is a proposal, not implemented yet -->
+ <input type="file" name="upload_file-f" allowdirs multiple/>
+ <br/><br/>
+ <p>{or_upload_directory_desc}</p>
+ <!-- "directory"/"webkitdirectory" works in Chrome only yet, "multiple" is just in case here -->
+ <input type="file" name="upload_file-d" directory webkitdirectory multiple/>
+ <input type="submit" value="{upload_file_button}"/>
+ </form>
+ </div>
+ </div>
+ <div class="content">
+ <table class="files_list">
+ <td></td><td><b class="directory_name">{index_of_directory}</b></td><td></td>
+ {content}
+ </table>
+ </div>
+ </div>
+ <script>
+ function show(id) {
+ var e = document.getElementById(id);
+ var visible = (e.style.display == "block");
+ if (visible) id = ""; //hide
+
+ e = document.getElementById("create_directory");
+ e.style.display = (e.id == id ? "block" : "none");
+ e = document.getElementById("upload_file");
+ e.style.display = (e.id == id ? "block" : "none");
+ }
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/backends/networking/wwwroot/.filesAJAX.html b/backends/networking/wwwroot/.filesAJAX.html
new file mode 100644
index 0000000000..d45c73069d
--- /dev/null
+++ b/backends/networking/wwwroot/.filesAJAX.html
@@ -0,0 +1,240 @@
+<!doctype html>
+<html>
+ <head>
+ <title>ScummVM</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" type="text/css" href="style.css"/>
+ </head>
+ <body>
+ <div class="container">
+ <div class='header'>
+ <center><img src="logo.png"/></center>
+ </div>
+ <div class="controls">
+ <table class="buttons"><tr>
+ <td><a href="javascript:show('create_directory');">{create_directory_button}</a></td>
+ <td><a href="javascript:show('upload_file');">{upload_files_button}</a></td>
+ </tr></table>
+ <div id="create_directory" class="modal">
+ <p>{create_directory_desc}</p>
+ <form action="create" id="create_directory_form" onsubmit="return createDirectory();">
+ <input type="hidden" name="path" value="{path}"/>
+ <input type="hidden" name="ajax" value="true"/>
+ <input type="text" name="directory_name" value=""/>
+ <input type="submit" value="{create_directory_button}"/>
+ </form>
+ </div>
+ <div id="upload_file" class="modal">
+ <p>{upload_file_desc}</p>
+ <form action="upload?path={path}&ajax=true" method="post" enctype="multipart/form-data" id="files_upload_form">
+ <!-- we don't need "[]" in the name, as our webserver is not using PHP -->
+ <!-- "allowdirs" is a proposal, not implemented yet -->
+ <input type="file" name="upload_file-f" allowdirs multiple/>
+ <br/><br/>
+ <p>{or_upload_directory_desc}</p>
+ <!-- "directory"/"webkitdirectory" works in Chrome only yet, "multiple" is just in case here -->
+ <input type="file" name="upload_file-d" directory webkitdirectory multiple/>
+ <input type="submit" value="{upload_file_button}"/>
+ </form>
+ </div>
+ </div>
+ <div class="content">
+ <div id="loading_message">{loading}</div>
+ <div id="error_message">{error}</div>
+ <table class="files_list" id="files_list">
+ </table>
+ </div>
+ </div>
+ <script>
+ function show(id) {
+ var e = document.getElementById(id);
+ var visible = (e.style.display == "block");
+ if (visible) id = ""; //hide
+
+ e = document.getElementById("create_directory");
+ e.style.display = (e.id == id ? "block" : "none");
+ e = document.getElementById("upload_file");
+ e.style.display = (e.id == id ? "block" : "none");
+ }
+ </script>
+ <script src="ajax.js"></script>
+ <script>
+ window.onload = function () {
+ showDirectory("{start_path}");
+ }
+
+ function showDirectory(path) {
+ if (isLoading) return;
+ showLoading();
+ ajax.getAndParseJson("./list", {"path": path}, getCallback(path));
+ }
+
+ function getCallback(path) {
+ return function (jsonResponse) {
+ if (jsonResponse.type == "error") {
+ showError();
+ return;
+ }
+
+ openDirectory(path, jsonResponse.items);
+ hideLoading();
+ };
+ }
+
+ function createDirectory() {
+ if (isLoading) return;
+ showLoading();
+
+ var data = {"answer_json": "true"};
+ var elements = document.getElementById("create_directory_form").elements;
+ for (var el in elements)
+ data[elements[el].name] = elements[el].value;
+
+ ajax.getAndParseJson("./create", data, getCreateDirectoryCallback(data["path"]));
+ show("create_directory");
+ return false; // invalidate form, so it won't submit
+ }
+
+ function getCreateDirectoryCallback(path) {
+ return function (jsonResponse) {
+ console.log(jsonResponse);
+
+ if (jsonResponse.type == "error") {
+ showError();
+ return;
+ }
+
+ hideLoading();
+ showDirectory(path);
+ };
+ }
+
+ var isLoading = false;
+
+ function showLoading() {
+ isLoading = true;
+ var e = document.getElementById("loading_message");
+ e.style.display = "block";
+ e = document.getElementById("error_message");
+ e.style.display = "none";
+ }
+
+ function showError() {
+ isLoading = false;
+ var e = document.getElementById("loading_message");
+ e.style.display = "none";
+ e = document.getElementById("error_message");
+ e.style.display = "block";
+ //TODO: pass the actual message there?
+ }
+
+ function hideLoading() {
+ isLoading = false;
+ var e = document.getElementById("loading_message");
+ e.style.display = "none";
+ e = document.getElementById("error_message");
+ e.style.display = "none";
+ }
+
+ function openDirectory(path, items) {
+ // update path
+ document.getElementById("create_directory_form").elements["path"].value = path;
+ document.getElementById("files_upload_form").action = "upload?path=" + path + "&ajax=true";
+
+ // update table contents
+ listDirectory(path, items);
+ }
+
+ function makeBreadcrumb(name, path) {
+ var a = createElementWithContents("a", name);
+ a.onclick = function () { showDirectory(path); };
+ a.href = "javascript:void(0);";
+ return a;
+ }
+
+ function makeBreadcrumbs(path) {
+ var b = document.createElement("b");
+ b.className = "directory_name";
+
+ b.appendChild(createElementWithContents("span", "{index_of}"));
+ var slashes = true;
+ var crumb = "";
+ var currentPath = "";
+ path += ' '; //so the last slash is added
+ for (var i=0; i<path.length; ++i) {
+ if (path[i] == '/' || path[i] == '\\') {
+ if (!slashes) {
+ currentPath += crumb;
+ b.appendChild(makeBreadcrumb(crumb, currentPath+'/'));
+ slashes = true;
+ }
+ } else {
+ if (slashes) {
+ currentPath += "/";
+ if (currentPath == "/") { //make special '/' crumb here
+ b.appendChild(makeBreadcrumb('/', '/'));
+ } else {
+ b.appendChild(createElementWithContents("span", "/"));
+ }
+ slashes = false;
+ crumb = "";
+ }
+ crumb += path[i];
+ }
+ }
+ return b;
+ }
+
+ function listDirectory(path, items) {
+ // cleanup the list
+ var files_list = document.getElementById("files_list");
+ while (files_list.hasChildNodes())
+ files_list.removeChild(files_list.firstChild);
+ var tbody = document.createElement("tbody");
+
+ // add header item
+ var tr = document.createElement("tr");
+ tr.appendChild(createElementWithContents("td", ""));
+ var td = document.createElement("td");
+ td.appendChild(makeBreadcrumbs(path));
+ tr.appendChild(td);
+ tr.appendChild(createElementWithContents("td", ""));
+ tbody.appendChild(tr);
+
+ // add items
+ for (var i in items)
+ addItem(tbody, items[i]);
+
+ files_list.appendChild(tbody);
+ }
+
+ function addItem(tbody, item) {
+ var tr = document.createElement("tr");
+ var td = document.createElement("td");
+ var img = document.createElement("img");
+ img.src = "./icons/" + item.icon;
+ td.appendChild(img);
+ tr.appendChild(td);
+
+ td = document.createElement("td");
+ var a = createElementWithContents("a", item.name);
+ if (item.isDirectory) {
+ a.onclick = function () { showDirectory(item.path); };
+ a.href = "javascript:void(0);";
+ } else
+ a.href = "./download?path=" + encodeURIComponent(item.path);
+ td.appendChild(a);
+ tr.appendChild(td);
+
+ tr.appendChild(createElementWithContents("td", ""));
+ tbody.appendChild(tr);
+ }
+
+ function createElementWithContents(type, innerHTML) {
+ var e = document.createElement(type);
+ e.innerHTML = innerHTML;
+ return e;
+ }
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/backends/networking/wwwroot/.index.html b/backends/networking/wwwroot/.index.html
new file mode 100644
index 0000000000..2a3d9d382d
--- /dev/null
+++ b/backends/networking/wwwroot/.index.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+ <head>
+ <title>ScummVM</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" type="text/css" href="style.css"/>
+ </head>
+ <body>
+ <div class="container">
+ <div class='header'>
+ <center><img src="logo.png"/></center>
+ </div>
+ <div class="content">
+ <p>{message}</p>
+ </div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/backends/networking/wwwroot/ajax.js b/backends/networking/wwwroot/ajax.js
new file mode 100644
index 0000000000..c01d7e93fc
--- /dev/null
+++ b/backends/networking/wwwroot/ajax.js
@@ -0,0 +1,48 @@
+// the following is snippet from http://stackoverflow.com/a/18078705
+// I changed a few things though
+
+var ajax = {};
+ajax.x = function () { return new XMLHttpRequest(); }; // "no one uses IE6"
+
+ajax.send = function (url, callback, errorCallback, method, data, async) {
+ if (async === undefined) async = true;
+
+ var x = ajax.x();
+ x.open(method, url, async);
+ x.onreadystatechange = function () {
+ if (x.readyState == XMLHttpRequest.DONE) {
+ if (x.status == 200)
+ callback(x.responseText);
+ else
+ errorCallback(x);
+ }
+ };
+ if (method == 'POST') {
+ x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+ }
+ x.send(data)
+};
+
+ajax.get = function (url, data, callback, errorCallback, async) {
+ var query = [];
+ for (var key in data) {
+ query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
+ }
+ ajax.send(url + (query.length ? '?' + query.join('&') : ''), callback, errorCallback, 'GET', null, async)
+};
+
+ajax.post = function (url, data, callback, errorCallback, async) {
+ var query = [];
+ for (var key in data) {
+ query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
+ }
+ ajax.send(url, callback, errorCallback, 'POST', query.join('&'), async)
+};
+
+ajax.getAndParseJson = function (url, data, callback) {
+ ajax.get(
+ url, data,
+ function (responseText) { callback(JSON.parse(responseText)); },
+ function (x) { console.log("error: " + x.status); }
+ );
+}; \ No newline at end of file
diff --git a/backends/networking/wwwroot/favicon.ico b/backends/networking/wwwroot/favicon.ico
new file mode 100644
index 0000000000..0283e8432e
--- /dev/null
+++ b/backends/networking/wwwroot/favicon.ico
Binary files differ
diff --git a/backends/networking/wwwroot/icons/7z.png b/backends/networking/wwwroot/icons/7z.png
new file mode 100644
index 0000000000..656e7e7c62
--- /dev/null
+++ b/backends/networking/wwwroot/icons/7z.png
Binary files differ
diff --git a/backends/networking/wwwroot/icons/dir.png b/backends/networking/wwwroot/icons/dir.png
new file mode 100644
index 0000000000..bcdec04a57
--- /dev/null
+++ b/backends/networking/wwwroot/icons/dir.png
Binary files differ
diff --git a/backends/networking/wwwroot/icons/txt.png b/backends/networking/wwwroot/icons/txt.png
new file mode 100644
index 0000000000..023d2ee24a
--- /dev/null
+++ b/backends/networking/wwwroot/icons/txt.png
Binary files differ
diff --git a/backends/networking/wwwroot/icons/unk.png b/backends/networking/wwwroot/icons/unk.png
new file mode 100644
index 0000000000..346eebecc3
--- /dev/null
+++ b/backends/networking/wwwroot/icons/unk.png
Binary files differ
diff --git a/backends/networking/wwwroot/icons/up.png b/backends/networking/wwwroot/icons/up.png
new file mode 100644
index 0000000000..2dc3df022b
--- /dev/null
+++ b/backends/networking/wwwroot/icons/up.png
Binary files differ
diff --git a/backends/networking/wwwroot/icons/zip.png b/backends/networking/wwwroot/icons/zip.png
new file mode 100644
index 0000000000..cdfc5763dd
--- /dev/null
+++ b/backends/networking/wwwroot/icons/zip.png
Binary files differ
diff --git a/backends/networking/wwwroot/logo.png b/backends/networking/wwwroot/logo.png
new file mode 100644
index 0000000000..9fdd2d0d1e
--- /dev/null
+++ b/backends/networking/wwwroot/logo.png
Binary files differ
diff --git a/backends/networking/wwwroot/style.css b/backends/networking/wwwroot/style.css
new file mode 100644
index 0000000000..ba31587c4d
--- /dev/null
+++ b/backends/networking/wwwroot/style.css
@@ -0,0 +1,113 @@
+html {
+ background: rgb(212, 117, 11);
+ background: linear-gradient(to bottom, rgb(212, 117, 11) 0%, rgb(212, 117, 11) 36%, rgb(239, 196, 24) 100%);
+ min-height: 100vh;
+}
+
+.container {
+ width: 80%;
+ margin: 0 auto;
+}
+
+.header {
+ padding: 10pt;
+ margin-bottom: 0;
+}
+
+.content {
+ padding: 8pt;
+ background: rgb(251, 241, 206);
+ font-family: Tahoma;
+ font-size: 16pt;
+}
+
+.content p { margin: 0 0 6pt 0; }
+
+.controls {
+ padding: 8pt;
+ background: #FFF;
+ font-family: Tahoma;
+ font-size: 16pt;
+}
+
+.controls .buttons {
+ width: 100%;
+ max-width: 500pt;
+ margin: -8pt auto;
+ border: 0;
+ border-spacing: 0;
+}
+
+.controls .buttons td {
+ width: 50%;
+ text-align: center;
+ margin: 0;
+ padding: 0;
+}
+
+.controls .buttons a {
+ display: block;
+ height: 40pt;
+ line-height: 38pt;
+ vertical-align: middle;
+ color: #000;
+ text-decoration: none;
+}
+
+.controls .buttons a:hover {
+ background: #F3F3F3;
+}
+
+.modal {
+ margin-top: 10pt;
+ display: none;
+}
+
+.modal p { margin: 0 0 6pt 0; }
+
+#create_directory input[type="text"], #upload_file input[type="file"] {
+ width: calc(100% - 2 * 5pt);
+}
+
+.modal input {
+ border: 1px solid #EEE;
+ padding: 5pt;
+ font-size: 12pt;
+}
+
+.modal input[type="submit"] {
+ display: block;
+ margin: 6pt auto;
+ background: #DDD;
+ border: 0;
+}
+
+.modal input[type="submit"]:hover {
+ background: #F3F3F3;
+ cursor: pointer;
+}
+
+td img { vertical-align: middle; height: 20px; }
+
+.directory_name {
+ display: block;
+ padding-bottom: 6px;
+}
+
+.directory_name a { color: black; }
+.directory_name a:hover { color: blue; }
+
+#loading_message, #error_message {
+ margin: -8pt;
+ margin-bottom: 5pt;
+ padding: 4pt;
+ text-align: center;
+}
+
+#loading_message {
+ background: #99FF99;
+}
+
+#error_message {
+ background: #FF9999;
+}
diff --git a/backends/platform/android/jni.cpp b/backends/platform/android/jni.cpp
index 22e6a749c2..256ae09ef8 100644
--- a/backends/platform/android/jni.cpp
+++ b/backends/platform/android/jni.cpp
@@ -76,6 +76,8 @@ bool JNI::_ready_for_events = 0;
jmethodID JNI::_MID_getDPI = 0;
jmethodID JNI::_MID_displayMessageOnOSD = 0;
+jmethodID JNI::_MID_openUrl = 0;
+jmethodID JNI::_MID_isConnectionLimited = 0;
jmethodID JNI::_MID_setWindowCaption = 0;
jmethodID JNI::_MID_showVirtualKeyboard = 0;
jmethodID JNI::_MID_getSysArchives = 0;
@@ -232,6 +234,41 @@ void JNI::displayMessageOnOSD(const char *msg) {
env->DeleteLocalRef(java_msg);
}
+bool JNI::openUrl(const char *url) {
+ bool success = true;
+ JNIEnv *env = JNI::getEnv();
+ jstring javaUrl = env->NewStringUTF(url);
+
+ env->CallVoidMethod(_jobj, _MID_openUrl, javaUrl);
+
+ if (env->ExceptionCheck()) {
+ LOGE("Failed to open URL");
+
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ success = false;
+ }
+
+ env->DeleteLocalRef(javaUrl);
+ return success;
+}
+
+bool JNI::isConnectionLimited() {
+ bool limited = false;
+ JNIEnv *env = JNI::getEnv();
+ limited = env->CallBooleanMethod(_jobj, _MID_isConnectionLimited);
+
+ if (env->ExceptionCheck()) {
+ LOGE("Failed to check whether connection's limited");
+
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ limited = true;
+ }
+
+ return limited;
+}
+
void JNI::setWindowCaption(const char *caption) {
JNIEnv *env = JNI::getEnv();
jstring java_caption = env->NewStringUTF(caption);
@@ -411,6 +448,8 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V");
FIND_METHOD(, getDPI, "([F)V");
FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V");
+ FIND_METHOD(, openUrl, "(Ljava/lang/String;)V");
+ FIND_METHOD(, isConnectionLimited, "()Z");
FIND_METHOD(, showVirtualKeyboard, "(Z)V");
FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
diff --git a/backends/platform/android/jni.h b/backends/platform/android/jni.h
index 70feaaf72a..0798db448a 100644
--- a/backends/platform/android/jni.h
+++ b/backends/platform/android/jni.h
@@ -58,6 +58,8 @@ public:
static void setWindowCaption(const char *caption);
static void getDPI(float *values);
static void displayMessageOnOSD(const char *msg);
+ static bool openUrl(const char *url);
+ static bool isConnectionLimited();
static void showVirtualKeyboard(bool enable);
static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority);
@@ -89,6 +91,8 @@ private:
static jmethodID _MID_getDPI;
static jmethodID _MID_displayMessageOnOSD;
+ static jmethodID _MID_openUrl;
+ static jmethodID _MID_isConnectionLimited;
static jmethodID _MID_setWindowCaption;
static jmethodID _MID_showVirtualKeyboard;
static jmethodID _MID_getSysArchives;
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVM.java b/backends/platform/android/org/scummvm/scummvm/ScummVM.java
index 3b370a583d..47dcb32b22 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVM.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVM.java
@@ -53,6 +53,8 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable {
// Callbacks from C++ peer instance
abstract protected void getDPI(float[] values);
abstract protected void displayMessageOnOSD(String msg);
+ abstract protected void openUrl(String url);
+ abstract protected boolean isConnectionLimited();
abstract protected void setWindowCaption(String caption);
abstract protected void showVirtualKeyboard(boolean enable);
abstract protected String[] getSysArchives();
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
index 5b2dcae175..225496ca0d 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
@@ -2,9 +2,13 @@ package org.scummvm.scummvm;
import android.app.Activity;
import android.app.AlertDialog;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.AudioManager;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
@@ -75,6 +79,21 @@ public class ScummVMActivity extends Activity {
}
@Override
+ protected void openUrl(String url) {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
+ }
+
+ @Override
+ protected boolean isConnectionLimited() {
+ WifiManager wifiMgr = (WifiManager)getSystemService(Context.WIFI_SERVICE);
+ if (wifiMgr != null && wifiMgr.isWifiEnabled()) {
+ WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
+ return (wifiInfo == null || wifiInfo.getNetworkId() == -1); //WiFi is on, but it's not connected to any network
+ }
+ return true;
+ }
+
+ @Override
protected void setWindowCaption(final String caption) {
runOnUiThread(new Runnable() {
public void run() {
diff --git a/backends/platform/ds/arm9/source/gbampsave.cpp b/backends/platform/ds/arm9/source/gbampsave.cpp
index ef6091e2a2..236ec55801 100644
--- a/backends/platform/ds/arm9/source/gbampsave.cpp
+++ b/backends/platform/ds/arm9/source/gbampsave.cpp
@@ -56,7 +56,7 @@ Common::OutSaveFile *GBAMPSaveFileManager::openForSaving(const Common::String &f
Common::WriteStream *stream = DS::DSFileStream::makeFromPath(fileSpec, true);
// Use a write buffer
stream = Common::wrapBufferedWriteStream(stream, SAVE_BUFFER_SIZE);
- return stream;
+ return new OutSaveFile(stream);
}
Common::InSaveFile *GBAMPSaveFileManager::openForLoading(const Common::String &filename) {
diff --git a/backends/platform/n64/framfs_save_manager.h b/backends/platform/n64/framfs_save_manager.h
index 9bd4ee579e..aa26942bdc 100644
--- a/backends/platform/n64/framfs_save_manager.h
+++ b/backends/platform/n64/framfs_save_manager.h
@@ -106,7 +106,7 @@ public:
virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) {
OutFRAMSave *s = new OutFRAMSave(filename.c_str());
if (!s->err()) {
- return compress ? Common::wrapCompressedWriteStream(s) : s;
+ return new OutSaveFile(compress ? Common::wrapCompressedWriteStream(s) : s);
} else {
delete s;
return 0;
diff --git a/backends/platform/n64/pakfs_save_manager.h b/backends/platform/n64/pakfs_save_manager.h
index 0c08f0c506..31aa01444c 100644
--- a/backends/platform/n64/pakfs_save_manager.h
+++ b/backends/platform/n64/pakfs_save_manager.h
@@ -108,7 +108,7 @@ public:
virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) {
OutPAKSave *s = new OutPAKSave(filename.c_str());
if (!s->err()) {
- return compress ? Common::wrapCompressedWriteStream(s) : s;
+ return new OutSaveFile(compress ? Common::wrapCompressedWriteStream(s) : s);
} else {
delete s;
return NULL;
diff --git a/backends/platform/ps2/savefilemgr.cpp b/backends/platform/ps2/savefilemgr.cpp
index 4fd2b1c72b..569d0e13e3 100644
--- a/backends/platform/ps2/savefilemgr.cpp
+++ b/backends/platform/ps2/savefilemgr.cpp
@@ -192,7 +192,7 @@ Common::OutSaveFile *Ps2SaveFileManager::openForSaving(const Common::String &fil
}
_screen->wantAnim(false);
- return compress ? Common::wrapCompressedWriteStream(sf) : sf;
+ return new OutSaveFile(compress ? Common::wrapCompressedWriteStream(sf) : sf);
}
bool Ps2SaveFileManager::removeSavefile(const Common::String &filename) {
diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp
index dca6891fef..6862bb349f 100644
--- a/backends/platform/sdl/sdl.cpp
+++ b/backends/platform/sdl/sdl.cpp
@@ -60,6 +60,15 @@
#endif // !WIN32
#endif
+#ifdef USE_SDL_NET
+#include <SDL/SDL_net.h>
+#endif
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_clipboard.h>
+#endif
+
OSystem_SDL::OSystem_SDL()
:
#ifdef USE_OPENGL
@@ -73,6 +82,9 @@ OSystem_SDL::OSystem_SDL()
#endif
_inited(false),
_initedSDL(false),
+#ifdef USE_SDL_NET
+ _initedSDLnet(false),
+#endif
_logger(0),
_mixerManager(0),
_eventSource(0),
@@ -120,6 +132,10 @@ OSystem_SDL::~OSystem_SDL() {
delete _logger;
_logger = 0;
+#ifdef USE_SDL_NET
+ if (_initedSDLnet) SDLNet_Quit();
+#endif
+
SDL_Quit();
}
@@ -160,6 +176,13 @@ void OSystem_SDL::init() {
}
+bool OSystem_SDL::hasFeature(Feature f) {
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ if (f == kFeatureClipboardSupport) return true;
+#endif
+ return ModularBackend::hasFeature(f);
+}
+
void OSystem_SDL::initBackend() {
// Check if backend has not been initialized
assert(!_inited);
@@ -294,6 +317,17 @@ void OSystem_SDL::initSDL() {
_initedSDL = true;
}
+
+#ifdef USE_SDL_NET
+ // Check if SDL_net has not been initialized
+ if (!_initedSDLnet) {
+ // Initialize SDL_net
+ if (SDLNet_Init() == -1)
+ error("Could not initialize SDL_net: %s", SDLNet_GetError());
+
+ _initedSDLnet = true;
+ }
+#endif
}
void OSystem_SDL::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) {
@@ -431,6 +465,26 @@ Common::String OSystem_SDL::getSystemLanguage() const {
#endif // USE_DETECTLANG
}
+bool OSystem_SDL::hasTextInClipboard() {
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ return SDL_HasClipboardText() == SDL_TRUE;
+#else
+ return false;
+#endif
+}
+
+Common::String OSystem_SDL::getTextFromClipboard() {
+ if (!hasTextInClipboard()) return "";
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ char *text = SDL_GetClipboardText();
+ if (text == nullptr) return "";
+ return text;
+#else
+ return "";
+#endif
+}
+
uint32 OSystem_SDL::getMillis(bool skipRecord) {
uint32 millis = SDL_GetTicks();
diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h
index 1fe670c5c3..17b4e9b001 100644
--- a/backends/platform/sdl/sdl.h
+++ b/backends/platform/sdl/sdl.h
@@ -55,6 +55,8 @@ public:
*/
virtual SdlMixerManager *getMixerManager();
+ virtual bool hasFeature(Feature f);
+
// Override functions from ModularBackend and OSystem
virtual void initBackend();
#if defined(USE_TASKBAR)
@@ -69,6 +71,10 @@ public:
virtual Common::String getSystemLanguage() const;
+ // Clipboard
+ virtual bool hasTextInClipboard();
+ virtual Common::String getTextFromClipboard();
+
virtual void setWindowCaption(const char *caption);
virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
virtual uint32 getMillis(bool skipRecord = false);
@@ -81,6 +87,9 @@ public:
protected:
bool _inited;
bool _initedSDL;
+#ifdef USE_SDL_NET
+ bool _initedSDLnet;
+#endif
/**
* Mixer manager that configures and setups SDL for
diff --git a/backends/saves/default/default-saves.cpp b/backends/saves/default/default-saves.cpp
index daec36ae72..8a7fba46f7 100644
--- a/backends/saves/default/default-saves.cpp
+++ b/backends/saves/default/default-saves.cpp
@@ -27,6 +27,11 @@
#include "common/scummsys.h"
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#include "common/file.h"
+#endif
+
#if !defined(DISABLE_DEFAULT_SAVEFILEMANAGER)
#include "backends/saves/default/default-saves.h"
@@ -42,6 +47,10 @@
#include <errno.h> // for removeSavefile()
#endif
+#ifdef USE_LIBCURL
+const char *DefaultSaveFileManager::TIMESTAMPS_FILENAME = "timestamps";
+#endif
+
DefaultSaveFileManager::DefaultSaveFileManager() {
}
@@ -59,28 +68,62 @@ void DefaultSaveFileManager::checkPath(const Common::FSNode &dir) {
}
}
+void DefaultSaveFileManager::updateSavefilesList(Common::StringArray &lockedFiles) {
+ //make it refresh the cache next time it lists the saves
+ _cachedDirectory = "";
+
+ //remember the locked files list because some of these files don't exist yet
+ _lockedFiles = lockedFiles;
+}
+
Common::StringArray DefaultSaveFileManager::listSavefiles(const Common::String &pattern) {
// Assure the savefile name cache is up-to-date.
assureCached(getSavePath());
if (getError().getCode() != Common::kNoError)
return Common::StringArray();
+ Common::HashMap<Common::String, bool> locked;
+ for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) {
+ locked[*i] = true;
+ }
+
Common::StringArray results;
for (SaveFileCache::const_iterator file = _saveFileCache.begin(), end = _saveFileCache.end(); file != end; ++file) {
- if (file->_key.matchString(pattern, true)) {
+ if (!locked.contains(file->_key) && file->_key.matchString(pattern, true)) {
results.push_back(file->_key);
}
}
-
return results;
}
+Common::InSaveFile *DefaultSaveFileManager::openRawFile(const Common::String &filename) {
+ // Assure the savefile name cache is up-to-date.
+ assureCached(getSavePath());
+ if (getError().getCode() != Common::kNoError)
+ return nullptr;
+
+ SaveFileCache::const_iterator file = _saveFileCache.find(filename);
+ if (file == _saveFileCache.end()) {
+ return nullptr;
+ } else {
+ // Open the file for loading.
+ Common::SeekableReadStream *sf = file->_value.createReadStream();
+ return sf;
+ }
+}
+
Common::InSaveFile *DefaultSaveFileManager::openForLoading(const Common::String &filename) {
// Assure the savefile name cache is up-to-date.
assureCached(getSavePath());
if (getError().getCode() != Common::kNoError)
return nullptr;
+ for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) {
+ if (filename == *i) {
+ return nullptr; //file is locked, no loading available
+ }
+ }
+
SaveFileCache::const_iterator file = _saveFileCache.find(filename);
if (file == _saveFileCache.end()) {
return nullptr;
@@ -98,6 +141,19 @@ Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const Common::String
if (getError().getCode() != Common::kNoError)
return nullptr;
+ for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) {
+ if (filename == *i) {
+ return nullptr; //file is locked, no saving available
+ }
+ }
+
+#ifdef USE_LIBCURL
+ // Update file's timestamp
+ Common::HashMap<Common::String, uint32> timestamps = loadTimestamps();
+ timestamps[filename] = INVALID_TIMESTAMP;
+ saveTimestamps(timestamps);
+#endif
+
// Obtain node.
SaveFileCache::const_iterator file = _saveFileCache.find(filename);
Common::FSNode fileNode;
@@ -112,7 +168,7 @@ Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const Common::String
// Open the file for saving.
Common::WriteStream *const sf = fileNode.createWriteStream();
- Common::OutSaveFile *const result = compress ? Common::wrapCompressedWriteStream(sf) : sf;
+ Common::OutSaveFile *const result = new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(sf) : sf);
// Add file to cache now that it exists.
_saveFileCache[filename] = Common::FSNode(fileNode.getPath());
@@ -181,6 +237,12 @@ void DefaultSaveFileManager::assureCached(const Common::String &savePathName) {
// Check that path exists and is usable.
checkPath(Common::FSNode(savePathName));
+#ifdef USE_LIBCURL
+ Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing
+ if (!files.empty()) updateSavefilesList(files); //makes this cache invalid
+ else _lockedFiles = files;
+#endif
+
if (_cachedDirectory == savePathName) {
return;
}
@@ -216,4 +278,102 @@ void DefaultSaveFileManager::assureCached(const Common::String &savePathName) {
_cachedDirectory = savePathName;
}
+#ifdef USE_LIBCURL
+
+Common::HashMap<Common::String, uint32> DefaultSaveFileManager::loadTimestamps() {
+ Common::HashMap<Common::String, uint32> timestamps;
+
+ //refresh the files list
+ Common::Array<Common::String> files;
+ g_system->getSavefileManager()->updateSavefilesList(files);
+
+ //start with listing all the files in saves/ directory and setting invalid timestamp to them
+ Common::StringArray localFiles = g_system->getSavefileManager()->listSavefiles("*");
+ for (uint32 i = 0; i < localFiles.size(); ++i)
+ timestamps[localFiles[i]] = INVALID_TIMESTAMP;
+
+ //now actually load timestamps from file
+ Common::InSaveFile *file = g_system->getSavefileManager()->openRawFile(TIMESTAMPS_FILENAME);
+ if (!file) {
+ warning("DefaultSaveFileManager: failed to open '%s' file to load timestamps", TIMESTAMPS_FILENAME);
+ return timestamps;
+ }
+
+ while (!file->eos()) {
+ //read filename into buffer (reading until the first ' ')
+ Common::String buffer;
+ while (!file->eos()) {
+ byte b = file->readByte();
+ if (b == ' ') break;
+ buffer += (char)b;
+ }
+
+ //read timestamp info buffer (reading until ' ' or some line ending char)
+ Common::String filename = buffer;
+ while (true) {
+ bool lineEnded = false;
+ buffer = "";
+ while (!file->eos()) {
+ byte b = file->readByte();
+ if (b == ' ' || b == '\n' || b == '\r') {
+ lineEnded = (b == '\n');
+ break;
+ }
+ buffer += (char)b;
+ }
+
+ if (buffer == "" && file->eos()) break;
+ if (!lineEnded) filename += " " + buffer;
+ else break;
+ }
+
+ //parse timestamp
+ uint32 timestamp = buffer.asUint64();
+ if (buffer == "" || timestamp == 0) break;
+ timestamps[filename] = timestamp;
+ }
+
+ delete file;
+ return timestamps;
+}
+
+void DefaultSaveFileManager::saveTimestamps(Common::HashMap<Common::String, uint32> &timestamps) {
+ Common::DumpFile f;
+ Common::String filename = concatWithSavesPath(TIMESTAMPS_FILENAME);
+ if (!f.open(filename, true)) {
+ warning("DefaultSaveFileManager: failed to open '%s' file to save timestamps", filename.c_str());
+ return;
+ }
+
+ for (Common::HashMap<Common::String, uint32>::iterator i = timestamps.begin(); i != timestamps.end(); ++i) {
+ Common::String data = i->_key + Common::String::format(" %u\n", i->_value);
+ if (f.write(data.c_str(), data.size()) != data.size()) {
+ warning("DefaultSaveFileManager: failed to write timestamps data into '%s'", filename.c_str());
+ return;
+ }
+ }
+
+ f.flush();
+ f.finalize();
+ f.close();
+}
+
+Common::String DefaultSaveFileManager::concatWithSavesPath(Common::String name) {
+ DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
+ Common::String path = (manager ? manager->getSavePath() : ConfMan.get("savepath"));
+ if (path.size() > 0 && (path.lastChar() == '/' || path.lastChar() == '\\'))
+ return path + name;
+
+ //simple heuristic to determine which path separator to use
+ int backslashes = 0;
+ for (uint32 i = 0; i < path.size(); ++i)
+ if (path[i] == '/') --backslashes;
+ else if (path[i] == '\\') ++backslashes;
+
+ if (backslashes > 0) return path + '\\' + name;
+ return path + '/' + name;
+}
+
+#endif // ifdef USE_LIBCURL
+
#endif // !defined(DISABLE_DEFAULT_SAVEFILEMANAGER)
diff --git a/backends/saves/default/default-saves.h b/backends/saves/default/default-saves.h
index bf4ca0229d..f2453810bf 100644
--- a/backends/saves/default/default-saves.h
+++ b/backends/saves/default/default-saves.h
@@ -27,7 +27,8 @@
#include "common/savefile.h"
#include "common/str.h"
#include "common/fs.h"
-#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include <limits.h>
/**
* Provides a default savefile manager implementation for common platforms.
@@ -37,11 +38,24 @@ public:
DefaultSaveFileManager();
DefaultSaveFileManager(const Common::String &defaultSavepath);
+ virtual void updateSavefilesList(Common::StringArray &lockedFiles);
virtual Common::StringArray listSavefiles(const Common::String &pattern);
+ virtual Common::InSaveFile *openRawFile(const Common::String &filename);
virtual Common::InSaveFile *openForLoading(const Common::String &filename);
virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true);
virtual bool removeSavefile(const Common::String &filename);
+#ifdef USE_LIBCURL
+
+ static const uint32 INVALID_TIMESTAMP = UINT_MAX;
+ static const char *TIMESTAMPS_FILENAME;
+
+ static Common::HashMap<Common::String, uint32> loadTimestamps();
+ static void saveTimestamps(Common::HashMap<Common::String, uint32> &timestamps);
+ static Common::String concatWithSavesPath(Common::String name);
+
+#endif
+
protected:
/**
* Get the path to the savegame directory.
@@ -74,6 +88,12 @@ protected:
*/
SaveFileCache _saveFileCache;
+ /**
+ * List of "locked" files. These cannot be used for saving/loading
+ * because CloudManager is downloading those.
+ */
+ Common::StringArray _lockedFiles;
+
private:
/**
* The currently cached directory.
diff --git a/backends/saves/savefile.cpp b/backends/saves/savefile.cpp
index b04c53d832..e21ea16ad8 100644
--- a/backends/saves/savefile.cpp
+++ b/backends/saves/savefile.cpp
@@ -23,9 +23,37 @@
#include "common/util.h"
#include "common/savefile.h"
#include "common/str.h"
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#endif
namespace Common {
+OutSaveFile::OutSaveFile(WriteStream *w): _wrapped(w) {}
+
+OutSaveFile::~OutSaveFile() {}
+
+bool OutSaveFile::err() const { return _wrapped->err(); }
+
+void OutSaveFile::clearErr() { _wrapped->clearErr(); }
+
+void OutSaveFile::finalize() {
+ _wrapped->finalize();
+#ifdef USE_LIBCURL
+ CloudMan.syncSaves();
+#endif
+}
+
+bool OutSaveFile::flush() { return _wrapped->flush(); }
+
+uint32 OutSaveFile::write(const void *dataPtr, uint32 dataSize) {
+ return _wrapped->write(dataPtr, dataSize);
+}
+
+int32 OutSaveFile::pos() const {
+ return _wrapped->pos();
+}
+
bool SaveFileManager::copySavefile(const String &oldFilename, const String &newFilename) {
InSaveFile *inFile = 0;
OutSaveFile *outFile = 0;