Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef EMSCRIPTEN
#include "backends/fs/emscripten/cloud-fs.h"
#include "backends/cloud/downloadrequest.h"
#include "backends/fs/fs-factory.h"
#include "backends/fs/posix/posix-fs.h"
#include "backends/fs/posix/posix-iostream.h"
#include "common/system.h"
Common::HashMap<Common::String, AbstractFSList> CloudFilesystemNode::_cloudFolders = Common::HashMap<Common::String, AbstractFSList>();
CloudFilesystemNode::CloudFilesystemNode(const Common::String &p) : _isDirectory(false), _isValid(false), _path(p), _storageFileId(nullptr) {
debug(5, "CloudFilesystemNode::CloudFilesystemNode(Common::String %s)", p.c_str());
assert(p.size() > 0);
// Normalize the path (that is, remove unneeded slashes etc.)
_path = Common::normalizePath(_path, '/');
_displayName = Common::lastPathComponent(_path, '/');
if (_path == CLOUD_FS_PATH) { // need special case for handling the root of the cloud-filesystem
_displayName = "[" + Common::lastPathComponent(_path, '/') + "]";
_isDirectory = true;
_isValid = true;
return;
} else { // we need to peek in the parent folder to see if file exists and if it's a directory
AbstractFSNode *parent = getParent();
AbstractFSList tmp = AbstractFSList();
parent->getChildren(tmp, Common::FSNode::kListAll, true);
for (AbstractFSList::iterator i = tmp.begin(); i != tmp.end(); ++i) {
CloudFilesystemNode *child = (CloudFilesystemNode *)*i;
if (child->getPath() == _path) {
_isDirectory = child->isDirectory();
_isValid = true;
_storageFileId = child->_storageFileId;
break;
}
}
return;
}
}
AbstractFSNode *CloudFilesystemNode::getChild(const Common::String &n) const {
assert(!_path.empty());
assert(_isDirectory);
// Make sure the string contains no slashes
assert(!n.contains('/'));
// We assume here that _path is already normalized (hence don't bother to call
// Common::normalizePath on the final path).
Common::String newPath(_path);
if (_path.lastChar() != '/')
newPath += '/';
newPath += n;
return makeNode(newPath);
}
void CloudFilesystemNode::directoryListedCallback(const Cloud::Storage::ListDirectoryResponse &response) {
debug(5, "CloudFilesystemNode::directoryListedCallback %s", _path.c_str());
Common::Array<Cloud::StorageFile> storageFiles = response.value;
AbstractFSList *dirList = new AbstractFSList();
for (Common::Array<Cloud::StorageFile>::iterator i = storageFiles.begin(); i != storageFiles.end(); ++i) {
Cloud::StorageFile storageFile = *i;
CloudFilesystemNode *file_node = new CloudFilesystemNode();
file_node->_isDirectory = storageFile.isDirectory();
file_node->_path = _path + "/" + storageFile.name();
file_node->_isValid = true;
file_node->_displayName = "" + storageFile.name();
file_node->_storageFileId = storageFile.id();
dirList->push_back(file_node);
}
_cloudFolders[_path] = *dirList;
}
void CloudFilesystemNode::directoryListedErrorCallback(const Networking::ErrorResponse &_error) {
// _workingRequest = nullptr; // TODO: HANDLE THIS SOMEWHERE
error("Response %ld: %s", _error.httpResponseCode, _error.response.c_str());
}
void CloudFilesystemNode::fileDownloadedCallback(const Cloud::Storage::BoolResponse &response) {
// _workingRequest = nullptr; // TODO: HANDLE THIS SOMEWHERE
debug(5, "CloudFilesystemNode::fileDownloadedCallback %s", _path.c_str());
}
void CloudFilesystemNode::fileDownloadedErrorCallback(const Networking::ErrorResponse &_error) {
// _workingRequest = nullptr; // TODO: HANDLE THIS SOMEWHERE
error("Response %ld: %s", _error.httpResponseCode, _error.response.c_str());
}
bool CloudFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, bool hidden) const {
assert(_isDirectory);
if (!_cloudFolders.contains(_path)) {
debug(5, "CloudFilesystemNode::getChildren Fetching Children: %s", _path.c_str());
Common::String _cloud_path = _path.substr(sizeof(CLOUD_FS_PATH), _path.size() - sizeof(CLOUD_FS_PATH));
CloudMan.listDirectory(
_cloud_path,
new Common::Callback<CloudFilesystemNode, const Cloud::Storage::ListDirectoryResponse &>((CloudFilesystemNode *)this, &CloudFilesystemNode::directoryListedCallback),
new Common::Callback<CloudFilesystemNode, const Networking::ErrorResponse &>((CloudFilesystemNode *)this, &CloudFilesystemNode::directoryListedErrorCallback),
false);
while (!_cloudFolders.contains(_path)) {
g_system->delayMillis(10);
}
debug(5, "CloudFilesystemNode::getChildren %s size %u", _path.c_str(), _cloudFolders[_path].size());
}
for (AbstractFSList::iterator i = _cloudFolders[_path].begin(); i != _cloudFolders[_path].end(); ++i) {
// TODO: Respect mode and hidden getChildren parameters
CloudFilesystemNode *node = (CloudFilesystemNode *)*i;
// we need to copy node here as FSNode will take ownership of the pointer and destroy it after use
myList.push_back(new CloudFilesystemNode(*node));
}
return true;
}
AbstractFSNode *CloudFilesystemNode::getParent() const {
if (_path == "/")
return 0; // The filesystem root has no parent
const char *start = _path.c_str();
const char *end = start + _path.size();
// Strip of the last component. We make use of the fact that at this
// point, _path is guaranteed to be normalized
while (end > start && *(end - 1) != '/')
end--;
if (end == start) {
// This only happens if we were called with a relative path, for which
// there simply is no parent.
// TODO: We could also resolve this by assuming that the parent is the
// current working directory, and returning a node referring to that.
return 0;
}
Common::String _parent_path = Common::normalizePath(Common::String(start, end), '/');
FilesystemFactory *factory = g_system->getFilesystemFactory();
return factory->makeFileNodePath(_parent_path);
}
Common::SeekableReadStream *CloudFilesystemNode::createReadStream() {
debug(5, "CloudFilesystemNode::createReadStream() %s", _path.c_str());
Common::String fsCachePath = Common::normalizePath("/.cache/" + _path, '/');
POSIXFilesystemNode *cacheFile = new POSIXFilesystemNode(fsCachePath);
if (!cacheFile->exists()) {
Cloud::Storage *_storage = CloudMan.getCurrentStorage();
Networking::Request *_workingRequest = _storage->downloadById(
_storageFileId,
Common::Path(fsCachePath),
new Common::Callback<CloudFilesystemNode, const Cloud::Storage::BoolResponse &>(this, &CloudFilesystemNode::fileDownloadedCallback),
new Common::Callback<CloudFilesystemNode, const Networking::ErrorResponse &>(this, &CloudFilesystemNode::fileDownloadedErrorCallback));
while (_workingRequest->state() != Networking::RequestState::FINISHED) {
g_system->delayMillis(10);
}
debug(5, "CloudFilesystemNode::createReadStream() file written %s", fsCachePath.c_str());
}
return PosixIoStream::makeFromPath(fsCachePath, StdioStream::WriteMode_Read);
}
Common::SeekableWriteStream *CloudFilesystemNode::createWriteStream(bool atomic) {
return 0;
}
bool CloudFilesystemNode::createDirectory() {
return false;
}
bool CloudFilesystemNode::exists() const {
return _isValid;
}
bool CloudFilesystemNode::isReadable() const {
return exists();
}
bool CloudFilesystemNode::isWritable() const {
return false;
}
#endif // #if defined(EMSCRIPTEN)

