mirror of
https://github.com/elasota/Aerofoil.git
synced 2025-09-23 14:53:52 +00:00
Web save mechanism
This commit is contained in:
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[submodule "AerofoilWeb/FileSaverDotJS"]
|
||||||
|
path = AerofoilWeb/FileSaverDotJS
|
||||||
|
url = https://github.com/eligrey/FileSaver.js.git
|
||||||
|
branch = master
|
1
AerofoilWeb/FileSaverDotJS
Submodule
1
AerofoilWeb/FileSaverDotJS
Submodule
Submodule AerofoilWeb/FileSaverDotJS added at b5e61ec889
@@ -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)
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -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();
|
||||||
|
@@ -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%
|
||||||
|
@@ -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>
|
||||||
|
Reference in New Issue
Block a user