@signalk/resources-provider
Version:
Resources provider plugin for Signal K server.
238 lines (237 loc) • 9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileStore = exports.getUuid = void 0;
/* eslint-disable @typescript-eslint/no-explicit-any */
const fs_1 = require("fs");
const promises_1 = require("fs/promises");
const path_1 = __importDefault(require("path"));
const utils_1 = require("./utils");
const getUuid = (skIdentifier) => skIdentifier.split(':').slice(-1)[0];
exports.getUuid = getUuid;
// File Resource Store Class
class FileStore {
debug;
savePath;
resources;
pkg;
initPromise = null;
constructor(pluginId, debug) {
this.debug = debug;
this.savePath = '';
this.resources = {};
this.pkg = { id: pluginId };
}
// Wait for initialization to complete before performing operations
async waitForInit() {
if (this.initPromise) {
await this.initPromise;
}
}
// check / create path to persist resources
init(config) {
// Store the init promise so operations can wait for it
this.initPromise = this.doInit(config);
return this.initPromise;
}
async doInit(config) {
if (typeof config.settings.path === 'undefined') {
this.savePath = config.basePath + '/resources';
}
else if (config.settings.path[0] === '/') {
this.savePath = config.settings.path;
}
else {
this.savePath = path_1.default.join(config.basePath, config.settings.path);
}
// std resources
if (config.settings.standard) {
Object.keys(config.settings.standard).forEach((i) => {
this.resources[i] = { path: path_1.default.join(this.savePath, `/${i}`) };
});
}
// other resources
const enabledResTypes = {};
Object.assign(enabledResTypes, config.settings.standard);
if (config.settings.custom && Array.isArray(config.settings.custom)) {
config.settings.custom.forEach((i) => {
this.resources[i.name] = {
path: path_1.default.join(this.savePath, `/${i.name}`)
};
enabledResTypes[i.name] = true;
});
}
try {
await this.checkPath(this.savePath);
}
catch (_error) {
throw new Error(`Unable to create ${this.savePath}!`);
}
return await this.createSavePaths(enabledResTypes);
}
// create save paths for resource types
async createSavePaths(resTypes) {
this.debug('** Initialising resource storage **');
const result = { error: false, message: `` };
for (const t of Object.keys(this.resources)) {
if (resTypes[t]) {
try {
await (0, promises_1.access)(this.resources[t].path, fs_1.constants.W_OK | fs_1.constants.R_OK);
this.debug(`${this.resources[t].path} - OK....`);
}
catch (_error) {
this.debug(`${this.resources[t].path} NOT available...`);
this.debug(`Creating ${this.resources[t].path} ...`);
try {
await (0, promises_1.mkdir)(this.resources[t].path, { recursive: true });
this.debug(`Created ${this.resources[t].path} - OK....`);
}
catch (_error) {
result.error = true;
result.message += `ERROR creating ${this.resources[t].path} folder\r\n `;
}
}
}
}
return result;
}
// return resource or property value of supplied resource id
async getResource(type, itemUuid, property) {
await this.waitForInit();
try {
let result = JSON.parse(await (0, promises_1.readFile)(path_1.default.join(this.resources[type].path, itemUuid), 'utf8'));
if (property) {
const value = property.split('.').reduce((acc, val) => {
return acc[val];
}, result);
if (value) {
result = { value: value };
}
else {
throw new Error(`${type}/${itemUuid}.${property} not found!`);
}
}
const stats = await (0, promises_1.stat)(path_1.default.join(this.resources[type].path, itemUuid));
return {
...result,
timestamp: stats.mtime,
$source: this.pkg.id
};
}
catch (e) {
if (e.code === 'ENOENT') {
throw new Error(`No such resource ${type} ${itemUuid}`);
}
console.error(e);
throw new Error(`Error retrieving resource ${type} ${itemUuid}`);
}
}
// return persisted resources from storage
async getResources(type, params) {
await this.waitForInit();
const result = {};
// ** parse supplied params
params = (0, utils_1.processParameters)(params);
try {
// return matching resources
const rt = this.resources[type];
const files = await (0, promises_1.readdir)(rt.path, { withFileTypes: true });
// check resource count
const fcount = params.limit && files.length > params.limit
? params.limit
: files.length;
let count = 0;
for (const f in files) {
if (!files[f].isFile()) {
this.debug(`${files[f].name} is not a File => ignore.`);
continue;
}
if (++count > fcount) {
break;
}
try {
const res = JSON.parse(await (0, promises_1.readFile)(path_1.default.join(rt.path, files[f].name), 'utf8'));
// apply param filters
if ((0, utils_1.passFilter)(res, type, params)) {
const uuid = files[f].name;
const stats = await (0, promises_1.stat)(path_1.default.join(rt.path, files[f].name));
result[uuid] = {
...res,
timestamp: stats.mtime,
$source: this.pkg.id
};
}
}
catch (err) {
console.error(err);
throw new Error(`Invalid file contents: ${files[f]}`);
}
}
return result;
}
catch (error) {
console.error(error);
throw new Error(`Error retrieving resources from ${this.savePath}. Ensure plugin is active or restart plugin!`);
}
}
// save / delete (r.value==null) resource file
async setResource(r) {
await this.waitForInit();
const fname = (0, exports.getUuid)(r.id);
const p = path_1.default.join(this.resources[r.type].path, fname);
if (r.value === null) {
// delete file
try {
await (0, promises_1.unlink)(p);
this.debug(`** DELETED: ${r.type} entry ${fname} **`);
return;
}
catch (error) {
console.error('Error deleting resource!');
error.message = 'Error deleting resource!';
throw error;
}
}
else {
// add / update file
try {
await (0, promises_1.writeFile)(p, JSON.stringify(r.value));
this.debug(`** ${r.type} written to ${fname} **`);
return;
}
catch (error) {
console.error('Error updating resource!');
throw error;
}
}
}
// check path exists / create it if it doesn't
async checkPath(path = this.savePath) {
if (!path) {
throw new Error(`Path not supplied!`);
}
try {
await (0, promises_1.access)(
// check path exists
path, fs_1.constants.W_OK | fs_1.constants.R_OK);
this.debug(`${path} - OK...`);
return true;
}
catch (_error) {
// if not then create it
this.debug(`${path} does NOT exist...`);
this.debug(`Creating ${path} ...`);
try {
await (0, promises_1.mkdir)(path, { recursive: true });
this.debug(`Created ${path} - OK...`);
return true;
}
catch (_error) {
throw new Error(`Unable to create ${path}!`);
}
}
}
}
exports.FileStore = FileStore;