View File

@@ -0,0 +1,95 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CLOUD_FILESYSTEM_H
#define CLOUD_FILESYSTEM_H
#include "backends/fs/abstract-fs.h"
#ifdef USE_CLOUD
#include "backends/cloud/cloudmanager.h"
#include "backends/cloud/storage.h"
#include "backends/cloud/storagefile.h"
#include "backends/networking/http/connectionmanager.h"
#include "backends/networking/http/httpjsonrequest.h"
#include "backends/networking/http/request.h"
#endif
#include "backends/fs/posix/posix-fs.h"
/**
* Implementation of the ScummVM file system API based on POSIX.
*
* Parts of this class are documented in the base interface class, AbstractFSNode.
*/
class CloudFilesystemNode : public AbstractFSNode {
#define CLOUD_FS_PATH "/cloud"
protected:
static Common::HashMap<Common::String, AbstractFSList> _cloudFolders;
Common::String _displayName;
Common::String _path;
Common::String _storageFileId;
bool _isDirectory;
bool _isValid;
/**
* Plain constructor, for internal use only (hence protected).
*/
CloudFilesystemNode() : _isDirectory(false), _isValid(false), _storageFileId(nullptr) {}
virtual AbstractFSNode *makeNode(const Common::String &path) const {
return new CloudFilesystemNode(path);
}
/**
* Callbacks for network calls (file download and directory listing)
*/
void directoryListedCallback(const Cloud::Storage::ListDirectoryResponse &response);
void directoryListedErrorCallback(const Networking::ErrorResponse &error);
void fileDownloadedCallback(const Cloud::Storage::BoolResponse &response);
void fileDownloadedErrorCallback(const Networking::ErrorResponse &error);
public:
/**
* Creates a CloudFilesystemNode for a given path.
*
* @param path the path the new node should point to.
*/
CloudFilesystemNode(const Common::String &path);
bool exists() const override;
Common::U32String getDisplayName() const override { return _displayName; }
Common::String getName() const override { return _displayName; }
Common::String getPath() const override { return _path; }
bool isDirectory() const override { return _isDirectory; }
bool isReadable() const override;
bool isWritable() const override;
AbstractFSNode *getChild(const Common::String &n) const override;
bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const override;
AbstractFSNode *getParent() const override;
Common::SeekableReadStream *createReadStream() override;
Common::SeekableWriteStream *createWriteStream(bool atomic) override;
bool createDirectory() override;
};
#endif

