diff --git a/server/objects/LibraryItem.js b/server/objects/LibraryItem.js index e81fddb8..2db38b7e 100644 --- a/server/objects/LibraryItem.js +++ b/server/objects/LibraryItem.js @@ -3,7 +3,7 @@ const Logger = require('../Logger') const LibraryFile = require('./files/LibraryFile') const Book = require('./entities/Book') const Podcast = require('./entities/Podcast') -const { areEquivalent, copyValue } = require('../utils/index') +const { areEquivalent, copyValue, getId } = require('../utils/index') class LibraryItem { constructor(libraryItem = null) { @@ -149,6 +149,7 @@ class LibraryItem { // Data comes from scandir library item data setData(libraryMediaType, payload) { + this.id = getId('li') if (libraryMediaType === 'podcast') { this.mediaType = 'podcast' this.media = new Podcast() @@ -319,7 +320,6 @@ class LibraryItem { dataFound.libraryFiles.forEach((lf) => { var fileFoundCheck = this.checkFileFound(lf, true) - console.log('Check library file', fileFoundCheck, lf.metadata.filename) if (fileFoundCheck === null) { newLibraryFiles.push(lf) } else if (fileFoundCheck) { @@ -381,21 +381,47 @@ class LibraryItem { } } + findLibraryFileWithIno(inode) { + return this.libraryFiles.find(lf => lf.ino === inode) + } + // Set metadata from files async syncFiles(preferOpfMetadata) { + var hasUpdated = false + + if (this.mediaType === 'book') { + // Add/update ebook files (ebooks that were removed are removed in checkScanData) + this.libraryFiles.forEach((lf) => { + if (lf.fileType === 'ebook') { + var existingFile = this.media.findFileWithInode(lf.ino) + if (!existingFile) { + this.media.addEbookFile(lf) + hasUpdated = true + } else if (existingFile.ebookFormat) { + if (existingFile.updateFromLibraryFile(lf)) {// EBookFile.js + hasUpdated = true + } + } + } + }) + } + + // Set cover image if not set var imageFiles = this.libraryFiles.filter(lf => lf.fileType === 'image') - console.log('image files', imageFiles.length, 'has cover', this.media.coverPath) if (imageFiles.length && !this.media.coverPath) { this.media.coverPath = imageFiles[0].metadata.path Logger.debug('[LibraryItem] Set media cover path', this.media.coverPath) + hasUpdated = true } + // Parse metadata files var textMetadataFiles = this.libraryFiles.filter(lf => lf.fileType === 'metadata' || lf.fileType === 'text') - if (!textMetadataFiles.length) { - return false + if (textMetadataFiles.length) { + if (await this.media.syncMetadataFiles(textMetadataFiles, preferOpfMetadata)) { + hasUpdated = true + } } - var hasUpdated = await this.media.syncMetadataFiles(textMetadataFiles, preferOpfMetadata) if (hasUpdated) { this.updatedAt = Date.now() } diff --git a/server/objects/entities/Book.js b/server/objects/entities/Book.js index 745ba702..d9184670 100644 --- a/server/objects/entities/Book.js +++ b/server/objects/entities/Book.js @@ -341,5 +341,11 @@ class Book { } return payload } + + addEbookFile(libraryFile) { + var newEbook = new EBookFile() + newEbook.setData(libraryFile) + this.ebookFiles.push(newEbook) + } } module.exports = Book \ No newline at end of file diff --git a/server/objects/files/EBookFile.js b/server/objects/files/EBookFile.js index d5e81f01..70e790ae 100644 --- a/server/objects/files/EBookFile.js +++ b/server/objects/files/EBookFile.js @@ -30,5 +30,28 @@ class EBookFile { updatedAt: this.updatedAt } } + + setData(libraryFile) { + this.ino = libraryFile.ino + this.metadata = libraryFile.metadata.clone() + this.ebookFormat = libraryFile.metadata.format + this.addedAt = Date.now() + this.updatedAt = Date.now() + } + + updateFromLibraryFile(libraryFile) { + var hasUpdated = false + + if (this.metadata.update(libraryFile.metadata)) { + hasUpdated = true + } + + if (this.ebookFormat !== libraryFile.metadata.format) { + this.ebookFormat = libraryFile.metadata.format + hasUpdated = true + } + + return hasUpdated + } } module.exports = EBookFile \ No newline at end of file diff --git a/server/objects/metadata/BookMetadata.js b/server/objects/metadata/BookMetadata.js index 71c3d199..940362f0 100644 --- a/server/objects/metadata/BookMetadata.js +++ b/server/objects/metadata/BookMetadata.js @@ -247,7 +247,7 @@ class BookMetadata { parseAuthorsTag(authorsTag) { var parsed = parseNameString(authorsTag) if (!parsed) return [] - return parsed.map((au) => { + return (parsed.names || []).map((au) => { return { id: `new-${Math.floor(Math.random() * 1000000)}`, name: au diff --git a/server/scanner/AudioFileScanner.js b/server/scanner/AudioFileScanner.js index a58dd7c7..691b9a48 100644 --- a/server/scanner/AudioFileScanner.js +++ b/server/scanner/AudioFileScanner.js @@ -185,7 +185,7 @@ class AudioFileScanner { var totalAudioFilesToInclude = audioScanResult.audioFiles.length var newAudioFiles = audioScanResult.audioFiles.filter(af => { - return !libraryItem.libraryFiles.find(lf => lf.ino === af.ino) + return !libraryItem.media.findFileWithInode(af.ino) }) // Adding audio files to book media diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index d2182d55..298d7089 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -354,6 +354,9 @@ class Scanner { } } + // Temp authors & series are inserted - create them if found + await this.createNewAuthorsAndSeries(libraryItem) + if (!libraryItem.media.hasMediaFiles) { // Library item is invalid libraryItem.setInvalid() hasUpdated = true @@ -407,51 +410,58 @@ class Scanner { libraryItem.media.updateLastCoverSearch(updatedCover) } - // Create or match all new authors and series - if (libraryItem.media.metadata.authors.some(au => au.id.startsWith('new'))) { - var newAuthors = [] - libraryItem.media.metadata.authors = libraryItem.media.metadata.authors.map((tempMinAuthor) => { - var _author = this.db.authors.find(au => au.checkNameEquals(tempMinAuthor.name)) - if (!_author) { - _author = new Author() - _author.setData(tempMinAuthor) - newAuthors.push(_author) - } - return { - id: _author.id, - name: _author.name - } - }) - if (newAuthors.length) { - await this.db.insertEntities('author', newAuthors) - this.emitter('authors_added', newAuthors.map(au => au.toJSON())) - } - } - if (libraryItem.media.metadata.series.some(se => se.id.startsWith('new'))) { - var newSeries = [] - libraryItem.media.metadata.series = libraryItem.media.metadata.series.map((tempMinSeries) => { - var _series = this.db.series.find(se => se.checkNameEquals(tempMinSeries.name)) - if (!_series) { - _series = new Series() - _series.setData(tempMinSeries) - newSeries.push(_series) - } - return { - id: _series.id, - name: _series.name, - sequence: tempMinSeries.sequence - } - }) - if (newSeries.length) { - await this.db.insertEntities('series', newSeries) - this.emitter('series_added', newSeries.map(se => se.toJSON())) - } - } + // Temp authors & series are inserted - create them if found + await this.createNewAuthorsAndSeries(libraryItem) } return libraryItem } + async createNewAuthorsAndSeries(libraryItem) { + // Create or match all new authors and series + if (libraryItem.media.metadata.authors.some(au => au.id.startsWith('new'))) { + var newAuthors = [] + libraryItem.media.metadata.authors = libraryItem.media.metadata.authors.map((tempMinAuthor) => { + var _author = this.db.authors.find(au => au.checkNameEquals(tempMinAuthor.name)) + if (!_author) _author = newAuthors.find(au => au.checkNameEquals(tempMinAuthor.name)) // Check new unsaved authors + if (!_author) { + _author = new Author() + _author.setData(tempMinAuthor) + newAuthors.push(_author) + } + return { + id: _author.id, + name: _author.name + } + }) + if (newAuthors.length) { + await this.db.insertEntities('author', newAuthors) + this.emitter('authors_added', newAuthors.map(au => au.toJSON())) + } + } + if (libraryItem.media.metadata.series.some(se => se.id.startsWith('new'))) { + var newSeries = [] + libraryItem.media.metadata.series = libraryItem.media.metadata.series.map((tempMinSeries) => { + var _series = this.db.series.find(se => se.checkNameEquals(tempMinSeries.name)) + if (!_series) _series = newSeries.find(se => se.checkNameEquals(tempMinSeries.name)) // Check new unsaved series + if (!_series) { + _series = new Series() + _series.setData(tempMinSeries) + newSeries.push(_series) + } + return { + id: _series.id, + name: _series.name, + sequence: tempMinSeries.sequence + } + }) + if (newSeries.length) { + await this.db.insertEntities('series', newSeries) + this.emitter('series_added', newSeries.map(se => se.toJSON())) + } + } + } + getFileUpdatesGrouped(fileUpdates) { var folderGroups = {} fileUpdates.forEach((file) => { @@ -529,7 +539,6 @@ class Scanner { // Check if book dir group is already an item var existingLibraryItem = this.db.libraryItems.find(li => fullPath.startsWith(li.path)) if (existingLibraryItem) { - // Is the item exactly - check if was deleted if (existingLibraryItem.path === fullPath) { var exists = await fs.pathExists(fullPath) diff --git a/server/utils/dbMigration.js b/server/utils/dbMigration.js index c409a2b6..1437b0e0 100644 --- a/server/utils/dbMigration.js +++ b/server/utils/dbMigration.js @@ -18,7 +18,9 @@ const FileMetadata = require('../objects/metadata/FileMetadata') const AudioMetaTags = require('../objects/metadata/AudioMetaTags') var authorsToAdd = [] +var existingDbAuthors = [] var seriesToAdd = [] +var existingDbSeries = [] // Load old audiobooks async function loadAudiobooks() { @@ -41,6 +43,10 @@ function makeAuthorsFromOldAb(authorsList) { if (existingAuthor) { return existingAuthor.toJSONMinimal() } + var existingDbAuthor = existingDbAuthors.find(a => a.name.toLowerCase() === authorName.toLowerCase()) + if (existingDbAuthor) { + return existingDbAuthor.toJSONMinimal() + } var newAuthor = new Author() newAuthor.setData({ name: authorName }) @@ -55,6 +61,10 @@ function makeSeriesFromOldAb({ series, volumeNumber }) { if (existingSeries) { return [existingSeries.toJSONMinimal(volumeNumber)] } + var existingDbSeriesItem = existingDbSeries.find(s => s.name.toLowerCase() === series.toLowerCase()) + if (existingDbSeriesItem) { + return [existingDbSeriesItem.toJSONMinimal(volumeNumber)] + } var newSeries = new Series() newSeries.setData({ name: series }) seriesToAdd.push(newSeries) @@ -190,6 +200,13 @@ async function migrateDb(db) { return } + if (db.authors && db.authors.length) { + existingDbAuthors = db.authors + } + if (db.series && db.series.length) { + existingDbSeries = db.series + } + var libraryItems = audiobooks.map((ab) => makeLibraryItemFromOldAb(ab)) Logger.info(`>>> ${libraryItems.length} Library Items made`) @@ -202,7 +219,10 @@ async function migrateDb(db) { Logger.info(`>>> ${seriesToAdd.length} Series made`) await db.insertEntities('series', seriesToAdd) } - + existingDbSeries = [] + existingDbAuthors = [] + authorsToAdd = [] + seriesToAdd = [] Logger.info(`==== DB Migration Complete ====`) } module.exports = migrateDb \ No newline at end of file