From aae808544e686bb1e8ab49dee8f275b7a508df76 Mon Sep 17 00:00:00 2001 From: Jason Axley Date: Thu, 21 Aug 2025 11:48:59 -0700 Subject: [PATCH] Updated logic for checkAudioBookRemoved --- server/models/Book.js | 1 + server/scanner/LibraryItemScanData.js | 2 +- .../objects/LibraryItemScanData.test.js | 102 ++++++++++++++---- 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/server/models/Book.js b/server/models/Book.js index 96371f3a2..701c9d1bc 100644 --- a/server/models/Book.js +++ b/server/models/Book.js @@ -45,6 +45,7 @@ const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilter * @typedef AudioFileObject * @property {number} index * @property {string} ino + * @property {string} deviceId * @property {{filename:string, ext:string, path:string, relPath:string, size:number, mtimeMs:number, ctimeMs:number, birthtimeMs:number}} metadata * @property {number} addedAt * @property {number} updatedAt diff --git a/server/scanner/LibraryItemScanData.js b/server/scanner/LibraryItemScanData.js index 032a636cd..01adafce0 100644 --- a/server/scanner/LibraryItemScanData.js +++ b/server/scanner/LibraryItemScanData.js @@ -332,7 +332,7 @@ class LibraryItemScanData { return true } // Fallback to check inode value - return this.audioLibraryFilesRemoved.some((af) => af.ino === existingAudioFile.ino) + return this.audioLibraryFilesRemoved.some((af) => af.ino === existingAudioFile.ino && af.deviceId === existingAudioFile.deviceId) } /** diff --git a/test/server/objects/LibraryItemScanData.test.js b/test/server/objects/LibraryItemScanData.test.js index 03ef963aa..463a3f75a 100644 --- a/test/server/objects/LibraryItemScanData.test.js +++ b/test/server/objects/LibraryItemScanData.test.js @@ -38,6 +38,52 @@ describe('compareUpdateLibraryFileWithDeviceId', () => { }) }) +describe('checkAudioFileRemoved', function () { + this.timeout(0) + it('doesNotDetectFileRemovedWhenInodeIsSameButDeviceIdDiffers', () => { + const lisd = new LibraryItemScanData(buildFileProperties('/library/book/file.mp3', '1', '1000')) + lisd.libraryFilesRemoved.push(buildLibraryFileObject('/library/book/file.mp3', '1', '1000')) + const af_obj = buildAudioFileObject('/library/someotherbook/chapter1.mp3', '1', '200') + + const fileRemoved = lisd.checkAudioFileRemoved(af_obj) + + expect(fileRemoved).to.be.false + }) + + it('detectsFileRemovedWhenNameDoesNotMatchButInodeAndDeviceIdMatch', () => { + const lisd = new LibraryItemScanData(buildFileProperties('/library/book/file.mp3', '1', '1000')) + lisd.libraryFilesRemoved.push(buildLibraryFileObject('/library/book/file.mp3', '1', '1000')) + const af_obj = buildAudioFileObject('/library/someotherbook/chapter1.mp3', '1', '1000') + + expect(lisd.path).to.not.equal(af_obj.metadata.path) + const fileRemoved = lisd.checkAudioFileRemoved(af_obj) + + expect(fileRemoved).to.be.true + }) +}) + +// checkEbookFileRemoved + +// libraryItemObject() +/* +new LibraryItemScanData({ + libraryFolderId: folder.id, + libraryId: library.id, + mediaType: library.mediaType, + ino: libraryItemStats.ino, + deviceId: libraryItemStats.dev, + mtimeMs: libraryItemStats.mtimeMs || 0, + ctimeMs: libraryItemStats.ctimeMs || 0, + birthtimeMs: libraryItemStats.birthtimeMs || 0, + path: libraryItemData.path, + relPath: libraryItemData.relPath, + isFile: isSingleMediaItem, + mediaMetadata: libraryItemData.mediaMetadata || null, + libraryFiles + }) + +*/ + /** * @returns {import('../../../server/models/LibraryItem').LibraryFileObject} * @param {string} [path] @@ -63,24 +109,40 @@ function buildLibraryFileObject(path, ino, deviceId) { } } } -// checkEbookFileRemoved -// checkAudioFileRemoved -// libraryItemObject() -/* -new LibraryItemScanData({ - libraryFolderId: folder.id, - libraryId: library.id, - mediaType: library.mediaType, - ino: libraryItemStats.ino, - deviceId: libraryItemStats.dev, - mtimeMs: libraryItemStats.mtimeMs || 0, - ctimeMs: libraryItemStats.ctimeMs || 0, - birthtimeMs: libraryItemStats.birthtimeMs || 0, - path: libraryItemData.path, - relPath: libraryItemData.relPath, - isFile: isSingleMediaItem, - mediaMetadata: libraryItemData.mediaMetadata || null, - libraryFiles - }) -*/ +/** @returns {import('../../../server/models/Book').AudioFileObject} */ +function buildAudioFileObject(path = '/library/somebook/file.mp3', ino = '1', deviceId = '1000') { + return { + index: 0, + ino: ino, + deviceId: deviceId, + metadata: { + filename: Path.basename(path), + ext: Path.extname(path), + path: path, + relPath: path, + size: 0, + mtimeMs: 0, + ctimeMs: 0, + birthtimeMs: 0 + }, + addedAt: 0, + updatedAt: 0, + trackNumFromMeta: 0, + discNumFromMeta: 0, + trackNumFromFilename: 0, + discNumFromFilename: 0, + manuallyVerified: false, + format: '', + duration: 0, + bitRate: 0, + language: '', + codec: '', + timeBase: '', + channels: 0, + channelLayout: '', + chapters: [], + metaTags: undefined, + mimeType: '' + } +}