View File

@@ -0,0 +1,95 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef EMSCRIPTEN
#define FORBIDDEN_SYMBOL_EXCEPTION_getenv
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
#include "backends/fs/emscripten/emscripten-fs-factory.h"
#include "backends/fs/emscripten/emscripten-posix-fs.h"
#include "backends/fs/emscripten/http-fs.h"
#include "common/debug.h"
#ifdef USE_CLOUD
#include "backends/fs/emscripten/cloud-fs.h"
#endif
#include <emscripten.h>
EM_ASYNC_JS(void, _initSettings, (const char *pathPtr), {
try {
const path = UTF8ToString(pathPtr);
const settingsPath = path + "/scummvm.ini";
// Mount the filesystem
FS.mount(IDBFS, { autoPersist: true }, path);
// Sync the filesystem
await new Promise((resolve, reject) => {
FS.syncfs(true, (err) => err ? reject(err) : resolve());
});
// Check if settings file exists and download if needed
if (!FS.analyzePath(settingsPath).exists) {
const response = await fetch("scummvm.ini");
if (response.ok) {
const text = await response.text();
FS.writeFile(settingsPath, text);
}
}
} catch (err) {
console.error("Error initializing files:", err);
alert("Error initializing files: " + err);
throw err;
}
});
EmscriptenFilesystemFactory::EmscriptenFilesystemFactory() {
_initSettings(getenv("HOME"));
_httpNodes = new Common::HashMap<Common::String, HTTPFilesystemNode *>();
}
AbstractFSNode *EmscriptenFilesystemFactory::makeCurrentDirectoryFileNode() const {
// getcwd() defaults to root on emscripten and ScummVM doesn't use setcwd()
return makeRootFileNode();
}
AbstractFSNode *EmscriptenFilesystemFactory::makeRootFileNode() const {
return new EmscriptenPOSIXFilesystemNode("/");
}
AbstractFSNode *EmscriptenFilesystemFactory::makeFileNodePath(const Common::String &path) const {
debug(5, "EmscriptenFilesystemFactory::makeFileNodePath(%s)", path.c_str());
assert(!path.empty());
if (path.hasPrefix(DATA_PATH)) {
if (!_httpNodes->contains(path)) {
// finding a node by path requires a http request to the server, so we cache the nodes
_httpNodes->setVal(path, new HTTPFilesystemNode(path));
}
return new HTTPFilesystemNode(*(_httpNodes->getVal(path)));
#ifdef USE_CLOUD
} else if (path.hasPrefix(CLOUD_FS_PATH) && CloudMan.isStorageEnabled()) {
return new CloudFilesystemNode(path);
#endif
} else {
return new EmscriptenPOSIXFilesystemNode(path);
}
}
#endif

