From e5f8be5b2460ed98f38564e9ff4a04622c578951 Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 10 Jan 2022 11:12:47 -0600 Subject: [PATCH] Fix:Scanner smart re-order --- server/Watcher.js | 1 + server/scanner/AudioFileScanner.js | 61 ++++++++++++++---------------- server/scanner/Scanner.js | 33 ++++++++++++++-- 3 files changed, 59 insertions(+), 36 deletions(-) diff --git a/server/Watcher.js b/server/Watcher.js index c9fede87..7912d485 100644 --- a/server/Watcher.js +++ b/server/Watcher.js @@ -120,6 +120,7 @@ class FolderWatcher extends EventEmitter { onRename(libraryId, pathFrom, pathTo) { Logger.debug(`[Watcher] Rename ${pathFrom} => ${pathTo}`) + this.addFileUpdate(libraryId, pathFrom, 'renamed') this.addFileUpdate(libraryId, pathTo, 'renamed') } diff --git a/server/scanner/AudioFileScanner.js b/server/scanner/AudioFileScanner.js index 9700bf44..9cfd6660 100644 --- a/server/scanner/AudioFileScanner.js +++ b/server/scanner/AudioFileScanner.js @@ -109,7 +109,6 @@ class AudioFileScanner { return nodupes } - // Must be all audiofiles in audiobook runSmartTrackOrder(audiobook, audioFiles) { var discsFromFilename = [] var tracksFromFilename = [] @@ -159,7 +158,12 @@ class AudioFileScanner { for (let i = 0; i < audioFiles.length; i++) { audioFiles[i].index = i + 1 - audiobook.addAudioFile(audioFiles[i]) + var existingAF = audiobook.getAudioFileByIno(audioFiles[i].ino) + if (existingAF) { + audiobook.updateAudioFile(audioFiles[i]) + } else { + audiobook.addAudioFile(audioFiles[i]) + } } } @@ -172,43 +176,34 @@ class AudioFileScanner { libraryScan.addLog(LogLevel.DEBUG, `Book "${bookScanData.path}" Audio file scan took ${audioScanResult.elapsed}ms for ${audioScanResult.audioFiles.length} with average time of ${audioScanResult.averageScanDuration}ms`) } - var numExistingAudioFilesToInclude = audiobook.audioFilesToInclude.filter(af => !audioScanResult.audioFiles.find(_af => _af.ino === af.ino)).length - var totalAudioFilesToInclude = numExistingAudioFilesToInclude + audioScanResult.audioFiles.length + var totalAudioFilesToInclude = audioScanResult.audioFiles.length + var newAudioFiles = audioScanResult.audioFiles.filter(af => { + return !audiobook.audioFilesToInclude.find(_af => _af.ino === af.ino) + }) - if (numExistingAudioFilesToInclude <= 0) { // SMART TRACK ORDER for New or empty audiobooks - this.runSmartTrackOrder(audiobook, audioScanResult.audioFiles) - hasUpdated = true + if (newAudioFiles.length) { + // Single Track Audiobooks + if (totalAudioFilesToInclude === 1) { + var af = audioScanResult.audioFiles[0] + af.index = 1 + audiobook.addAudioFile(af) + hasUpdated = true + } else { + this.runSmartTrackOrder(audiobook, audioScanResult.audioFiles) + hasUpdated = true + } } else { - // validate & add/update audio files to existing audiobook - for (let i = 0; i < audioScanResult.audioFiles.length; i++) { - var newAF = audioScanResult.audioFiles[i] - var existingAF = audiobook.getAudioFileByIno(newAF.ino) - - var trackIndex = null - if (totalAudioFilesToInclude === 1) { // Single track audiobooks - trackIndex = 1 - } else if (existingAF && existingAF.manuallyVerified) { // manually verified audio files use existing index - trackIndex = existingAF.index - } else { - trackIndex = newAF.validateTrackIndex() - } - - if (trackIndex !== null) { - if (audiobook.checkHasTrackNum(trackIndex, newAF.ino)) { - newAF.setDuplicateTrackNumber(trackIndex) - } else { - newAF.index = trackIndex - } - } + Logger.debug(`[AudioFileScanner] No audio track re-order required`) + // Only update metadata not index + audioScanResult.audioFiles.forEach((af) => { + var existingAF = audiobook.getAudioFileByIno(af.ino) if (existingAF) { - if (audiobook.updateAudioFile(newAF)) { + af.index = existingAF.index + if (audiobook.updateAudioFile(af)) { hasUpdated = true } - } else { - audiobook.addAudioFile(newAF) - hasUpdated = true } - } + }) } // Set book details from audio file ID3 tags, optional prefer diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index 8290c4c2..cf6aea17 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -322,7 +322,7 @@ class Scanner { } async rescanAudiobook(audiobookCheckData, libraryScan) { - const { newAudioFileData, newOtherFileData, audiobook, bookScanData, updated, existingAudioFileData, existingOtherFileData } = audiobookCheckData + const { newAudioFileData, audioFilesRemoved, newOtherFileData, audiobook, bookScanData, updated, existingAudioFileData, existingOtherFileData } = audiobookCheckData libraryScan.addLog(LogLevel.DEBUG, `Library "${libraryScan.libraryName}" Re-scanning "${audiobook.path}"`) var hasUpdated = updated @@ -342,7 +342,7 @@ class Scanner { } } // Scan new audio files - if (newAudioFileData.length) { + if (newAudioFileData.length || audioFilesRemoved.length) { if (await AudioFileScanner.scanAudioFiles(newAudioFileData, bookScanData, audiobook, libraryScan.preferAudioMetadata, libraryScan)) { hasUpdated = true } @@ -459,11 +459,38 @@ class Scanner { async scanFolderUpdates(library, folder, fileUpdateBookGroup) { Logger.debug(`[Scanner] Scanning file update groups in folder "${folder.id}" of library "${library.name}"`) + // First pass - Remove files in parent dirs of audiobooks and remap the fileupdate group + // Test Case: Moving audio files from audiobook folder to author folder should trigger a re-scan of audiobook + var updateGroup = { ...fileUpdateBookGroup } + for (const bookDir in updateGroup) { + var bookDirNestedFiles = fileUpdateBookGroup[bookDir].filter(b => b.includes('/')) + if (!bookDirNestedFiles.length) continue; + + var firstNest = bookDirNestedFiles[0].split('/').shift() + var altDir = `${bookDir}/${firstNest}` + + var fullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), bookDir) + var childAudiobook = this.db.audiobooks.find(ab => ab.fullPath !== fullPath && ab.fullPath.startsWith(fullPath)) + if (!childAudiobook) { + continue; + } + var altFullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), altDir) + var altChildAudiobook = this.db.audiobooks.find(ab => ab.fullPath !== altFullPath && ab.fullPath.startsWith(altFullPath)) + if (altChildAudiobook) { + continue; + } + + delete fileUpdateBookGroup[bookDir] + fileUpdateBookGroup[altDir] = bookDirNestedFiles.map((f) => f.split('/').slice(1).join('/')) + Logger.warn(`[Scanner] Some files were modified in a parent directory of an audiobook "${childAudiobook.title}" - ignoring`) + } + + // Second pass: Check for new/updated/removed audiobooks var bookGroupingResults = {} for (const bookDir in fileUpdateBookGroup) { var fullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), bookDir) - // Check if book dir group is already an audiobook or in a subdir of an audiobook + // Check if book dir group is already an audiobook var existingAudiobook = this.db.audiobooks.find(ab => fullPath.startsWith(ab.fullPath)) if (existingAudiobook) {