From 0db34dcab5202916156813418a3627da2005d5d1 Mon Sep 17 00:00:00 2001 From: Mark Cooper Date: Fri, 1 Oct 2021 14:52:10 -0500 Subject: [PATCH] Scanner update check for mismatched inode and update --- client/package.json | 2 +- package.json | 2 +- server/Scanner.js | 55 +++++++++++++++++++++++++++++++++++-- server/objects/Audiobook.js | 15 ++++++---- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/client/package.json b/client/package.json index 1de00cec..e753a7ca 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "1.3.1", + "version": "1.3.2", "description": "Audiobook manager and player", "main": "index.js", "scripts": { diff --git a/package.json b/package.json index 36cc57c7..46696561 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "1.3.1", + "version": "1.3.2", "description": "Self-hosted audiobook server for managing and playing audiobooks", "main": "index.js", "scripts": { diff --git a/server/Scanner.js b/server/Scanner.js index 8eae3bb6..8226daa7 100644 --- a/server/Scanner.js +++ b/server/Scanner.js @@ -60,10 +60,59 @@ class Scanner { return audiobookDataAudioFiles.filter(abdFile => !!abdFile.ino) } + // Only updates audio files with matching paths + syncAudiobookInodeValues(audiobook, { audioFiles, otherFiles }) { + var filesUpdated = 0 + + // Sync audio files & audio tracks with updated inodes + audiobook._audioFiles.forEach((audioFile) => { + var matchingAudioFile = audioFiles.find(af => af.ino !== audioFile.ino && af.path === audioFile.path) + if (matchingAudioFile) { + // Audio Track should always have the same ino as the equivalent audio file (not all audio files have a track) + var audioTrack = audiobook.tracks.find(t => t.ino === audioFile.ino) + if (audioTrack) { + Logger.debug(`[Scanner] Found audio file & track with mismatched inode "${audioFile.filename}" - updating it`) + audioTrack.ino = matchingAudioFile.ino + filesUpdated++ + } else { + Logger.debug(`[Scanner] Found audio file with mismatched inode "${audioFile.filename}" - updating it`) + } + + audioFile.ino = matchingAudioFile.ino + filesUpdated++ + } + }) + + // Sync other files with updated inodes + audiobook._otherFiles.forEach((otherFile) => { + var matchingOtherFile = otherFiles.find(of => of.ino !== otherFile.ino && of.path === otherFile.path) + if (matchingOtherFile) { + Logger.debug(`[Scanner] Found other file with mismatched inode "${otherFile.filename}" - updating it`) + otherFile.ino = matchingOtherFile.ino + filesUpdated++ + } + }) + + return filesUpdated + } + async scanAudiobookData(audiobookData, forceAudioFileScan = false) { var existingAudiobook = this.audiobooks.find(a => a.ino === audiobookData.ino) - // Logger.debug(`[Scanner] Scanning "${audiobookData.title}" (${audiobookData.ino}) - ${!!existingAudiobook ? 'Exists' : 'New'}`) + // inode value may change when using shared drives, update inode if matching path is found + // Note: inode will not change on rename + var hasUpdatedIno = false + if (!existingAudiobook) { + // check an audiobook exists with matching path, then update inodes + existingAudiobook = this.audiobooks.find(a => a.path === audiobookData.path) + if (existingAudiobook) { + hasUpdatedIno = true + var filesUpdated = this.syncAudiobookInodeValues(existingAudiobook, audiobookData) + Logger.info(`[Scanner] Updating inode value for "${existingAudiobook.title}" - ${filesUpdated} files updated`) + } + } + + // Logger.debug(`[Scanner] Scanning "${audiobookData.title}" (${audiobookData.ino}) - ${!!existingAudiobook ? 'Exists' : 'New'}`) if (existingAudiobook) { // TEMP: Check if is older audiobook and needs force rescan @@ -158,7 +207,7 @@ class Scanner { return ScanResult.REMOVED } - var hasUpdates = removedAudioFiles.length || removedAudioTracks.length || newAudioFiles.length || hasUpdatedAudioFiles + var hasUpdates = hasUpdatedIno || removedAudioFiles.length || removedAudioTracks.length || newAudioFiles.length || hasUpdatedAudioFiles // Check that audio tracks are in sequential order with no gaps if (existingAudiobook.checkUpdateMissingParts()) { @@ -177,12 +226,14 @@ class Scanner { hasUpdates = true } + // If audiobook was missing before, it is now found if (existingAudiobook.isMissing) { existingAudiobook.isMissing = false hasUpdates = true Logger.info(`[Scanner] "${existingAudiobook.title}" was missing but now it is found`) } + // Save changes and notify users if (hasUpdates) { existingAudiobook.setChapters() diff --git a/server/objects/Audiobook.js b/server/objects/Audiobook.js index 26b23642..2fec1e86 100644 --- a/server/objects/Audiobook.js +++ b/server/objects/Audiobook.js @@ -105,23 +105,26 @@ class Audiobook { } get invalidParts() { - return (this.audioFiles || []).filter(af => af.invalid).map(af => ({ filename: af.filename, error: af.error || 'Unknown Error' })) + return this._audioFiles.filter(af => af.invalid).map(af => ({ filename: af.filename, error: af.error || 'Unknown Error' })) } + get _audioFiles() { return this.audioFiles || [] } + get _otherFiles() { return this.otherFiles || [] } + get ebooks() { return this.otherFiles.filter(file => file.filetype === 'ebook') } get hasMissingIno() { - return !this.ino || (this.audioFiles || []).find(abf => !abf.ino) || (this.otherFiles || []).find(f => !f.ino) || (this.tracks || []).find(t => !t.ino) + return !this.ino || this._audioFiles.find(abf => !abf.ino) || this._otherFiles.find(f => !f.ino) || (this.tracks || []).find(t => !t.ino) } get hasEmbeddedCoverArt() { - return !!(this.audioFiles || []).find(af => af.embeddedCoverArt) + return !!this._audioFiles.find(af => af.embeddedCoverArt) } get hasDescriptionTextFile() { - return !!(this.otherFiles || []).find(of => of.filename === 'desc.txt') + return !!this._otherFiles.find(of => of.filename === 'desc.txt') } bookToJSON() { @@ -148,8 +151,8 @@ class Audiobook { tags: this.tags, book: this.bookToJSON(), tracks: this.tracksToJSON(), - audioFiles: (this.audioFiles || []).map(audioFile => audioFile.toJSON()), - otherFiles: (this.otherFiles || []).map(otherFile => otherFile.toJSON()), + audioFiles: this._audioFiles.map(audioFile => audioFile.toJSON()), + otherFiles: this._otherFiles.map(otherFile => otherFile.toJSON()), chapters: this.chapters || [], isMissing: !!this.isMissing }