View File

@@ -0,0 +1,45 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef EMSCRIPTEN_FILESYSTEM_FACTORY_H
#define EMSCRIPTEN_FILESYSTEM_FACTORY_H
#include "backends/fs/emscripten/http-fs.h"
#include "backends/fs/fs-factory.h"
#include "common/singleton.h"
/**
* Creates POSIXFilesystemNode objects.
*
* Parts of this class are documented in the base interface class, FilesystemFactory.
*/
class EmscriptenFilesystemFactory : public FilesystemFactory {
public:
EmscriptenFilesystemFactory();
AbstractFSNode *makeRootFileNode() const override;
AbstractFSNode *makeCurrentDirectoryFileNode() const override;
AbstractFSNode *makeFileNodePath(const Common::String &path) const override;
private:
Common::HashMap<Common::String, HTTPFilesystemNode *> *_httpNodes;
};
#endif /*EMSCRIPTEN_FILESYSTEM_FACTORY_H*/

View File

@@ -0,0 +1,58 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_getenv
#include <stdio.h>
#include "backends/fs/emscripten/emscripten-fs-factory.h"
#include "backends/fs/emscripten/emscripten-posix-fs.h"
#include "backends/fs/emscripten/http-fs.h"
#include "backends/fs/posix/posix-fs.h"
#include "backends/fs/posix/posix-iostream.h"
#include "common/system.h"
#ifdef USE_CLOUD
#include "backends/cloud/cloudmanager.h"
#include "backends/fs/emscripten/cloud-fs.h"
#endif
AbstractFSNode *EmscriptenPOSIXFilesystemNode::makeNode(const Common::String &path) const {
return g_system->getFilesystemFactory()->makeFileNodePath(path);
}
EmscriptenPOSIXFilesystemNode::EmscriptenPOSIXFilesystemNode(const Common::String &path) : POSIXFilesystemNode(path) {}
EmscriptenPOSIXFilesystemNode::EmscriptenPOSIXFilesystemNode() : POSIXFilesystemNode() {}
bool EmscriptenPOSIXFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, bool hidden) const {
if (_path == "/") {
HTTPFilesystemNode *data_entry = new HTTPFilesystemNode(DATA_PATH);
myList.push_back(data_entry);
#ifdef USE_CLOUD
if (CloudMan.isStorageEnabled()) {
CloudFilesystemNode *cloud_entry = new CloudFilesystemNode(CLOUD_FS_PATH);
myList.push_back(cloud_entry);
}
#endif
}
return POSIXFilesystemNode::getChildren(myList, mode, hidden);
}

View File

