From 20635996114ca67a35fc03afd8bb29c2d1cc4f90 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe <259325684+Sucukdeluxe@users.noreply.github.com> Date: Wed, 26 Aug 2026 09:46:44 +0200 Subject: [PATCH] fix: preserve delivered folder reservations --- lib/folder-monitor.js | 9 ++++++--- tests/folder-monitor.test.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/folder-monitor.js b/lib/folder-monitor.js index 128cf37..d88eeee 100644 --- a/lib/folder-monitor.js +++ b/lib/folder-monitor.js @@ -227,11 +227,14 @@ class FolderMonitor extends EventEmitter { const files = this._batchBuffer.splice(0); this._batchTimer = null; if (files.length === 0) return; - const listenerError = this._emitEvent('new-files', [files], generation); + const dispatchSeenReservations = new Set(); for (const emittedPath of files) { const emittedNormalized = this._normalizePath(emittedPath); - if (!this._batchSeenReservations.delete(emittedNormalized)) continue; - if (listenerError) this._seenFiles.delete(emittedNormalized); + if (this._batchSeenReservations.delete(emittedNormalized)) dispatchSeenReservations.add(emittedNormalized); + } + const listenerError = this._emitEvent('new-files', [files], generation); + if (listenerError) { + for (const emittedNormalized of dispatchSeenReservations) this._seenFiles.delete(emittedNormalized); } if (listenerError && this._acceptCallback(generation)) { this._lastError = 'Ordnerüberwachung fehlgeschlagen'; diff --git a/tests/folder-monitor.test.js b/tests/folder-monitor.test.js index 3c98058..5018b73 100644 --- a/tests/folder-monitor.test.js +++ b/tests/folder-monitor.test.js @@ -374,6 +374,39 @@ test('watcher add paused before batch timeout is emitted exactly once by resume assert.equal(monitor.status().seenCount, 1); }); +test('synchronous pause during successful batch emission does not duplicate on resume', async () => { + const timers = createManualTimers(); + const watchers = []; + const newFiles = []; + const filePath = 'C:\\watch\\delivered.mkv'; + let pausePromise; + const monitor = new FolderMonitor({ + watch: () => { + const watcher = new EventEmitter(); + watcher.close = async () => {}; + watchers.push(watcher); + return watcher; + }, + access: async () => {}, + walkFolder: async () => [{ path: filePath, name: 'delivered.mkv', size: 1 }], + stat: async () => ({ mtimeMs: 1 }), + ...timers + }); + monitor.on('new-files', (files) => { + newFiles.push(files); + pausePromise = monitor.pause(); + }); + const settings = { folderPath: 'C:\\watch', extensions: 'mkv', skipDuplicates: true, reconcileIntervalMinutes: 5 }; + monitor.start(settings); + watchers[0].emit('add', filePath); + await timers.runTimeouts(); + await pausePromise; + assert.deepEqual(newFiles, [[filePath]]); + await monitor.resume(settings); + assert.deepEqual(newFiles, [[filePath]]); + assert.equal(monitor.status().seenCount, 1); +}); + test('pause rollback never deletes historical seen state from a dedupe-off batch', async () => { const timers = createManualTimers(); const watchers = [];