ldc-showroom/electron/UnzipData.ts
2025-06-27 10:57:34 +02:00

115 lines
4 KiB
TypeScript

import fs from "node:fs";
import path from "path";
import AdmZip from "adm-zip";
import { Ajv } from "ajv";
import addFormats from "ajv-formats";
import log from "electron-log/main";
import packageJson from "./../package.json";
const isDev = process.env.NODE_ENV === "development";
const root = path.join(__dirname, "..");
const publicPath = isDev ? path.join(root, "public") : path.join(root, ".output/public");
/**
* In charge to unzip data from showroom Bo,
* validate data config and check version
*/
export default abstract class UnzipData {
static zipFileToUse: string|null;
/**
* Init zipFileToUse if not already inited
*/
private static init() {
if (!UnzipData.zipFileToUse) {
// * Try to locate last zip file and load it.
const zipFile = fs.readdirSync("static/data")
.map(path => { return {path: `static/data/${path}`, "stat": fs.lstatSync(`static/data/${path}`)}; })
.filter(obj => obj.stat.isFile() && obj.path.endsWith(".zip"))
.sort((objA, objB) => objB.stat.mtime.getTime() - objA.stat.mtime.getTime())
.at(0) ?? null;
if (zipFile) {
UnzipData.zipFileToUse = zipFile.path;
}
}
}
/**
* Decompress zip file content
* And fetch data.json content
*/
public static loadZipFileContent(): void {
UnzipData.init();
if (!UnzipData.zipFileToUse) {
throw new Error("No json data to load");
}
const zip = new AdmZip(fs.readFileSync(UnzipData.zipFileToUse));
UnzipData.removeOldExtractedFiles();
zip.extractAllTo("public/data");
}
public static removeOldExtractedFiles() {
// * Clear export dir
fs.rmSync("public/data/pictures", { recursive: true, force: true });
fs.rmSync("public/data/data.json", { recursive: true, force: true });
}
/**
* Load json data file that was extracted
*/
public static loadJsonDataFile(): TypeApp | null {
const jsonData = fs.readFileSync(
path.join(publicPath, "data/data.json"),
{ encoding: "utf8", flag: "r" }
);
const jsonSchemaPath = path.join(publicPath, "schemas");
if (!UnzipData.validateAgainstJsonSchema(jsonSchemaPath, jsonData)) {
log.warn(`Data for file ${UnzipData.zipFileToUse} is not valid JSON`);
return null;
}
const appJsonConfig: TypeApp = JSON.parse(jsonData);
const appVersion: string = packageJson.version;
if (appJsonConfig.version !== appVersion) {
throw new Error(`App version ${appVersion} is not compatible with config version ${appJsonConfig.version}`);
}
return appJsonConfig;
}
private static validateAgainstJsonSchema(jsonSchemaPath:string, jsonData: string): boolean {
try {
const schemaList = fs.readdirSync(jsonSchemaPath)
.filter(schemaPath => schemaPath.endsWith(".schema.json"))
.map(schemaPath => JSON.parse(fs.readFileSync(`${jsonSchemaPath}/${schemaPath}`, { encoding: "utf8", flag: "r" })) as Record<string,string|any>);
const appSchemaIndex = schemaList.findIndex((schema) => String(schema.$id).endsWith("app.schema.json"));
if (appSchemaIndex === -1) {
throw new Error("app.schema.json not found");
}
const appSchema = schemaList.splice(appSchemaIndex, 1).at(0);
if (!appSchema) {
throw new Error("app.schema.json not found");
}
const ajv = new Ajv();
addFormats(ajv);
ajv.addMetaSchema(appSchema);
ajv.addSchema(schemaList);
return ajv.validate(appSchema, JSON.parse(jsonData));
} catch (err:unknown) {
if (err instanceof Error) {
log.error(err);
return false;
}
throw new Error("validateAgainstJsonSchema unkown error");
}
}
}