@@ -0,0 +1,36 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef EMSCRIPTEN_FILESYSTEM_H
#define EMSCRIPTEN_FILESYSTEM_H
#include "backends/fs/posix/posix-fs.h"
class EmscriptenPOSIXFilesystemNode : public POSIXFilesystemNode {
public:
EmscriptenPOSIXFilesystemNode(const Common::String &path);
EmscriptenPOSIXFilesystemNode();
bool getChildren(AbstractFSList &myList, ListMode mode, bool hidden) const override;
AbstractFSNode *makeNode(const Common::String &path) const override;
};
#endif

View File

@@ -0,0 +1,272 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
// Disable symbol overrides so that we can use system headers.
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
#define FORBIDDEN_SYMBOL_EXCEPTION_getenv
#ifdef EMSCRIPTEN
#include "backends/fs/emscripten/http-fs.h"
#include "backends/cloud/downloadrequest.h"
#include "backends/fs/fs-factory.h"
#include "backends/fs/posix/posix-fs.h"
#include "backends/fs/posix/posix-iostream.h"
#include "common/debug.h"
#include "common/formats/json.h"
#include <emscripten.h>
HTTPFilesystemNode::HTTPFilesystemNode(const Common::String &path, const Common::String &displayName, const Common::String &baseUrl, bool isValid, bool isDirectory, int size) : _path(path), _displayName(displayName), _url(baseUrl), _isValid(isValid), _isDirectory(isDirectory), _size(size) {
debug(5, "HTTPFilesystemNode::HTTPFilesystemNode(%s, %s)", path.c_str(), baseUrl.c_str());
assert(path.size() > 0);
assert(isDirectory || size >= 0 || !isValid);
_children = new AbstractFSList();
}
HTTPFilesystemNode::HTTPFilesystemNode(const Common::String &p) : _path(p), _isValid(false), _isDirectory(false), _size(-1) {
debug(5, "HTTPFilesystemNode::HTTPFilesystemNode(%s)", p.c_str());
assert(p.size() > 0);
_children = new AbstractFSList();
// Normalize the path (that is, remove unneeded slashes etc.)
_path = Common::normalizePath(_path, '/');
_displayName = Common::lastPathComponent(_path, '/');
if (_path == DATA_PATH) { // need special case for handling the root of the http-filesystem
_isDirectory = true;
_isValid = true;
_url = _path;
} else { // we need to peek in the parent folder to see if the node exists and if it's a directory
AbstractFSNode *parent = getParent();
AbstractFSList tmp = AbstractFSList();
parent->getChildren(tmp, Common::FSNode::kListAll, true);
for (AbstractFSList::iterator i = tmp.begin(); i != tmp.end(); ++i) {
AbstractFSNode *child = *i;
if (child->getPath() == _path) {
_isDirectory = child->isDirectory();
_isValid = true;
_url = ((HTTPFilesystemNode *)child)->_url;
_size = ((HTTPFilesystemNode *)child)->_size;
break;
}
}
}
assert(_isDirectory || _size >= 0 || !_isValid);
debug(5, "HTTPFilesystemNode::HTTPFilesystemNode(%s) - %s isValid %s isDirectory %s", p.c_str(), _url.c_str(), _isValid ? "True" : "false", _isDirectory ? "True" : "false");
}
AbstractFSNode *HTTPFilesystemNode::getChild(const Common::String &n) const {
assert(!_path.empty());
assert(_isDirectory);
// Make sure the string contains no slashes
assert(!n.contains('/'));
// We assume here that _path is already normalized (hence don't bother to call
// Common::normalizePath on the final path).
Common::String newPath(_path);
if (_path.lastChar() != '/')
newPath += '/';
newPath += n;
return makeNode(newPath);
}
EM_ASYNC_JS(char *, _httpFsFetchIndex, (const char *url), {
globalThis['httpFsIndexCache'] = globalThis['httpFsIndexCache'] || {};
returnString = "";
url = UTF8ToString(url);
console.debug("Downloading %s", url);
if (globalThis['httpFsIndexCache'][url]) {
console.debug("Cache hit for %s", url);
returnString = globalThis['httpFsIndexCache'][url];
} else {
try {
const response = await fetch(url);
if (response.ok) {
returnString = await response.text();
globalThis['httpFsIndexCache'][url] = returnString;
}
} catch (error) {
console.error(error);
}
}
var size = lengthBytesUTF8(returnString) + 1;
var ret = Module._malloc(size);
stringToUTF8Array(returnString, HEAP8, ret, size);
return ret;
});
EM_ASYNC_JS(bool, _httpFsFetchFile, (const char *url, byte *dataPtr, int dataSize), {
returnBytes = new Uint8Array();
url = UTF8ToString(url);
console.debug("Downloading %s", url);
try {
const response = await fetch(url);
if (response.ok) {
returnBytes = await response.bytes();
if (returnBytes.length == dataSize) {
Module.writeArrayToMemory(returnBytes, dataPtr);
return true;
}
} else {
console.error("HTTPFilesystemNode::_httpFsFetchFile: %s", response.statusText);
}
} catch (error) {
console.error(error);
}
return false;
});
bool HTTPFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, bool hidden) const {
if (!_isValid) {
return false;
}
assert(_isDirectory);
if (_children->size() == 0) {
// if we don't have a children list yet, we need to fetch it from the server
debug(5, "HTTPFilesystemNode::getChildren Fetching Children: %s at %s", _path.c_str(), _url.c_str());
Common::String url = _url + "/index.json";
char *response = _httpFsFetchIndex(url.c_str());
if (strcmp(response, "") == 0) {
return false;
}
Common::JSONObject jsonObj = Common::JSON::parse(response)->asObject();
// add dummy element so we know that we fetched the list
_children->push_back(new HTTPFilesystemNode(_path, ".", _url, false, false, 0));
for (typename Common::HashMap<Common::String, Common::JSONValue *>::iterator i = jsonObj.begin(); i != jsonObj.end(); ++i) {
Common::String name = i->_key;
bool isDir = false;
int size = -1;
Common::String baseUrl = _url + "/" + name;
if (i->_value->isObject()) {
isDir = true;
if (i->_value->asObject().contains("baseUrl")) {
debug(5, "HTTPFilesystemNode::directoryListedCallback - Directory with baseUrl %s", name.c_str());
baseUrl = i->_value->asObject()["baseUrl"]->asString();
}
} else if (i->_value->isIntegerNumber()) {
size = i->_value->asIntegerNumber();
}
HTTPFilesystemNode *file_node = new HTTPFilesystemNode(_path + "/" + name, name, baseUrl, true, isDir, size);
_children->push_back(file_node);
}
}
for (AbstractFSList::iterator i = (*_children).begin(); i != (*_children).end(); ++i) {
HTTPFilesystemNode *node = (HTTPFilesystemNode *)*i;
if (node->_isValid && (mode == Common::FSNode::kListAll ||
(mode == Common::FSNode::kListFilesOnly && !node->_isDirectory) ||
(mode == Common::FSNode::kListDirectoriesOnly && node->_isDirectory))) {
// we need to copy node here as FSNode will take ownership of the pointer and destroy it after use
HTTPFilesystemNode *file_node = new HTTPFilesystemNode(*node);
myList.push_back(file_node);
} else {
debug(5, "HTTPFilesystemNode::getChildren - skipping %s", node->_path.c_str());
}
}
return true;
}
AbstractFSNode *HTTPFilesystemNode::getParent() const {
if (_path == "/")
return 0; // The filesystem root has no parent
const char *start = _path.c_str();
const char *end = start + _path.size();
// Strip of the last component. We make use of the fact that at this
// point, _path is guaranteed to be normalized
while (end > start && *(end - 1) != '/')
end--;
if (end == start) {
// This only happens if we were called with a relative path, for which
// there simply is no parent.
// TODO: We could also resolve this by assuming that the parent is the
// current working directory, and returning a node referring to that.
return 0;
}
Common::String _parent_path = Common::normalizePath(Common::String(start, end), '/');
FilesystemFactory *factory = g_system->getFilesystemFactory();
return factory->makeFileNodePath(_parent_path);
}
Common::SeekableReadStream *HTTPFilesystemNode::createReadStream() {
debug(5, "*HTTPFilesystemNode::createReadStream() %s (size %d) ", _path.c_str(), _size);
Common::String fsCachePath = Common::normalizePath("/.cache/" + _path, '/');
POSIXFilesystemNode *cacheFile = new POSIXFilesystemNode(fsCachePath);
// todo: this should not be cached on the filesystem, but in memory
// and support range requests
// port https://github.com/emscripten-core/emscripten/blob/main/src/lib/libwasmfs_fetch.js over
if (!cacheFile->exists() && _size > 0) {
byte *buffer = new byte[_size];
bool success = _httpFsFetchFile(_url.c_str(), buffer, _size);
if (success) {
Common::DumpFile *_localFile = new Common::DumpFile();
if (!_localFile->open(Common::Path(fsCachePath), true)) {
error("Storage: unable to open file to download into");
return 0;
}
debug(5, "HTTPFilesystemNode::createReadStream() file downloaded %s", _path.c_str());
_localFile->write(buffer, _size);
_localFile->close();
free(buffer);
} else {
warning("Storage: unable to download file %s", _url.c_str());
free(buffer);
return 0;
}
} else if (_size == 0) {
debug(5, "HTTPFilesystemNode::createReadStream() file empty %s", _path.c_str());
Common::DumpFile *_localFile = new Common::DumpFile();
if (!_localFile->open(Common::Path(fsCachePath), true)) {
warning("Storage: unable to open file to download into");
return 0;
}
_localFile->close();
}
return PosixIoStream::makeFromPath(fsCachePath, StdioStream::WriteMode_Read);
}
Common::SeekableWriteStream *HTTPFilesystemNode::createWriteStream(bool atomic) {
return 0;
}
bool HTTPFilesystemNode::createDirectory() {
return false;
}
bool HTTPFilesystemNode::exists() const {
return _isValid;
}
bool HTTPFilesystemNode::isReadable() const {
return exists();
}
bool HTTPFilesystemNode::isWritable() const {
return false;
}
#endif // #if defined(EMSCRIPTEN)

