/* IMPORT */ import { RENAME_TIMEOUT } from './constants.js'; import { FileType, TargetEvent } from './enums.js'; import Utils from './utils.js'; import WatcherLocksResolver from './watcher_locks_resolver.js'; /* MAIN */ //TODO: Use a better name for this thing, maybe "RenameDetector" class WatcherLocker { /* CONSTRUCTOR */ constructor(watcher) { this._watcher = watcher; this.reset(); } /* API */ getLockAdd(config, timeout = RENAME_TIMEOUT) { const { ino, targetPath, events, locks } = config; const emit = () => { const otherPath = this._watcher._poller.paths.find(ino || -1, path => path !== targetPath); // Maybe this is actually a rename in a case-insensitive filesystem if (otherPath && otherPath !== targetPath) { if (Utils.fs.getRealPath(targetPath, true) === otherPath) return; //TODO: This seems a little too special-casey this._watcher.event(events.rename, otherPath, targetPath); } else { this._watcher.event(events.add, targetPath); } }; if (!ino) return emit(); const cleanup = () => { locks.add.delete(ino); WatcherLocksResolver.remove(free); }; const free = () => { cleanup(); emit(); }; WatcherLocksResolver.add(free, timeout); const resolve = () => { const unlink = locks.unlink.get(ino); if (!unlink) return; // No matching "unlink" lock found, skipping cleanup(); const targetPathPrev = unlink(); if (targetPath === targetPathPrev) { if (events.change) { if (this._watcher._poller.stats.has(targetPath)) { this._watcher.event(events.change, targetPath); } } } else { this._watcher.event(events.rename, targetPathPrev, targetPath); } }; locks.add.set(ino, resolve); resolve(); } getLockUnlink(config, timeout = RENAME_TIMEOUT) { const { ino, targetPath, events, locks } = config; const emit = () => { this._watcher.event(events.unlink, targetPath); }; if (!ino) return emit(); const cleanup = () => { locks.unlink.delete(ino); WatcherLocksResolver.remove(free); }; const free = () => { cleanup(); emit(); }; WatcherLocksResolver.add(free, timeout); const overridden = () => { cleanup(); return targetPath; }; locks.unlink.set(ino, overridden); locks.add.get(ino)?.(); } getLockTargetAdd(targetPath, timeout) { const ino = this._watcher._poller.getIno(targetPath, TargetEvent.ADD, FileType.FILE); return this.getLockAdd({ ino, targetPath, events: WatcherLocker.FILE_EVENTS, locks: this._locksFile }, timeout); } getLockTargetAddDir(targetPath, timeout) { const ino = this._watcher._poller.getIno(targetPath, TargetEvent.ADD_DIR, FileType.DIR); return this.getLockAdd({ ino, targetPath, events: WatcherLocker.DIR_EVENTS, locks: this._locksDir }, timeout); } getLockTargetUnlink(targetPath, timeout) { const ino = this._watcher._poller.getIno(targetPath, TargetEvent.UNLINK, FileType.FILE); return this.getLockUnlink({ ino, targetPath, events: WatcherLocker.FILE_EVENTS, locks: this._locksFile }, timeout); } getLockTargetUnlinkDir(targetPath, timeout) { const ino = this._watcher._poller.getIno(targetPath, TargetEvent.UNLINK_DIR, FileType.DIR); return this.getLockUnlink({ ino, targetPath, events: WatcherLocker.DIR_EVENTS, locks: this._locksDir }, timeout); } reset() { this._locksAdd = new Map(); this._locksAddDir = new Map(); this._locksUnlink = new Map(); this._locksUnlinkDir = new Map(); this._locksDir = { add: this._locksAddDir, unlink: this._locksUnlinkDir }; this._locksFile = { add: this._locksAdd, unlink: this._locksUnlink }; } } WatcherLocker.DIR_EVENTS = { add: TargetEvent.ADD_DIR, rename: TargetEvent.RENAME_DIR, unlink: TargetEvent.UNLINK_DIR }; WatcherLocker.FILE_EVENTS = { add: TargetEvent.ADD, change: TargetEvent.CHANGE, rename: TargetEvent.RENAME, unlink: TargetEvent.UNLINK }; /* EXPORT */ export default WatcherLocker;