UNPKG

file-state-monitor

Version:

Completely customizable file monitoring using states

236 lines (184 loc) 6.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _fsExtra = _interopRequireDefault(require("fs-extra")); var _path = _interopRequireDefault(require("path")); var _states = require("./states"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const FILE_STATUS_CREATED = 'created'; const FILE_STATUS_CHANGED = 'changed'; const FILE_STATUS_DELETED = 'deleted'; /** * Monitors the state for a set of files */ class FileMonitor { /** * Constrcuts a new FileStateManager using the given file state implementation * to compare files */ constructor(fileStateClass) { /** * Map of the initially loaded files from the state file * * @type {Map.<string, BaseFileState>} * @private */ this._loadedFiles = new Map(); /** * Map containing processed files * * @type {Map.<string, BaseFileState>} * @private */ this._processedFiles = new Map(); /** * Map with state of changed files * * @type {Map.<string, string>} * @private */ this._changedFiles = new Map(); if (!fileStateClass || !(fileStateClass.prototype instanceof _states.BaseFileState)) { throw new TypeError('You need to provide a valid file state class which extends BaseFileState'); } /** * The file state class used to compare two files * * @type {BaseFileState} * @private */ this._fileStateClass = fileStateClass; } static get FILE_STATUS_CREATED() { return FILE_STATUS_CREATED; } static get FILE_STATUS_CHANGED() { return FILE_STATUS_CHANGED; } static get FILE_STATUS_DELETED() { return FILE_STATUS_DELETED; } /** * Loads cached file state information from a state file into this FileMonitor * instance * * Returns true opon successfully loading the previous state file. If no * previous state file is available or its content can not be parsed, this * method will return false and the FileMonitor instance is left untouched. * * @param {string} statePathAndFilename Full path to the state file * @return {boolean} True if the state file was loaded successfully, false if not */ load(statePathAndFilename) { if (!_fsExtra.default.existsSync(statePathAndFilename)) { return false; } try { let stateJson = JSON.parse(_fsExtra.default.readFileSync(statePathAndFilename).toString()); for (let stateEntry of stateJson.files) { let fileState = new this._fileStateClass(stateEntry); this._loadedFiles.set(fileState.path, fileState); } return true; } catch (e) {// possibly corrupted state data, just silently ignore it } return false; } /** * Write the current file states back to disk * * @param {string} statePathAndFilename Full path to the state file */ write(statePathAndFilename) { let stateData = { files: [] }; for (let fileState of this._processedFiles.values()) { stateData.files.push(fileState.toJson()); } _fsExtra.default.ensureDirSync(_path.default.dirname(statePathAndFilename)); _fsExtra.default.writeFileSync(statePathAndFilename, JSON.stringify(stateData)); } /** * Monitor a new file or directory with this state monitor * * @param {string} pathToMonitor Full path of the file or directory to be monitored */ monitorPath(pathToMonitor) { if (!_fsExtra.default.existsSync(pathToMonitor)) { return; } let stats = _fsExtra.default.statSync(pathToMonitor); if (stats.isFile()) { this.updateFileState(pathToMonitor); } else if (stats.isDirectory()) { for (let entryName of _fsExtra.default.readdirSync(pathToMonitor)) { let fullPath = _path.default.join(pathToMonitor, entryName); this.monitorPath(fullPath); } } } /** * Detects changes to the file and updates its state * * If the file did not exists in our loaded state before, add it as a new one. * For existing files check wether they have changed and update the file states * map and move them from the loaded map to processed. * * @param {string} pathAndFilename Full path and filename of the file to update * @private */ updateFileState(pathAndFilename) { let newFileState = new this._fileStateClass({ path: pathAndFilename }); let existingFileState = this._loadedFiles.get(pathAndFilename); if (existingFileState === undefined) { this._changedFiles.set(pathAndFilename, FILE_STATUS_CREATED); this._processedFiles.set(pathAndFilename, newFileState); } else { this._loadedFiles.delete(pathAndFilename); if (newFileState.isDifferentThan(existingFileState)) { this._changedFiles.set(pathAndFilename, FILE_STATUS_CHANGED); this._processedFiles.set(pathAndFilename, newFileState); } else { this._processedFiles.set(pathAndFilename, existingFileState); } } } /** * Updates the file monitor with the set of paths, replacing any previously * monitored files. * * @param {Array.<string>} paths Array of new paths to monitor */ update(paths) { if (!Array.isArray(paths)) { throw new TypeError('The file monitor needs to be updated with an array of paths.'); } this._loadedFiles.clear(); this._processedFiles.forEach((fileState, path) => { this._loadedFiles.set(path, fileState); }); this._processedFiles.clear(); this._changedFiles.clear(); paths.forEach(path => { this.monitorPath(path); }); } /** * Gets a map of all changed files and their respective FILE_STATUS_* value * * @return {Map.<string, string>} */ getChangedFiles() { let changedFiles = new Map(this._changedFiles); for (let removedFile of this._loadedFiles.values()) { changedFiles.set(removedFile.path, FILE_STATUS_DELETED); } return changedFiles; } } exports.default = FileMonitor;