diff --git a/server/Watcher.js b/server/Watcher.js index 555dce06..d2166b6e 100644 --- a/server/Watcher.js +++ b/server/Watcher.js @@ -162,13 +162,6 @@ class FolderWatcher extends EventEmitter { } var folderFullPath = folder.fullPath.replace(/\\/g, '/') - // Check if file was added to root directory - var dir = Path.dirname(path) - if (dir === folderFullPath) { - Logger.warn(`[Watcher] New file "${Path.basename(path)}" added to folder root - ignoring it`) - return - } - var relPath = path.replace(folderFullPath, '') var hasDotPath = relPath.split('/').find(p => p.startsWith('.')) diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index 7e9ffc2e..74e181eb 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -62,7 +62,8 @@ class Scanner { } async scanLibraryItem(libraryMediaType, folder, libraryItem) { - var libraryItemData = await getLibraryItemFileData(libraryMediaType, folder, libraryItem.path, this.db.serverSettings) + // TODO: Support for single media item + var libraryItemData = await getLibraryItemFileData(libraryMediaType, folder, libraryItem.path, false, this.db.serverSettings) if (!libraryItemData) { return ScanResult.NOTHING } @@ -499,7 +500,11 @@ class Scanner { continue; } var relFilePaths = folderGroups[folderId].fileUpdates.map(fileUpdate => fileUpdate.relPath) - var fileUpdateGroup = groupFilesIntoLibraryItemPaths(relFilePaths, true) + var fileUpdateGroup = groupFilesIntoLibraryItemPaths(library.mediaType, relFilePaths) + if (!Object.keys(fileUpdateGroup).length) { + Logger.info(`[Scanner] No important changes to scan for in folder "${folderId}"`) + continue; + } var folderScanResults = await this.scanFolderUpdates(library, folder, fileUpdateGroup) Logger.debug(`[Scanner] Folder scan results`, folderScanResults) } @@ -513,6 +518,8 @@ class Scanner { // Test Case: Moving audio files from library item folder to author folder should trigger a re-scan of the item var updateGroup = { ...fileUpdateGroup } for (const itemDir in updateGroup) { + if (itemDir == fileUpdateGroup[itemDir]) continue; // Media in root path + var itemDirNestedFiles = fileUpdateGroup[itemDir].filter(b => b.includes('/')) if (!itemDirNestedFiles.length) continue; @@ -582,7 +589,8 @@ class Scanner { } Logger.debug(`[Scanner] Folder update group must be a new item "${itemDir}" in library "${library.name}"`) - var newLibraryItem = await this.scanPotentialNewLibraryItem(library.mediaType, folder, fullPath) + var isSingleMediaItem = itemDir === fileUpdateGroup[itemDir] + var newLibraryItem = await this.scanPotentialNewLibraryItem(library.mediaType, folder, fullPath, isSingleMediaItem) if (newLibraryItem) { await this.createNewAuthorsAndSeries(newLibraryItem) await this.db.insertLibraryItem(newLibraryItem) @@ -594,8 +602,8 @@ class Scanner { return itemGroupingResults } - async scanPotentialNewLibraryItem(libraryMediaType, folder, fullPath) { - var libraryItemData = await getLibraryItemFileData(libraryMediaType, folder, fullPath, this.db.serverSettings) + async scanPotentialNewLibraryItem(libraryMediaType, folder, fullPath, isSingleMediaItem = false) { + var libraryItemData = await getLibraryItemFileData(libraryMediaType, folder, fullPath, isSingleMediaItem, this.db.serverSettings) if (!libraryItemData) return null var serverSettings = this.db.serverSettings return this.scanNewLibraryItem(libraryItemData, libraryMediaType, serverSettings.scannerPreferAudioMetadata, serverSettings.scannerPreferOpfMetadata, serverSettings.scannerFindCovers) diff --git a/server/utils/scandir.js b/server/utils/scandir.js index 699a47b9..0c68dacf 100644 --- a/server/utils/scandir.js +++ b/server/utils/scandir.js @@ -17,11 +17,14 @@ function isMediaFile(mediaType, ext) { // TODO: Function needs to be re-done // Input: array of relative file paths // Output: map of files grouped into potential item dirs -function groupFilesIntoLibraryItemPaths(paths) { - // Step 1: Clean path, Remove leading "/", Filter out files in root dir +function groupFilesIntoLibraryItemPaths(mediaType, paths) { + // Step 1: Clean path, Remove leading "/", Filter out non-media files in root dir var pathsFiltered = paths.map(path => { return path.startsWith('/') ? path.slice(1) : path - }).filter(path => Path.parse(path).dir) + }).filter(path => { + let parsedPath = Path.parse(path) + return parsedPath.dir || (mediaType === 'book' && isMediaFile(mediaType, parsedPath.ext)) + }) // Step 2: Sort by least number of directories pathsFiltered.sort((a, b) => { @@ -33,25 +36,30 @@ function groupFilesIntoLibraryItemPaths(paths) { // Step 3: Group files in dirs var itemGroup = {} pathsFiltered.forEach((path) => { - var dirparts = Path.dirname(path).split('/') + var dirparts = Path.dirname(path).split('/').filter(p => !!p && p !== '.') // dirname returns . if no directory var numparts = dirparts.length var _path = '' - // Iterate over directories in path - for (let i = 0; i < numparts; i++) { - var dirpart = dirparts.shift() - _path = Path.posix.join(_path, dirpart) + if (!numparts) { + // Media file in root + itemGroup[path] = path + } else { + // Iterate over directories in path + for (let i = 0; i < numparts; i++) { + var dirpart = dirparts.shift() + _path = Path.posix.join(_path, dirpart) - if (itemGroup[_path]) { // Directory already has files, add file - var relpath = Path.posix.join(dirparts.join('/'), Path.basename(path)) - itemGroup[_path].push(relpath) - return - } else if (!dirparts.length) { // This is the last directory, create group - itemGroup[_path] = [Path.basename(path)] - return - } else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) { // Next directory is the last and is a CD dir, create group - itemGroup[_path] = [Path.posix.join(dirparts[0], Path.basename(path))] - return + if (itemGroup[_path]) { // Directory already has files, add file + var relpath = Path.posix.join(dirparts.join('/'), Path.basename(path)) + itemGroup[_path].push(relpath) + return + } else if (!dirparts.length) { // This is the last directory, create group + itemGroup[_path] = [Path.basename(path)] + return + } else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) { // Next directory is the last and is a CD dir, create group + itemGroup[_path] = [Path.posix.join(dirparts[0], Path.basename(path))] + return + } } } }) @@ -62,9 +70,9 @@ module.exports.groupFilesIntoLibraryItemPaths = groupFilesIntoLibraryItemPaths // Input: array of relative file items (see recurseFiles) // Output: map of files grouped into potential libarary item dirs function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems) { - // Step 1: Filter out non-media files in root dir (with depth of 0) + // Step 1: Filter out non-book-media files in root dir (with depth of 0) var itemsFiltered = fileItems.filter(i => { - return i.deep > 0 || isMediaFile(mediaType, i.extension) + return i.deep > 0 || (mediaType === 'book' && isMediaFile(mediaType, i.extension)) }) // Step 2: Seperate media files and other files @@ -147,16 +155,6 @@ async function scanFolder(libraryMediaType, folder, serverSettings = {}) { } var fileItems = await recurseFiles(folderPath) - var basePath = folderPath - - const isOpenAudibleFolder = fileItems.find(fi => fi.deep === 0 && fi.name === 'books.json') - if (isOpenAudibleFolder) { - Logger.info(`[scandir] Detected Open Audible Folder, looking in books folder`) - basePath = Path.posix.join(folderPath, 'books') - fileItems = await recurseFiles(basePath) - Logger.debug(`[scandir] ${fileItems.length} files found in books folder`) - } - var libraryItemGrouping = groupFileItemsIntoLibraryItemDirs(libraryMediaType, fileItems) if (!Object.keys(libraryItemGrouping).length) { @@ -175,10 +173,10 @@ async function scanFolder(libraryMediaType, folder, serverSettings = {}) { mediaMetadata: { title: Path.basename(libraryItemPath, Path.extname(libraryItemPath)) }, - path: Path.posix.join(basePath, libraryItemPath), + path: Path.posix.join(folderPath, libraryItemPath), relPath: libraryItemPath } - fileObjs = await cleanFileObjects(basePath, [libraryItemPath]) + fileObjs = await cleanFileObjects(folderPath, [libraryItemPath]) isFile = true } else { libraryItemData = getDataFromMediaDir(libraryMediaType, folderPath, libraryItemPath, serverSettings) @@ -335,14 +333,34 @@ function getDataFromMediaDir(libraryMediaType, folderPath, relPath, serverSettin } // Called from Scanner.js -async function getLibraryItemFileData(libraryMediaType, folder, libraryItemPath, serverSettings = {}) { - var fileItems = await recurseFiles(libraryItemPath) - +async function getLibraryItemFileData(libraryMediaType, folder, libraryItemPath, isSingleMediaItem, serverSettings = {}) { libraryItemPath = libraryItemPath.replace(/\\/g, '/') var folderFullPath = folder.fullPath.replace(/\\/g, '/') var libraryItemDir = libraryItemPath.replace(folderFullPath, '').slice(1) - var libraryItemData = getDataFromMediaDir(libraryMediaType, folderFullPath, libraryItemDir, serverSettings) + var libraryItemData = {} + + var fileItems = [] + + if (isSingleMediaItem) { // Single media item in root of folder + fileItems = [ + { + fullpath: libraryItemPath, + path: libraryItemDir // actually the relPath (only filename here) + } + ] + libraryItemData = { + path: libraryItemPath, // full path + relPath: libraryItemDir, // only filename + mediaMetadata: { + title: Path.basename(libraryItemDir, Path.extname(libraryItemDir)) + } + } + } else { + fileItems = await recurseFiles(libraryItemPath) + libraryItemData = getDataFromMediaDir(libraryMediaType, folderFullPath, libraryItemDir, serverSettings) + } + var libraryItemDirStats = await getFileTimestampsWithIno(libraryItemData.path) var libraryItem = { ino: libraryItemDirStats.ino, @@ -353,6 +371,7 @@ async function getLibraryItemFileData(libraryMediaType, folder, libraryItemPath, libraryId: folder.libraryId, path: libraryItemData.path, relPath: libraryItemData.relPath, + isFile: isSingleMediaItem, media: { metadata: libraryItemData.mediaMetadata || null },