Web save mechanism

This commit is contained in:
elasota
2021-05-07 23:09:35 -04:00
parent 1ac8032411
commit fb7b9b02d9
7 changed files with 977 additions and 924 deletions

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "AerofoilWeb/FileSaverDotJS"]
path = AerofoilWeb/FileSaverDotJS
url = https://github.com/eligrey/FileSaver.js.git
branch = master

View File

@@ -39,6 +39,22 @@ EM_JS(void, FlushFileSystem, (), {
}); });
}); });
EM_JS(void, DownloadAndDeleteFile, (const char *fileNamePtr, const char *prettyNamePtr), {
var fileName = UTF8ToString(fileNamePtr);
var prettyName = UTF8ToString(prettyNamePtr);
console.log("Flush download of file " + fileName + " as " + prettyName);
var mimeType = "application/octet-stream";
if (prettyName.endsWith(".bin"))
mimeType = "application/macbinary";
else if (prettyName.endsWith(".gpf"))
mimeType = "application/zip";
var byteArray = FS.readFile(fileName, { encoding: "binary" });
var blob = new Blob([byteArray], { type: mimeType });
saveAs(blob, prettyName);
});
class GpDirectoryCursor_Web final : public IGpDirectoryCursor class GpDirectoryCursor_Web final : public IGpDirectoryCursor
{ {
@@ -255,7 +271,7 @@ void GpFileStream_Web_StaticMemFile::Flush()
class GpFileStream_Web_File final : public GpIOStream class GpFileStream_Web_File final : public GpIOStream
{ {
public: public:
GpFileStream_Web_File(FILE *f, bool readOnly, bool writeOnly, bool synchronizeOnClose); GpFileStream_Web_File(FILE *f, const std::string &filePath, const std::string &prettyName, bool readOnly, bool writeOnly, bool synchronizeOnClose, bool isIDB);
~GpFileStream_Web_File(); ~GpFileStream_Web_File();
size_t Read(void *bytesOut, size_t size) override; size_t Read(void *bytesOut, size_t size) override;
@@ -273,18 +289,24 @@ public:
private: private:
FILE *m_f; FILE *m_f;
std::string m_filePath;
std::string m_prettyName;
bool m_seekable; bool m_seekable;
bool m_isReadOnly; bool m_isReadOnly;
bool m_isWriteOnly; bool m_isWriteOnly;
bool m_synchronizeOnClose; bool m_synchronizeOnClose;
bool m_isIDB;
}; };
GpFileStream_Web_File::GpFileStream_Web_File(FILE *f, bool readOnly, bool writeOnly, bool synchronizeOnClose) GpFileStream_Web_File::GpFileStream_Web_File(FILE *f, const std::string &filePath, const std::string &prettyName, bool readOnly, bool writeOnly, bool synchronizeOnClose, bool isIDB)
: m_f(f) : m_f(f)
, m_isReadOnly(readOnly) , m_isReadOnly(readOnly)
, m_isWriteOnly(writeOnly) , m_isWriteOnly(writeOnly)
, m_synchronizeOnClose(synchronizeOnClose) , m_synchronizeOnClose(synchronizeOnClose)
, m_isIDB(isIDB)
, m_filePath(filePath)
, m_prettyName(prettyName)
{ {
m_seekable = (fseek(m_f, 0, SEEK_CUR) == 0); m_seekable = (fseek(m_f, 0, SEEK_CUR) == 0);
} }
@@ -294,7 +316,12 @@ GpFileStream_Web_File::~GpFileStream_Web_File()
fclose(m_f); fclose(m_f);
if (m_synchronizeOnClose) if (m_synchronizeOnClose)
{
if (m_isIDB)
GpFileSystem_Web::MarkFSStateDirty(); GpFileSystem_Web::MarkFSStateDirty();
else
GpFileSystem_Web::SyncDownloadFile(m_filePath, m_prettyName);
}
} }
size_t GpFileStream_Web_File::Read(void *bytesOut, size_t size) size_t GpFileStream_Web_File::Read(void *bytesOut, size_t size)
@@ -384,11 +411,12 @@ void GpFileStream_Web_File::Flush()
bool GpFileSystem_Web::ms_fsStateDirty; bool GpFileSystem_Web::ms_fsStateDirty;
bool GpFileSystem_Web::ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, bool trailingSlash, std::string &resolution) bool GpFileSystem_Web::ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, bool trailingSlash, std::string &resolution, bool &outIsIDB)
{ {
const char *pathAppend = nullptr; const char *pathAppend = nullptr;
const std::string *rootPath = nullptr; const std::string *rootPath = nullptr;
std::string unsanitized; std::string unsanitized;
bool isIDB = false;
switch (virtualDirectory) switch (virtualDirectory)
{ {
@@ -404,22 +432,27 @@ bool GpFileSystem_Web::ResolvePath(PortabilityLayer::VirtualDirectory_t virtualD
case PortabilityLayer::VirtualDirectories::kHighScores: case PortabilityLayer::VirtualDirectories::kHighScores:
pathAppend = "HighScores"; pathAppend = "HighScores";
rootPath = &m_basePath; rootPath = &m_basePath;
isIDB = true;
break; break;
case PortabilityLayer::VirtualDirectories::kUserData: case PortabilityLayer::VirtualDirectories::kUserData:
pathAppend = "Houses"; pathAppend = "Houses";
rootPath = &m_basePath; rootPath = &m_basePath;
isIDB = true;
break; break;
case PortabilityLayer::VirtualDirectories::kUserSaves: case PortabilityLayer::VirtualDirectories::kUserSaves:
pathAppend = "SavedGames"; pathAppend = "SavedGames";
rootPath = &m_basePath; rootPath = &m_basePath;
isIDB = true;
break; break;
case PortabilityLayer::VirtualDirectories::kPrefs: case PortabilityLayer::VirtualDirectories::kPrefs:
pathAppend = "Prefs"; pathAppend = "Prefs";
rootPath = &m_basePath; rootPath = &m_basePath;
isIDB = true;
break; break;
case PortabilityLayer::VirtualDirectories::kSourceExport: case PortabilityLayer::VirtualDirectories::kSourceExport:
pathAppend = "Export"; pathAppend = "Export";
rootPath = &m_exportPath; rootPath = &m_exportPath;
isIDB = false;
break; break;
default: default:
return false; return false;
@@ -472,6 +505,7 @@ bool GpFileSystem_Web::ResolvePath(PortabilityLayer::VirtualDirectory_t virtualD
resolution = sanitized; resolution = sanitized;
} }
outIsIDB = isIDB;
return true; return true;
} }
@@ -513,7 +547,8 @@ bool GpFileSystem_Web::FileExists(PortabilityLayer::VirtualDirectory_t virtualDi
} }
std::string resolvedPath; std::string resolvedPath;
if (!ResolvePath(virtualDirectory, &path, 1, false, resolvedPath)) bool isIDB = false;
if (!ResolvePath(virtualDirectory, &path, 1, false, resolvedPath, isIDB))
return false; return false;
struct stat s; struct stat s;
@@ -539,7 +574,8 @@ bool GpFileSystem_Web::FileLocked(PortabilityLayer::VirtualDirectory_t virtualDi
} }
std::string resolvedPath; std::string resolvedPath;
if (!ResolvePath(virtualDirectory, &path, 1, false, resolvedPath)) bool isIDB = false;
if (!ResolvePath(virtualDirectory, &path, 1, false, resolvedPath, isIDB))
{ {
if (exists) if (exists)
exists = false; exists = false;
@@ -594,11 +630,9 @@ GpIOStream *GpFileSystem_Web::OpenFileNested(PortabilityLayer::VirtualDirectory_
return nullptr; return nullptr;
}; };
if (virtualDirectory == PortabilityLayer::VirtualDirectories::kSourceExport)
return nullptr;
std::string resolvedPath; std::string resolvedPath;
if (!ResolvePath(virtualDirectory, subPaths, numSubPaths, false, resolvedPath)) bool isIDB = false;
if (!ResolvePath(virtualDirectory, subPaths, numSubPaths, false, resolvedPath, isIDB))
return nullptr; return nullptr;
void *objStorage = malloc(sizeof(GpFileStream_Web_File)); void *objStorage = malloc(sizeof(GpFileStream_Web_File));
@@ -625,7 +659,11 @@ GpIOStream *GpFileSystem_Web::OpenFileNested(PortabilityLayer::VirtualDirectory_
} }
} }
return new (objStorage) GpFileStream_Web_File(f, !writeAccess, false, writeAccess); std::string prettyName;
if (numSubPaths > 0)
prettyName = subPaths[numSubPaths - 1];
return new (objStorage) GpFileStream_Web_File(f, resolvedPath, prettyName, !writeAccess, false, writeAccess, isIDB);
} }
bool GpFileSystem_Web::DeleteFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool &existed) bool GpFileSystem_Web::DeleteFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool &existed)
@@ -634,7 +672,8 @@ bool GpFileSystem_Web::DeleteFile(PortabilityLayer::VirtualDirectory_t virtualDi
return false; return false;
std::string resolvedPath; std::string resolvedPath;
if (!ResolvePath(virtualDirectory, &path, 1, false, resolvedPath)) bool isIDB = false;
if (!ResolvePath(virtualDirectory, &path, 1, false, resolvedPath, isIDB))
{ {
existed = false; existed = false;
return false; return false;
@@ -643,7 +682,7 @@ bool GpFileSystem_Web::DeleteFile(PortabilityLayer::VirtualDirectory_t virtualDi
if (unlink(resolvedPath.c_str()) < 0) if (unlink(resolvedPath.c_str()) < 0)
{ {
existed = (errno != ENOENT); existed = (errno != ENOENT);
if (existed) if (existed && isIDB)
FlushFileSystem(); FlushFileSystem();
return false; return false;
@@ -753,7 +792,8 @@ IGpDirectoryCursor *GpFileSystem_Web::ScanDirectoryNested(PortabilityLayer::Virt
return ScanCatalog(*catalog); return ScanCatalog(*catalog);
std::string resolvedPrefix; std::string resolvedPrefix;
if (!ResolvePath(virtualDirectory, paths, numPaths, true, resolvedPrefix)) bool isIDB = false;
if (!ResolvePath(virtualDirectory, paths, numPaths, true, resolvedPrefix, isIDB))
return nullptr; return nullptr;
std::string trimmedPrefix = resolvedPrefix.substr(m_prefsPath.size() + 1); std::string trimmedPrefix = resolvedPrefix.substr(m_prefsPath.size() + 1);
@@ -790,6 +830,11 @@ void GpFileSystem_Web::MarkFSStateDirty()
ms_fsStateDirty = true; ms_fsStateDirty = true;
} }
void GpFileSystem_Web::SyncDownloadFile(const std::string &filePath, const std::string &prettyName)
{
DownloadAndDeleteFile(filePath.c_str(), prettyName.c_str());
}
void GpFileSystem_Web::FlushFS() void GpFileSystem_Web::FlushFS()
{ {
if (ms_fsStateDirty) if (ms_fsStateDirty)

View File

@@ -30,6 +30,7 @@ public:
void SetDelayCallback(DelayCallback_t delayCallback) override; void SetDelayCallback(DelayCallback_t delayCallback) override;
static void MarkFSStateDirty(); static void MarkFSStateDirty();
static void SyncDownloadFile(const std::string &filePath, const std::string &prettyName);
static void FlushFS(); static void FlushFS();
static GpFileSystem_Web *GetInstance(); static GpFileSystem_Web *GetInstance();
@@ -54,7 +55,7 @@ private:
static IGpDirectoryCursor *ScanCatalog(const GpFileSystem_Web_Resources::FileCatalog &catalog); static IGpDirectoryCursor *ScanCatalog(const GpFileSystem_Web_Resources::FileCatalog &catalog);
bool ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, bool trailingSlash, std::string &resolution); bool ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, bool trailingSlash, std::string &resolution, bool &outIsIDB);
DelayCallback_t m_delayCallback; DelayCallback_t m_delayCallback;

View File

@@ -31,10 +31,9 @@ IGpInputDriver *GpDriver_CreateInputDriver_SDL2_Gamepad(const GpInputDriverPrope
EM_JS(void, InitFileSystem, (), { EM_JS(void, InitFileSystem, (), {
Asyncify.handleSleep(wakeUp => { Asyncify.handleSleep(wakeUp => {
FS.mkdir('/aerofoil'); FS.mkdir('/aerofoil');
//FS.mkdir('/aerofoil_memfs'); FS.mkdir('/aerofoil_memfs');
FS.mount(IDBFS, {}, '/aerofoil'); FS.mount(IDBFS, {}, '/aerofoil');
//FS.mount(MEMFS, {}, '/aerofoil_memfs'); FS.mount(MEMFS, {}, '/aerofoil_memfs');
//FS.mkdir('/aerofoil_memfs/Export');
FS.syncfs(true, function (err) { FS.syncfs(true, function (err) {
assert(!err); assert(!err);
wakeUp(); wakeUp();

View File

@@ -4,5 +4,7 @@ set OUTPUT_DIR=bin
rem set DEBUG_LEVEL_FLAGS=-g4 -O0 rem set DEBUG_LEVEL_FLAGS=-g4 -O0
set DEBUG_LEVEL_FLAGS=-O3 set DEBUG_LEVEL_FLAGS=-O3
copy /Y FileSaverDotJS\dist\FileSaver.js bin\FileSaver.js
set FLAGS=-flto %DEBUG_LEVEL_FLAGS% -s USE_SDL=2 -s USE_ZLIB=1 -s ASYNCIFY -s ASYNCIFY_IGNORE_INDIRECT -s INITIAL_MEMORY=33554432 -s ASYNCIFY_ADVISE -lidbfs.js -s ASYNCIFY_IMPORTS=['InitFileSystem','FlushFileSystem'] --shell-file shell_minimal.html set FLAGS=-flto %DEBUG_LEVEL_FLAGS% -s USE_SDL=2 -s USE_ZLIB=1 -s ASYNCIFY -s ASYNCIFY_IGNORE_INDIRECT -s INITIAL_MEMORY=33554432 -s ASYNCIFY_ADVISE -lidbfs.js -s ASYNCIFY_IMPORTS=['InitFileSystem','FlushFileSystem'] --shell-file shell_minimal.html
emcc obj/AerofoilWeb_Combined.o obj/AerofoilWeb_Resources.o obj/GpShell_Combined.o obj/AerofoilSDL_Combined.o obj/AerofoilPortable_Combined.o obj/GpApp_Combined.o obj/PortabilityLayer_Combined.o obj/MacRomanConversion.o -o %OUTPUT_DIR%/aerofoil.html %FLAGS% emcc obj/AerofoilWeb_Combined.o obj/AerofoilWeb_Resources.o obj/GpShell_Combined.o obj/AerofoilSDL_Combined.o obj/AerofoilPortable_Combined.o obj/GpApp_Combined.o obj/PortabilityLayer_Combined.o obj/MacRomanConversion.o -o %OUTPUT_DIR%/aerofoil.html %FLAGS%

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Aerofoil Web Pre-Release</title> <title>Aerofoil</title>
<style> <style>
.emscripten { padding-right: 0; padding-top: 0; margin-left: auto; margin-right: auto; margin-top: 0; display: block; } .emscripten { padding-right: 0; padding-top: 0; margin-left: auto; margin-right: auto; margin-top: 0; display: block; }
textarea.emscripten { font-family: monospace; width: 80%; } textarea.emscripten { font-family: monospace; width: 80%; }
@@ -135,6 +135,7 @@
}; };
}; };
</script> </script>
<script type="text/javascript" src="FileSaver.js"></script>
{{{ SCRIPT }}} {{{ SCRIPT }}}
</body> </body>
</html> </html>