From 2cc9d1b7f8f42fa3f8965343851b7c673060555e Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 1 May 2025 17:17:40 -0500 Subject: [PATCH] Update watcher to re-scan library items when non-media files are added/updated #4245 --- server/models/LibraryItem.js | 1 - server/scanner/LibraryScanner.js | 2 +- server/utils/scandir.js | 22 ++++++++++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index bf561d5e..16a52161 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -246,7 +246,6 @@ class LibraryItem extends Model { include }) if (!libraryItem) { - Logger.error(`[LibraryItem] Library item not found`) return null } diff --git a/server/scanner/LibraryScanner.js b/server/scanner/LibraryScanner.js index 4d0285dd..bc174d7a 100644 --- a/server/scanner/LibraryScanner.js +++ b/server/scanner/LibraryScanner.js @@ -407,7 +407,7 @@ class LibraryScanner { const folder = library.libraryFolders[0] const filePathItems = folderGroups[folderId].fileUpdates.map((fileUpdate) => fileUtils.getFilePathItemFromFileUpdate(fileUpdate)) - const fileUpdateGroup = scanUtils.groupFileItemsIntoLibraryItemDirs(library.mediaType, filePathItems, !!library.settings?.audiobooksOnly) + const fileUpdateGroup = scanUtils.groupFileItemsIntoLibraryItemDirs(library.mediaType, filePathItems, !!library.settings?.audiobooksOnly, true) if (!Object.keys(fileUpdateGroup).length) { Logger.info(`[LibraryScanner] No important changes to scan for in folder "${folderId}"`) diff --git a/server/utils/scandir.js b/server/utils/scandir.js index 0aaa5e19..6dd2d67f 100644 --- a/server/utils/scandir.js +++ b/server/utils/scandir.js @@ -24,6 +24,12 @@ function isMediaFile(mediaType, ext, audiobooksOnly = false) { return globals.SupportedAudioTypes.includes(extclean) || globals.SupportedEbookTypes.includes(extclean) } +function isScannableNonMediaFile(ext) { + if (!ext) return false + const extclean = ext.slice(1).toLowerCase() + return globals.TextFileTypes.includes(extclean) || globals.MetadataFileTypes.includes(extclean) || globals.SupportedImageTypes.includes(extclean) +} + function checkFilepathIsAudioFile(filepath) { const ext = Path.extname(filepath) if (!ext) return false @@ -35,27 +41,31 @@ module.exports.checkFilepathIsAudioFile = checkFilepathIsAudioFile /** * @param {string} mediaType * @param {import('./fileUtils').FilePathItem[]} fileItems - * @param {boolean} [audiobooksOnly=false] + * @param {boolean} audiobooksOnly + * @param {boolean} [includeNonMediaFiles=false] - Used by the watcher to re-scan when covers/metadata files are added/removed * @returns {Record} map of files grouped into potential libarary item dirs */ -function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly = false) { +function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly, includeNonMediaFiles = false) { // Step 1: Filter out non-book-media files in root dir (with depth of 0) const itemsFiltered = fileItems.filter((i) => { return i.deep > 0 || (mediaType === 'book' && isMediaFile(mediaType, i.extension, audiobooksOnly)) }) // Step 2: Separate media files and other files - // - Directories without a media file will not be included + // - Directories without a media file will not be included (unless includeNonMediaFiles is true) /** @type {import('./fileUtils').FilePathItem[]} */ const mediaFileItems = [] /** @type {import('./fileUtils').FilePathItem[]} */ const otherFileItems = [] itemsFiltered.forEach((item) => { - if (isMediaFile(mediaType, item.extension, audiobooksOnly)) mediaFileItems.push(item) - else otherFileItems.push(item) + if (isMediaFile(mediaType, item.extension, audiobooksOnly) || (includeNonMediaFiles && isScannableNonMediaFile(item.extension))) { + mediaFileItems.push(item) + } else { + otherFileItems.push(item) + } }) - // Step 3: Group audio files in library items + // Step 3: Group media files (or non-media files if includeNonMediaFiles is true) in library items const libraryItemGroup = {} mediaFileItems.forEach((item) => { const dirparts = item.reldirpath.split('/').filter((p) => !!p)