ALL: Sync with ScummVM rev. 55dba55056

This commit is contained in:
Bastien Bouclet 2019-12-07 15:31:33 +01:00
parent aaec28e12f
commit feaf9dc365
200 changed files with 13353 additions and 13638 deletions

View file

@ -126,7 +126,7 @@ endif
.PHONY: print-dists print-executables print-version print-distversion
print-dists:
@echo $(DIST_FILES_DOCS) $(DIST_FILES_THEMES) $(DIST_FILES_NETWORKING) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_PLATFORM) $(srcdir)/doc
@echo $(DIST_FILES_DOCS) $(DIST_FILES_THEMES) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_PLATFORM) $(srcdir)/doc
print-executables:
@echo $(if $(DIST_EXECUTABLES),$(DIST_EXECUTABLES),$(EXECUTABLE) $(PLUGINS))

View file

@ -138,6 +138,10 @@ ifdef CXX_UPDATE_DEP_FLAG
$(QUIET)$(MKDIR) $(*D)/$(DEPDIR)
$(QUIET_AS)$(CXX) $(CXX_UPDATE_DEP_FLAG) $(ASFLAGS) -c $(<) -o $*.o
base/version.o: base/version.cpp
$(QUIET)$(MKDIR) $(*D)/$(DEPDIR)
$(QUIET_CXX)$(CXX) $(CXX_UPDATE_DEP_FLAG) $(CXXFLAGS) $(VERFLAGS) $(CPPFLAGS) -c $(<) -o $*.o
else
# Dumb compile rule, for C++ compilers that don't allow dependency tracking or
@ -151,6 +155,9 @@ else
$(QUIET)$(MKDIR) $(*D)
$(QUIET_AS)$(CXX) $(ASFLAGS) -c $(<) -o $*.o
base/version.o: base/version.cpp
$(QUIET)$(MKDIR) $(*D)
$(QUIET_CXX)$(CXX) $(CXXFLAGS) $(VERFLAGS) $(CPPFLAGS) -c $(<) -o $*.o
endif
# Build rule for assembler files
@ -158,6 +165,12 @@ endif
$(QUIET)$(MKDIR) $(*D)
$(QUIET_AS)$(AS) $(ASFLAGS) $(<) -o $*.o
# Build rule for Windows resource files
# TODO: Support dependency tracking
%.o: %.rc
$(QUIET)$(MKDIR) $(*D)
$(QUIET_WINDRES)$(WINDRES) $(WINDRESFLAGS) $(CPPFLAGS) $(<) -o $*.o
ifdef USE_NASM
# Build rule for NASM assembler files
%.o: %.asm
@ -188,7 +201,7 @@ VER_EXTRA = $(shell echo $(VERSION) | cut -d. -f 3 | cut -c2-)
ifdef AMIGAOS
# Amiga needs date in specific format for the version cookie
AMIGA_DATE = $(shell gdate '+%d.%m.%Y')
base/version.o: CXXFLAGS:=$(CXXFLAGS) -DAMIGA_DATE=\"$(AMIGA_DATE)\"
VERFLAGS += -DAMIGA_DATE=\"$(AMIGA_DATE)\"
endif
######################################################################
@ -211,7 +224,7 @@ endif
# Define the Subversion revision if available, either autodetected or
# specified by the user, but only for base/version.cpp.
ifneq ($(origin VER_REV), undefined)
base/version.o: CXXFLAGS:=$(CXXFLAGS) -DSCUMMVM_REVISION=\"$(VER_REV)\"
VERFLAGS += -DSCUMMVM_REVISION=\"$(VER_REV)\"
endif
######################################################################

View file

@ -23,7 +23,6 @@
#include "gui/EventRecorder.h"
#include "common/util.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "audio/mixer_intern.h"
@ -173,8 +172,7 @@ private:
#pragma mark --- Mixer ---
#pragma mark -
// TODO: parameter "system" is unused
MixerImpl::MixerImpl(OSystem *system, uint sampleRate)
MixerImpl::MixerImpl(uint sampleRate)
: _mutex(), _sampleRate(sampleRate), _mixerReady(false), _handleSeed(0), _soundTypeSettings() {
assert(sampleRate > 0);

View file

@ -73,7 +73,7 @@ private:
public:
MixerImpl(OSystem *system, uint sampleRate);
MixerImpl(uint sampleRate);
~MixerImpl();
virtual bool isReady() const { return _mixerReady; }

View file

@ -57,7 +57,7 @@ void BaseBackend::initBackend() {
void BaseBackend::fillScreen(uint32 col) {
Graphics::Surface *screen = lockScreen();
if (screen && screen->getPixels())
memset(screen->getPixels(), col, screen->h * screen->pitch);
if (screen)
screen->fillRect(Common::Rect(screen->w, screen->h), col);
unlockScreen();
}

View file

@ -83,7 +83,7 @@ void BoxListDirectoryByIdRequest::responseCallback(Networking::JsonResponse resp
if (response.request)
_date = response.request->date();
Networking::ErrorResponse error(this);
Networking::ErrorResponse error(this, "BoxListDirectoryByIdRequest::responseCallback: unknown error");
Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();

View file

@ -35,142 +35,34 @@
#include "common/debug.h"
#include "common/json.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;
BoxStorage::BoxStorage(Common::String token, Common::String refreshToken, bool enabled):
IdStorage(token, refreshToken, enabled) {}
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 token, Common::String refreshToken):
_token(token), _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(Common::String code, Networking::ErrorCallback cb) {
getAccessToken(code, cb);
}
BoxStorage::~BoxStorage() {}
void BoxStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) {
if (!KEY || !SECRET)
loadKeyAndSecret();
bool codeFlow = (code != "");
Common::String BoxStorage::cloudProvider() { return "box"; }
if (!codeFlow && _refreshToken == "") {
warning("BoxStorage: no refresh token available to get new access token.");
if (callback) (*callback)(BoolResponse(nullptr, false));
return;
}
uint32 BoxStorage::storageIndex() { return kStorageBoxId; }
Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::tokenRefreshed, callback);
if (errorCallback == nullptr)
errorCallback = getErrorPrintingCallback();
bool BoxStorage::needsRefreshToken() { return true; }
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);
}
bool BoxStorage::canReuseRefreshToken() { return false; }
void BoxStorage::saveConfig(Common::String keyPrefix) {
ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
saveIsEnabledFlag(keyPrefix);
}
Common::String BoxStorage::name() const {
@ -321,8 +213,6 @@ Networking::Request *BoxStorage::info(StorageInfoCallback callback, Networking::
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;
@ -335,7 +225,13 @@ BoxStorage *BoxStorage::loadFromConfig(Common::String keyPrefix) {
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);
return new BoxStorage(accessToken, refreshToken, loadIsEnabledFlag(keyPrefix));
}
void BoxStorage::removeFromConfig(Common::String keyPrefix) {
ConfMan.removeKey(keyPrefix + "access_token", ConfMan.kCloudDomain);
ConfMan.removeKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
removeIsEnabledFlag(keyPrefix);
}
Common::String BoxStorage::getRootDirectoryId() {

View file

@ -30,26 +30,32 @@ 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);
BoxStorage(Common::String token, Common::String refreshToken, bool enabled);
/** Constructs StorageInfo based on JSON response from cloud. */
void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json);
void createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response);
protected:
/**
* @return "box"
*/
virtual Common::String cloudProvider();
/**
* @return kStorageBoxId
*/
virtual uint32 storageIndex();
virtual bool needsRefreshToken();
virtual bool canReuseRefreshToken();
public:
/** This constructor uses OAuth code flow to get tokens. */
BoxStorage(Common::String code);
BoxStorage(Common::String code, Networking::ErrorCallback cb);
virtual ~BoxStorage();
/**
@ -98,14 +104,12 @@ public:
*/
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.
* Remove all BoxStorage-related data from config.
*/
void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = "");
static void removeFromConfig(Common::String keyPrefix);
virtual Common::String getRootDirectoryId();
Common::String accessToken() const { return _token; }
};

View file

@ -41,7 +41,7 @@ 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));
finishError(Networking::ErrorResponse(this, false, true, "BoxTokenRefresher::tokenRefreshed: failed to refresh token", -1));
return;
}
@ -99,7 +99,7 @@ void BoxTokenRefresher::finishJson(Common::JSONValue *json) {
pause();
delete json;
_parentStorage->getAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed));
_parentStorage->refreshAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed));
return;
}
}
@ -111,7 +111,7 @@ void BoxTokenRefresher::finishJson(Common::JSONValue *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));
_parentStorage->refreshAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed));
return;
}

View file

