UNPKG

@gecogvidanto/plugin-nedb

Version:

Nebd local database management plugin for ĞecoĞvidanto

169 lines 6.03 kB
"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