From d656849d4077465f2684d089840b45e2ba85a204 Mon Sep 17 00:00:00 2001 From: alMukaafih Date: Sun, 20 Oct 2024 10:22:26 +0100 Subject: [PATCH] Added install state for better update of plugins --- src/lib/installPlugin.js | 43 ++++++++++++++++- src/lib/installState.js | 99 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 src/lib/installState.js diff --git a/src/lib/installPlugin.js b/src/lib/installPlugin.js index 5f34e4988..8a83cdcf1 100644 --- a/src/lib/installPlugin.js +++ b/src/lib/installPlugin.js @@ -3,6 +3,7 @@ import fsOperation from "fileSystem"; import JSZip from "jszip"; import Url from "utils/Url"; import constants from "./constants"; +import InstallState from "./installState"; import loadPlugin from "./loadPlugin"; /** @@ -70,6 +71,8 @@ export default async function installPlugin(id, name, purchaseToken) { pluginDir = Url.join(PLUGIN_DIR, id); } + const state = await InstallState.new(id); + if (!(await fsOperation(pluginDir).exists())) { await fsOperation(PLUGIN_DIR).createDirectory(id); } @@ -81,7 +84,7 @@ export default async function installPlugin(id, name, purchaseToken) { } const fileUrl = Url.join(pluginDir, correctFile); - if (!(await fsOperation(fileUrl).exists())) { + if (!state.exists(correctFile)) { await createFileRecursive(pluginDir, correctFile); } @@ -93,11 +96,18 @@ export default async function installPlugin(id, name, purchaseToken) { data = JSON.stringify(pluginJson); } + if (!(await state.isUpdated(correctFile, data))) { + return; + } + await fsOperation(fileUrl).writeFile(data); + return; }); await Promise.all(promises); await loadPlugin(id, true); + await state.save(); + deleteRedundantFiles(pluginDir, state); } } catch (err) { try { @@ -139,3 +149,34 @@ async function createFileRecursive(parent, dir) { await createFileRecursive(newParent, dir); } } +/** + * + * @param {string} dir + * @param {Array} files + */ +async function listFileRecursive(dir, files) { + for (const child of await fsOperation(dir).lsDir()) { + const fileUrl = Url.join(dir, child.name); + if (child.isDirectory) { + await listFileRecursive(fileUrl, files); + } else { + files.push(fileUrl); + } + } +} + +/** + * + * @param {Record} files + */ +async function deleteRedundantFiles(pluginDir, state) { + /** @type string[] */ + let files = []; + await listFileRecursive(pluginDir, files); + + for (const file of files) { + if (!state.exists(file.replace(`${pluginDir}/`, ""))) { + fsOperation(file).delete(); + } + } +} diff --git a/src/lib/installState.js b/src/lib/installState.js new file mode 100644 index 000000000..75b393a78 --- /dev/null +++ b/src/lib/installState.js @@ -0,0 +1,99 @@ +import fsOperation from "fileSystem"; +import Url from "utils/Url"; + +const INSTALL_STATE_STORAGE = Url.join(DATA_STORAGE, ".install-state"); + +export default class InstallState { + /** @type Record */ + store; + /** @type Record */ + updatedStore; + + /** + * + * @param {string} id + * @returns + */ + static async new(id) { + const state = new InstallState(); + state.id = await checksumText(id); + state.updatedStore = {}; + + if (!(await fsOperation(INSTALL_STATE_STORAGE).exists())) { + await fsOperation(DATA_STORAGE).createDirectory(".install-state"); + } + + state.storeUrl = Url.join(INSTALL_STATE_STORAGE, state.id); + if (await fsOperation(state.storeUrl).exists()) { + state.store = JSON.parse( + await fsOperation(state.storeUrl).readFile("utf-8"), + ); + } else { + state.store = {}; + await fsOperation(INSTALL_STATE_STORAGE).createFile(state.id); + } + + return state; + } + + /** + * + * @param {string} url + * @param {ArrayBuffer | string} content + * @param {boolean} isString + * @returns + */ + async isUpdated(url, content) { + const current = this.store[url]; + const update = + typeof content === "string" + ? await checksumText(content) + : await checksum(content); + this.updatedStore[url] = update; + + if (current === update) { + return false; + } else { + return true; + } + } + + exists(url) { + if (typeof this.store[url] !== "undefined") { + return true; + } else { + return false; + } + } + + async save() { + this.store = this.updatedStore; + await fsOperation(this.storeUrl).writeFile( + JSON.stringify(this.updatedStore), + ); + } +} + +/** + * Derives the checksum of a Buffer + * @param {BufferSource} data + * @returns the derived checksum + */ +async function checksum(data) { + const hashBuffer = await window.crypto.subtle.digest("SHA-256", data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray + .map((byte) => byte.toString(16).padStart(2, "0")) + .join(""); + return hashHex; +} + +/** + * + * @param {string} text + * @returns + */ +async function checksumText(text) { + const textUint8 = new TextEncoder().encode(text); + return await checksum(textUint8); +}