@ -148,12 +148,17 @@ void CloudManager::replaceStorage(Storage *storage, uint32 index) {
}
_activeStorage = storage;
_currentStorageIndex = index;
if (_storages[index].username == "") {
// options' Cloud tab believes Storage is connected once it has non-empty username
_storages[index].username = _("<syncing...>");
_storages[index].lastSyncDate = _("<right now>");
_storages[index].usedBytes = 0;
}
save();
//do what should be done on first Storage connect
if (_activeStorage) {
_activeStorage->info(nullptr, nullptr); //automatically calls setStorageUsername()
_activeStorage->syncSaves(nullptr, nullptr);
}
}
@ -250,21 +255,21 @@ void CloudManager::setStorageLastSync(uint32 index, Common::String date) {
save();
}
void CloudManager::connectStorage(uint32 index, Common::String code) {
void CloudManager::connectStorage(uint32 index, Common::String code, Networking::ErrorCallback cb) {
freeStorages();
switch (index) {
case kStorageDropboxId:
new Dropbox::DropboxStorage(code);
new Dropbox::DropboxStorage(code, cb);
break;
case kStorageOneDriveId:
new OneDrive::OneDriveStorage(code);
new OneDrive::OneDriveStorage(code, cb);
break;
case kStorageGoogleDriveId:
new GoogleDrive::GoogleDriveStorage(code);
new GoogleDrive::GoogleDriveStorage(code, cb);
break;
case kStorageBoxId:
new Box::BoxStorage(code);
new Box::BoxStorage(code, cb);
break;
}
// in these constructors Storages request token using the passed code
@ -273,6 +278,42 @@ void CloudManager::connectStorage(uint32 index, Common::String code) {
// thus, no memory leak happens
}
void CloudManager::disconnectStorage(uint32 index) {
if (index >= kStorageTotal)
error("CloudManager::disconnectStorage: invalid index passed");
Common::String name = getStorageConfigName(index);
switch (index) {
case kStorageDropboxId:
Dropbox::DropboxStorage::removeFromConfig(kStoragePrefix + name + "_");
break;
case kStorageOneDriveId:
OneDrive::OneDriveStorage::removeFromConfig(kStoragePrefix + name + "_");
break;
case kStorageGoogleDriveId:
GoogleDrive::GoogleDriveStorage::removeFromConfig(kStoragePrefix + name + "_");
break;
case kStorageBoxId:
Box::BoxStorage::removeFromConfig(kStoragePrefix + name + "_");
break;
}
switchStorage(kStorageNoneId);
ConfMan.removeKey(kStoragePrefix + name + "_username", ConfMan.kCloudDomain);
ConfMan.removeKey(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain);
ConfMan.removeKey(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain);
StorageConfig config;
config.name = _(name);
config.username = "";
config.lastSyncDate = "";
config.usedBytes = 0;
_storages[index] = config;
}
Networking::Request *CloudManager::listDirectory(Common::String path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
Storage *storage = getCurrentStorage();
if (storage) {
@ -316,6 +357,28 @@ Common::String CloudManager::savesDirectoryPath() {
return "";
}
bool CloudManager::canSyncFilename(const Common::String &filename) const {
if (filename == "" || filename[0] == '.')
return false;
return true;
}
bool CloudManager::isStorageEnabled() const {
Storage *storage = getCurrentStorage();
if (storage)
return storage->isEnabled();
return false;
}
void CloudManager::enableStorage() {
Storage *storage = getCurrentStorage();
if (storage) {
storage->enable();
save();
}
}
SavesSyncRequest *CloudManager::syncSaves(Storage::BoolCallback callback, Networking::ErrorCallback errorCallback) {
Storage *storage = getCurrentStorage();
if (storage) {
@ -336,14 +399,6 @@ bool CloudManager::isWorking() const {
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 {

View file

@ -204,8 +204,16 @@ public:
*
* @param index Storage's index
* @param code OAuth2 code received from user
* @param cb callback to notify of success or error
*/
void connectStorage(uint32 index, Common::String code);
void connectStorage(uint32 index, Common::String code, Networking::ErrorCallback cb = nullptr);
/**
* Remove Storage with a given index from config.
*
* @param index Storage's index
*/
void disconnectStorage(uint32 index);
/** Returns ListDirectoryResponse with list of files. */
Networking::Request *listDirectory(Common::String path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
@ -219,6 +227,15 @@ public:
/** Returns storage's saves directory path with the trailing slash. */
Common::String savesDirectoryPath();
/** Returns whether given filename could be uploaded to or downloaded from storage. */
bool canSyncFilename(const Common::String &filename) const;
/** Returns whether current Storage is manually enabled by user (or false, if there is no active Storage). */
bool isStorageEnabled() const;
/** Sets Storage::_isEnabled to true and updates the config. */
void enableStorage();
/**
* Starts saves syncing process in currently active storage if there is any.
*/
@ -227,9 +244,6 @@ public:
/** 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. */

View file

@ -73,13 +73,13 @@ void DownloadRequest::streamErrorCallback(Networking::ErrorResponse error) {
void DownloadRequest::handle() {
if (!_localFile) {
warning("DownloadRequest: no file to write");
finishError(Networking::ErrorResponse(this, false, true, "", -1));
finishError(Networking::ErrorResponse(this, false, true, "DownloadRequest::handle: no file to write into", -1));
return;
}
if (!_localFile->isOpen()) {
warning("DownloadRequest: failed to open file to write");
finishError(Networking::ErrorResponse(this, false, true, "", -1));
finishError(Networking::ErrorResponse(this, false, true, "DownloadRequest::handle: failed to open file to write", -1));
return;
}
@ -93,7 +93,7 @@ void DownloadRequest::handle() {
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));
finishError(Networking::ErrorResponse(this, false, true, "DownloadRequest::handle: failed to write all bytes into a file", -1));
return;
}
@ -113,7 +113,7 @@ void DownloadRequest::handle() {
void DownloadRequest::restart() {
warning("DownloadRequest: can't restart as there are no means to reopen DumpFile");
finishError(Networking::ErrorResponse(this, false, true, "", -1));
finishError(Networking::ErrorResponse(this, false, true, "DownloadRequest::restart: can't restart as there are no means to reopen DumpFile", -1));
//start();
}

View file

@ -74,7 +74,7 @@ void DropboxCreateDirectoryRequest::responseCallback(Networking::JsonResponse re
}
if (response.request) _date = response.request->date();
Networking::ErrorResponse error(this);
Networking::ErrorResponse error(this, "DropboxCreateDirectoryRequest::responseCallback: unknown error");
Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();

View file

@ -71,7 +71,7 @@ void DropboxInfoRequest::userResponseCallback(Networking::JsonResponse response)
return;
}
Networking::ErrorResponse error(this);
Networking::ErrorResponse error(this, "DropboxInfoRequest::userResponseCallback: unknown error");
Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
@ -125,7 +125,7 @@ void DropboxInfoRequest::quotaResponseCallback(Networking::JsonResponse response
return;
}
Networking::ErrorResponse error(this);
Networking::ErrorResponse error(this, "DropboxInfoRequest::quotaResponseCallback: unknown error");
Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();

View file

@ -84,7 +84,7 @@ void DropboxListDirectoryRequest::responseCallback(Networking::JsonResponse resp
if (response.request)
_date = response.request->date();
Networking::ErrorResponse error(this);
Networking::ErrorResponse error(this, "DropboxListDirectoryRequest::responseCallback: unknown error");
Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();

View file

@ -35,103 +35,30 @@
#include "common/debug.h"
#include "common/json.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;
DropboxStorage::DropboxStorage(Common::String accessToken, bool enabled): BaseStorage(accessToken, "", enabled) {}
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(Common::String code, Networking::ErrorCallback cb): BaseStorage() {
getAccessToken(code, cb);
}
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);
}
Common::String DropboxStorage::cloudProvider() { return "dropbox"; }
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;
}
uint32 DropboxStorage::storageIndex() { return kStorageDropboxId; }
if (!json->isObject()) {
debug(9, "DropboxStorage::codeFlowComplete: Passed JSON is not an object!");
CloudMan.removeStorage(this);
delete json;
return;
}
bool DropboxStorage::needsRefreshToken() { return false; }
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);
}
bool DropboxStorage::canReuseRefreshToken() { return false; }
void DropboxStorage::saveConfig(Common::String keyPrefix) {
ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
ConfMan.set(keyPrefix + "user_id", _uid, ConfMan.kCloudDomain);
saveIsEnabledFlag(keyPrefix);
}
Common::String DropboxStorage::name() const {
@ -177,22 +104,18 @@ Networking::Request *DropboxStorage::info(StorageInfoCallback callback, Networki
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);
return new DropboxStorage(accessToken, loadIsEnabledFlag(keyPrefix));
}
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);
void DropboxStorage::removeFromConfig(Common::String keyPrefix) {
ConfMan.removeKey(keyPrefix + "access_token", ConfMan.kCloudDomain);
removeIsEnabledFlag(keyPrefix);
}
} // End of namespace Dropbox

View file

@ -23,30 +23,35 @@
#ifndef BACKENDS_CLOUD_DROPBOX_STORAGE_H
#define BACKENDS_CLOUD_DROPBOX_STORAGE_H
#include "backends/cloud/storage.h"
#include "backends/cloud/basestorage.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;
class DropboxStorage: public Cloud::BaseStorage {
/** This private constructor is called from loadFromConfig(). */
DropboxStorage(Common::String token, Common::String uid);
DropboxStorage(Common::String token, bool enabled);
void getAccessToken(Common::String code);
void codeFlowComplete(Networking::JsonResponse response);
void codeFlowFailed(Networking::ErrorResponse error);
protected:
/**
* @return "dropbox"
*/
virtual Common::String cloudProvider();
/**
* @return kStorageDropboxId
*/
virtual uint32 storageIndex();
virtual bool needsRefreshToken();
virtual bool canReuseRefreshToken();
public:
/** This constructor uses OAuth code flow to get tokens. */
DropboxStorage(Common::String code);
DropboxStorage(Common::String code, Networking::ErrorCallback cb);
virtual ~DropboxStorage();
/**
@ -93,6 +98,11 @@ public:
* @return pointer to the newly created DropboxStorage or 0 if some problem occured.
*/
static DropboxStorage *loadFromConfig(Common::String keyPrefix);
/**
* Remove all DropboxStorage-related data from config.
*/
static void removeFromConfig(Common::String keyPrefix);
};
} // End of namespace Dropbox

View file

@ -54,12 +54,12 @@ void DropboxUploadRequest::start() {
_workingRequest->finish();
if (!_contentsStream) {
warning("DropboxUploadRequest: cannot start because stream is invalid");
finishError(Networking::ErrorResponse(this, false, true, "", -1));
finishError(Networking::ErrorResponse(this, false, true, "DropboxUploadRequest::start: cannot start because stream is invalid", -1));
return;
}
if (!_contentsStream->seek(0)) {
warning("DropboxUploadRequest: cannot restart because stream couldn't seek(0)");
finishError(Networking::ErrorResponse(this, false, true, "", -1));
finishError(Networking::ErrorResponse(this, false, true, "DropboxUploadRequest::start: cannot restart because stream couldn't seek(0)", -1));
return;
}
_ignoreCallback = false;

View file

@ -26,6 +26,7 @@
#include "common/debug.h"
#include "gui/downloaddialog.h"
#include <backends/networking/curl/connectionmanager.h>
#include "cloudmanager.h"
namespace Cloud {
@ -73,8 +74,9 @@ void FolderDownloadRequest::directoryListedCallback(Storage::ListDirectoryRespon
// remove all directories
// non-empty directories would be created by DumpFile, and empty ones are just ignored
// also skip all hidden files (with names starting with '.') or with other names that are forbidden to sync in CloudManager
for (Common::Array<StorageFile>::iterator i = _pendingFiles.begin(); i != _pendingFiles.end();)
if (i->isDirectory())
if (i->isDirectory() || !CloudMan.canSyncFilename(i->name()))
_pendingFiles.erase(i);
else {
_totalBytes += i->size();

View file

@ -81,7 +81,7 @@ void GoogleDriveListDirectoryByIdRequest::responseCallback(Networking::JsonRespo
if (response.request)
_date = response.request->date();
Networking::ErrorResponse error(this);
Networking::ErrorResponse error(this, "GoogleDriveListDirectoryByIdRequest::responseCallback");
Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();

View file

@ -36,142 +36,34 @@
#include "common/json.h"
#include "common/debug.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;
GoogleDriveStorage::GoogleDriveStorage(Common::String token, Common::String refreshToken, bool enabled):
IdStorage(token, refreshToken, enabled) {}
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 token, Common::String refreshToken):
_token(token), _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(Common::String code, Networking::ErrorCallback cb) {
getAccessToken(code, cb);
}
GoogleDriveStorage::~GoogleDriveStorage() {}
void GoogleDriveStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) {
if (!KEY || !SECRET) loadKeyAndSecret();
bool codeFlow = (code != "");
Common::String GoogleDriveStorage::cloudProvider() { return "gdrive"; }
if (!codeFlow && _refreshToken == "") {
warning("GoogleDriveStorage: no refresh token available to get new access token.");
if (callback)
(*callback)(BoolResponse(nullptr, false));
return;
}
uint32 GoogleDriveStorage::storageIndex() { return kStorageGoogleDriveId; }
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);
}
bool GoogleDriveStorage::needsRefreshToken() { return true; }
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);
}
bool GoogleDriveStorage::canReuseRefreshToken() { return true; }
void GoogleDriveStorage::saveConfig(Common::String keyPrefix) {
ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
saveIsEnabledFlag(keyPrefix);
}
Common::String GoogleDriveStorage::name() const {
@ -325,8 +217,6 @@ Networking::Request *GoogleDriveStorage::info(StorageInfoCallback callback, Netw
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;
@ -339,7 +229,13 @@ GoogleDriveStorage *GoogleDriveStorage::loadFromConfig(Common::String keyPrefix)
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);
return new GoogleDriveStorage(accessToken, refreshToken, loadIsEnabledFlag(keyPrefix));
}
void GoogleDriveStorage::removeFromConfig(Common::String keyPrefix) {
ConfMan.removeKey(keyPrefix + "access_token", ConfMan.kCloudDomain);
ConfMan.removeKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
removeIsEnabledFlag(keyPrefix);
}
Common::String GoogleDriveStorage::getRootDirectoryId() {

View file

@ -30,18 +30,8 @@ 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);
GoogleDriveStorage(Common::String token, Common::String refreshToken, bool enabled);
/** Constructs StorageInfo based on JSON response from cloud. */
void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json);
@ -50,9 +40,25 @@ class GoogleDriveStorage: public Id::IdStorage {
void createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse json);
void printInfo(StorageInfoResponse response);
protected:
/**
* @return "gdrive"
*/
virtual Common::String cloudProvider();
/**
* @return kStorageGoogleDriveId
*/
virtual uint32 storageIndex();
virtual bool needsRefreshToken();
virtual bool canReuseRefreshToken();
public:
/** This constructor uses OAuth code flow to get tokens. */
GoogleDriveStorage(Common::String code);
GoogleDriveStorage(Common::String code, Networking::ErrorCallback cb);
virtual ~GoogleDriveStorage();
/**
@ -100,14 +106,12 @@ public:
*/
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.
* Remove all GoogleDriveStorage-related data from config.
*/
void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = "");
static void removeFromConfig(Common::String keyPrefix);
virtual Common::String getRootDirectoryId();
Common::String accessToken() const { return _token; }
};

View file

@ -41,7 +41,7 @@ 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));
finishError(Networking::ErrorResponse(this, false, true, "GoogleDriveTokenRefresher::tokenRefreshed: failed to refresh token", -1));
return;
}
@ -77,7 +77,7 @@ void GoogleDriveTokenRefresher::finishJson(Common::JSONValue *json) {
Common::JSONObject error = result.getVal("error")->asObject();
bool irrecoverable = true;
uint32 code = -1;
uint32 code = 0xFFFFFFFF; // Invalid
Common::String message;
if (jsonContainsIntegerNumber(error, "code", "GoogleDriveTokenRefresher")) {
code = error.getVal("code")->asIntegerNumber();
@ -100,7 +100,7 @@ void GoogleDriveTokenRefresher::finishJson(Common::JSONValue *json) {
pause();
delete json;
_parentStorage->getAccessToken(new Common::Callback<GoogleDriveTokenRefresher, Storage::BoolResponse>(this, &GoogleDriveTokenRefresher::tokenRefreshed));
_parentStorage->refreshAccessToken(new Common::Callback<GoogleDriveTokenRefresher, Storage::BoolResponse>(this, &GoogleDriveTokenRefresher::tokenRefreshed));
return;
}
}

View file

@ -55,7 +55,7 @@ void GoogleDriveUploadRequest::start() {
_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));
finishError(Networking::ErrorResponse(this, false, true, "GoogleDriveUploadRequest::start: couldn't restart because failed to seek(0)", -1));
return;
}
_resolvedId = ""; //used to update file contents
@ -146,33 +146,31 @@ void GoogleDriveUploadRequest::startUploadCallback(Networking::JsonResponse resp
if (_ignoreCallback)
return;
Networking::ErrorResponse error(this, false, true, "", -1);
Networking::ErrorResponse error(this, false, true, "GoogleDriveUploadRequest::startUploadCallback", -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;
Common::HashMap<Common::String, Common::String> headers = stream->responseHeadersMap();
if (headers.contains("location")) {
_uploadUrl = headers["location"];
uploadNextPart();
return;
} else {
error.response += ": response must provide Location header, but it's not there";
}
} else {
error.response += ": response is not 200 OK";
}
error.httpResponseCode = code;
} else {
error.response += ": missing response stream [improbable]";
}
} else {
error.response += ": missing request object [improbable]";
}
Common::JSONValue *json = response.value;
@ -202,7 +200,7 @@ void GoogleDriveUploadRequest::uploadNextPart() {
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));
finishError(Networking::ErrorResponse(this, false, true, "GoogleDriveUploadRequest::uploadNextPart: seek() didn't work", -1));
return;
}
oldPos = _serverReceivedBytes;
@ -230,25 +228,19 @@ bool GoogleDriveUploadRequest::handleHttp308(const Networking::NetworkReadStream
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);
Common::HashMap<Common::String, Common::String> headers = stream->responseHeadersMap();
if (headers.contains("range")) {
Common::String range = headers["range"];
for (int rangeTry = 0; rangeTry < 2; ++rangeTry) {
const char *needle = (rangeTry == 0 ? "0-" : "bytes=0-"); //if it lost the first part, I refuse to talk with it
uint32 needleLength = (rangeTry == 0 ? 2 : 8);
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;
if (range.hasPrefix(needle)) {
range.erase(0, needleLength);
_serverReceivedBytes = range.asUint64() + 1;
uploadNextPart();
return true;
}
_serverReceivedBytes = result.asUint64() + 1;
uploadNextPart();
return true;
}
}

View file