View File

@@ -0,0 +1,78 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef HTTP_FILESYSTEM_H
#define HTTP_FILESYSTEM_H
#include "backends/fs/abstract-fs.h"
#include "backends/fs/posix/posix-fs.h"
/**
* Implementation of the ScummVM file system API based on POSIX.
*
* Parts of this class are documented in the base interface class, AbstractFSNode.
*/
class HTTPFilesystemNode : public AbstractFSNode {
protected:
static Common::HashMap<Common::String, AbstractFSList> _httpFolders;
Common::String _displayName;
Common::String _path;
Common::String _url;
AbstractFSList *_children;
bool _isDirectory;
bool _isValid;
int _size;
/**
* Full constructor, for internal use only (hence protected).
*/
HTTPFilesystemNode(const Common::String &path, const Common::String &displayName, const Common::String &baseUrl, bool isValid, bool isDirectory, int size);
virtual AbstractFSNode *makeNode(const Common::String &path) const {
return new HTTPFilesystemNode(path);
}
public:
/**
* Creates a HTTPFilesystemNode for a given path.
*
* @param path the path the new node should point to.
*/
HTTPFilesystemNode(const Common::String &path);
~HTTPFilesystemNode() {}
bool exists() const override;
Common::U32String getDisplayName() const override { return _displayName; }
Common::String getName() const override { return _displayName; }
Common::String getPath() const override { return _path; }
bool isDirectory() const override { return _isDirectory; }
bool isReadable() const override;
bool isWritable() const override;
AbstractFSNode *getChild(const Common::String &n) const override;
bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const override;
AbstractFSNode *getParent() const override;
Common::SeekableReadStream *createReadStream() override;
Common::SeekableWriteStream *createWriteStream(bool atomic) override;
bool createDirectory() override;
};
#endif