From 9511122bae10ae67dd2299961e35ea2e82c8f284 Mon Sep 17 00:00:00 2001 From: mikiher Date: Tue, 19 Mar 2024 19:28:26 +0200 Subject: [PATCH 1/4] Fix LibraryItem and Media file update logic for library scans --- server/scanner/BookScanner.js | 32 +++++++++++++++-- server/scanner/LibraryItemScanData.js | 49 +++++++++++++++++++++++---- server/scanner/PodcastScanner.js | 18 ++++++++-- 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/server/scanner/BookScanner.js b/server/scanner/BookScanner.js index c738c52e..c12441b2 100644 --- a/server/scanner/BookScanner.js +++ b/server/scanner/BookScanner.js @@ -20,6 +20,7 @@ const LibraryScan = require("./LibraryScan") const OpfFileScanner = require('./OpfFileScanner') const NfoFileScanner = require('./NfoFileScanner') const AbsMetadataFileScanner = require('./AbsMetadataFileScanner') +const EBookFile = require("../objects/files/EBookFile") /** * Metadata for books pulled from files @@ -84,7 +85,7 @@ class BookScanner { // Update audio files that were modified if (libraryItemData.audioLibraryFilesModified.length) { - let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(existingLibraryItem.mediaType, libraryItemData, libraryItemData.audioLibraryFilesModified) + let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(existingLibraryItem.mediaType, libraryItemData, libraryItemData.audioLibraryFilesModified.map(lf => lf.new)) media.audioFiles = media.audioFiles.map((audioFileObj) => { let matchedScannedAudioFile = scannedAudioFiles.find(saf => saf.metadata.path === audioFileObj.metadata.path) if (!matchedScannedAudioFile) { @@ -138,11 +139,25 @@ class BookScanner { } // Check if cover was removed - if (media.coverPath && !libraryItemData.imageLibraryFiles.some(lf => lf.metadata.path === media.coverPath) && !(await fsExtra.pathExists(media.coverPath))) { + if (media.coverPath && libraryItemData.imageLibraryFilesRemoved.some(lf => lf.metadata.path === media.coverPath) && !(await fsExtra.pathExists(media.coverPath))) { media.coverPath = null hasMediaChanges = true } + // Update cover if it was modified + if (media.coverPath && libraryItemData.imageLibraryFilesModified.length) { + let coverMatch = libraryItemData.imageLibraryFilesModified.find(iFile => iFile.old.metadata.path === media.coverPath) + if (coverMatch) { + const coverPath = coverMatch.new.metadata.path + if (coverPath !== media.coverPath) { + libraryScan.addLog(LogLevel.DEBUG, `Updating book cover "${media.coverPath}" => "${coverPath}" for book "${media.title}"`) + media.coverPath = coverPath + media.changed('coverPath', true) + hasMediaChanges = true + } + } + } + // Check if cover is not set and image files were found if (!media.coverPath && libraryItemData.imageLibraryFiles.length) { // Prefer using a cover image with the name "cover" otherwise use the first image @@ -157,6 +172,19 @@ class BookScanner { hasMediaChanges = true } + // Update ebook if it was modified + if (media.ebookFile && libraryItemData.ebookLibraryFilesModified.length) { + let ebookMatch = libraryItemData.ebookLibraryFilesModified.find(eFile => eFile.old.metadata.path === media.ebookFile.metadata.path) + if (ebookMatch) { + const ebookFile = new EBookFile(ebookMatch.new) + ebookFile.ebookFormat = ebookFile.metadata.ext.slice(1).toLowerCase() + libraryScan.addLog(LogLevel.DEBUG, `Updating book ebook file "${media.ebookFile.metadata.path}" => "${ebookFile.metadata.path}" for book "${media.title}"`) + media.ebookFile = ebookFile.toJSON() + media.changed('ebookFile', true) + hasMediaChanges = true + } + } + // Check if ebook is not set and ebooks were found if (!media.ebookFile && !librarySettings.audiobooksOnly && libraryItemData.ebookLibraryFiles.length) { // Prefer to use an epub ebook then fallback to the first ebook found diff --git a/server/scanner/LibraryItemScanData.js b/server/scanner/LibraryItemScanData.js index b604e4d7..d5a4a7a2 100644 --- a/server/scanner/LibraryItemScanData.js +++ b/server/scanner/LibraryItemScanData.js @@ -4,6 +4,12 @@ const LibraryItem = require('../models/LibraryItem') const globals = require('../utils/globals') class LibraryItemScanData { + /** + * @typedef LibraryFileModifiedObject + * @property {LibraryItem.LibraryFileObject} old + * @property {LibraryItem.LibraryFileObject} new + */ + constructor(data) { /** @type {string} */ this.libraryFolderId = data.libraryFolderId @@ -39,7 +45,7 @@ class LibraryItemScanData { this.libraryFilesRemoved = [] /** @type {LibraryItem.LibraryFileObject[]} */ this.libraryFilesAdded = [] - /** @type {LibraryItem.LibraryFileObject[]} */ + /** @type {LibraryFileModifiedObject[]} */ this.libraryFilesModified = [] } @@ -77,9 +83,9 @@ class LibraryItemScanData { return (this.audioLibraryFilesRemoved.length + this.audioLibraryFilesAdded.length + this.audioLibraryFilesModified.length) > 0 } - /** @type {LibraryItem.LibraryFileObject[]} */ + /** @type {LibraryFileModifiedObject[]} */ get audioLibraryFilesModified() { - return this.libraryFilesModified.filter(lf => globals.SupportedAudioTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || '')) + return this.libraryFilesModified.filter(lf => globals.SupportedAudioTypes.includes(lf.old.metadata.ext?.slice(1).toLowerCase() || '')) } /** @type {LibraryItem.LibraryFileObject[]} */ @@ -97,12 +103,42 @@ class LibraryItemScanData { return this.libraryFiles.filter(lf => globals.SupportedAudioTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || '')) } + /** @type {LibraryFileModifiedObject[]} */ + get imageLibraryFilesModified() { + return this.libraryFilesModified.filter(lf => globals.SupportedImageTypes.includes(lf.old.metadata.ext?.slice(1).toLowerCase() || '')) + } + + /** @type {LibraryItem.LibraryFileObject[]} */ + get imageLibraryFilesRemoved() { + return this.libraryFilesRemoved.filter(lf => globals.SupportedImageTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || '')) + } + + /** @type {LibraryItem.LibraryFileObject[]} */ + get imageLibraryFilesAdded() { + return this.libraryFilesAdded.filter(lf => globals.SupportedImageTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || '')) + } + /** @type {LibraryItem.LibraryFileObject[]} */ get imageLibraryFiles() { return this.libraryFiles.filter(lf => globals.SupportedImageTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || '')) } - /** @type {import('../objects/files/LibraryFile')[]} */ + /** @type {LibraryFileModifiedObject[]} */ + get ebookLibraryFilesModified() { + return this.libraryFilesModified.filter(lf => globals.SupportedEbookTypes.includes(lf.old.metadata.ext?.slice(1).toLowerCase() || '')) + } + + /** @type {LibraryItem.LibraryFileObject[]} */ + get ebookLibraryFilesRemoved() { + return this.libraryFilesRemoved.filter(lf => globals.SupportedEbookTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || '')) + } + + /** @type {LibraryItem.LibraryFileObject[]} */ + get ebookLibraryFilesAdded() { + return this.libraryFilesAdded.filter(lf => globals.SupportedEbookTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || '')) + } + + /** @type {LibraryItem.LibraryFileObject[]} */ get ebookLibraryFiles() { return this.libraryFiles.filter(lf => globals.SupportedEbookTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || '')) } @@ -153,7 +189,7 @@ class LibraryItemScanData { existingLibraryItem[key] = this[key] this.hasChanges = true - if (key === 'relPath') { + if (key === 'relPath' || key === 'path') { this.hasPathChange = true } } @@ -202,8 +238,9 @@ class LibraryItemScanData { this.hasChanges = true } else { libraryFilesAdded = libraryFilesAdded.filter(lf => lf !== matchingLibraryFile) + let existingLibraryFileBefore = structuredClone(existingLibraryFile) if (this.compareUpdateLibraryFile(existingLibraryItem.path, existingLibraryFile, matchingLibraryFile, libraryScan)) { - this.libraryFilesModified.push(existingLibraryFile) + this.libraryFilesModified.push({old: existingLibraryFileBefore, new: existingLibraryFile}) this.hasChanges = true } } diff --git a/server/scanner/PodcastScanner.js b/server/scanner/PodcastScanner.js index 07dcbb11..4958d5f7 100644 --- a/server/scanner/PodcastScanner.js +++ b/server/scanner/PodcastScanner.js @@ -71,7 +71,7 @@ class PodcastScanner { // Update audio files that were modified if (libraryItemData.audioLibraryFilesModified.length) { - let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(existingLibraryItem.mediaType, libraryItemData, libraryItemData.audioLibraryFilesModified) + let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(existingLibraryItem.mediaType, libraryItemData, libraryItemData.audioLibraryFilesModified.map(lf => lf.new)) for (const podcastEpisode of existingPodcastEpisodes) { let matchedScannedAudioFile = scannedAudioFiles.find(saf => saf.metadata.path === podcastEpisode.audioFile.metadata.path) @@ -132,11 +132,25 @@ class PodcastScanner { let hasMediaChanges = false // Check if cover was removed - if (media.coverPath && !libraryItemData.imageLibraryFiles.some(lf => lf.metadata.path === media.coverPath)) { + if (media.coverPath && libraryItemData.imageLibraryFilesRemoved.some(lf => lf.metadata.path === media.coverPath)) { media.coverPath = null hasMediaChanges = true } + // Update cover if it was modified + if (media.coverPath && libraryItemData.imageLibraryFilesModified.length) { + let coverMatch = libraryItemData.imageLibraryFilesModified.find(iFile => iFile.old.metadata.path === media.coverPath) + if (coverMatch) { + const coverPath = coverMatch.new.metadata.path + if (coverPath !== media.coverPath) { + libraryScan.addLog(LogLevel.DEBUG, `Updating podcast cover "${media.coverPath}" => "${coverPath}" for podcast "${media.title}"`) + media.coverPath = coverPath + media.changed('coverPath', true) + hasMediaChanges = true + } + } + } + // Check if cover is not set and image files were found if (!media.coverPath && libraryItemData.imageLibraryFiles.length) { // Prefer using a cover image with the name "cover" otherwise use the first image From 1bee0827200d95d2cf7839bff99b46cf21dc5ac6 Mon Sep 17 00:00:00 2001 From: mikiher Date: Wed, 20 Mar 2024 11:40:50 +0200 Subject: [PATCH 2/4] Update libraryFolderID correctly in scanFolderUpdates --- server/scanner/LibraryItemScanner.js | 11 ++++++----- server/scanner/LibraryScanner.js | 9 +++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/server/scanner/LibraryItemScanner.js b/server/scanner/LibraryItemScanner.js index 588b7744..872000d8 100644 --- a/server/scanner/LibraryItemScanner.js +++ b/server/scanner/LibraryItemScanner.js @@ -21,10 +21,10 @@ class LibraryItemScanner { * Scan single library item * * @param {string} libraryItemId - * @param {{relPath:string, path:string}} [renamedPaths] used by watcher when item folder was renamed + * @param {{relPath:string, path:string}} [updateLibraryItemDetails] used by watcher when item folder was renamed * @returns {number} ScanResult */ - async scanLibraryItem(libraryItemId, renamedPaths = null) { + async scanLibraryItem(libraryItemId, updateLibraryItemDetails = null) { // TODO: Add task manager const libraryItem = await Database.libraryItemModel.findByPk(libraryItemId) if (!libraryItem) { @@ -32,11 +32,12 @@ class LibraryItemScanner { return ScanResult.NOTHING } + const libraryFolderId = updateLibraryItemDetails?.libraryFolderId || libraryItem.libraryFolderId const library = await Database.libraryModel.findByPk(libraryItem.libraryId, { include: { model: Database.libraryFolderModel, where: { - id: libraryItem.libraryFolderId + id: libraryFolderId } } }) @@ -51,9 +52,9 @@ class LibraryItemScanner { const scanLogger = new ScanLogger() scanLogger.verbose = true - scanLogger.setData('libraryItem', renamedPaths?.relPath || libraryItem.relPath) + scanLogger.setData('libraryItem', updateLibraryItemDetails?.relPath || libraryItem.relPath) - const libraryItemPath = renamedPaths?.path || fileUtils.filePathToPOSIX(libraryItem.path) + const libraryItemPath = updateLibraryItemDetails?.path || fileUtils.filePathToPOSIX(libraryItem.path) const folder = library.libraryFolders[0] const libraryItemScanData = await this.getLibraryItemScanData(libraryItemPath, library, folder, false) diff --git a/server/scanner/LibraryScanner.js b/server/scanner/LibraryScanner.js index 27c507bd..ac422c79 100644 --- a/server/scanner/LibraryScanner.js +++ b/server/scanner/LibraryScanner.js @@ -525,7 +525,7 @@ class LibraryScanner { path: potentialChildDirs }) - let renamedPaths = {} + let updatedLibraryItemDetails = {} if (!existingLibraryItem) { const dirIno = await fileUtils.getIno(fullPath) existingLibraryItem = await Database.libraryItemModel.findOneOld({ @@ -536,8 +536,9 @@ class LibraryScanner { // Update library item paths for scan existingLibraryItem.path = fullPath existingLibraryItem.relPath = itemDir - renamedPaths.path = fullPath - renamedPaths.relPath = itemDir + updatedLibraryItemDetails.path = fullPath + updatedLibraryItemDetails.relPath = itemDir + updatedLibraryItemDetails.libraryFolderId = folder.id } } if (existingLibraryItem) { @@ -557,7 +558,7 @@ class LibraryScanner { // Scan library item for updates Logger.debug(`[LibraryScanner] Folder update for relative path "${itemDir}" is in library item "${existingLibraryItem.media.metadata.title}" - scan for updates`) - itemGroupingResults[itemDir] = await LibraryItemScanner.scanLibraryItem(existingLibraryItem.id, renamedPaths) + itemGroupingResults[itemDir] = await LibraryItemScanner.scanLibraryItem(existingLibraryItem.id, updatedLibraryItemDetails) continue } else if (library.settings.audiobooksOnly && !hasAudioFiles(fileUpdateGroup, itemDir)) { Logger.debug(`[LibraryScanner] Folder update for relative path "${itemDir}" has no audio files`) From 68276fe30ba4397e1d293e855e8740f4efb93453 Mon Sep 17 00:00:00 2001 From: mikiher Date: Sat, 23 Mar 2024 18:31:52 +0200 Subject: [PATCH 3/4] Fix handling of file moves from root folder to sub folder and back --- server/scanner/LibraryItemScanner.js | 2 +- server/scanner/LibraryScanner.js | 75 +++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/server/scanner/LibraryItemScanner.js b/server/scanner/LibraryItemScanner.js index 872000d8..1c3123df 100644 --- a/server/scanner/LibraryItemScanner.js +++ b/server/scanner/LibraryItemScanner.js @@ -56,7 +56,7 @@ class LibraryItemScanner { const libraryItemPath = updateLibraryItemDetails?.path || fileUtils.filePathToPOSIX(libraryItem.path) const folder = library.libraryFolders[0] - const libraryItemScanData = await this.getLibraryItemScanData(libraryItemPath, library, folder, false) + const libraryItemScanData = await this.getLibraryItemScanData(libraryItemPath, library, folder, updateLibraryItemDetails?.isFile || false) let libraryItemDataUpdated = await libraryItemScanData.checkLibraryItemData(libraryItem, scanLogger) diff --git a/server/scanner/LibraryScanner.js b/server/scanner/LibraryScanner.js index ac422c79..d394128c 100644 --- a/server/scanner/LibraryScanner.js +++ b/server/scanner/LibraryScanner.js @@ -154,7 +154,11 @@ class LibraryScanner { let libraryItemData = libraryItemDataFound.find(lid => lid.path === existingLibraryItem.path) if (!libraryItemData) { // Fallback to finding matching library item with matching inode value - libraryItemData = libraryItemDataFound.find(lid => lid.ino === existingLibraryItem.ino) + libraryItemData = libraryItemDataFound.find(lid => + ItemToItemInoMatch(lid, existingLibraryItem) || + ItemToFileInoMatch(lid, existingLibraryItem) || + ItemToFileInoMatch(existingLibraryItem, lid) + ) if (libraryItemData) { libraryScan.addLog(LogLevel.INFO, `Library item with path "${existingLibraryItem.path}" was not found, but library item inode "${existingLibraryItem.ino}" was found at path "${libraryItemData.path}"`) } @@ -522,23 +526,25 @@ class LibraryScanner { // Check if book dir group is already an item let existingLibraryItem = await Database.libraryItemModel.findOneOld({ + libraryId: library.id, path: potentialChildDirs }) let updatedLibraryItemDetails = {} if (!existingLibraryItem) { - const dirIno = await fileUtils.getIno(fullPath) - existingLibraryItem = await Database.libraryItemModel.findOneOld({ - ino: dirIno - }) + const isSingleMedia = isSingleMediaFile(fileUpdateGroup, itemDir) + existingLibraryItem = + await findLibraryItemByItemToItemInoMatch(library.id, fullPath) || + await findLibraryItemByItemToFileInoMatch(library.id, fullPath, isSingleMedia) || + await findLibraryItemByFileToItemInoMatch(library.id, fullPath, isSingleMedia, fileUpdateGroup[itemDir]) if (existingLibraryItem) { - Logger.debug(`[LibraryScanner] scanFolderUpdates: Library item found by inode value=${dirIno}. "${existingLibraryItem.relPath} => ${itemDir}"`) // Update library item paths for scan existingLibraryItem.path = fullPath existingLibraryItem.relPath = itemDir updatedLibraryItemDetails.path = fullPath updatedLibraryItemDetails.relPath = itemDir updatedLibraryItemDetails.libraryFolderId = folder.id + updatedLibraryItemDetails.isFile = isSingleMedia } } if (existingLibraryItem) { @@ -555,7 +561,6 @@ class LibraryScanner { continue } } - // Scan library item for updates Logger.debug(`[LibraryScanner] Folder update for relative path "${itemDir}" is in library item "${existingLibraryItem.media.metadata.title}" - scan for updates`) itemGroupingResults[itemDir] = await LibraryItemScanner.scanLibraryItem(existingLibraryItem.id, updatedLibraryItemDetails) @@ -595,6 +600,14 @@ class LibraryScanner { } module.exports = new LibraryScanner() +function ItemToFileInoMatch(libraryItem1, libraryItem2) { + return libraryItem1.isFile && libraryItem2.libraryFiles.some(lf => lf.ino === libraryItem1.ino) +} + +function ItemToItemInoMatch(libraryItem1, libraryItem2) { + return libraryItem1.ino === libraryItem2.ino +} + function hasAudioFiles(fileUpdateGroup, itemDir) { return isSingleMediaFile(fileUpdateGroup, itemDir) ? scanUtils.checkFilepathIsAudioFile(fileUpdateGroup[itemDir]) : @@ -604,3 +617,51 @@ function hasAudioFiles(fileUpdateGroup, itemDir) { function isSingleMediaFile(fileUpdateGroup, itemDir) { return itemDir === fileUpdateGroup[itemDir] } + +async function findLibraryItemByItemToItemInoMatch(libraryId, fullPath) { + const ino = await fileUtils.getIno(fullPath) + if (!ino) return null + const existingLibraryItem = await Database.libraryItemModel.findOneOld({ + libraryId: libraryId, + ino: ino + }) + if (existingLibraryItem) + Logger.debug(`[LibraryScanner] Found library item with matching inode "${ino}" at path "${existingLibraryItem.path}"`) + return existingLibraryItem +} + +async function findLibraryItemByItemToFileInoMatch(libraryId, fullPath, isSingleMedia) { + if (!isSingleMedia) return null + // check if it was moved from another folder by comparing the ino to the library files + const ino = await fileUtils.getIno(fullPath) + if (!ino) return null + const existingLibraryItem = await Database.libraryItemModel.findOneOld({ + libraryId: libraryId, + libraryFiles: { + [ sequelize.Op.substring ]: ino + } + }) + if (existingLibraryItem) + Logger.debug(`[LibraryScanner] Found library item with a library file matching inode "${ino}" at path "${existingLibraryItem.path}"`) + return existingLibraryItem +} + +async function findLibraryItemByFileToItemInoMatch(libraryId, fullPath, isSingleMedia, itemFiles) { + if (isSingleMedia) return null + // check if it was moved from the root folder by comparing the ino to the ino of the scanned files + let itemFileInos = [] + for (const itemFile of itemFiles) { + const ino = await fileUtils.getIno(Path.posix.join(fullPath, itemFile)) + if (ino) itemFileInos.push(ino) + } + if (!itemFileInos.length) return null + const existingLibraryItem = await Database.libraryItemModel.findOneOld({ + libraryId: libraryId, + ino: { + [ sequelize.Op.in ] : itemFileInos + } + }) + if (existingLibraryItem) + Logger.debug(`[LibraryScanner] Found library item with inode matching one of "${itemFileInos.join(',')}" at path "${existingLibraryItem.path}"`) + return existingLibraryItem +} \ No newline at end of file From f827aa97f8d74aead118387f2e8cd608cad26393 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 23 Mar 2024 14:56:32 -0500 Subject: [PATCH 4/4] Update library scanner findLibraryItemByItemToFileInoMatch query to iterate through json objects comparing inodes --- server/Logger.js | 2 +- server/models/LibraryItem.js | 10 ++++++---- server/scanner/LibraryScanner.js | 28 ++++++++++++++++------------ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/server/Logger.js b/server/Logger.js index 7cc7aa4c..8283e1f0 100644 --- a/server/Logger.js +++ b/server/Logger.js @@ -93,7 +93,7 @@ class Logger { // Save log to file if (level >= this.logLevel) { - await this.logManager.logToFile(logObj) + await this.logManager?.logToFile(logObj) } } diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index 44758750..704e2f10 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -1,4 +1,4 @@ -const { DataTypes, Model, WhereOptions } = require('sequelize') +const { DataTypes, Model } = require('sequelize') const Logger = require('../Logger') const oldLibraryItem = require('../objects/LibraryItem') const libraryFilters = require('../utils/queries/libraryFilters') @@ -116,7 +116,7 @@ class LibraryItem extends Model { /** * Currently unused because this is too slow and uses too much mem - * @param {[WhereOptions]} where + * @param {import('sequelize').WhereOptions} [where] * @returns {Array} old library items */ static async getAllOldLibraryItems(where = null) { @@ -773,12 +773,14 @@ class LibraryItem extends Model { /** * - * @param {WhereOptions} where + * @param {import('sequelize').WhereOptions} where + * @param {import('sequelize').BindOrReplacements} replacements * @returns {Object} oldLibraryItem */ - static async findOneOld(where) { + static async findOneOld(where, replacements = {}) { const libraryItem = await this.findOne({ where, + replacements, include: [ { model: this.sequelize.models.book, diff --git a/server/scanner/LibraryScanner.js b/server/scanner/LibraryScanner.js index d394128c..8131300d 100644 --- a/server/scanner/LibraryScanner.js +++ b/server/scanner/LibraryScanner.js @@ -155,10 +155,10 @@ class LibraryScanner { if (!libraryItemData) { // Fallback to finding matching library item with matching inode value libraryItemData = libraryItemDataFound.find(lid => - ItemToItemInoMatch(lid, existingLibraryItem) || - ItemToFileInoMatch(lid, existingLibraryItem) || + ItemToItemInoMatch(lid, existingLibraryItem) || + ItemToFileInoMatch(lid, existingLibraryItem) || ItemToFileInoMatch(existingLibraryItem, lid) - ) + ) if (libraryItemData) { libraryScan.addLog(LogLevel.INFO, `Library item with path "${existingLibraryItem.path}" was not found, but library item inode "${existingLibraryItem.ino}" was found at path "${libraryItemData.path}"`) } @@ -533,8 +533,8 @@ class LibraryScanner { let updatedLibraryItemDetails = {} if (!existingLibraryItem) { const isSingleMedia = isSingleMediaFile(fileUpdateGroup, itemDir) - existingLibraryItem = - await findLibraryItemByItemToItemInoMatch(library.id, fullPath) || + existingLibraryItem = + await findLibraryItemByItemToItemInoMatch(library.id, fullPath) || await findLibraryItemByItemToFileInoMatch(library.id, fullPath, isSingleMedia) || await findLibraryItemByFileToItemInoMatch(library.id, fullPath, isSingleMedia, fileUpdateGroup[itemDir]) if (existingLibraryItem) { @@ -630,16 +630,20 @@ async function findLibraryItemByItemToItemInoMatch(libraryId, fullPath) { return existingLibraryItem } -async function findLibraryItemByItemToFileInoMatch(libraryId, fullPath, isSingleMedia) { +async function findLibraryItemByItemToFileInoMatch(libraryId, fullPath, isSingleMedia) { if (!isSingleMedia) return null // check if it was moved from another folder by comparing the ino to the library files const ino = await fileUtils.getIno(fullPath) if (!ino) return null - const existingLibraryItem = await Database.libraryItemModel.findOneOld({ - libraryId: libraryId, - libraryFiles: { - [ sequelize.Op.substring ]: ino - } + const existingLibraryItem = await Database.libraryItemModel.findOneOld([ + { + libraryId: libraryId + }, + sequelize.where(sequelize.literal('(SELECT count(*) FROM json_each(libraryFiles) WHERE json_valid(json_each.value) AND json_each.value->>"$.ino" = :inode)'), { + [sequelize.Op.gt]: 0 + }) + ], { + inode: ino }) if (existingLibraryItem) Logger.debug(`[LibraryScanner] Found library item with a library file matching inode "${ino}" at path "${existingLibraryItem.path}"`) @@ -658,7 +662,7 @@ async function findLibraryItemByFileToItemInoMatch(libraryId, fullPath, isSingle const existingLibraryItem = await Database.libraryItemModel.findOneOld({ libraryId: libraryId, ino: { - [ sequelize.Op.in ] : itemFileInos + [sequelize.Op.in]: itemFileInos } }) if (existingLibraryItem)