@ -33,6 +33,11 @@
namespace Cloud {
namespace Id {
IdStorage::IdStorage() {}
IdStorage::IdStorage(Common::String token, Common::String refreshToken, bool enabled):
BaseStorage(token, refreshToken, enabled) {}
IdStorage::~IdStorage() {}
void IdStorage::printFiles(FileArrayResponse response) {

View file

@ -23,7 +23,7 @@
#ifndef BACKENDS_CLOUD_ID_IDSTORAGE_H
#define BACKENDS_CLOUD_ID_IDSTORAGE_H
#include "backends/cloud/storage.h"
#include "backends/cloud/basestorage.h"
#include "backends/networking/curl/curljsonrequest.h"
/*
@ -43,7 +43,7 @@
namespace Cloud {
namespace Id {
class IdStorage: public Cloud::Storage {
class IdStorage: public Cloud::BaseStorage {
protected:
void printFiles(FileArrayResponse response);
void printBool(BoolResponse response);
@ -52,6 +52,8 @@ protected:
ListDirectoryCallback getPrintFilesCallback();
public:
IdStorage();
IdStorage(Common::String token, Common::String refreshToken, bool enabled);
virtual ~IdStorage();
/** Public Cloud API comes down there. */

View file

@ -31,7 +31,7 @@
namespace Cloud {
namespace OneDrive {
#define ONEDRIVE_API_SPECIAL_APPROOT "https://api.onedrive.com/v1.0/drive/special/approot"
#define ONEDRIVE_API_SPECIAL_APPROOT "https://graph.microsoft.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),
@ -96,7 +96,7 @@ void OneDriveCreateDirectoryRequest::responseCallback(Networking::JsonResponse r
if (response.request)
_date = response.request->date();
Networking::ErrorResponse error(this);
Networking::ErrorResponse error(this, "OneDriveCreateDirectoryRequest::responseCallback: unknown error");
Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();

View file

@ -31,7 +31,8 @@
namespace Cloud {
namespace OneDrive {
#define ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/children"
#define ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN "https://graph.microsoft.com/v1.0/drive/special/approot:/%s:/children"
#define ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN_ROOT_ITSELF "https://graph.microsoft.com/v1.0/drive/special/approot/children"
OneDriveListDirectoryRequest::OneDriveListDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive):
Networking::Request(nullptr, ecb),
@ -77,6 +78,7 @@ void OneDriveListDirectoryRequest::listNextDirectory() {
Common::String dir = _currentDirectory;
dir.deleteLastChar();
Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN, ConnMan.urlEncode(dir).c_str());
if (dir == "") url = Common::String(ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN_ROOT_ITSELF);
makeRequest(url);
}
@ -84,7 +86,7 @@ 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());
request->addHeader("Authorization: bearer " + _storage->accessToken());
_workingRequest = ConnMan.addRequest(request);
}
@ -100,7 +102,7 @@ void OneDriveListDirectoryRequest::listedDirectoryCallback(Networking::JsonRespo
if (response.request)
_date = response.request->date();
Networking::ErrorResponse error(this);
Networking::ErrorResponse error(this, "OneDriveListDirectoryRequest::listedDirectoryCallback: unknown error");
Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();

View file

@ -36,143 +36,33 @@
#include "common/debug.h"
#include "common/json.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"
#define ONEDRIVE_API_SPECIAL_APPROOT_ID "https://graph.microsoft.com/v1.0/drive/special/approot:/"
#define ONEDRIVE_API_SPECIAL_APPROOT "https://graph.microsoft.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;
OneDriveStorage::OneDriveStorage(Common::String token, Common::String refreshToken, bool enabled):
BaseStorage(token, refreshToken, enabled) {}
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 token, Common::String uid, Common::String refreshToken):
_token(token), _uid(uid), _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(Common::String code, Networking::ErrorCallback cb) {
getAccessToken(code, cb);
}
OneDriveStorage::~OneDriveStorage() {}
void OneDriveStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) {
if (!KEY || !SECRET)
loadKeyAndSecret();
bool codeFlow = (code != "");
Common::String OneDriveStorage::cloudProvider() { return "onedrive"; }
if (!codeFlow && _refreshToken == "") {
warning("OneDriveStorage: no refresh token available to get new access token.");
if (callback)
(*callback)(BoolResponse(nullptr, false));
return;
}
uint32 OneDriveStorage::storageIndex() { return kStorageOneDriveId; }
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);
}
bool OneDriveStorage::needsRefreshToken() { return true; }
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);
}
bool OneDriveStorage::canReuseRefreshToken() { return false; }
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);
saveIsEnabledFlag(keyPrefix);
}
Common::String OneDriveStorage::name() const {
@ -247,7 +137,7 @@ void OneDriveStorage::fileInfoCallback(Networking::NetworkReadStreamCallback out
}
Common::JSONObject result = response.value->asObject();
if (!Networking::CurlJsonRequest::jsonContainsString(result, "@content.downloadUrl", "OneDriveStorage::fileInfoCallback")) {
if (!Networking::CurlJsonRequest::jsonContainsString(result, "@microsoft.graph.downloadUrl", "OneDriveStorage::fileInfoCallback")) {
warning("OneDriveStorage: downloadUrl not found in passed JSON");
debug(9, "%s", response.value->stringify().c_str());
if (outerCallback)
@ -257,7 +147,7 @@ void OneDriveStorage::fileInfoCallback(Networking::NetworkReadStreamCallback out
return;
}
const char *url = result.getVal("@content.downloadUrl")->asString().c_str();
const char *url = result.getVal("@microsoft.graph.downloadUrl")->asString().c_str();
if (outerCallback)
(*outerCallback)(Networking::NetworkReadStreamResponse(
response.request,
@ -269,28 +159,33 @@ void OneDriveStorage::fileInfoCallback(Networking::NetworkReadStreamCallback out
}
Networking::Request *OneDriveStorage::listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
debug(9, "OneDrive: `ls \"%s\"`", path.c_str());
return addRequest(new OneDriveListDirectoryRequest(this, path, callback, errorCallback, recursive));
}
Networking::Request *OneDriveStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
debug(9, "OneDrive: `upload \"%s\"`", path.c_str());
return addRequest(new OneDriveUploadRequest(this, path, contents, callback, errorCallback));
}
Networking::Request *OneDriveStorage::streamFileById(Common::String path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) {
debug(9, "OneDrive: `download \"%s\"`", path.c_str());
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);
request->addHeader("Authorization: bearer " + _token);
return addRequest(request);
}
Networking::Request *OneDriveStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) {
debug(9, "OneDrive: `mkdir \"%s\"`", path.c_str());
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
return addRequest(new OneDriveCreateDirectoryRequest(this, path, callback, errorCallback));
}
Networking::Request *OneDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
debug(9, "OneDrive: `info`");
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);
@ -300,27 +195,25 @@ Networking::Request *OneDriveStorage::info(StorageInfoCallback callback, Network
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);
return new OneDriveStorage(accessToken, refreshToken, loadIsEnabledFlag(keyPrefix));
}
void OneDriveStorage::removeFromConfig(Common::String keyPrefix) {
ConfMan.removeKey(keyPrefix + "access_token", ConfMan.kCloudDomain);
ConfMan.removeKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
removeIsEnabledFlag(keyPrefix);
}
} // End of namespace OneDrive

View file

@ -23,33 +23,39 @@
#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H
#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H
#include "backends/cloud/storage.h"
#include "backends/cloud/basestorage.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;
class OneDriveStorage: public Cloud::BaseStorage {
/** 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);
OneDriveStorage(Common::String token, Common::String refreshToken, bool enabled);
/** Constructs StorageInfo based on JSON response from cloud. */
void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json);
void fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response);
protected:
/**
* @return "onedrive"
*/
virtual Common::String cloudProvider();
/**
* @return kStorageOneDriveId
*/
virtual uint32 storageIndex();
virtual bool needsRefreshToken();
virtual bool canReuseRefreshToken();
public:
/** This constructor uses OAuth code flow to get tokens. */
OneDriveStorage(Common::String code);
OneDriveStorage(Common::String code, Networking::ErrorCallback cb);
virtual ~OneDriveStorage();
/**
@ -98,11 +104,9 @@ public:
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.
* Remove all OneDriveStorage-related data from config.
*/
void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = "");
static void removeFromConfig(Common::String keyPrefix);
Common::String accessToken() const { return _token; }
};

View file

