mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Attempt #1 on preserving savegame files
This commit is contained in:
parent
71d5b02e02
commit
5dddb846ef
@ -3,10 +3,11 @@
|
|||||||
const { app, BrowserWindow, Menu, MenuItem, session } = require("electron");
|
const { app, BrowserWindow, Menu, MenuItem, session } = require("electron");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const url = require("url");
|
const url = require("url");
|
||||||
const childProcess = require("child_process");
|
|
||||||
const { ipcMain, shell } = require("electron");
|
const { ipcMain, shell } = require("electron");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const steam = require('./steam');
|
const steam = require("./steam");
|
||||||
|
const asyncLock = require("async-lock");
|
||||||
|
|
||||||
const isDev = process.argv.indexOf("--dev") >= 0;
|
const isDev = process.argv.indexOf("--dev") >= 0;
|
||||||
const isLocal = process.argv.indexOf("--local") >= 0;
|
const isLocal = process.argv.indexOf("--local") >= 0;
|
||||||
|
|
||||||
@ -153,7 +154,82 @@ ipcMain.on("exit-app", (event, flag) => {
|
|||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
function performFsJob(job) {
|
let renameCounter = 1;
|
||||||
|
|
||||||
|
const fileLock = new asyncLock({
|
||||||
|
timeout: 30000,
|
||||||
|
maxPending: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
function niceFileName(filename) {
|
||||||
|
return filename.replace(storePath, "@");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeFileSafe(filename, contents) {
|
||||||
|
++renameCounter;
|
||||||
|
const prefix = "[ " + renameCounter + ":" + niceFileName(filename) + " ] ";
|
||||||
|
const transactionId = String(new Date().getTime()) + "." + renameCounter;
|
||||||
|
|
||||||
|
if (fileLock.isBusy()) {
|
||||||
|
console.warn(prefix, "Concurrent write process on", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileLock.acquire(filename, async () => {
|
||||||
|
console.log(prefix, "Starting write on", niceFileName(filename), "in transaction", transactionId);
|
||||||
|
|
||||||
|
if (!fs.existsSync(filename)) {
|
||||||
|
// this one is easy
|
||||||
|
console.log(prefix, "Writing file instantly because it does not exist:", niceFileName(filename));
|
||||||
|
await fs.promises.writeFile(filename, contents, { encoding: "utf8" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first, write a temporary file (.tmp-XXX)
|
||||||
|
const tempName = filename + ".tmp-" + transactionId;
|
||||||
|
console.log(prefix, "Writing temporary file", niceFileName(tempName));
|
||||||
|
await fs.promises.writeFile(tempName, contents, { encoding: "utf8" });
|
||||||
|
|
||||||
|
// now, rename the original file to (.backup-XXX)
|
||||||
|
const oldTemporaryName = filename + ".backup-" + transactionId;
|
||||||
|
console.log(
|
||||||
|
prefix,
|
||||||
|
"Renaming old file",
|
||||||
|
niceFileName(filename),
|
||||||
|
"to",
|
||||||
|
niceFileName(oldTemporaryName)
|
||||||
|
);
|
||||||
|
await fs.promises.rename(filename, oldTemporaryName);
|
||||||
|
|
||||||
|
// now, rename the temporary file (.tmp-XXX) to the target
|
||||||
|
console.log(
|
||||||
|
prefix,
|
||||||
|
"Renaming the temporary file",
|
||||||
|
niceFileName(tempName),
|
||||||
|
"to the original",
|
||||||
|
niceFileName(filename)
|
||||||
|
);
|
||||||
|
await fs.promises.rename(tempName, filename);
|
||||||
|
|
||||||
|
// we are done now, try to create a backup, but don't fail if the backup fails
|
||||||
|
try {
|
||||||
|
// check if there is an old backup file
|
||||||
|
const backupFileName = filename + ".backup";
|
||||||
|
if (fs.existsSync(backupFileName)) {
|
||||||
|
console.log(prefix, "Deleting old backup file", niceFileName(backupFileName));
|
||||||
|
// delete the old backup
|
||||||
|
await fs.promises.unlink(backupFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename the old file to the new backup file
|
||||||
|
console.log(prefix, "Moving", niceFileName(oldTemporaryName), "to the backup file location");
|
||||||
|
await fs.promises.rename(oldTemporaryName, backupFileName);
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(prefix, "Failed to switch backup files:", ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function performFsJob(job) {
|
||||||
const fname = path.join(storePath, job.filename);
|
const fname = path.join(storePath, job.filename);
|
||||||
|
|
||||||
switch (job.type) {
|
switch (job.type) {
|
||||||
@ -165,38 +241,35 @@ function performFsJob(job) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let contents = "";
|
|
||||||
try {
|
try {
|
||||||
contents = fs.readFileSync(fname, { encoding: "utf8" });
|
const data = await fs.promises.readFile(fname, { encoding: "utf8" });
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data,
|
||||||
|
};
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return {
|
return {
|
||||||
error: ex,
|
error: ex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: contents,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
case "write": {
|
case "write": {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(fname, job.contents);
|
await writeFileSafe(fname, job.contents);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: job.contents,
|
||||||
|
};
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return {
|
return {
|
||||||
error: ex,
|
error: ex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: job.contents,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "delete": {
|
case "delete": {
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(fname);
|
await fs.promises.unlink(fname);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return {
|
return {
|
||||||
error: ex,
|
error: ex,
|
||||||
@ -214,15 +287,10 @@ function performFsJob(job) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.on("fs-job", (event, arg) => {
|
ipcMain.on("fs-job", async (event, arg) => {
|
||||||
const result = performFsJob(arg);
|
const result = await performFsJob(arg);
|
||||||
event.reply("fs-response", { id: arg.id, result });
|
event.reply("fs-response", { id: arg.id, result });
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on("fs-sync-job", (event, arg) => {
|
|
||||||
const result = performFsJob(arg);
|
|
||||||
event.returnValue = result;
|
|
||||||
});
|
|
||||||
|
|
||||||
steam.init(isDev);
|
steam.init(isDev);
|
||||||
steam.listen();
|
steam.listen();
|
||||||
|
@ -14,5 +14,8 @@
|
|||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"shapez.io-private-artifacts": "github:tobspr/shapez.io-private-artifacts#abi-v85"
|
"shapez.io-private-artifacts": "github:tobspr/shapez.io-private-artifacts#abi-v85"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"async-lock": "^1.2.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/node/-/node-12.20.5.tgz"
|
resolved "https://registry.npmjs.org/@types/node/-/node-12.20.5.tgz"
|
||||||
integrity sha512-5Oy7tYZnu3a4pnJ//d4yVvOImExl4Vtwf0D40iKUlU+XlUsyV9iyFWyCFlwy489b72FMAik/EFwRkNLjjOdSPg==
|
integrity sha512-5Oy7tYZnu3a4pnJ//d4yVvOImExl4Vtwf0D40iKUlU+XlUsyV9iyFWyCFlwy489b72FMAik/EFwRkNLjjOdSPg==
|
||||||
|
|
||||||
|
async-lock@^1.2.8:
|
||||||
|
version "1.2.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.2.8.tgz#7b02bdfa2de603c0713acecd11184cf97bbc7c4c"
|
||||||
|
integrity sha512-G+26B2jc0Gw0EG/WN2M6IczuGepBsfR1+DtqLnyFSH4p2C668qkOCtEkGNVEaaNAVlYwEMazy1+/jnLxltBkIQ==
|
||||||
|
|
||||||
boolean@^3.0.1:
|
boolean@^3.0.1:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.npmjs.org/boolean/-/boolean-3.0.2.tgz"
|
resolved "https://registry.npmjs.org/boolean/-/boolean-3.0.2.tgz"
|
||||||
|
@ -58,11 +58,6 @@ export class StorageImplBrowser extends StorageInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFileSyncIfSupported(filename, contents) {
|
|
||||||
window.localStorage.setItem(filename, contents);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
readFileAsync(filename) {
|
readFileAsync(filename) {
|
||||||
if (this.currentBusyFilename === filename) {
|
if (this.currentBusyFilename === filename) {
|
||||||
logger.warn("Attempt to read", filename, "while write progress on it is ongoing!");
|
logger.warn("Attempt to read", filename, "while write progress on it is ongoing!");
|
||||||
|
@ -94,12 +94,6 @@ export class StorageImplBrowserIndexedDB extends StorageInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFileSyncIfSupported(filename, contents) {
|
|
||||||
// Not supported
|
|
||||||
this.writeFileAsync(filename, contents);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
readFileAsync(filename) {
|
readFileAsync(filename) {
|
||||||
if (!this.database) {
|
if (!this.database) {
|
||||||
return Promise.reject("Storage not ready");
|
return Promise.reject("Storage not ready");
|
||||||
|
@ -46,14 +46,6 @@ export class StorageImplElectron extends StorageInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFileSyncIfSupported(filename, contents) {
|
|
||||||
return getIPCRenderer().sendSync("fs-sync-job", {
|
|
||||||
type: "write",
|
|
||||||
filename,
|
|
||||||
contents,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
readFileAsync(filename) {
|
readFileAsync(filename) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// ipcMain
|
// ipcMain
|
||||||
|
@ -30,16 +30,6 @@ export class StorageInterface {
|
|||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to write a file synchronously, used in unload handler
|
|
||||||
* @param {string} filename
|
|
||||||
* @param {string} contents
|
|
||||||
*/
|
|
||||||
writeFileSyncIfSupported(filename, contents) {
|
|
||||||
abstract;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a string asynchronously. Returns Promise<FILE_NOT_FOUND> if file was not found.
|
* Reads a string asynchronously. Returns Promise<FILE_NOT_FOUND> if file was not found.
|
||||||
* @param {string} filename
|
* @param {string} filename
|
||||||
|
Loading…
Reference in New Issue
Block a user