diff --git a/server/scanner/LibraryItemScanData.js b/server/scanner/LibraryItemScanData.js index f583b8278..032a636cd 100644 --- a/server/scanner/LibraryItemScanData.js +++ b/server/scanner/LibraryItemScanData.js @@ -243,7 +243,7 @@ class LibraryItemScanData { } else { libraryFilesAdded = libraryFilesAdded.filter((lf) => lf !== matchingLibraryFile) let existingLibraryFileBefore = structuredClone(existingLibraryFile) - if (this.compareUpdateLibraryFile(existingLibraryItem.path, existingLibraryFile, matchingLibraryFile, libraryScan)) { + if (LibraryItemScanData.compareUpdateLibraryFile(existingLibraryItem.path, existingLibraryFile, matchingLibraryFile, libraryScan)) { this.libraryFilesModified.push({ old: existingLibraryFileBefore, new: existingLibraryFile }) this.hasChanges = true } @@ -289,10 +289,10 @@ class LibraryItemScanData { * @param {string} libraryItemPath * @param {LibraryItem.LibraryFileObject} existingLibraryFile * @param {import('../objects/files/LibraryFile')} scannedLibraryFile - * @param {import('./LibraryScan')} libraryScan + * @param {import('./LibraryScan') | import('./ScanLogger')} libraryScan * @returns {boolean} false if no changes */ - compareUpdateLibraryFile(libraryItemPath, existingLibraryFile, scannedLibraryFile, libraryScan) { + static compareUpdateLibraryFile(libraryItemPath, existingLibraryFile, scannedLibraryFile, libraryScan) { let hasChanges = false if (existingLibraryFile.ino !== scannedLibraryFile.ino && existingLibraryFile.deviceId !== scannedLibraryFile.deviceId) { diff --git a/test/server/MockDatabase.js b/test/server/MockDatabase.js index 880eded69..142eb4af2 100644 --- a/test/server/MockDatabase.js +++ b/test/server/MockDatabase.js @@ -2,6 +2,8 @@ const Database = require('../../server/Database') const { Sequelize } = require('sequelize') const LibraryFile = require('../../server/objects/files/LibraryFile') const fileUtils = require('../../server/utils/fileUtils') +const FileMetadata = require('../../server/objects/metadata/FileMetadata') +const Path = require('path') const sinon = require('sinon') async function loadTestDatabase(mockFileInfo) { @@ -120,3 +122,22 @@ function stubFileUtils() { }) } exports.stubFileUtils = stubFileUtils + +/** @returns {{ libraryFolderId: any; libraryId: any; mediaType: any; ino: any; deviceId: any; mtimeMs: any; ctimeMs: any; birthtimeMs: any; path: any; relPath: any; isFile: any; mediaMetadata: any; libraryFiles: any; }} */ +function buildFileProperties(path = '/tmp/foo.epub', ino = '12345', deviceId = '9876') { + const metadata = new FileMetadata() + metadata.filename = Path.basename(path) + metadata.path = path + metadata.relPath = path + metadata.ext = Path.extname(path) + + return { + ino: ino, + deviceId: deviceId, + metadata: metadata, + isSupplementary: false, + addedAt: Date.now(), + updatedAt: Date.now() + } +} +exports.buildFileProperties = buildFileProperties diff --git a/test/server/objects/LibraryItemScanData.test.js b/test/server/objects/LibraryItemScanData.test.js index 6322ef7db..03ef963aa 100644 --- a/test/server/objects/LibraryItemScanData.test.js +++ b/test/server/objects/LibraryItemScanData.test.js @@ -1,5 +1,68 @@ +const chai = require('chai') +const expect = chai.expect +const sinon = require('sinon') +const rewire = require('rewire') +const Path = require('path') + +const { stubFileUtils, getMockFileInfo, loadTestDatabase, buildFileProperties } = require('../MockDatabase') + +const LibraryItemScanData = require('../../../server/scanner/LibraryItemScanData') +const LibraryFile = require('../../../server/objects/files/LibraryFile') +const LibraryScan = require('../../../server/scanner/LibraryScan') + // TODO - need to check -// compareUpdateLibraryFile +// compareUpdateLibraryFile - returns false if no changes; true if changes +describe('compareUpdateLibraryFileWithDeviceId', () => { + it('fileChangeDetectedWhenInodeAndDeviceIdPairDiffers', () => { + const existing_lf = buildLibraryFileObject('/tmp/file.pdf', '4432', '300') + const scanned_lf = new LibraryFile({ + ino: '1', + deviceId: '100' + }) + + expect(existing_lf.ino).to.not.equal(scanned_lf.ino) + expect(existing_lf.deviceId).to.not.equal(scanned_lf.deviceId) + const changeDetected = LibraryItemScanData.compareUpdateLibraryFile('/file/path.pdf', existing_lf, scanned_lf, new LibraryScan()) + expect(changeDetected).to.be.true + }) + + it('fileChangeNotDetectedWhenInodeSameButDeviceIdDiffers', () => { + // Same inode on different deviceId does NOT mean these are the same file + const existing_lf = buildLibraryFileObject('/tmp/file.pdf', '4432', '300') + const scanned_lf = new LibraryFile(buildLibraryFileObject('/tmp/file.pdf', '4432', '100')) + + expect(existing_lf.ino).to.equal(scanned_lf.ino) + expect(existing_lf.deviceId).to.not.equal(scanned_lf.deviceId) + const changeDetected = LibraryItemScanData.compareUpdateLibraryFile('/file/path.pdf', existing_lf, scanned_lf, new LibraryScan()) + expect(changeDetected).to.be.false + }) +}) + +/** + * @returns {import('../../../server/models/LibraryItem').LibraryFileObject} + * @param {string} [path] + * @param {string} [ino] + * @param {string} [deviceId] + */ +function buildLibraryFileObject(path, ino, deviceId) { + return { + ino: ino, + deviceId: deviceId, + isSupplementary: false, + addedAt: 0, + updatedAt: 0, + metadata: { + filename: Path.basename(path), + ext: Path.extname(path), + path: path, + relPath: path, + size: 0, + mtimeMs: 0, + ctimeMs: 0, + birthtimeMs: 0 + } + } +} // checkEbookFileRemoved // checkAudioFileRemoved // libraryItemObject() diff --git a/test/server/objects/SimilarLibraryFileObjects.test.js b/test/server/objects/SimilarLibraryFileObjects.test.js index 0b9308fb0..d71df8fa0 100644 --- a/test/server/objects/SimilarLibraryFileObjects.test.js +++ b/test/server/objects/SimilarLibraryFileObjects.test.js @@ -4,14 +4,13 @@ const sinon = require('sinon') const Path = require('path') const Database = require('../../../server/Database') -const { loadTestDatabase, stubFileUtils, getMockFileInfo } = require('../MockDatabase') +const { loadTestDatabase, stubFileUtils, getMockFileInfo, buildFileProperties } = require('../MockDatabase') // TODO: all of these classes duplicate each other. const LibraryFile = require('../../../server/objects/files/LibraryFile') const EBookFile = require('../../../server/objects/files/EBookFile') const AudioFile = require('../../../server/objects/files/AudioFile') const LibraryItemScanData = require('../../../server/scanner/LibraryItemScanData') -const FileMetadata = require('../../../server/objects/metadata/FileMetadata') const fileProperties = buildFileProperties() const lf = new LibraryFile(fileProperties) @@ -77,25 +76,6 @@ describe('ObjectSetsDeviceIdWhenSerialized', () => { }) }) -/** @returns {{ libraryFolderId: any; libraryId: any; mediaType: any; ino: any; deviceId: any; mtimeMs: any; ctimeMs: any; birthtimeMs: any; path: any; relPath: any; isFile: any; mediaMetadata: any; libraryFiles: any; }} */ -function buildFileProperties() { - const path = '/tmp/foo.epub' - const metadata = new FileMetadata() - metadata.filename = Path.basename(path) - metadata.path = path - metadata.relPath = path - metadata.ext = Path.extname(path) - - return { - ino: '12345', - deviceId: '9876', - metadata: metadata, - isSupplementary: false, - addedAt: Date.now(), - updatedAt: Date.now() - } -} - function buildLibraryItemProperties(fileProperties) { return { id: '7792E90F-D526-4636-8A38-EA8342E71FEE',