@ -41,7 +41,7 @@ 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));
finishError(Networking::ErrorResponse(this, false, true, "OneDriveTokenRefresher::tokenRefreshed: failed to refresh token", -1));
return;
}
@ -94,18 +94,19 @@ void OneDriveTokenRefresher::finishJson(Common::JSONValue *json) {
irrecoverable = false;
}
if (code == "unauthenticated")
if (code == "unauthenticated" || code == "InvalidAuthenticationToken")
irrecoverable = false;
if (irrecoverable) {
finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpResponseCode));
Common::String errorContents = "<irrecoverable> " + json->stringify(true);
finishError(Networking::ErrorResponse(this, false, true, errorContents, httpResponseCode));
delete json;
return;
}
pause();
delete json;
_parentStorage->getAccessToken(new Common::Callback<OneDriveTokenRefresher, Storage::BoolResponse>(this, &OneDriveTokenRefresher::tokenRefreshed));
_parentStorage->refreshAccessToken(new Common::Callback<OneDriveTokenRefresher, Storage::BoolResponse>(this, &OneDriveTokenRefresher::tokenRefreshed));
return;
}
}
@ -114,6 +115,29 @@ void OneDriveTokenRefresher::finishJson(Common::JSONValue *json) {
CurlJsonRequest::finishJson(json);
}
void OneDriveTokenRefresher::finishError(Networking::ErrorResponse error) {
if (error.failed) {
Common::JSONValue *value = Common::JSON::parse(error.response.c_str());
//somehow OneDrive returns JSON with '.' in unexpected places, try fixing it
if (!value) {
Common::String fixedResponse = error.response;
for (uint32 i = 0; i < fixedResponse.size(); ++i) {
if (fixedResponse[i] == '.')
fixedResponse.replace(i, 1, " ");
}
value = Common::JSON::parse(fixedResponse.c_str());
}
if (value) {
finishJson(value);
return;
}
}
Request::finishError(error); //call closest base class's method
}
void OneDriveTokenRefresher::setHeaders(Common::Array<Common::String> &headers) {
_headers = headers;
curl_slist_free_all(_headersList);

View file

@ -38,6 +38,7 @@ class OneDriveTokenRefresher: public Networking::CurlJsonRequest {
void tokenRefreshed(Storage::BoolResponse response);
virtual void finishJson(Common::JSONValue *json);
virtual void finishError(Networking::ErrorResponse error);
public:
OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url);
virtual ~OneDriveTokenRefresher();

View file

@ -33,8 +33,8 @@
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"
#define ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD "https://graph.microsoft.com/v1.0/drive/special/approot:/%s:/upload.createSession"
#define ONEDRIVE_API_SPECIAL_APPROOT_CONTENT "https://graph.microsoft.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),
@ -56,12 +56,12 @@ void OneDriveUploadRequest::start() {
_workingRequest->finish();
if (_contentsStream == nullptr) {
warning("OneDriveUploadRequest: cannot restart because no stream given");
finishError(Networking::ErrorResponse(this, false, true, "No stream given", -1));
finishError(Networking::ErrorResponse(this, false, true, "OneDriveUploadRequest::start: can't restart, because 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));
finishError(Networking::ErrorResponse(this, false, true, "OneDriveUploadRequest::start: can't restart, because seek(0) didn't work", -1));
return;
}
_ignoreCallback = false;

View file

@ -72,7 +72,7 @@ void SavesSyncRequest::start() {
new Common::Callback<SavesSyncRequest, Storage::ListDirectoryResponse>(this, &SavesSyncRequest::directoryListedCallback),
new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryListedErrorCallback)
);
if (!_workingRequest) finishError(Networking::ErrorResponse(this));
if (!_workingRequest) finishError(Networking::ErrorResponse(this, "SavesSyncRequest::start: Storage couldn't create Request to list directory"));
}
void SavesSyncRequest::directoryListedCallback(Storage::ListDirectoryResponse response) {
@ -90,17 +90,19 @@ void SavesSyncRequest::directoryListedCallback(Storage::ListDirectoryResponse re
//determine which files to download and which files to upload
Common::Array<StorageFile> &remoteFiles = response.value;
uint64 totalSize = 0;
debug(9, "SavesSyncRequest decisions:");
for (uint32 i = 0; i < remoteFiles.size(); ++i) {
StorageFile &file = remoteFiles[i];
if (file.isDirectory())
continue;
totalSize += file.size();
if (file.name() == DefaultSaveFileManager::TIMESTAMPS_FILENAME)
if (file.name() == DefaultSaveFileManager::TIMESTAMPS_FILENAME || !CloudMan.canSyncFilename(file.name()))
continue;
Common::String name = file.name();
if (!_localFilesTimestamps.contains(name)) {
_filesToDownload.push_back(file);
debug(9, "- downloading file %s, because it is not present on local", name.c_str());
} else {
localFileNotAvailableInCloud[name] = false;
@ -113,6 +115,13 @@ void SavesSyncRequest::directoryListedCallback(Storage::ListDirectoryResponse re
_filesToUpload.push_back(file.name());
else
_filesToDownload.push_back(file);
if (_localFilesTimestamps[name] == DefaultSaveFileManager::INVALID_TIMESTAMP)
debug(9, "- uploading file %s, because it is has invalid timestamp", name.c_str());
else if (_localFilesTimestamps[name] > file.timestamp())
debug(9, "- uploading file %s, because it is %d seconds newer than remote\n\tlocal = %d; \tremote = %d", name.c_str(), _localFilesTimestamps[name] - file.timestamp(), _localFilesTimestamps[name], file.timestamp());
else
debug(9, "- downloading file %s, because it is %d seconds older than remote\n\tlocal = %d; \tremote = %d", name.c_str(), file.timestamp() - _localFilesTimestamps[name], _localFilesTimestamps[name], file.timestamp());
}
}
@ -120,24 +129,41 @@ void SavesSyncRequest::directoryListedCallback(Storage::ListDirectoryResponse re
//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)
if (i->_key == DefaultSaveFileManager::TIMESTAMPS_FILENAME || !CloudMan.canSyncFilename(i->_key))
continue;
if (i->_value)
if (i->_value) {
_filesToUpload.push_back(i->_key);
debug(9, "- uploading file %s, because it is not present on remote", i->_key.c_str());
}
}
debug(9, "\nSavesSyncRequest: download files:");
for (uint32 i = 0; i < _filesToDownload.size(); ++i) {
debug(9, "%s", _filesToDownload[i].name().c_str());
debug(9, "\nSavesSyncRequest: ");
if (_filesToDownload.size() > 0) {
debug(9, "download files:");
for (uint32 i = 0; i < _filesToDownload.size(); ++i) {
debug(9, " %s", _filesToDownload[i].name().c_str());
}
debug(9, "%s", "");
} else {
debug(9, "nothing to download");
}
debug(9, "\nSavesSyncRequest: upload files:");
for (uint32 i = 0; i < _filesToUpload.size(); ++i) {
debug(9, "%s", _filesToUpload[i].c_str());
debug(9, "SavesSyncRequest: ");
if (_filesToUpload.size() > 0) {
debug(9, "upload files:");
for (uint32 i = 0; i < _filesToUpload.size(); ++i) {
debug(9, " %s", _filesToUpload[i].c_str());
}
} else {
debug(9, "nothing to upload");
}
_totalFilesToHandle = _filesToDownload.size() + _filesToUpload.size();
//start downloading files
downloadNextFile();
if (!_filesToDownload.empty()) {
downloadNextFile();
} else {
uploadNextFile();
}
}
void SavesSyncRequest::directoryListedErrorCallback(Networking::ErrorResponse error) {
@ -145,9 +171,22 @@ void SavesSyncRequest::directoryListedErrorCallback(Networking::ErrorResponse er
if (_ignoreCallback)
return;
if (error.failed) debug(9, "%s", error.response.c_str());
bool irrecoverable = error.interrupted || error.failed;
if (error.failed) {
Common::JSONValue *value = Common::JSON::parse(error.response.c_str());
// somehow OneDrive returns JSON with '.' in unexpected places, try fixing it
if (!value) {
Common::String fixedResponse = error.response;
for (uint32 i = 0; i < fixedResponse.size(); ++i) {
if (fixedResponse[i] == '.')
fixedResponse.replace(i, 1, " ");
}
value = Common::JSON::parse(fixedResponse.c_str());
}
if (value) {
if (value->isObject()) {
Common::JSONObject object = value->asObject();
@ -174,11 +213,13 @@ void SavesSyncRequest::directoryListedErrorCallback(Networking::ErrorResponse er
delete value;
}
//Google Drive and Box-related ScummVM-based error
//Google Drive, Box and OneDrive-related ScummVM-based error
if (error.response.contains("subdirectory not found")) {
irrecoverable = false; //base "/ScummVM/" folder not found
} else if (error.response.contains("no such file found in its parent directory")) {
irrecoverable = false; //"Saves" folder within "/ScummVM/" not found
} else if (error.response.contains("itemNotFound") && error.response.contains("Item does not exist")) {
irrecoverable = false; //"saves" folder within application folder is not found
}
}
@ -191,14 +232,14 @@ void SavesSyncRequest::directoryListedErrorCallback(Networking::ErrorResponse er
Common::String dir = _storage->savesDirectoryPath();
if (dir.lastChar() == '/')
dir.deleteLastChar();
debug(9, "SavesSyncRequest: creating %s", dir.c_str());
debug(9, "\nSavesSyncRequest: creating %s", dir.c_str());
_workingRequest = _storage->createDirectory(
dir,
new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::directoryCreatedCallback),
new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryCreatedErrorCallback)
);
if (!_workingRequest)
finishError(Networking::ErrorResponse(this));
finishError(Networking::ErrorResponse(this, "SavesSyncRequest::directoryListedErrorCallback: Storage couldn't create Request to create remote directory"));
}
void SavesSyncRequest::directoryCreatedCallback(Storage::BoolResponse response) {
@ -208,7 +249,7 @@ void SavesSyncRequest::directoryCreatedCallback(Storage::BoolResponse response)
//stop syncing if failed to create saves directory
if (!response.value) {
finishError(Networking::ErrorResponse(this, false, true, "", -1));
finishError(Networking::ErrorResponse(this, false, true, "SavesSyncRequest::directoryCreatedCallback: failed to create remote directory", -1));
return;
}
@ -239,7 +280,7 @@ void SavesSyncRequest::downloadNextFile() {
sendCommand(GUI::kSavesSyncProgressCmd, (int)(getDownloadingProgress() * 100));
debug(9, "SavesSyncRequest: downloading %s (%d %%)", _currentDownloadingFile.name().c_str(), (int)(getProgress() * 100));
debug(9, "\nSavesSyncRequest: downloading %s (%d %%)", _currentDownloadingFile.name().c_str(), (int)(getProgress() * 100));
_workingRequest = _storage->downloadById(
_currentDownloadingFile.id(),
DefaultSaveFileManager::concatWithSavesPath(_currentDownloadingFile.name()),
@ -247,7 +288,7 @@ void SavesSyncRequest::downloadNextFile() {
new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileDownloadedErrorCallback)
);
if (!_workingRequest)
finishError(Networking::ErrorResponse(this));
finishError(Networking::ErrorResponse(this, "SavesSyncRequest::downloadNextFile: Storage couldn't create Request to download a file"));
}
void SavesSyncRequest::fileDownloadedCallback(Storage::BoolResponse response) {
@ -259,7 +300,7 @@ void SavesSyncRequest::fileDownloadedCallback(Storage::BoolResponse response) {
if (!response.value) {
//delete the incomplete file
g_system->getSavefileManager()->removeSavefile(_currentDownloadingFile.name());
finishError(Networking::ErrorResponse(this, false, true, "", -1));
finishError(Networking::ErrorResponse(this, false, true, "SavesSyncRequest::fileDownloadedCallback: failed to download a file", -1));
return;
}
@ -290,7 +331,7 @@ void SavesSyncRequest::uploadNextFile() {
_currentUploadingFile = _filesToUpload.back();
_filesToUpload.pop_back();
debug(9, "SavesSyncRequest: uploading %s (%d %%)", _currentUploadingFile.c_str(), (int)(getProgress() * 100));
debug(9, "\nSavesSyncRequest: uploading %s (%d %%)", _currentUploadingFile.c_str(), (int)(getProgress() * 100));
if (_storage->uploadStreamSupported()) {
_workingRequest = _storage->upload(
_storage->savesDirectoryPath() + _currentUploadingFile,
@ -306,7 +347,7 @@ void SavesSyncRequest::uploadNextFile() {
new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback)
);
}
if (!_workingRequest) finishError(Networking::ErrorResponse(this));
if (!_workingRequest) finishError(Networking::ErrorResponse(this, "SavesSyncRequest::uploadNextFile: Storage couldn't create Request to upload a file"));
}
void SavesSyncRequest::fileUploadedCallback(Storage::UploadResponse response) {

View file

@ -34,10 +34,18 @@ namespace Cloud {
Storage::Storage():
_runningRequestsCount(0), _savesSyncRequest(nullptr), _syncRestartRequestsed(false),
_downloadFolderRequest(nullptr) {}
_downloadFolderRequest(nullptr), _isEnabled(false) {}
Storage::~Storage() {}
bool Storage::isEnabled() const {
return _isEnabled;
}
void Storage::enable() {
_isEnabled = true;
}
Networking::ErrorCallback Storage::getErrorPrintingCallback() {
return new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::printErrorResponse);
}
@ -121,6 +129,12 @@ Networking::Request *Storage::downloadById(Common::String remoteId, Common::Stri
}
Networking::Request *Storage::downloadFolder(Common::String remotePath, Common::String localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
if (!_isEnabled) {
warning("Storage::downloadFolder: cannot be run while Storage is disabled");
if (errorCallback)
(*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "Storage is disabled.", -1));
return nullptr;
}
if (!errorCallback)
errorCallback = getErrorPrintingCallback();
return addRequest(new FolderDownloadRequest(this, callback, errorCallback, remotePath, localPath, recursive));
@ -128,6 +142,13 @@ Networking::Request *Storage::downloadFolder(Common::String remotePath, Common::
SavesSyncRequest *Storage::syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback) {
_runningRequestsMutex.lock();
if (!_isEnabled) {
warning("Storage::syncSaves: cannot be run while Storage is disabled");
if (errorCallback)
(*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "Storage is disabled.", -1));
_runningRequestsMutex.unlock();
return nullptr;
}
if (_savesSyncRequest) {
warning("Storage::syncSaves: there is a sync in progress already");
_syncRestartRequestsed = true;
@ -208,7 +229,6 @@ void Storage::savesSyncDefaultCallback(BoolResponse response) {
if (!response.value)
warning("SavesSyncRequest called success callback with `false` argument");
Common::OSDMessageQueue::instance().addMessage(_("Saved games sync complete."));
}
void Storage::savesSyncDefaultErrorCallback(Networking::ErrorResponse error) {

View file

@ -70,6 +70,9 @@ protected:
/** FolderDownloadRequest-related */
FolderDownloadRequest *_downloadFolderRequest;
/** Whether user manually enabled the Storage. */
bool _isEnabled;
/** Returns default error callback (printErrorResponse). */
virtual Networking::ErrorCallback getErrorPrintingCallback();
@ -115,6 +118,16 @@ public:
*/
virtual Common::String name() const = 0;
/**
* Return whether Storage has been manually enabled by user.
*/
bool isEnabled() const;
/**
* Set _isEnabled to true.
*/
void enable();
/**
* Public Cloud API comes down there.
*

View file

@ -661,6 +661,10 @@ bool SdlEventSource::dispatchSDLEvent(SDL_Event &ev, Common::Event &event) {
event.path = Common::String(ev.drop.file);
SDL_free(ev.drop.file);
return true;
case SDL_CLIPBOARDUPDATE:
event.type = Common::EVENT_CLIPBOARD_UPDATE;
return true;
#else
case SDL_VIDEOEXPOSE:
if (_graphicsManager)

View file

@ -193,13 +193,11 @@ public:
virtual Common::WriteStream *createWriteStream() = 0;
/**
* Creates a file referred by this node.
* Creates a directory referred by this node.
*
* @param isDirectoryFlag true if created file must be a directory
*
* @return true if file is created successfully
* @return true if the directory is created successfully
*/
virtual bool create(bool isDirectoryFlag) = 0;
virtual bool createDirectory() = 0;
};

View file

@ -443,9 +443,9 @@ Common::WriteStream *AmigaOSFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
bool AmigaOSFilesystemNode::create(bool isDirectoryFlag) {
error("Not supported");
return false;
bool AmigaOSFilesystemNode::createDirectory() {
warning("AmigaOSFilesystemNode::createDirectory(): Not supported");
return _bIsValid && _bIsDirectory;
}
#endif //defined(__amigaos4__)

View file

@ -116,7 +116,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
virtual bool create(bool isDirectoryFlag);
virtual bool createDirectory();
};

View file

@ -22,16 +22,6 @@
#if defined(POSIX)
// Re-enable some forbidden symbols to avoid clashes with stat.h and unistd.h.
// Also with clock() in sys/time.h in some Mac OS X SDKs.
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h
#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir
#define FORBIDDEN_SYMBOL_EXCEPTION_getenv
#define FORBIDDEN_SYMBOL_EXCEPTION_exit //Needed for IRIX's unistd.h
#define FORBIDDEN_SYMBOL_EXCEPTION_random
#define FORBIDDEN_SYMBOL_EXCEPTION_srandom
#include "backends/fs/chroot/chroot-fs.h"
ChRootFilesystemNode::ChRootFilesystemNode(const Common::String &root, POSIXFilesystemNode *node) {
@ -110,9 +100,8 @@ Common::WriteStream *ChRootFilesystemNode::createWriteStream() {
return _realNode->createWriteStream();
}
bool ChRootFilesystemNode::create(bool isDirectoryFlag) {
error("Not supported");
return false;
bool ChRootFilesystemNode::createDirectory() {
return _realNode->createDirectory();
}
Common::String ChRootFilesystemNode::addPathComponent(const Common::String &path, const Common::String &component) {

View file

@ -49,7 +49,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
virtual bool create(bool isDirectoryFlag);
virtual bool createDirectory();
private:
static Common::String addPathComponent(const Common::String &path, const Common::String &component);

View file

@ -33,7 +33,7 @@
#define FORBIDDEN_SYMBOL_EXCEPTION_srandom
#include "backends/fs/posix/posix-fs.h"
#include "backends/fs/stdiostream.h"
#include "backends/fs/posix/posix-iostream.h"
#include "common/algorithm.h"
#include <sys/param.h>
@ -91,13 +91,13 @@ POSIXFilesystemNode::POSIXFilesystemNode(const Common::String &p) {
#endif
// Expand "~/" to the value of the HOME env variable
if (p.hasPrefix("~/")) {
if (p.hasPrefix("~/") || p == "~") {
const char *home = getenv("HOME");
if (home != NULL && strlen(home) < MAXPATHLEN) {
_path = home;
// Skip over the tilda. We know that p contains at least
// two chars, so this is safe:
_path += p.c_str() + 1;
// Skip over the tilda.
if (p.size() > 1)
_path += p.c_str() + 1;
}
} else {
_path = p;
@ -292,40 +292,18 @@ AbstractFSNode *POSIXFilesystemNode::getParent() const {
}
Common::SeekableReadStream *POSIXFilesystemNode::createReadStream() {
return StdioStream::makeFromPath(getPath(), false);
return PosixIoStream::makeFromPath(getPath(), false);
}
Common::WriteStream *POSIXFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
return PosixIoStream::makeFromPath(getPath(), true);
}
bool POSIXFilesystemNode::create(bool isDirectoryFlag) {
bool success;
if (isDirectoryFlag) {
success = mkdir(_path.c_str(), 0755) == 0;
} else {
int fd = open(_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0755);
success = fd >= 0;
if (fd >= 0) {
close(fd);
}
}
if (success) {
bool POSIXFilesystemNode::createDirectory() {
if (mkdir(_path.c_str(), 0755) == 0)
setFlags();
if (_isValid) {
if (_isDirectory != isDirectoryFlag) warning("failed to create %s: got %s", isDirectoryFlag ? "directory" : "file", _isDirectory ? "directory" : "file");
return _isDirectory == isDirectoryFlag;
}
warning("POSIXFilesystemNode: %s() was a success, but stat indicates there is no such %s",
isDirectoryFlag ? "mkdir" : "creat", isDirectoryFlag ? "directory" : "file");
return false;
}
return false;
return _isValid && _isDirectory;
}
namespace Posix {

View file

@ -68,7 +68,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
virtual bool create(bool isDirectoryFlag);
virtual bool createDirectory();
private:
/**

View file

@ -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.
*
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/fs/posix/posix-iostream.h"
#include <sys/stat.h>
PosixIoStream *PosixIoStream::makeFromPath(const Common::String &path, bool writeMode) {
FILE *handle = fopen(path.c_str(), writeMode ? "wb" : "rb");
if (handle)
return new PosixIoStream(handle);
return nullptr;
}
PosixIoStream::PosixIoStream(void *handle) :
StdioStream(handle) {
}
int32 PosixIoStream::size() const {
int fd = fileno((FILE *)_handle);
if (fd == -1) {
return StdioStream::size();
}
// Using fstat to obtain the file size is generally faster than fseek / ftell
// because it does not affect the IO buffer.
struct stat st;
if (fstat(fd, &st) == -1) {
return StdioStream::size();
}
return st.st_size;
}

View file

@ -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 BACKENDS_FS_POSIX_POSIXIOSTREAM_H
#define BACKENDS_FS_POSIX_POSIXIOSTREAM_H
#include "backends/fs/stdiostream.h"
/**
* A file input / output stream using POSIX interfaces
*/
class PosixIoStream : public StdioStream {
public:
static PosixIoStream *makeFromPath(const Common::String &path, bool writeMode);
PosixIoStream(void *handle);
int32 size() const override;
};
#endif

View file

@ -20,11 +20,13 @@
*
*/
#if defined(RISCOS)
// Re-enable some forbidden symbols to avoid clashes with stat.h and unistd.h.
#define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h
#include "common/scummsys.h"
#if defined(RISCOS)
#include "backends/fs/riscos/riscos-fs-factory.h"
#include "backends/fs/riscos/riscos-fs.h"

View file

@ -20,23 +20,21 @@
*
*/
#if defined(RISCOS)
// Re-enable some forbidden symbols to avoid clashes with stat.h and unistd.h.
#define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h
#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir
#include "common/scummsys.h"
#if defined(RISCOS)
#include "backends/platform/sdl/riscos/riscos-utils.h"
#include "backends/fs/riscos/riscos-fs.h"
#include "backends/fs/stdiostream.h"
#include "common/algorithm.h"
#include <limits.h>
#include <stdio.h>
// TODO: Replace use of access()
#include <unistd.h>
#include <fcntl.h>
#include <unixlib/local.h>
#include <kernel.h>
#include <swis.h>
@ -52,23 +50,33 @@ bool RISCOSFilesystemNode::isWritable() const {
return access(_path.c_str(), W_OK) == 0;
}
RISCOSFilesystemNode::RISCOSFilesystemNode(const Common::String &p) {
_path = p;
if (p == "/") {
void RISCOSFilesystemNode::setFlags() {
_kernel_swi_regs regs;
regs.r[0] = 23;
regs.r[1] = (int)_nativePath.c_str();
_kernel_swi(OS_File, &regs, &regs);
if (regs.r[0] == 0) {
_isDirectory = false;
_isValid = false;
} else if (regs.r[0] == 2) {
_isDirectory = true;
_isValid = true;
} else {
int type = _swi(OS_File, _INR(0,1)|_RETURN(0), 20, RISCOS_Utils::toRISCOS(_path).c_str());
if (type == 0) {
_isDirectory = false;
_isValid = false;
} else if (type == 2) {
_isDirectory = true;
_isValid = true;
} else {
_isDirectory = false;
_isValid = true;
}
_isDirectory = false;
_isValid = true;
}
}
RISCOSFilesystemNode::RISCOSFilesystemNode(const Common::String &p) {
_path = p;
if (p == "/") {
_nativePath = "";
_isDirectory = true;
_isValid = true;
} else {
_nativePath = RISCOS_Utils::toRISCOS(p);
setFlags();
}
}
@ -94,47 +102,60 @@ bool RISCOSFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, bo
if (_path == "/") {
// Special case for the root dir: List all drives
char fsname[PATH_MAX] = "";
char fsname[MAXPATHLEN] = "";
for (int fsNum = 0; fsNum < 256; fsNum += 1) {
_swi(OS_FSControl, _INR(0,3), 33, fsNum, fsname, sizeof(fsname));
if (strcmp(fsname, "") != 0) {
if (!(fsNum == 46 || fsNum == 53 || fsNum == 99)) {
int drives = 9;
if (fsNum == 193)
drives = 23;
if (fsNum == 33 || fsNum == 46 || fsNum == 53 || fsNum == 99)
continue;
for (int discnum = 0; discnum <= drives; discnum += 1) {
const Common::String path = Common::String::format("%s::%d.$", fsname, discnum);
char outpath[PATH_MAX] = "";
if(_swix(OS_FSControl, _INR(0,2)|_IN(5), 37, path.c_str(), outpath, sizeof(outpath)) == NULL) {
int exist;
if (_swix(OS_File, _INR(0,1)|_OUT(0), 23, outpath, &exist) != NULL || exist != 2)
continue;
_kernel_swi_regs regs;
regs.r[0] = 33;
regs.r[1] = fsNum;
regs.r[2] = (int)fsname;
regs.r[3] = sizeof(fsname);
_kernel_swi(OS_FSControl, &regs, &regs);
if (fsname[0] == 0)
continue;
RISCOSFilesystemNode *entry = new RISCOSFilesystemNode();
entry->_isDirectory = true;
entry->_isValid = true;
entry->_path = Common::String::format("/%s", outpath);
entry->_displayName = outpath;
myList.push_back(entry);
}
}
}
int drives = (fsNum == 193) ? 23 : 9;
for (int discnum = 0; discnum <= drives; discnum += 1) {
const Common::String path = Common::String::format("%s::%d.$", fsname, discnum);
char outpath[MAXPATHLEN] = "";
regs.r[0] = 37;
regs.r[1] = (int)path.c_str();
regs.r[2] = (int)outpath;
regs.r[3] = 0;
regs.r[4] = 0;
regs.r[5] = sizeof(outpath);
if (_kernel_swi(OS_FSControl, &regs, &regs) != NULL)
continue;
RISCOSFilesystemNode *entry = new RISCOSFilesystemNode();
entry->_nativePath = outpath;
entry->_path = Common::String('/') + outpath;
entry->_displayName = outpath;
entry->setFlags();
if (entry->_isDirectory)
myList.push_back(entry);
}
}
return true;
}
int count = 0;
int read = 0;
char file[PATH_MAX];
Common::String dir = _path;
char file[MAXPATHLEN];
_kernel_swi_regs regs;
regs.r[0] = 9;
regs.r[1] = (int)_nativePath.c_str();
regs.r[2] = (int)file;
regs.r[3] = 1;
regs.r[4] = 0;
regs.r[5] = sizeof(file);
regs.r[6] = 0;
while (regs.r[4] != -1) {
_kernel_swi(OS_GBPB, &regs, &regs);
while (count != -1) {
_swix(OS_GBPB, _INR(0,5)|_OUTR(3,4), 9, RISCOS_Utils::toRISCOS(dir).c_str(), file, 1, count, sizeof(file), &read, &count);
if (count == -1)
continue;
if (regs.r[4] == -1)
break;
// Start with a clone of this node, with the correct path set
RISCOSFilesystemNode entry(*this);
@ -143,15 +164,10 @@ bool RISCOSFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, bo
if (_path.lastChar() != '/')
entry._path += '/';
entry._path += entry._displayName;
int type = _swi(OS_File, _INR(0,1)|_RETURN(0), 20, RISCOS_Utils::toRISCOS(entry._path).c_str());
if (type == 0) {
entry._nativePath = RISCOS_Utils::toRISCOS(entry._path);
entry.setFlags();
if (!entry._isValid)
continue;
} else if (type == 2) {
entry._isDirectory = true;
} else {
entry._isDirectory = false;
}
// Honor the chosen mode
if ((mode == Common::FSNode::kListFilesOnly && entry._isDirectory) ||
@ -198,33 +214,14 @@ Common::WriteStream *RISCOSFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
bool RISCOSFilesystemNode::create(bool isDirectoryFlag) {
bool success;
bool RISCOSFilesystemNode::createDirectory() {
_kernel_swi_regs regs;
regs.r[0] = 8;
regs.r[1] = (int)_nativePath.c_str();
if (_kernel_swi(OS_File, &regs, &regs) == NULL)
setFlags();
if (isDirectoryFlag) {
success = _swix(OS_File, _INR(0,1), 8, RISCOS_Utils::toRISCOS(_path).c_str()) == NULL;
} else {
int fd = open(_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0755);
success = fd >= 0;
if (fd >= 0) {
close(fd);
}
}
if (success) {
if (exists()) {
_isDirectory = _swi(OS_File, _INR(0,1)|_RETURN(0), 20, RISCOS_Utils::toRISCOS(_path).c_str()) == 2;
if (_isDirectory != isDirectoryFlag) warning("failed to create %s: got %s", isDirectoryFlag ? "directory" : "file", _isDirectory ? "directory" : "file");
return _isDirectory == isDirectoryFlag;
}
warning("RISCOSFilesystemNode: Attempting to create a %s was a success, but access indicates there is no such %s",
isDirectoryFlag ? "directory" : "file", isDirectoryFlag ? "directory" : "file");
return false;
}
return false;
return _isValid && _isDirectory;
}
namespace Riscos {
@ -270,7 +267,7 @@ bool assureDirectoryExists(const Common::String &dir, const char *prefix) {
}
node = new RISCOSFilesystemNode(path);
if (!node->create(true)) {
if (!node->createDirectory()) {
if (node->exists()) {
if (!node->isDirectory()) {
return false;

View file

@ -33,6 +33,7 @@
class RISCOSFilesystemNode : public AbstractFSNode {
protected:
Common::String _displayName;
Common::String _nativePath;
Common::String _path;
bool _isDirectory;
bool _isValid;
@ -67,7 +68,12 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
virtual bool create(bool isDirectoryFlag);
virtual bool createDirectory();
private:
/**
* Tests and sets the _isValid and _isDirectory flags, using OS_File 20.
*/
virtual void setFlags();
};
namespace Riscos {

View file

@ -134,6 +134,12 @@ WindowsFilesystemNode::WindowsFilesystemNode(const Common::String &p, const bool
_displayName = lastPathComponent(_path, '\\');
setFlags();
_isPseudoRoot = false;
}
void WindowsFilesystemNode::setFlags() {
// Check whether it is a directory, and whether the file actually exists
DWORD fileAttribs = GetFileAttributes(toUnicode(_path.c_str()));
@ -148,7 +154,6 @@ WindowsFilesystemNode::WindowsFilesystemNode(const Common::String &p, const bool
_path += '\\';
}
}
_isPseudoRoot = false;
}
AbstractFSNode *WindowsFilesystemNode::getChild(const Common::String &n) const {
@ -244,36 +249,11 @@ Common::WriteStream *WindowsFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
bool WindowsFilesystemNode::create(bool isDirectoryFlag) {
bool success;
bool WindowsFilesystemNode::createDirectory() {
if (CreateDirectory(toUnicode(_path.c_str()), NULL) != 0)
setFlags();
if (isDirectoryFlag) {
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 != isDirectoryFlag) warning("failed to create %s: got %s", isDirectoryFlag ? "directory" : "file", _isDirectory ? "directory" : "file");
return _isDirectory == isDirectoryFlag;
}
warning("WindowsFilesystemNode: Create%s() was a success, but GetFileAttributes() indicates there is no such %s",
isDirectoryFlag ? "Directory" : "File", isDirectoryFlag ? "directory" : "file");
return false;
}
return false;
return _isValid && _isDirectory;
}
#endif //#ifdef WIN32

View file

@ -84,7 +84,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
virtual bool create(bool isDirectoryFlag);
virtual bool createDirectory();
private:
/**
@ -114,6 +114,11 @@ private:
* @return str in Unicode format.
*/
static const TCHAR* toUnicode(const char *str);
/**
* Tests and sets the _isValid and _isDirectory flags, using the GetFileAttributes() function.
*/
virtual void setFlags();
};
#endif

View file

@ -81,7 +81,7 @@ void SdlMixerManager::init() {
warning("Could not open audio device: %s", SDL_GetError());
// The mixer is not marked as ready
_mixer = new Audio::MixerImpl(g_system, desired.freq);
_mixer = new Audio::MixerImpl(desired.freq);
return;
}
@ -96,7 +96,7 @@ void SdlMixerManager::init() {
warning("Could not open audio device: %s", SDL_GetError());
// The mixer is not marked as ready
_mixer = new Audio::MixerImpl(g_system, desired.freq);
_mixer = new Audio::MixerImpl(desired.freq);
return;
}
@ -118,7 +118,7 @@ void SdlMixerManager::init() {
error("SDL mixer output requires stereo output device");
#endif
_mixer = new Audio::MixerImpl(g_system, _obtained.freq);
_mixer = new Audio::MixerImpl(_obtained.freq);
assert(_mixer);
_mixer->setReady(true);

View file

@ -20,8 +20,6 @@
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_exit
#include "backends/modular-backend.h"
#include "backends/graphics/graphics.h"
@ -302,7 +300,3 @@ void ModularBackend::displayMessageOnOSD(const char *msg) {
void ModularBackend::displayActivityIconOnOSD(const Graphics::Surface *icon) {
_graphicsManager->displayActivityIconOnOSD(icon);
}
void ModularBackend::quit() {
exit(0);
}

View file

@ -40,6 +40,7 @@ class MutexManager;
* OSystem::getMillis()
* OSystem::delayMillis()
* OSystem::getTimeAndDate()
* OSystem::quit()
*
* And, it should also initialize all the managers variables
* declared in this class, or override their related functions.
@ -138,7 +139,6 @@ public:
/** @name Miscellaneous */
//@{
virtual void quit() override;
virtual void displayMessageOnOSD(const char *msg) override;
virtual void displayActivityIconOnOSD(const Graphics::Surface *icon) override;

View file

@ -17,6 +17,7 @@ ifdef USE_CLOUD
ifdef USE_LIBCURL
MODULE_OBJS += \
cloud/basestorage.o \
cloud/cloudicon.o \
cloud/cloudmanager.o \
cloud/iso8601.o \
@ -58,6 +59,7 @@ MODULE_OBJS += \
networking/curl/networkreadstream.o \
networking/curl/curlrequest.o \
networking/curl/curljsonrequest.o \
networking/curl/postrequest.o \
networking/curl/request.o
endif
@ -141,6 +143,7 @@ ifdef POSIX
MODULE_OBJS += \
fs/posix/posix-fs.o \
fs/posix/posix-fs-factory.o \
fs/posix/posix-iostream.o \
fs/chroot/chroot-fs-factory.o \
fs/chroot/chroot-fs.o \
plugins/posix/posix-provider.o \
@ -191,6 +194,7 @@ ifdef PLAYSTATION3
MODULE_OBJS += \
fs/posix/posix-fs.o \
fs/posix/posix-fs-factory.o \
fs/posix/posix-iostream.o \
fs/ps3/ps3-fs-factory.o \
events/ps3sdl/ps3sdl-events.o
endif
@ -269,6 +273,7 @@ endif
ifeq ($(BACKEND),psp2)
MODULE_OBJS += \
fs/posix/posix-fs.o \
fs/posix/posix-iostream.o \
fs/psp2/psp2-fs-factory.o \
fs/psp2/psp2-dirent.o \
events/psp2sdl/psp2sdl-events.o \

View file

@ -26,6 +26,7 @@
#include "backends/networking/curl/connectionmanager.h"
#include "backends/networking/curl/networkreadstream.h"
#include "common/debug.h"
#include "common/fs.h"
#include "common/system.h"
#include "common/timer.h"
@ -98,6 +99,29 @@ uint32 ConnectionManager::getCloudRequestsPeriodInMicroseconds() {
return TIMER_INTERVAL * CLOUD_PERIOD;
}
const char *ConnectionManager::getCaCertPath() {
#if defined(DATA_PATH)
static enum {
kNotInitialized,
kFileNotFound,
kFileExists
} state = kNotInitialized;
if (state == kNotInitialized) {
Common::FSNode node(DATA_PATH"/cacert.pem");
state = node.exists() ? kFileExists : kFileNotFound;
}
if (state == kFileExists) {
return DATA_PATH"/cacert.pem";
} else {
return nullptr;
}
#else
return nullptr;
#endif
}
//private goes here:
void connectionsThread(void *ignored) {
@ -151,7 +175,8 @@ void ConnectionManager::interateRequests() {
_addedRequestsMutex.unlock();
//call handle() of all running requests (so they can do their work)
debug(9, "handling %d request(s)", _requests.size());
if (_frame % DEBUG_PRINT_PERIOD == 0)
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) {
@ -183,20 +208,19 @@ void ConnectionManager::processTransfers() {
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 *easyHandle = curlMsg->easy_handle;
curl_multi_remove_handle(_multi, easyHandle);
NetworkReadStream *stream = nullptr;
curl_easy_getinfo(easyHandle, CURLINFO_PRIVATE, &stream);
if (stream)
stream->finished(curlMsg->data.result);
curl_multi_remove_handle(_multi, easyHandle);
} else {
warning("Unknown libcurl message type %d", curlMsg->msg);
}
}
}

View file

@ -38,10 +38,11 @@ namespace Networking {
class NetworkReadStream;
class ConnectionManager : public Common::Singleton<ConnectionManager> {
static const uint32 FRAMES_PER_SECOND = 20;
static const uint32 FRAMES_PER_SECOND = 25;
static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND;
static const uint32 CLOUD_PERIOD = 20; //every 20th frame
static const uint32 CLOUD_PERIOD = 1; //every frame
static const uint32 CURL_PERIOD = 1; //every frame
static const uint32 DEBUG_PRINT_PERIOD = FRAMES_PER_SECOND; // once per second
friend void connectionsThread(void *); //calls handle()
@ -117,6 +118,9 @@ public:
Common::String urlEncode(Common::String s) const;
static uint32 getCloudRequestsPeriodInMicroseconds();
/** Return the path to the CA certificates bundle. */
static const char *getCaCertPath();
};
/** Shortcut for accessing the connection manager. */

View file

@ -31,6 +31,7 @@ namespace Networking {
typedef Response<Common::JSONValue *> JsonResponse;
typedef Common::BaseCallback<JsonResponse> *JsonCallback;
typedef Common::BaseCallback<Common::JSONValue *> *JSONValueCallback;
#define CURL_JSON_REQUEST_BUFFER_SIZE 512 * 1024

View file

@ -53,7 +53,7 @@ void CurlRequest::handle() {
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());
ErrorResponse error(this, false, true, "HTTP response code is not 200 OK", _stream->httpResponseCode());
finishError(error);
return;
}
@ -71,20 +71,9 @@ void CurlRequest::restart() {
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;
}
Common::HashMap<Common::String, Common::String> headers = _stream->responseHeadersMap();
if (headers.contains("date"))
return headers["date"];
}
return "";
}

View file

@ -26,6 +26,7 @@
#include "backends/networking/curl/networkreadstream.h"
#include "backends/networking/curl/connectionmanager.h"
#include "base/version.h"
#include "common/debug.h"
namespace Networking {
@ -64,6 +65,7 @@ int NetworkReadStream::curlProgressCallbackOlder(void *p, double dltotal, double
void NetworkReadStream::init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
_eos = _requestComplete = false;
_errorBuffer = (char *)calloc(CURL_ERROR_SIZE, 1);
_sendingContentsBuffer = nullptr;
_sendingContentsSize = _sendingContentsPos = 0;
_progressDownloaded = _progressTotal = 0;
@ -77,6 +79,7 @@ void NetworkReadStream::init(const char *url, curl_slist *headersList, const byt
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_ERRORBUFFER, _errorBuffer);
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);
@ -84,6 +87,12 @@ void NetworkReadStream::init(const char *url, curl_slist *headersList, const byt
curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
const char *caCertPath = ConnMan.getCaCertPath();
if (caCertPath) {
curl_easy_setopt(_easy, CURLOPT_CAINFO, caCertPath);
}
#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
@ -116,6 +125,7 @@ void NetworkReadStream::init(const char *url, curl_slist *headersList, const byt
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;
_errorBuffer = (char *)calloc(CURL_ERROR_SIZE, 1);
_sendingContentsBuffer = nullptr;
_sendingContentsSize = _sendingContentsPos = 0;
_progressDownloaded = _progressTotal = 0;
@ -129,6 +139,7 @@ void NetworkReadStream::init(const char *url, curl_slist *headersList, Common::H
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_ERRORBUFFER, _errorBuffer);
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);
@ -136,6 +147,12 @@ void NetworkReadStream::init(const char *url, curl_slist *headersList, Common::H
curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
const char *caCertPath = ConnMan.getCaCertPath();
if (caCertPath) {
curl_easy_setopt(_easy, CURLOPT_CAINFO, caCertPath);
}
#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
@ -197,6 +214,7 @@ NetworkReadStream::~NetworkReadStream() {
if (_easy)
curl_easy_cleanup(_easy);
free(_bufferCopy);
free(_errorBuffer);
}
bool NetworkReadStream::eos() const {
@ -215,8 +233,18 @@ uint32 NetworkReadStream::read(void *dataPtr, uint32 dataSize) {
return actuallyRead;
}
void NetworkReadStream::finished() {
void NetworkReadStream::finished(uint32 errorCode) {
_requestComplete = true;
char *url = nullptr;
curl_easy_getinfo(_easy, CURLINFO_EFFECTIVE_URL, &url);
if (errorCode == CURLE_OK) {
debug(9, "NetworkReadStream: %s - Request succeeded", url);
} else {
warning("NetworkReadStream: %s - Request failed (%d - %s)", url, errorCode,
strlen(_errorBuffer) ? _errorBuffer : curl_easy_strerror((CURLcode)errorCode));
}
}
long NetworkReadStream::httpResponseCode() const {
@ -240,6 +268,78 @@ Common::String NetworkReadStream::responseHeaders() const {
return _responseHeaders;
}
Common::HashMap<Common::String, Common::String> NetworkReadStream::responseHeadersMap() const {
// HTTP headers are described at RFC 2616: https://tools.ietf.org/html/rfc2616#section-4.2
// this implementation tries to follow it, but for simplicity it does not support multi-line header values
Common::HashMap<Common::String, Common::String> headers;
Common::String headerName, headerValue, trailingWhitespace;
char c;
bool readingName = true;
for (uint i = 0; i < _responseHeaders.size(); ++i) {
c = _responseHeaders[i];
if (readingName) {
if (c == ' ' || c == '\r' || c == '\n' || c == '\t') {
// header names should not contain any whitespace, this is invalid
// ignore what's been before
headerName = "";
continue;
}
if (c == ':') {
if (!headerName.empty()) {
readingName = false;
}
continue;
}
headerName += c;
continue;
}
// reading value:
if (c == ' ' || c == '\t') {
if (headerValue.empty()) {
// skip leading whitespace
continue;
} else {
// accumulate trailing whitespace
trailingWhitespace += c;
continue;
}
}
if (c == '\r' || c == '\n') {
// not sure if RFC allows empty values, we'll ignore such
if (!headerName.empty() && !headerValue.empty()) {
// add header value
// RFC allows header with the same name to be sent multiple times
// and requires it to be equivalent of just listing all header values separated with comma
// so if header already was met, we'll add new value to the old one
headerName.toLowercase();
if (headers.contains(headerName)) {
headers[headerName] += "," + headerValue;
} else {
headers[headerName] = headerValue;
}
}
headerName = "";
headerValue = "";
trailingWhitespace = "";
readingName = true;
continue;
}
// if we meet non-whitespace character, turns out those "trailing" whitespace characters were not so trailing
headerValue += trailingWhitespace;
trailingWhitespace = "";
headerValue += c;
}
return headers;
}
uint32 NetworkReadStream::fillWithSendingContents(char *bufferToFill, uint32 maxSize) {
uint32 sendSize = _sendingContentsSize - _sendingContentsPos;
if (sendSize > maxSize)

View file

@ -38,6 +38,7 @@ class NetworkReadStream: public Common::ReadStream {
CURL *_easy;
Common::MemoryReadWriteStream _backingStream;
bool _eos, _requestComplete;
char *_errorBuffer;
const byte *_sendingContentsBuffer;
uint32 _sendingContentsSize;
uint32 _sendingContentsPos;
@ -111,7 +112,7 @@ public:
*
* @note It's called on failure too.
*/
void finished();
void finished(uint32 errorCode);
/**
* Returns HTTP response code from inner CURL handle.
@ -136,6 +137,14 @@ public:
*/
Common::String responseHeaders() const;
/**
* Return response headers as HashMap. All header names in
* it are lowercase.
*
* @note This method should be called when eos() == true.
*/
Common::HashMap<Common::String, Common::String> responseHeadersMap() const;
/** Returns a number in range [0, 1], where 1 is "complete". */
double getProgress() const;

View file

@ -24,8 +24,8 @@
namespace Networking {
ErrorResponse::ErrorResponse(Request *rq):
request(rq), interrupted(false), failed(true), response(""), httpResponseCode(-1) {}
ErrorResponse::ErrorResponse(Request *rq, Common::String resp):
request(rq), interrupted(false), failed(true), response(resp), 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) {}
@ -50,7 +50,7 @@ void Request::handleRetry() {
void Request::pause() { _state = PAUSED; }
void Request::finish() {
ErrorResponse error(this, true, false, "", -1);
ErrorResponse error(this, true, false, "Request::finish() was called (i.e. interrupted)", -1);
finishError(error);
}

View file

@ -78,12 +78,12 @@ struct ErrorResponse {
Common::String response;
long httpResponseCode;
ErrorResponse(Request *rq);
ErrorResponse(Request *rq, Common::String resp);
ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode);
};
typedef Response<void *> DataReponse;
typedef Common::BaseCallback<DataReponse> *DataCallback;
typedef Response<void *> DataResponse;
typedef Common::BaseCallback<DataResponse> *DataCallback;
typedef Common::BaseCallback<ErrorResponse> *ErrorCallback;
/**

View file

@ -107,7 +107,7 @@ const char *GetClientHandler::responseMessage(long responseCode) {
void GetClientHandler::prepareHeaders() {
if (!_specialHeaders.contains("Content-Type"))
setHeader("Content-Type", "text/html");
setHeader("Content-Type", "text/html; charset=UTF-8");
if (!_specialHeaders.contains("Content-Length") && _stream)
setHeader("Content-Length", Common::String::format("%u", _stream->size()));

View file

@ -57,35 +57,35 @@ void CreateDirectoryHandler::handle(Client &client) {
// check that <path> is not an absolute root
if (path == "" || path == "/") {
handleError(client, _("Can't create directory here!"));
handleError(client, HandlerUtils::toUtf8(_("Can't create directory here!")));
return;
}
// check that <path> contains no '../'
if (HandlerUtils::hasForbiddenCombinations(path)) {
handleError(client, _("Invalid path!"));
handleError(client, HandlerUtils::toUtf8(_("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!"));
handleError(client, HandlerUtils::toUtf8(_("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!"));
handleError(client, HandlerUtils::toUtf8(_("Invalid path!")));
return;
}
if (!node->exists()) {
handleError(client, _("Parent directory doesn't exists!"));
handleError(client, HandlerUtils::toUtf8(_("Parent directory doesn't exists!")));
return;
}
if (!node->isDirectory()) {
handleError(client, _("Can't create a directory within a file!"));
handleError(client, HandlerUtils::toUtf8(_("Can't create a directory within a file!")));
return;
}
@ -95,20 +95,20 @@ void CreateDirectoryHandler::handle(Client &client) {
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!"));
handleError(client, HandlerUtils::toUtf8(_("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!"));
if (!node->createDirectory()) {
handleError(client, HandlerUtils::toUtf8(_("Failed to create the directory!")));
return;
}
}
// if json requested, respond with it
if (client.queryParameter("answer_json") == "true") {
setJsonResponseHandler(client, "success", _("Directory created successfully!"));
setJsonResponseHandler(client, "success", HandlerUtils::toUtf8(_("Directory created successfully!")));
return;
}
@ -117,9 +117,9 @@ void CreateDirectoryHandler::handle(Client &client) {
client,
Common::String::format(
"%s<br/><a href=\"files?path=%s\">%s</a>",
_("Directory created successfully!"),
HandlerUtils::toUtf8(_("Directory created successfully!")).c_str(),
client.queryParameter("path").c_str(),
_("Back to parent directory")
HandlerUtils::toUtf8(_("Back to parent directory")).c_str()
),
(client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") +
LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path"))

View file

@ -40,40 +40,40 @@ void DownloadFileHandler::handle(Client &client) {
// check that <path> is not an absolute root
if (path == "" || path == "/") {
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("Invalid path!")));
return;
}
// check that <path> contains no '../'
if (HandlerUtils::hasForbiddenCombinations(path)) {
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("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!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("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!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("Invalid path!")));
return;
}
if (!node->exists()) {
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The file doesn't exist!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("The file doesn't exist!")));
return;
}
if (node->isDirectory()) {
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't download a directory!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("Can't download a directory!")));
return;
}
Common::SeekableReadStream *stream = node->createReadStream();
if (stream == nullptr) {
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Failed to read the file!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("Failed to read the file!")));
return;
}

View file

@ -56,7 +56,7 @@ 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."));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("The page is not available without the resources. Make sure file wwwroot.zip from ResidualVM distribution is available in 'themepath'.")));
return;
}
@ -64,16 +64,16 @@ void FilesAjaxPageHandler::handle(Client &client) {
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, "{create_directory_button}", HandlerUtils::toUtf8(_("Create directory")));
replace(response, "{create_directory_button}", HandlerUtils::toUtf8(_("Create directory")));
replace(response, "{upload_files_button}", HandlerUtils::toUtf8(_("Upload files"))); //tab
replace(response, "{upload_file_button}", HandlerUtils::toUtf8(_("Upload files"))); //button in the tab
replace(response, "{create_directory_desc}", HandlerUtils::toUtf8(_("Type new directory name:")));
replace(response, "{upload_file_desc}", HandlerUtils::toUtf8(_("Select a file to upload:")));
replace(response, "{or_upload_directory_desc}", HandlerUtils::toUtf8(_("Or select a directory (works in Chrome only):")));
replace(response, "{index_of}", HandlerUtils::toUtf8(_("Index of ")));
replace(response, "{loading}", HandlerUtils::toUtf8(("Loading...")));
replace(response, "{error}", HandlerUtils::toUtf8(_("Error occurred")));
replace(response, "{start_path}", encodeDoubleQuotesAndSlashes(path));
LocalWebserver::setClientGetHandler(client, response);
}

View file

@ -78,8 +78,8 @@ Common::String getDisplayPath(Common::String s) {
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"));
addItem(content, itemTemplate, IT_DIRECTORY, "/root/", HandlerUtils::toUtf8(_("File system root")));
addItem(content, itemTemplate, IT_DIRECTORY, "/saves/", HandlerUtils::toUtf8(_("Saved games")));
return true;
}
@ -116,7 +116,7 @@ bool FilesPageHandler::listDirectory(Common::String path, Common::String &conten
filePath = "/";
else
filePath = parentPath(prefixToAdd + filePath);
addItem(content, itemTemplate, IT_PARENT_DIRECTORY, filePath, _("Parent directory"));
addItem(content, itemTemplate, IT_PARENT_DIRECTORY, filePath, HandlerUtils::toUtf8(_("Parent directory")));
}
// fill the content
@ -182,7 +182,7 @@ void FilesPageHandler::addItem(Common::String &content, const Common::String &it
void FilesPageHandler::handle(Client &client) {
Common::String response =
"<html>" \
"<head><title>ScummVM</title></head>" \
"<head><title>ResidualVM</title><meta charset=\"utf-8\"/></head>" \
"<body>" \
"<p>{create_directory_desc}</p>" \
"<form action=\"create\">" \
@ -215,21 +215,21 @@ void FilesPageHandler::handle(Client &client) {
// show an error message if failed to list directory
if (!listDirectory(path, content, itemTemplate)) {
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("ResidualVM couldn't list the directory you specified."));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("ResidualVM 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, "{create_directory_button}", HandlerUtils::toUtf8(_("Create directory")));
replace(response, "{create_directory_button}", HandlerUtils::toUtf8(_("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, "{upload_files_button}", HandlerUtils::toUtf8(_("Upload files"))); //tab
replace(response, "{upload_file_button}", HandlerUtils::toUtf8(_("Upload files"))); //button in the tab
replace(response, "{create_directory_desc}", HandlerUtils::toUtf8(_("Type new directory name:")));
replace(response, "{upload_file_desc}", HandlerUtils::toUtf8(_("Select a file to upload:")));
replace(response, "{or_upload_directory_desc}", HandlerUtils::toUtf8(_("Or select a directory (works in Chrome only):")));
replace(response, "{index_of_directory}", Common::String::format("%s %s", HandlerUtils::toUtf8(_("Index of")).c_str(), encodeHtmlEntities(getDisplayPath(client.queryParameter("path"))).c_str()));
replace(response, "{content}", content);
LocalWebserver::setClientGetHandler(client, response);
}

View file

@ -24,7 +24,6 @@
#include "backends/networking/sdl_net/handlerutils.h"
#include "backends/networking/sdl_net/localwebserver.h"
#include "common/translation.h"
#include "gui/storagewizarddialog.h"
namespace Networking {
@ -34,28 +33,17 @@ IndexPageHandler::~IndexPageHandler() {}
/// public
Common::String IndexPageHandler::code() const { return _code; }
void IndexPageHandler::handle(Client &client) {
Common::String queryCode = client.queryParameter("code");
if (queryCode == "") {
// 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 = queryCode;
sendCommand(GUI::kStorageCodePassedCmd, 0);
HandlerUtils::setMessageHandler(client, _("ResidualVM got the code and already connects to your cloud storage!"));
// redirect to "/filesAJAX"
HandlerUtils::setMessageHandler(
client,
Common::String::format(
"%s<br/><a href=\"files\">%s</a>",
HandlerUtils::toUtf8(_("This is a local webserver index page.")).c_str(),
HandlerUtils::toUtf8(_("Open Files manager")).c_str()
),
"/filesAJAX"
);
}
bool IndexPageHandler::minimalModeSupported() {

View file

@ -30,12 +30,10 @@ 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();
};

View file

@ -42,8 +42,8 @@ Common::JSONObject ListAjaxHandler::listDirectory(Common::String path) {
if (path == "" || path == "/") {
if (ConfMan.hasKey("rootpath", "cloud"))
addItem(itemsList, IT_DIRECTORY, "/root/", _("File system root"));
addItem(itemsList, IT_DIRECTORY, "/saves/", _("Saved games"));
addItem(itemsList, IT_DIRECTORY, "/root/", HandlerUtils::toUtf8(_("File system root")));
addItem(itemsList, IT_DIRECTORY, "/saves/", HandlerUtils::toUtf8(_("Saved games")));
successResult.setVal("items", new Common::JSONValue(itemsList));
return successResult;
}
@ -81,7 +81,7 @@ Common::JSONObject ListAjaxHandler::listDirectory(Common::String path) {
filePath = "/";
else
filePath = parentPath(prefixToAdd + filePath);
addItem(itemsList, IT_PARENT_DIRECTORY, filePath, _("Parent directory"));
addItem(itemsList, IT_PARENT_DIRECTORY, filePath, HandlerUtils::toUtf8(_("Parent directory")));
}
// fill the content

View file

@ -40,35 +40,35 @@ void UploadFileHandler::handle(Client &client) {
// check that <path> is not an absolute root
if (path == "" || path == "/" || path == "\\") {
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("Invalid path!")));
return;
}
// check that <path> contains no '../'
if (HandlerUtils::hasForbiddenCombinations(path)) {
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("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!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("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!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("Invalid path!")));
return;
}
if (!node->exists()) {
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The parent directory doesn't exist!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("The parent directory doesn't exist!")));
return;
}
if (!node->isDirectory()) {
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't upload into a file!"));
HandlerUtils::setFilesManagerErrorMessageHandler(client, HandlerUtils::toUtf8(_("Can't upload into a file!")));
return;
}

View file

@ -171,8 +171,13 @@ bool HandlerUtils::permittedPath(const Common::String path) {
return hasPermittedPrefix(path) && !isBlacklisted(path);
}
Common::String HandlerUtils::toUtf8(const char *text) {
// FIXME: Convert the GUI to use UTF8
return Common::String(text);
}
void HandlerUtils::setMessageHandler(Client &client, Common::String message, Common::String redirectTo) {
Common::String response = "<html><head><title>ScummVM</title></head><body>{message}</body></html>";
Common::String response = "<html><head><title>ResidualVM</title><meta charset=\"utf-8\"/></head><body>{message}</body></html>";
// load stylish response page from the archive
Common::SeekableReadStream *const stream = getArchiveFile(INDEX_PAGE_NAME);
@ -194,7 +199,7 @@ void HandlerUtils::setFilesManagerErrorMessageHandler(Client &client, Common::St
message.c_str(),
client.queryParameter("ajax") == "true" ? "AJAX" : "",
"%2F", //that's encoded "/"
_("Back to the files manager")
toUtf8(_("Back to the files manager")).c_str()
),
redirectTo
);

View file

@ -41,6 +41,8 @@ public:
static bool hasPermittedPrefix(const Common::String &path);
static bool permittedPath(const Common::String path);
static Common::String toUtf8(const char*);
static void setMessageHandler(Client &client, Common::String message, Common::String redirectTo = "");
static void setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo = "");
};

View file

@ -111,7 +111,7 @@ class Reader {
uint32 bytesLeft() const;
public:
static const uint32 SUSPICIOUS_HEADERS_SIZE = 1024 * 1024; // 1 MB is really a lot
static const int32 SUSPICIOUS_HEADERS_SIZE = 1024 * 1024; // 1 MB is really a lot
Reader();
~Reader();

View file

@ -66,7 +66,7 @@ void UploadFileClientHandler::handle(Client *client) {
// fail on suspicious headers
if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) {
setErrorMessageHandler(*client, _("Invalid request: headers are too long!"));
setErrorMessageHandler(*client, HandlerUtils::toUtf8(_("Invalid request: headers are too long!")));
}
break;
@ -108,7 +108,7 @@ void UploadFileClientHandler::handleBlockHeaders(Client *client) {
// fail on suspicious headers
if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) {
setErrorMessageHandler(*client, _("Invalid request: headers are too long!"));
setErrorMessageHandler(*client, HandlerUtils::toUtf8(_("Invalid request: headers are too long!")));
}
// search for "upload_file" field
@ -134,11 +134,11 @@ void UploadFileClientHandler::handleBlockHeaders(Client *client) {
path += '/';
AbstractFSNode *originalNode = g_system->getFilesystemFactory()->makeFileNodePath(path + filename);
if (!HandlerUtils::permittedPath(originalNode->getPath())) {
setErrorMessageHandler(*client, _("Invalid path!"));
setErrorMessageHandler(*client, HandlerUtils::toUtf8(_("Invalid path!")));
return;
}
if (originalNode->exists()) {
setErrorMessageHandler(*client, _("There is a file with that name in the parent directory!"));
setErrorMessageHandler(*client, HandlerUtils::toUtf8(_("There is a file with that name in the parent directory!")));
return;
}
@ -152,7 +152,7 @@ void UploadFileClientHandler::handleBlockHeaders(Client *client) {
Common::DumpFile *f = new Common::DumpFile();
if (!f->open(originalNode->getPath(), true)) {
delete f;
setErrorMessageHandler(*client, _("Failed to upload the file!"));
setErrorMessageHandler(*client, HandlerUtils::toUtf8(_("Failed to upload the file!")));
return;
}
@ -181,7 +181,7 @@ void UploadFileClientHandler::handleBlockContent(Client *client) {
if (client->noMoreContent()) {
// if no file field was found - failure
if (_uploadedFiles == 0) {
setErrorMessageHandler(*client, _("No file was passed!"));
setErrorMessageHandler(*client, HandlerUtils::toUtf8(_("No file was passed!")));
} else {
setSuccessHandler(*client);
}
@ -199,9 +199,9 @@ void UploadFileClientHandler::setSuccessHandler(Client &client) {
client,
Common::String::format(
"%s<br/><a href=\"files?path=%s\">%s</a>",
_("Uploaded successfully!"),
HandlerUtils::toUtf8(_("Uploaded successfully!")).c_str(),
client.queryParameter("path").c_str(),
_("Back to parent directory")
HandlerUtils::toUtf8(_("Back to parent directory")).c_str()
),
(client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") +
LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path"))

View file

@ -374,7 +374,7 @@ void OSystem_Android::initBackend() {
gettimeofday(&_startTime, 0);
_mixer = new Audio::MixerImpl(this, _audio_sample_rate);
_mixer = new Audio::MixerImpl(_audio_sample_rate);
_mixer->setReady(true);
_timer_thread_exit = false;

View file

@ -10,15 +10,21 @@ amigaosdist: $(EXECUTABLE)
ifdef DIST_FILES_ENGINEDATA
cp $(DIST_FILES_ENGINEDATA) $(AMIGAOSPATH)/extras/
endif
cat ${srcdir}/README | sed -f ${srcdir}/dists/amiga/convertRM.sed > README.conv
ifdef DIST_FILES_NETWORKING
cp $(DIST_FILES_NETWORKING) $(AMIGAOSPATH)/extras/
endif
ifdef DIST_FILES_VKEYBD
cp $(DIST_FILES_VKEYBD) $(AMIGAOSPATH)/extras/
endif
cat ${srcdir}/README.md | sed -f ${srcdir}/dists/amiga/convertRM.sed > README.conv
# AmigaOS's shell is not happy with indented comments, thus don't do it.
# AREXX seems to have problems when ${srcdir} is '.'. It will break with a
# "Program not found" error. Therefore we copy the script to the cwd and
# remove it again, once it has finished.
cp ${srcdir}/dists/amiga/RM2AG.rx .
rx RM2AG.rx README.conv
cp ${srcdir}/dists/amiga/RM2AG.rexx .
rx RM2AG.rexx README.conv
cp README.guide $(AMIGAOSPATH)
rm RM2AG.rx
rm RM2AG.rexx
rm README.conv
rm README.guide
cp $(DIST_FILES_DOCS) $(AMIGAOSPATH)

View file

@ -34,6 +34,7 @@
#include "backends/taskbar/macosx/macosx-taskbar.h"
#include "backends/dialogs/macosx/macosx-dialogs.h"
#include "backends/platform/sdl/macosx/macosx_wrapper.h"
#include "backends/fs/posix/posix-fs.h"
#include "common/archive.h"
#include "common/config-manager.h"
@ -199,6 +200,19 @@ Common::String OSystem_MacOSX::getSystemLanguage() const {
#endif // USE_DETECTLANG
}
Common::String OSystem_MacOSX::getDefaultLogFileName() {
const char *prefix = getenv("HOME");
if (prefix == nullptr) {
return Common::String();
}
if (!Posix::assureDirectoryExists("Library/Logs", prefix)) {
return Common::String();
}
return Common::String(prefix) + "/Library/Logs/scummvm.log";
}
Common::String OSystem_MacOSX::getScreenshotsPath() {
Common::String path = ConfMan.get("screenshotpath");
if (path.empty())

View file

@ -50,6 +50,8 @@ public:
virtual Common::String getScreenshotsPath();
protected:
virtual Common::String getDefaultLogFileName();
// Override createAudioCDManager() to get our Mac-specific
// version.
virtual AudioCDManager *createAudioCDManager();

View file

@ -269,55 +269,29 @@ void OSystem_POSIX::addSysArchivesToSearchSet(Common::SearchSet &s, int priority
OSystem_SDL::addSysArchivesToSearchSet(s, priority);
}
Common::WriteStream *OSystem_POSIX::createLogFile() {
// Start out by resetting _logFilePath, so that in case
// of a failure, we know that no log file is open.
_logFilePath.clear();
const char *prefix = nullptr;
Common::String OSystem_POSIX::getDefaultLogFileName() {
Common::String logFile;
#ifdef MACOSX
prefix = getenv("HOME");
if (prefix == nullptr) {
return 0;
}
logFile = "Library/Logs";
#elif SAMSUNGTV
prefix = nullptr;
logFile = "/mtd_ram";
#else
// On POSIX systems we follow the XDG Base Directory Specification for
// where to store files. The version we based our code upon can be found
// over here: http://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html
prefix = getenv("XDG_CACHE_HOME");
const char *prefix = getenv("XDG_CACHE_HOME");
if (prefix == nullptr || !*prefix) {
prefix = getenv("HOME");
if (prefix == nullptr) {
return 0;
return Common::String();
}
logFile = ".cache/";
}
logFile += "residualvm/logs";
#endif
if (!Posix::assureDirectoryExists(logFile, prefix)) {
return 0;
return Common::String();
}
if (prefix) {
logFile = Common::String::format("%s/%s", prefix, logFile.c_str());
}
logFile += "/residualvm.log";
Common::FSNode file(logFile);
Common::WriteStream *stream = file.createWriteStream();
if (stream)
_logFilePath = logFile;
return stream;
return Common::String::format("%s/%s/residualvm.log", prefix, logFile.c_str());
}
bool OSystem_POSIX::displayLogFile() {

View file

@ -52,19 +52,8 @@ protected:
*/
Common::String _baseConfigName;
/**
* The path of the currently open log file, if any.
*
* @note This is currently a string and not an FSNode for simplicity;
* e.g. we don't need to include fs.h here, and currently the
* only use of this value is to use it to open the log file in an
* editor; for that, we need it only as a string anyway.
*/
Common::String _logFilePath;
virtual Common::String getDefaultConfigFileName();
virtual Common::WriteStream *createLogFile();
virtual Common::String getDefaultLogFileName();
Common::String getXdgUserDir(const char *name);

View file

@ -78,7 +78,6 @@ Common::String OSystem_PS3::getDefaultConfigFileName() {
return PREFIX "/" + _baseConfigName;
}
Common::WriteStream *OSystem_PS3::createLogFile() {
Common::FSNode file(PREFIX "/residualvm.log");
return file.createWriteStream();
Common::String OSystem_PS3::getDefaultLogFileName() {
return PREFIX "/residualvm.log";
}

View file

@ -40,8 +40,7 @@ protected:
Common::String _baseConfigName;
virtual Common::String getDefaultConfigFileName();
virtual Common::WriteStream *createLogFile();
virtual Common::String getDefaultLogFileName();
};
#endif

View file

@ -8,11 +8,15 @@ ps3pkg: $(EXECUTABLE)
cp $(DIST_FILES_THEMES) ps3pkg/USRDIR/data/
ifdef DIST_FILES_ENGINEDATA
cp $(DIST_FILES_ENGINEDATA) ps3pkg/USRDIR/data/
endif
ifdef DIST_FILES_NETWORKING
cp $(DIST_FILES_NETWORKING) ps3pkg/USRDIR/data/
endif
ifdef DIST_FILES_VKEYBD
cp $(DIST_FILES_VKEYBD) ps3pkg/USRDIR/data/
endif
cp $(DIST_FILES_DOCS) ps3pkg/USRDIR/doc/
cp $(srcdir)/dists/ps3/readme-ps3.md ps3pkg/USRDIR/doc/
cp $(srcdir)/backends/vkeybd/packs/vkeybd_default.zip ps3pkg/USRDIR/data/
cp $(srcdir)/backends/vkeybd/packs/vkeybd_small.zip ps3pkg/USRDIR/data/
cp $(srcdir)/dists/ps3/ICON0.PNG ps3pkg/
sfo.py -f $(srcdir)/dists/ps3/sfo.xml ps3pkg/PARAM.SFO
pkg.py --contentid UP0001-RESI12000_00-0000000000000000 ps3pkg/ residualvm-ps3.pkg

View file

@ -119,24 +119,14 @@ Common::String OSystem_RISCOS::getDefaultConfigFileName() {
return "/<Choices$Write>/ResidualVM/residualvm";
}
Common::WriteStream *OSystem_RISCOS::createLogFile() {
// Start out by resetting _logFilePath, so that in case
// of a failure, we know that no log file is open.
_logFilePath.clear();
Common::String OSystem_RISCOS::getDefaultLogFileName() {
Common::String logFile = "/<Choices$Write>/ResidualVM/Logs";
if (!Riscos::assureDirectoryExists(logFile)) {
return 0;
return Common::String();
}
logFile += "/residualvm";
Common::FSNode file(logFile);
Common::WriteStream *stream = file.createWriteStream();
if (stream)
_logFilePath = logFile;
return stream;
return logFile + "/residualvm";
}
#endif

View file

@ -37,18 +37,8 @@ public:
virtual void logMessage(LogMessageType::Type type, const char *message);
protected:
/**
* The path of the currently open log file, if any.
*
* @note This is currently a string and not an FSNode for simplicity;
* e.g. we don't need to include fs.h here, and currently the
* only use of this value is to use it to open the log file in an
* editor; for that, we need it only as a string anyway.
*/
Common::String _logFilePath;
virtual Common::String getDefaultConfigFileName();
virtual Common::WriteStream *createLogFile();
virtual Common::String getDefaultLogFileName();
};
#endif

View file

@ -1,7 +1,7 @@
ifeq ($(shell echo a | iconv --to-code=RISCOS-LATIN1//TRANSLIT >/dev/null 2>&1; echo $$?),0)
ENCODING=RISCOS-LATIN1//TRANSLIT
ifeq ($(shell echo a | iconv --to-code=RISCOS-LATIN1//IGNORE//TRANSLIT >/dev/null 2>&1; echo $$?),0)
ENCODING=RISCOS-LATIN1//IGNORE//TRANSLIT
else
ENCODING=ISO-8859-1//TRANSLIT
ENCODING=ISO-8859-1//IGNORE//TRANSLIT
endif
APP_NAME=!ResidualVM
@ -27,6 +27,9 @@ ifdef USE_OPENGL_SHADERS
mkdir -p $(APP_NAME)/data/shaders
cp $(DIST_FILES_SHADERS) $(APP_NAME)/data/shaders/
endif
ifdef DIST_FILES_VKEYBD
cp $(DIST_FILES_VKEYBD) $(APP_NAME)/data/
endif
ifdef DYNAMIC_MODULES
mkdir -p $(APP_NAME)/plugins
cp $(PLUGINS) $(APP_NAME)/plugins/

View file

@ -191,8 +191,6 @@ void OSystem_SDL::initBackend() {
sdlDriverName[0] = '\0';
SDL_VideoDriverName(sdlDriverName, maxNameLen);
#endif
// Using printf rather than debug() here as debug()/logging
// is not active by this point.
debug(1, "Using SDL Video Driver \"%s\"", sdlDriverName);
// ResidualVM specific code start
@ -384,7 +382,7 @@ void OSystem_SDL::initSDL() {
if (ConfMan.hasKey("disable_sdl_parachute"))
sdlFlags |= SDL_INIT_NOPARACHUTE;
// Initialize SDL (SDL Subsystems are initiliazed in the corresponding sdl managers)
// Initialize SDL (SDL Subsystems are initialized in the corresponding sdl managers)
if (SDL_Init(sdlFlags) == -1)
error("Could not initialize SDL: %s", SDL_GetError());
@ -504,6 +502,22 @@ void OSystem_SDL::logMessage(LogMessageType::Type type, const char *message) {
_logger->print(message);
}
Common::WriteStream *OSystem_SDL::createLogFile() {
// Start out by resetting _logFilePath, so that in case
// of a failure, we know that no log file is open.
_logFilePath.clear();
Common::String logFile = getDefaultLogFileName();
if (logFile.empty())
return nullptr;
Common::FSNode file(logFile);
Common::WriteStream *stream = file.createWriteStream();
if (stream)
_logFilePath = logFile;
return stream;
}
Common::String OSystem_SDL::getSystemLanguage() const {
#if defined(USE_DETECTLANG) && !defined(WIN32)
// Activating current locale settings
@ -538,18 +552,14 @@ Common::String OSystem_SDL::getSystemLanguage() const {
#endif // USE_DETECTLANG
}
bool OSystem_SDL::hasTextInClipboard() {
#if SDL_VERSION_ATLEAST(2, 0, 0)
bool OSystem_SDL::hasTextInClipboard() {
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();
// The string returned by SDL is in UTF-8. Convert to the
// current TranslationManager encoding or ISO-8859-1.
@ -566,13 +576,9 @@ Common::String OSystem_SDL::getTextFromClipboard() {
SDL_free(text);
return strText;
#else
return "";
#endif
}
bool OSystem_SDL::setTextInClipboard(const Common::String &text) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
// The encoding we need to use is UTF-8. Assume we currently have the
// current TranslationManager encoding or ISO-8859-1.
#ifdef USE_TRANSLATION
@ -586,10 +592,8 @@ bool OSystem_SDL::setTextInClipboard(const Common::String &text) {
return status == 0;
}
return SDL_SetClipboardText(text.c_str()) == 0;
#else
return false;
#endif
}
#endif
uint32 OSystem_SDL::getMillis(bool skipRecord) {
uint32 millis = SDL_GetTicks();

View file

@ -70,10 +70,12 @@ public:
virtual Common::String getSystemLanguage() const;
#if SDL_VERSION_ATLEAST(2, 0, 0)
// Clipboard
virtual bool hasTextInClipboard();
virtual Common::String getTextFromClipboard();
virtual bool setTextInClipboard(const Common::String &text);
#endif
virtual void setWindowCaption(const char *caption);
virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
@ -101,6 +103,16 @@ protected:
bool _initedSDLnet;
#endif
/**
* The path of the currently open log file, if any.
*
* @note This is currently a string and not an FSNode for simplicity;
* e.g. we don't need to include fs.h here, and currently the
* only use of this value is to use it to open the log file in an
* editor; for that, we need it only as a string anyway.
*/
Common::String _logFilePath;
/**
* Mixer manager that configures and setups SDL for
* the wrapped Audio::Mixer, the true mixer.
@ -138,7 +150,8 @@ protected:
virtual AudioCDManager *createAudioCDManager();
// Logging
virtual Common::WriteStream *createLogFile() { return 0; }
virtual Common::String getDefaultLogFileName() { return Common::String(); }
virtual Common::WriteStream *createLogFile();
Backends::Log::Log *_logger;
};

View file

@ -110,7 +110,7 @@ void OSystem_Win32::initBackend() {
#if defined(USE_SPARKLE)
// Initialize updates manager
_updateManager = new Win32UpdateManager();
_updateManager = new Win32UpdateManager((SdlWindow_Win32*)_window);
#endif
// Invoke parent implementation of this method
@ -278,31 +278,22 @@ Common::String OSystem_Win32::getDefaultConfigFileName() {
return configFile;
}
Common::WriteStream *OSystem_Win32::createLogFile() {
// Start out by resetting _logFilePath, so that in case
// of a failure, we know that no log file is open.
_logFilePath.clear();
Common::String OSystem_Win32::getDefaultLogFileName() {
char logFile[MAXPATHLEN];
// Use the Application Data directory of the user profile.
if (SHGetFolderPathFunc(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, logFile) == S_OK) {
strcat(logFile, "\\ResidualVM");
CreateDirectory(logFile, NULL);
strcat(logFile, "\\Logs");
CreateDirectory(logFile, NULL);
strcat(logFile, "\\residualvm.log");
Common::FSNode file(logFile);
Common::WriteStream *stream = file.createWriteStream();
if (stream)
_logFilePath= logFile;
return stream;
} else {
if (SHGetFolderPathFunc(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, logFile) != S_OK) {
warning("Unable to access application data directory");
return 0;
return Common::String();
}
strcat(logFile, "\\ResidualVM");
CreateDirectory(logFile, NULL);
strcat(logFile, "\\Logs");
CreateDirectory(logFile, NULL);
strcat(logFile, "\\residualvm.log");
return logFile;
}
namespace {

View file

@ -46,20 +46,10 @@ public:
virtual Common::String getScreenshotsPath();
protected:
/**
* The path of the currently open log file, if any.
*
* @note This is currently a string and not an FSNode for simplicity;
* e.g. we don't need to include fs.h here, and currently the
* only use of this value is to use it to open the log file in an
* editor; for that, we need it only as a string anyway.
*/
Common::String _logFilePath;
virtual Common::String getDefaultConfigFileName();
virtual Common::WriteStream *createLogFile();
virtual Common::String getDefaultLogFileName();
// Override createAudioCDManager() to get our Mac-specific
// Override createAudioCDManager() to get our Windows-specific
// version.
virtual AudioCDManager *createAudioCDManager();

View file

@ -3,12 +3,12 @@
#
# ResidualVM: Added DIST_FILES_SHADERS
residualvmwinres.o: $(srcdir)/icons/residualvm.ico $(DIST_FILES_THEMES) $(DIST_FILES_NETWORKING) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_SHADERS) $(srcdir)/dists/residualvm.rc
$(QUIET_WINDRES)$(WINDRES) -DHAVE_CONFIG_H $(WINDRESFLAGS) $(DEFINES) -I. -I$(srcdir) $(srcdir)/dists/residualvm.rc residualvmwinres.o
dists/residualvm.o: $(srcdir)/icons/residualvm.ico $(DIST_FILES_THEMES) $(DIST_FILES_NETWORKING) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_SHADERS) config.h $(srcdir)/base/internal_version.h
# Special target to create a win32 snapshot binary (for Inno Setup)
win32dist: all
mkdir -p $(WIN32PATH)
mkdir -p $(WIN32PATH)/graphics
mkdir -p $(WIN32PATH)/doc
$(STRIP) $(EXECUTABLE) -o $(WIN32PATH)/$(EXECUTABLE)
cp $(srcdir)/AUTHORS $(WIN32PATH)/AUTHORS.txt

View file

@ -23,6 +23,8 @@
#ifndef PLATFORM_SDL_WIN32_WRAPPER_H
#define PLATFORM_SDL_WIN32_WRAPPER_H
#include "common/scummsys.h"
HRESULT SHGetFolderPathFunc(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPSTR pszPath);
// Helper functions

View file

@ -63,7 +63,9 @@ DefaultSaveFileManager::DefaultSaveFileManager(const Common::String &defaultSave
void DefaultSaveFileManager::checkPath(const Common::FSNode &dir) {
clearError();
if (!dir.exists()) {
setError(Common::kPathDoesNotExist, "The savepath '"+dir.getPath()+"' does not exist");
if (!dir.createDirectory()) {
setError(Common::kPathDoesNotExist, "Failed to create directory '"+dir.getPath()+"'");
}
} else if (!dir.isDirectory()) {
setError(Common::kPathNotDirectory, "The savepath '"+dir.getPath()+"' is not a directory");
}
@ -169,6 +171,8 @@ Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const Common::String
// Open the file for saving.
Common::WriteStream *const sf = fileNode.createWriteStream();
if (!sf)
return nullptr;
Common::OutSaveFile *const result = new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(sf) : sf);
// Add file to cache now that it exists.
@ -358,7 +362,10 @@ void DefaultSaveFileManager::saveTimestamps(Common::HashMap<Common::String, uint
}
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);
uint32 v = i->_value;
if (v < 1) v = 1; // 0 timestamp is treated as EOF up there, so we should never save zeros
Common::String data = i->_key + Common::String::format(" %u\n", v);
if (f.write(data.c_str(), data.size()) != data.size()) {
warning("DefaultSaveFileManager: failed to write timestamps data into '%s'", filename.c_str());
return;

View file

@ -38,9 +38,6 @@
#include "common/savefile.h"
#include "common/textconsole.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
POSIXSaveFileManager::POSIXSaveFileManager() {
@ -133,69 +130,4 @@ POSIXSaveFileManager::POSIXSaveFileManager() {
#endif
}
void POSIXSaveFileManager::checkPath(const Common::FSNode &dir) {
const Common::String path = dir.getPath();
clearError();
struct stat sb;
// Check whether the dir exists
if (stat(path.c_str(), &sb) == -1) {
// The dir does not exist, or stat failed for some other reason.
// If the problem was that the path pointed to nothing, try
// to create the dir (ENOENT case).
switch (errno) {
case EACCES:
setError(Common::kWritePermissionDenied, "Search or write permission denied: "+path);
break;
case ELOOP:
setError(Common::kUnknownError, "Too many symbolic links encountered while traversing the path: "+path);
break;
case ENAMETOOLONG:
setError(Common::kUnknownError, "The path name is too long: "+path);
break;
case ENOENT:
if (mkdir(path.c_str(), 0755) != 0) {
// mkdir could fail for various reasons: The parent dir doesn't exist,
// or is not writeable, the path could be completly bogus, etc.
warning("mkdir for '%s' failed", path.c_str());
perror("mkdir");
switch (errno) {
case EACCES:
setError(Common::kWritePermissionDenied, "Search or write permission denied: "+path);
break;
case EMLINK:
setError(Common::kUnknownError, "The link count of the parent directory would exceed {LINK_MAX}: "+path);
break;
case ELOOP:
setError(Common::kUnknownError, "Too many symbolic links encountered while traversing the path: "+path);
break;
case ENAMETOOLONG:
setError(Common::kUnknownError, "The path name is too long: "+path);
break;
case ENOENT:
setError(Common::kPathDoesNotExist, "A component of the path does not exist, or the path is an empty string: "+path);
break;
case ENOTDIR:
setError(Common::kPathDoesNotExist, "A component of the path prefix is not a directory: "+path);
break;
case EROFS:
setError(Common::kWritePermissionDenied, "The parent directory resides on a read-only file system:"+path);
break;
}
}
break;
case ENOTDIR:
setError(Common::kPathDoesNotExist, "A component of the path prefix is not a directory: "+path);
break;
}
} else {
// So stat() succeeded. But is the path actually pointing to a directory?
if (!S_ISDIR(sb.st_mode)) {
setError(Common::kPathDoesNotExist, "The given savepath is not a directory: "+path);
}
}
}
#endif

View file

@ -35,14 +35,6 @@
class POSIXSaveFileManager : public DefaultSaveFileManager {
public:
POSIXSaveFileManager();
protected:
/**
* Checks the given path for read access, existence, etc.
* In addition, tries to create a missing savedir, if possible.
* Sets the internal error and error message accordingly.
*/
virtual void checkPath(const Common::FSNode &dir);
};
#endif

View file

@ -36,7 +36,7 @@ struct TimerSlot {
TimerSlot *next;
TimerSlot() : refCon(0), interval(0), nextFireTime(0), nextFireTimeMicro(0), next(0) {}
TimerSlot() : callback(nullptr), refCon(nullptr), interval(0), nextFireTime(0), nextFireTimeMicro(0), next(nullptr) {}
};
void insertPrioQueue(TimerSlot *head, TimerSlot *newSlot) {

View file

@ -83,9 +83,6 @@ MacOSXUpdateManager::MacOSXUpdateManager() {
// Set the target of the new menu item
[updateMenuItem setTarget:sparkleUpdater];
// Finally give up our references to the objects
[menuItem release];
if (!ConfMan.hasKey("updates_check")
|| ConfMan.getInt("updates_check") == Common::UpdateManager::kUpdateIntervalNotSupported) {
setAutomaticallyChecksForUpdates(kUpdateStateDisabled);

View file

@ -27,10 +27,12 @@
#include "backends/updates/win32/win32-updates.h"
#ifdef USE_SPARKLE
#include "backends/platform/sdl/win32/win32-window.h"
#include "common/translation.h"
#include "common/config-manager.h"
#include <time.h>
#include <windows.h>
#include <winsparkle.h>
/**
@ -50,10 +52,15 @@
* https://winsparkle.org/
*
*/
Win32UpdateManager::Win32UpdateManager() {
const char *appcastUrl = "https://www.residualvm.org/appcasts/macosx/release.xml";
win_sparkle_set_appcast_url(appcastUrl);
static SdlWindow_Win32 *_window;
Win32UpdateManager::Win32UpdateManager(SdlWindow_Win32 *window) {
_window = window;
const char *appcastUrl = "https://www.scummvm.org/appcasts/macosx/release.xml";
win_sparkle_set_appcast_url(appcastUrl);
win_sparkle_set_can_shutdown_callback(canShutdownCallback);
win_sparkle_set_shutdown_request_callback(shutdownRequestCallback);
win_sparkle_init();
if (!ConfMan.hasKey("updates_check")
@ -129,4 +136,20 @@ bool Win32UpdateManager::getLastUpdateCheckTimeAndDate(TimeDate &t) {
return true;
}
// WinSparkle calls this to ask if we can shut down.
// At this point the download has completed, the user has
// selected Install Update, and the installer has started.
// This callback runs on a non-main thread.
int Win32UpdateManager::canShutdownCallback() {
return true;
}
// WinSparkle calls this to request that we shut down.
// This callback runs on a non-main thread so we post
// a WM_CLOSE message to our window so that we exit
// cleanly, as opposed to calling g_system->quit().
void Win32UpdateManager::shutdownRequestCallback() {
PostMessage(_window->getHwnd(), WM_CLOSE, 0, 0);
}
#endif

Some files were not shown because too many files have changed in this diff Show more