zwave-js-ui
Version:
Z-Wave Control Panel and MQTT Gateway
118 lines (117 loc) • 3.72 kB
JavaScript
import jsonFile from 'jsonfile';
import { storeBackupsDir, storeDir } from "../config/app.js";
import { module } from "./logger.js";
import { recursive as merge } from 'merge';
import archiver from 'archiver';
import { createWriteStream, existsSync } from 'node:fs';
import { ensureDir, fileDate, joinPath } from "./utils.js";
const logger = module('Store');
export const STORE_BACKUP_PREFIX = 'store-backup_';
// Default dependencies for production use
const defaultDeps = {
readFile: jsonFile.readFile.bind(jsonFile),
writeFile: jsonFile.writeFile.bind(jsonFile),
};
/**
Constructor
**/
export class StorageHelper {
_store;
config;
readFile;
writeFile;
get store() {
return this._store;
}
constructor(deps) {
this._store = {};
this.readFile = deps?.readFile || defaultDeps.readFile;
this.writeFile = deps?.writeFile || defaultDeps.writeFile;
}
async init(config) {
this.config = config;
for (const model in config) {
const res = await this._getFile(config[model]);
this._store[res.file] = res.data;
}
return this._store;
}
async backup(res) {
const backupFile = `${STORE_BACKUP_PREFIX}${fileDate()}.zip`;
await ensureDir(storeBackupsDir);
const fileStream = createWriteStream(joinPath(storeBackupsDir, backupFile));
return new Promise((resolve, reject) => {
const archive = archiver('zip');
archive.on('error', (err) => {
reject(err);
});
// on stream closed we can end the request
archive.on('end', () => {
resolve(backupFile);
});
if (res) {
res.set({
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="${backupFile}"`,
});
archive.pipe(res);
}
archive.pipe(fileStream);
// backup zwavejs files too
archive.glob('*.jsonl', {
cwd: storeDir,
});
for (const model in this.config) {
const config = this.config[model];
const filePath = joinPath(storeDir, config.file);
if (existsSync(filePath)) {
archive.file(filePath, {
name: config.file,
});
}
}
void archive.finalize();
});
}
async _getFile(config) {
let err;
let data;
try {
data = await this.readFile(joinPath(storeDir, config.file));
}
catch (error) {
err = error;
}
// ignore ENOENT error
if (err) {
if (err.code !== 'ENOENT') {
logger.error('Error reading file: ' + config.file, err);
}
else {
logger.warn(`${config.file} not found`);
}
}
// replace data with default
if (!data) {
data = config.default;
}
else {
data = Array.isArray(data) ? data : merge(config.default, data);
}
return { file: config.file, data: data };
}
get(model) {
if (this._store[model.file]) {
return this._store[model.file];
}
else {
throw Error('Requested file not present in store: ' + model.file);
}
}
async put(model, data) {
await this.writeFile(joinPath(storeDir, model.file), data);
this._store[model.file] = data;
return data;
}
}
export default new StorageHelper();