zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
125 lines (96 loc) • 3.76 kB
text/typescript
import fs from "node:fs";
import {logger} from "../utils/logger";
import type {DatabaseEntry, EntityType} from "./tstype";
const NS = "zh:controller:database";
export class Database {
private entries: {[id: number]: DatabaseEntry};
private path: string;
private maxId: number;
private constructor(entries: {[id: number]: DatabaseEntry}, path: string) {
this.entries = entries;
this.maxId = Math.max(...Object.keys(entries).map((t) => Number(t)), 0);
this.path = path;
}
public static open(path: string): Database {
const entries: {[id: number]: DatabaseEntry} = {};
if (fs.existsSync(path)) {
const file = fs.readFileSync(path, "utf-8");
for (const row of file.split("\n")) {
if (!row) {
continue;
}
try {
const json = JSON.parse(row);
if (json.id != null) {
entries[json.id] = json;
}
} catch (error) {
logger.error(`Corrupted database line, ignoring. ${error}`, NS);
}
}
}
return new Database(entries, path);
}
public *getEntriesIterator(type: EntityType[]): Generator<DatabaseEntry> {
for (const id in this.entries) {
const entry = this.entries[id];
if (type.includes(entry.type)) {
yield entry;
}
}
}
public insert(databaseEntry: DatabaseEntry): void {
if (this.entries[databaseEntry.id]) {
throw new Error(`DatabaseEntry with ID '${databaseEntry.id}' already exists`);
}
this.entries[databaseEntry.id] = databaseEntry;
this.write();
}
public update(databaseEntry: DatabaseEntry, write: boolean): void {
if (!this.entries[databaseEntry.id]) {
throw new Error(`DatabaseEntry with ID '${databaseEntry.id}' does not exist`);
}
this.entries[databaseEntry.id] = databaseEntry;
if (write) {
this.write();
}
}
public remove(id: number): void {
if (!this.entries[id]) {
throw new Error(`DatabaseEntry with ID '${id}' does not exist`);
}
delete this.entries[id];
this.write();
}
public has(id: number): boolean {
return Boolean(this.entries[id]);
}
public newID(): number {
this.maxId += 1;
return this.maxId;
}
public write(): void {
logger.debug(`Writing database to '${this.path}'`, NS);
let lines = "";
for (const id in this.entries) {
lines += `${JSON.stringify(this.entries[id])}\n`;
}
const tmpPath = `${this.path}.tmp`;
try {
// If there already exsits a database.db.tmp, rename it to database.db.tmp.<now>
const dateTmpPath = `${tmpPath}.${new Date().toISOString().replaceAll(":", "-")}`;
fs.renameSync(tmpPath, dateTmpPath);
// If we got this far, we succeeded! Warn the user about this
logger.warning(`Found '${tmpPath}' when writing database, indicating past write failure; renamed it to '${dateTmpPath}'`, NS);
} catch {
// Nothing to catch; if the renameSync fails, we ignore that exception
}
const fd = fs.openSync(tmpPath, "w");
fs.writeFileSync(fd, lines.slice(0, -1)); // remove last newline, no effect if empty string
// Ensure file is on disk https://github.com/Koenkk/zigbee2mqtt/issues/11759
fs.fsyncSync(fd);
fs.closeSync(fd);
fs.renameSync(tmpPath, this.path);
}
}
export default Database;