/
Configuration.js
141 lines (113 loc) · 4.05 KB
/
Configuration.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
const Ajv = require("ajv");
const EventEmitter = require("events").EventEmitter;
const fs = require("fs");
const nestedObjectAssign = require("nested-object-assign");
const os = require("os");
const path = require("path");
const DEFAULT_SETTINGS = require("./res/default_config.json");
const Logger = require("./Logger");
const SCHEMAS = require("./doc/Configuration.openapi.json");
const Tools = require("./utils/Tools");
class Configuration {
constructor() {
/** @private */
this.eventEmitter = new EventEmitter();
this.settings = structuredClone(DEFAULT_SETTINGS);
this.location = process.env.VALETUDO_CONFIG_PATH ?? path.join(os.tmpdir(), "valetudo_config.json");
this.loadConfig();
}
/**
* @param {string} key
* @returns {*}
*/
get(key) {
return this.settings[key];
}
getAll() {
return this.settings;
}
/**
* @param {string} key
* @param {string|object} val
*/
set(key, val) {
this.settings[key] = val;
this.persist();
this.eventEmitter.emit(CONFIG_UPDATE_EVENT, key);
}
persist() {
fs.writeFileSync(this.location, this.getSerializedConfig());
}
/**
* @public
* @param {(key: string) => void} listener
*/
onUpdate(listener) {
this.eventEmitter.on(CONFIG_UPDATE_EVENT, listener);
}
/**
* @private
*/
loadConfig() {
/* load an existing configuration file. if it is not present or invalid, create it using the default configuration */
if (fs.existsSync(this.location)) {
Logger.info("Loading configuration file:", this.location);
try {
//@ts-ignore
const ajv = new Ajv({removeAdditional: true});
Object.keys(SCHEMAS.components.schemas).forEach(schemaName => {
ajv.addSchema(SCHEMAS.components.schemas[schemaName], "#/components/schemas/" + schemaName);
});
const config = fs.readFileSync(this.location, {"encoding": "utf-8"}).toString();
const parsedConfig = JSON.parse(config);
if (!ajv.validate(SCHEMAS.components.schemas.Configuration, parsedConfig)) {
Logger.error("Error while validating configuration file", ajv.errors);
// noinspection ExceptionCaughtLocallyJS
throw new Error("Schema Validation Error");
}
this.settings = nestedObjectAssign(
{},
this.settings,
parsedConfig
);
if (this.getSerializedConfig() !== config) {
Logger.info(`Schema changes were applied to the configuration file at: ${this.location}`);
this.persist();
}
} catch (e) {
Logger.error("Invalid configuration file: ", e.message);
Logger.info("Writing new configuration file using defaults");
try {
fs.renameSync(this.location, this.location + ".backup");
Logger.info(`Backup moved to ${this.location}.backup`);
} catch (e) {
Logger.info("Failed to move backup", e);
}
this.persist();
}
} else {
Logger.info(`No configuration file present. Creating one at: ${this.location}`);
Tools.MK_DIR_PATH(path.dirname(this.location));
this.persist();
}
}
/**
* @public
*/
reset() {
Logger.info("Restoring config to default settings.");
this.settings = structuredClone(DEFAULT_SETTINGS);
this.persist();
Object.keys(this.settings).forEach(key => {
this.eventEmitter.emit(CONFIG_UPDATE_EVENT, key);
});
}
/**
* @private
*/
getSerializedConfig() {
return JSON.stringify(this.settings, null, 2);
}
}
const CONFIG_UPDATE_EVENT = "ConfigUpdated";
module.exports = Configuration;