@gecogvidanto/plugin-nedb
Version:
Nebd local database management plugin for ĞecoĞvidanto
169 lines • 6.03 kB
JavaScript
"use strict";
/*
* This file is part of @gecogvidanto/plugin-nedb.
* Copyright (C) 2020 Stéphane Veyret
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.isBinaryData = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const util_1 = require("util");
const tools_1 = require("../tools");
function isBinaryData(data) {
return (!!data &&
typeof data === 'object' &&
'type' in data &&
'hash' in data &&
data.type === '$$BinaryData$$');
}
exports.isBinaryData = isBinaryData;
/**
* A datastore managing binary data.
*/
class BinaryStore {
/**
* Create a new binary store.
*
* @param allDb - The full database system.
* @param db - The database (actually, the binary datastore).
* @param binaryDirectory - The directory where binary items will be saved.
* @param compactInterval - Interval between datastore cleaning.
*/
constructor(allDb, db, binaryDirectory, compactInterval) {
this.allDb = allDb;
this.db = db;
this.binaryDirectory = binaryDirectory;
this.compactInterval = compactInterval;
// eslint-disable-next-line id-blacklist
this.nextCompact = Number.MAX_SAFE_INTEGER;
}
/**
* Ensure store is ready.
*/
async start() {
await this.db.ensureIndex({
fieldName: 'hash',
unique: true,
});
await this.compact();
}
/**
* Save a binary item.
*
* @param storeName - The name of the store having binary data to store.
* @param recordId - The identifier of the record containing binary data.
* @param hash - The hash of the data to store.
* @param data - The data to store.
*/
async save(storeName, recordId, hash, data) {
if (!(await tools_1.exists(this.pathFor(hash)))) {
await util_1.promisify(fs_1.writeFile)(this.pathFor(hash), data);
}
await this.db.update({ hash }, { $addToSet: { usages: { storeName, recordId } } }, { upsert: true });
if (--this.nextCompact <= 0) {
await this.compact();
}
}
/**
* Read the binary data for a hash.
*
* @param hash - The hash for which to read data.
* @returns The read binary data.
*/
async read(hash) {
const data = await this.db.findOne({ hash });
if (data !== null && (await tools_1.exists(this.pathFor(hash)))) {
return util_1.promisify(fs_1.readFile)(this.pathFor(hash));
}
else {
throw new Error('Corrupted database — Binary file not found');
}
}
/**
* Clean the database.
*/
async compact() {
await this.checkUsage();
await this.checkConsistency();
this.db.db.persistence.compactDatafile();
this.nextCompact = this.compactInterval;
}
/**
* Remove unused binaries.
*/
async checkUsage() {
const allBinaries = await this.db.find({});
await Promise.all(allBinaries.map(async (binary) => {
const toRemove = [];
await Promise.all(binary.usages.map(async (usage) => {
let found = false;
if (usage.storeName in this.allDb.dbs) {
const store = this.allDb.dbs[usage.storeName];
const record = await store.findOne({
_id: usage.recordId,
});
if (record) {
for (const key in record) {
if (isBinaryData(record[key])) {
found = found || record[key].hash === binary.hash;
}
}
}
}
if (!found) {
toRemove.push(usage);
}
}));
if (toRemove.length > 0) {
binary.usages = binary.usages.filter(usage => !toRemove.includes(usage));
if (binary.usages.length > 0) {
await this.db.update({ _id: binary._id }, { $set: { usages: binary.usages } });
}
else {
await this.db.remove({ _id: binary._id });
if (await tools_1.exists(this.pathFor(binary.hash))) {
await util_1.promisify(fs_1.unlink)(this.pathFor(binary.hash));
}
}
}
}));
}
/**
* Check consistency between existing files and stored hashes.
*/
async checkConsistency() {
const files = await util_1.promisify(fs_1.readdir)(this.binaryDirectory);
await Promise.all(files
.filter(file => !file.endsWith('.nedb'))
.map(async (file) => {
const count = await this.db.count({ hash: file });
if (count === 0) {
await util_1.promisify(fs_1.unlink)(path_1.resolve(this.binaryDirectory, file));
}
}));
}
/**
* Get the path of the file containing data for the binary hash.
*
* @param hash - The binary hash.
* @returns The path of the file.
*/
pathFor(hash) {
return path_1.resolve(this.binaryDirectory, hash);
}
}
exports.default = BinaryStore;
//# sourceMappingURL=BinaryStore.js.map