mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	New data model fix scan for creating series/authors and mapping ebooks
This commit is contained in:
		
							parent
							
								
									ea9ec13845
								
							
						
					
					
						commit
						6597fca576
					
				| @ -3,7 +3,7 @@ const Logger = require('../Logger') | |||||||
| const LibraryFile = require('./files/LibraryFile') | const LibraryFile = require('./files/LibraryFile') | ||||||
| const Book = require('./entities/Book') | const Book = require('./entities/Book') | ||||||
| const Podcast = require('./entities/Podcast') | const Podcast = require('./entities/Podcast') | ||||||
| const { areEquivalent, copyValue } = require('../utils/index') | const { areEquivalent, copyValue, getId } = require('../utils/index') | ||||||
| 
 | 
 | ||||||
| class LibraryItem { | class LibraryItem { | ||||||
|   constructor(libraryItem = null) { |   constructor(libraryItem = null) { | ||||||
| @ -149,6 +149,7 @@ class LibraryItem { | |||||||
| 
 | 
 | ||||||
|   // Data comes from scandir library item data
 |   // Data comes from scandir library item data
 | ||||||
|   setData(libraryMediaType, payload) { |   setData(libraryMediaType, payload) { | ||||||
|  |     this.id = getId('li') | ||||||
|     if (libraryMediaType === 'podcast') { |     if (libraryMediaType === 'podcast') { | ||||||
|       this.mediaType = 'podcast' |       this.mediaType = 'podcast' | ||||||
|       this.media = new Podcast() |       this.media = new Podcast() | ||||||
| @ -319,7 +320,6 @@ class LibraryItem { | |||||||
| 
 | 
 | ||||||
|     dataFound.libraryFiles.forEach((lf) => { |     dataFound.libraryFiles.forEach((lf) => { | ||||||
|       var fileFoundCheck = this.checkFileFound(lf, true) |       var fileFoundCheck = this.checkFileFound(lf, true) | ||||||
|       console.log('Check library file', fileFoundCheck, lf.metadata.filename) |  | ||||||
|       if (fileFoundCheck === null) { |       if (fileFoundCheck === null) { | ||||||
|         newLibraryFiles.push(lf) |         newLibraryFiles.push(lf) | ||||||
|       } else if (fileFoundCheck) { |       } else if (fileFoundCheck) { | ||||||
| @ -381,21 +381,47 @@ class LibraryItem { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   findLibraryFileWithIno(inode) { | ||||||
|  |     return this.libraryFiles.find(lf => lf.ino === inode) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   // Set metadata from files
 |   // Set metadata from files
 | ||||||
|   async syncFiles(preferOpfMetadata) { |   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') |     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) { |     if (imageFiles.length && !this.media.coverPath) { | ||||||
|       this.media.coverPath = imageFiles[0].metadata.path |       this.media.coverPath = imageFiles[0].metadata.path | ||||||
|       Logger.debug('[LibraryItem] Set media cover path', this.media.coverPath) |       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') |     var textMetadataFiles = this.libraryFiles.filter(lf => lf.fileType === 'metadata' || lf.fileType === 'text') | ||||||
|     if (!textMetadataFiles.length) { |     if (textMetadataFiles.length) { | ||||||
|       return false |       if (await this.media.syncMetadataFiles(textMetadataFiles, preferOpfMetadata)) { | ||||||
|  |         hasUpdated = true | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     var hasUpdated = await this.media.syncMetadataFiles(textMetadataFiles, preferOpfMetadata) |  | ||||||
|     if (hasUpdated) { |     if (hasUpdated) { | ||||||
|       this.updatedAt = Date.now() |       this.updatedAt = Date.now() | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -341,5 +341,11 @@ class Book { | |||||||
|     } |     } | ||||||
|     return payload |     return payload | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   addEbookFile(libraryFile) { | ||||||
|  |     var newEbook = new EBookFile() | ||||||
|  |     newEbook.setData(libraryFile) | ||||||
|  |     this.ebookFiles.push(newEbook) | ||||||
|  |   } | ||||||
| } | } | ||||||
| module.exports = Book | module.exports = Book | ||||||
| @ -30,5 +30,28 @@ class EBookFile { | |||||||
|       updatedAt: this.updatedAt |       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 | module.exports = EBookFile | ||||||
| @ -247,7 +247,7 @@ class BookMetadata { | |||||||
|   parseAuthorsTag(authorsTag) { |   parseAuthorsTag(authorsTag) { | ||||||
|     var parsed = parseNameString(authorsTag) |     var parsed = parseNameString(authorsTag) | ||||||
|     if (!parsed) return [] |     if (!parsed) return [] | ||||||
|     return parsed.map((au) => { |     return (parsed.names || []).map((au) => { | ||||||
|       return { |       return { | ||||||
|         id: `new-${Math.floor(Math.random() * 1000000)}`, |         id: `new-${Math.floor(Math.random() * 1000000)}`, | ||||||
|         name: au |         name: au | ||||||
|  | |||||||
| @ -185,7 +185,7 @@ class AudioFileScanner { | |||||||
| 
 | 
 | ||||||
|       var totalAudioFilesToInclude = audioScanResult.audioFiles.length |       var totalAudioFilesToInclude = audioScanResult.audioFiles.length | ||||||
|       var newAudioFiles = audioScanResult.audioFiles.filter(af => { |       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
 |       // Adding audio files to book media
 | ||||||
|  | |||||||
| @ -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
 |     if (!libraryItem.media.hasMediaFiles) { // Library item is invalid
 | ||||||
|       libraryItem.setInvalid() |       libraryItem.setInvalid() | ||||||
|       hasUpdated = true |       hasUpdated = true | ||||||
| @ -407,51 +410,58 @@ class Scanner { | |||||||
|         libraryItem.media.updateLastCoverSearch(updatedCover) |         libraryItem.media.updateLastCoverSearch(updatedCover) | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       // Create or match all new authors and series
 |       // Temp authors & series are inserted - create them if found
 | ||||||
|       if (libraryItem.media.metadata.authors.some(au => au.id.startsWith('new'))) { |       await this.createNewAuthorsAndSeries(libraryItem) | ||||||
|         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())) |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return 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) { |   getFileUpdatesGrouped(fileUpdates) { | ||||||
|     var folderGroups = {} |     var folderGroups = {} | ||||||
|     fileUpdates.forEach((file) => { |     fileUpdates.forEach((file) => { | ||||||
| @ -529,7 +539,6 @@ class Scanner { | |||||||
|       // Check if book dir group is already an item
 |       // Check if book dir group is already an item
 | ||||||
|       var existingLibraryItem = this.db.libraryItems.find(li => fullPath.startsWith(li.path)) |       var existingLibraryItem = this.db.libraryItems.find(li => fullPath.startsWith(li.path)) | ||||||
|       if (existingLibraryItem) { |       if (existingLibraryItem) { | ||||||
| 
 |  | ||||||
|         // Is the item exactly - check if was deleted
 |         // Is the item exactly - check if was deleted
 | ||||||
|         if (existingLibraryItem.path === fullPath) { |         if (existingLibraryItem.path === fullPath) { | ||||||
|           var exists = await fs.pathExists(fullPath) |           var exists = await fs.pathExists(fullPath) | ||||||
|  | |||||||
| @ -18,7 +18,9 @@ const FileMetadata = require('../objects/metadata/FileMetadata') | |||||||
| const AudioMetaTags = require('../objects/metadata/AudioMetaTags') | const AudioMetaTags = require('../objects/metadata/AudioMetaTags') | ||||||
| 
 | 
 | ||||||
| var authorsToAdd = [] | var authorsToAdd = [] | ||||||
|  | var existingDbAuthors = [] | ||||||
| var seriesToAdd = [] | var seriesToAdd = [] | ||||||
|  | var existingDbSeries = [] | ||||||
| 
 | 
 | ||||||
| // Load old audiobooks
 | // Load old audiobooks
 | ||||||
| async function loadAudiobooks() { | async function loadAudiobooks() { | ||||||
| @ -41,6 +43,10 @@ function makeAuthorsFromOldAb(authorsList) { | |||||||
|     if (existingAuthor) { |     if (existingAuthor) { | ||||||
|       return existingAuthor.toJSONMinimal() |       return existingAuthor.toJSONMinimal() | ||||||
|     } |     } | ||||||
|  |     var existingDbAuthor = existingDbAuthors.find(a => a.name.toLowerCase() === authorName.toLowerCase()) | ||||||
|  |     if (existingDbAuthor) { | ||||||
|  |       return existingDbAuthor.toJSONMinimal() | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     var newAuthor = new Author() |     var newAuthor = new Author() | ||||||
|     newAuthor.setData({ name: authorName }) |     newAuthor.setData({ name: authorName }) | ||||||
| @ -55,6 +61,10 @@ function makeSeriesFromOldAb({ series, volumeNumber }) { | |||||||
|   if (existingSeries) { |   if (existingSeries) { | ||||||
|     return [existingSeries.toJSONMinimal(volumeNumber)] |     return [existingSeries.toJSONMinimal(volumeNumber)] | ||||||
|   } |   } | ||||||
|  |   var existingDbSeriesItem = existingDbSeries.find(s => s.name.toLowerCase() === series.toLowerCase()) | ||||||
|  |   if (existingDbSeriesItem) { | ||||||
|  |     return [existingDbSeriesItem.toJSONMinimal(volumeNumber)] | ||||||
|  |   } | ||||||
|   var newSeries = new Series() |   var newSeries = new Series() | ||||||
|   newSeries.setData({ name: series }) |   newSeries.setData({ name: series }) | ||||||
|   seriesToAdd.push(newSeries) |   seriesToAdd.push(newSeries) | ||||||
| @ -190,6 +200,13 @@ async function migrateDb(db) { | |||||||
|     return |     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)) |   var libraryItems = audiobooks.map((ab) => makeLibraryItemFromOldAb(ab)) | ||||||
| 
 | 
 | ||||||
|   Logger.info(`>>> ${libraryItems.length} Library Items made`) |   Logger.info(`>>> ${libraryItems.length} Library Items made`) | ||||||
| @ -202,7 +219,10 @@ async function migrateDb(db) { | |||||||
|     Logger.info(`>>> ${seriesToAdd.length} Series made`) |     Logger.info(`>>> ${seriesToAdd.length} Series made`) | ||||||
|     await db.insertEntities('series', seriesToAdd) |     await db.insertEntities('series', seriesToAdd) | ||||||
|   } |   } | ||||||
| 
 |   existingDbSeries = [] | ||||||
|  |   existingDbAuthors = [] | ||||||
|  |   authorsToAdd = [] | ||||||
|  |   seriesToAdd = [] | ||||||
|   Logger.info(`==== DB Migration Complete ====`) |   Logger.info(`==== DB Migration Complete ====`) | ||||||
| } | } | ||||||
| module.exports = migrateDb | module.exports = migrateDb | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user