mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Remove old library, folder and librarysettings model
This commit is contained in:
		
							parent
							
								
									fd827b2214
								
							
						
					
					
						commit
						c45c82306e
					
				| @ -19,7 +19,7 @@ class FolderWatcher extends EventEmitter { | ||||
|   constructor() { | ||||
|     super() | ||||
| 
 | ||||
|     /** @type {{id:string, name:string, folders:import('./objects/Folder')[], paths:string[], watcher:Watcher[]}[]} */ | ||||
|     /** @type {{id:string, name:string, libraryFolders:import('./models/Folder')[], paths:string[], watcher:Watcher[]}[]} */ | ||||
|     this.libraryWatchers = [] | ||||
|     /** @type {PendingFileUpdate[]} */ | ||||
|     this.pendingFileUpdates = [] | ||||
|  | ||||
| @ -172,17 +172,17 @@ class LibraryController { | ||||
|    * @param {Response} res | ||||
|    */ | ||||
|   async findAll(req, res) { | ||||
|     const libraries = await Database.libraryModel.getAllOldLibraries() | ||||
|     const libraries = await Database.libraryModel.getAllWithFolders() | ||||
| 
 | ||||
|     const librariesAccessible = req.user.permissions?.librariesAccessible || [] | ||||
|     if (librariesAccessible.length) { | ||||
|       return res.json({ | ||||
|         libraries: libraries.filter((lib) => librariesAccessible.includes(lib.id)).map((lib) => lib.toJSON()) | ||||
|         libraries: libraries.filter((lib) => librariesAccessible.includes(lib.id)).map((lib) => lib.toOldJSON()) | ||||
|       }) | ||||
|     } | ||||
| 
 | ||||
|     res.json({ | ||||
|       libraries: libraries.map((lib) => lib.toJSON()) | ||||
|       libraries: libraries.map((lib) => lib.toOldJSON()) | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
| @ -198,16 +198,15 @@ class LibraryController { | ||||
|       const filterdata = await libraryFilters.getFilterData(req.library.mediaType, req.library.id) | ||||
|       const customMetadataProviders = await Database.customMetadataProviderModel.getForClientByMediaType(req.library.mediaType) | ||||
| 
 | ||||
|       const oldLibrary = Database.libraryModel.getOldLibrary(req.library) | ||||
|       return res.json({ | ||||
|         filterdata, | ||||
|         issues: filterdata.numIssues, | ||||
|         numUserPlaylists: await Database.playlistModel.getNumPlaylistsForUserAndLibrary(req.user.id, req.library.id), | ||||
|         customMetadataProviders, | ||||
|         library: oldLibrary | ||||
|         library: req.library.toOldJSON() | ||||
|       }) | ||||
|     } | ||||
|     res.json(oldLibrary) | ||||
|     res.json(req.library.toOldJSON()) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
| @ -1051,9 +1050,7 @@ class LibraryController { | ||||
|       Logger.error(`[LibraryController] Non-root user "${req.user.username}" attempted to match library items`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
|     // TODO: Update to new library model
 | ||||
|     const oldLibrary = Database.libraryModel.getOldLibrary(req.library) | ||||
|     Scanner.matchLibraryItems(oldLibrary) | ||||
|     Scanner.matchLibraryItems(req.library) | ||||
|     res.sendStatus(200) | ||||
|   } | ||||
| 
 | ||||
| @ -1071,10 +1068,9 @@ class LibraryController { | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
|     res.sendStatus(200) | ||||
|     // TODO: Update to new library model
 | ||||
|     const oldLibrary = Database.libraryModel.getOldLibrary(req.library) | ||||
| 
 | ||||
|     const forceRescan = req.query.force === '1' | ||||
|     await LibraryScanner.scan(oldLibrary, forceRescan) | ||||
|     await LibraryScanner.scan(req.library, forceRescan) | ||||
| 
 | ||||
|     await Database.resetLibraryIssuesFilterData(req.library.id) | ||||
|     Logger.info('[LibraryController] Scan complete') | ||||
|  | ||||
| @ -65,12 +65,12 @@ class CronManager { | ||||
|   startCronForLibrary(_library) { | ||||
|     Logger.debug(`[CronManager] Init library scan cron for ${_library.name} on schedule ${_library.settings.autoScanCronExpression}`) | ||||
|     const libScanCron = cron.schedule(_library.settings.autoScanCronExpression, async () => { | ||||
|       const oldLibrary = await Database.libraryModel.getOldById(_library.id) | ||||
|       if (!oldLibrary) { | ||||
|       const library = await Database.libraryModel.findByIdWithFolders(_library.id) | ||||
|       if (!library) { | ||||
|         Logger.error(`[CronManager] Library not found for scan cron ${_library.id}`) | ||||
|       } else { | ||||
|         Logger.debug(`[CronManager] Library scan cron executing for ${oldLibrary.name}`) | ||||
|         LibraryScanner.scan(oldLibrary) | ||||
|         Logger.debug(`[CronManager] Library scan cron executing for ${library.name}`) | ||||
|         LibraryScanner.scan(library) | ||||
|       } | ||||
|     }) | ||||
|     this.libraryScanCrons.push({ | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| const { DataTypes, Model } = require('sequelize') | ||||
| const Logger = require('../Logger') | ||||
| const oldLibrary = require('../objects/Library') | ||||
| 
 | ||||
| /** | ||||
|  * @typedef LibrarySettingsObject | ||||
| @ -98,114 +97,6 @@ class Library extends Model { | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Get all old libraries | ||||
|    * @returns {Promise<oldLibrary[]>} | ||||
|    */ | ||||
|   static async getAllOldLibraries() { | ||||
|     const libraries = await this.findAll({ | ||||
|       include: this.sequelize.models.libraryFolder, | ||||
|       order: [['displayOrder', 'ASC']] | ||||
|     }) | ||||
|     return libraries.map((lib) => this.getOldLibrary(lib)) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Convert expanded Library to oldLibrary | ||||
|    * @param {Library} libraryExpanded | ||||
|    * @returns {oldLibrary} | ||||
|    */ | ||||
|   static getOldLibrary(libraryExpanded) { | ||||
|     const folders = libraryExpanded.libraryFolders.map((folder) => { | ||||
|       return { | ||||
|         id: folder.id, | ||||
|         fullPath: folder.path, | ||||
|         libraryId: folder.libraryId, | ||||
|         addedAt: folder.createdAt.valueOf() | ||||
|       } | ||||
|     }) | ||||
|     return new oldLibrary({ | ||||
|       id: libraryExpanded.id, | ||||
|       oldLibraryId: libraryExpanded.extraData?.oldLibraryId || null, | ||||
|       name: libraryExpanded.name, | ||||
|       folders, | ||||
|       displayOrder: libraryExpanded.displayOrder, | ||||
|       icon: libraryExpanded.icon, | ||||
|       mediaType: libraryExpanded.mediaType, | ||||
|       provider: libraryExpanded.provider, | ||||
|       settings: libraryExpanded.settings, | ||||
|       lastScan: libraryExpanded.lastScan?.valueOf() || null, | ||||
|       lastScanVersion: libraryExpanded.lastScanVersion || null, | ||||
|       lastScanMetadataPrecedence: libraryExpanded.extraData?.lastScanMetadataPrecedence || null, | ||||
|       createdAt: libraryExpanded.createdAt.valueOf(), | ||||
|       lastUpdate: libraryExpanded.updatedAt.valueOf() | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Update library and library folders | ||||
|    * @param {object} oldLibrary | ||||
|    * @returns {Promise<Library|null>} | ||||
|    */ | ||||
|   static async updateFromOld(oldLibrary) { | ||||
|     const existingLibrary = await this.findByPk(oldLibrary.id, { | ||||
|       include: this.sequelize.models.libraryFolder | ||||
|     }) | ||||
|     if (!existingLibrary) { | ||||
|       Logger.error(`[Library] Failed to update library ${oldLibrary.id} - not found`) | ||||
|       return null | ||||
|     } | ||||
| 
 | ||||
|     const library = this.getFromOld(oldLibrary) | ||||
| 
 | ||||
|     const libraryFolders = oldLibrary.folders.map((folder) => { | ||||
|       return { | ||||
|         id: folder.id, | ||||
|         path: folder.fullPath, | ||||
|         libraryId: library.id | ||||
|       } | ||||
|     }) | ||||
|     for (const libraryFolder of libraryFolders) { | ||||
|       const existingLibraryFolder = existingLibrary.libraryFolders.find((lf) => lf.id === libraryFolder.id) | ||||
|       if (!existingLibraryFolder) { | ||||
|         await this.sequelize.models.libraryFolder.create(libraryFolder) | ||||
|       } else if (existingLibraryFolder.path !== libraryFolder.path) { | ||||
|         await existingLibraryFolder.update({ path: libraryFolder.path }) | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const libraryFoldersRemoved = existingLibrary.libraryFolders.filter((lf) => !libraryFolders.some((_lf) => _lf.id === lf.id)) | ||||
|     for (const existingLibraryFolder of libraryFoldersRemoved) { | ||||
|       await existingLibraryFolder.destroy() | ||||
|     } | ||||
| 
 | ||||
|     return existingLibrary.update(library) | ||||
|   } | ||||
| 
 | ||||
|   static getFromOld(oldLibrary) { | ||||
|     const extraData = {} | ||||
|     if (oldLibrary.oldLibraryId) { | ||||
|       extraData.oldLibraryId = oldLibrary.oldLibraryId | ||||
|     } | ||||
|     if (oldLibrary.lastScanMetadataPrecedence) { | ||||
|       extraData.lastScanMetadataPrecedence = oldLibrary.lastScanMetadataPrecedence | ||||
|     } | ||||
|     return { | ||||
|       id: oldLibrary.id, | ||||
|       name: oldLibrary.name, | ||||
|       displayOrder: oldLibrary.displayOrder, | ||||
|       icon: oldLibrary.icon || null, | ||||
|       mediaType: oldLibrary.mediaType || null, | ||||
|       provider: oldLibrary.provider, | ||||
|       settings: oldLibrary.settings?.toJSON() || {}, | ||||
|       lastScan: oldLibrary.lastScan || null, | ||||
|       lastScanVersion: oldLibrary.lastScanVersion || null, | ||||
|       createdAt: oldLibrary.createdAt, | ||||
|       updatedAt: oldLibrary.lastUpdate, | ||||
|       extraData | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Destroy library by id | ||||
|    * @param {string} libraryId | ||||
| @ -231,20 +122,6 @@ class Library extends Model { | ||||
|     return libraries.map((l) => l.id) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Find Library by primary key & return oldLibrary | ||||
|    * @param {string} libraryId | ||||
|    * @returns {Promise<oldLibrary|null>} Returns null if not found | ||||
|    */ | ||||
|   static async getOldById(libraryId) { | ||||
|     if (!libraryId) return null | ||||
|     const library = await this.findByPk(libraryId, { | ||||
|       include: this.sequelize.models.libraryFolder | ||||
|     }) | ||||
|     if (!library) return null | ||||
|     return this.getOldLibrary(library) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Get the largest value in the displayOrder column | ||||
|    * Used for setting a new libraries display order | ||||
| @ -308,6 +185,12 @@ class Library extends Model { | ||||
|   get isBook() { | ||||
|     return this.mediaType === 'book' | ||||
|   } | ||||
|   /** | ||||
|    * @returns {string[]} | ||||
|    */ | ||||
|   get lastScanMetadataPrecedence() { | ||||
|     return this.extraData?.lastScanMetadataPrecedence || [] | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * TODO: Update to use new model | ||||
|  | ||||
| @ -1,38 +0,0 @@ | ||||
| const uuidv4 = require("uuid").v4 | ||||
| 
 | ||||
| class Folder { | ||||
|   constructor(folder = null) { | ||||
|     this.id = null | ||||
|     this.fullPath = null | ||||
|     this.libraryId = null | ||||
|     this.addedAt = null | ||||
| 
 | ||||
|     if (folder) { | ||||
|       this.construct(folder) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   construct(folder) { | ||||
|     this.id = folder.id | ||||
|     this.fullPath = folder.fullPath | ||||
|     this.libraryId = folder.libraryId | ||||
|     this.addedAt = folder.addedAt | ||||
|   } | ||||
| 
 | ||||
|   toJSON() { | ||||
|     return { | ||||
|       id: this.id, | ||||
|       fullPath: this.fullPath, | ||||
|       libraryId: this.libraryId, | ||||
|       addedAt: this.addedAt | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   setData(data) { | ||||
|     this.id = data.id || uuidv4() | ||||
|     this.fullPath = data.fullPath | ||||
|     this.libraryId = data.libraryId | ||||
|     this.addedAt = Date.now() | ||||
|   } | ||||
| } | ||||
| module.exports = Folder | ||||
| @ -1,95 +0,0 @@ | ||||
| const Folder = require('./Folder') | ||||
| const LibrarySettings = require('./settings/LibrarySettings') | ||||
| const { filePathToPOSIX } = require('../utils/fileUtils') | ||||
| 
 | ||||
| class Library { | ||||
|   constructor(library = null) { | ||||
|     this.id = null | ||||
|     this.oldLibraryId = null // TODO: Temp
 | ||||
|     this.name = null | ||||
|     this.folders = [] | ||||
|     this.displayOrder = 1 | ||||
|     this.icon = 'database' | ||||
|     this.mediaType = 'book' // book, podcast
 | ||||
|     this.provider = 'google' | ||||
| 
 | ||||
|     this.lastScan = 0 | ||||
|     this.lastScanVersion = null | ||||
|     this.lastScanMetadataPrecedence = null | ||||
| 
 | ||||
|     this.settings = null | ||||
| 
 | ||||
|     this.createdAt = null | ||||
|     this.lastUpdate = null | ||||
| 
 | ||||
|     if (library) { | ||||
|       this.construct(library) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   get isPodcast() { | ||||
|     return this.mediaType === 'podcast' | ||||
|   } | ||||
|   get isBook() { | ||||
|     return this.mediaType === 'book' | ||||
|   } | ||||
| 
 | ||||
|   construct(library) { | ||||
|     this.id = library.id | ||||
|     this.oldLibraryId = library.oldLibraryId | ||||
|     this.name = library.name | ||||
|     this.folders = (library.folders || []).map((f) => new Folder(f)) | ||||
|     this.displayOrder = library.displayOrder || 1 | ||||
|     this.icon = library.icon || 'database' | ||||
|     this.mediaType = library.mediaType | ||||
|     this.provider = library.provider || 'google' | ||||
| 
 | ||||
|     this.settings = new LibrarySettings(library.settings) | ||||
|     if (library.settings === undefined) { | ||||
|       // LibrarySettings added in v2, migrate settings
 | ||||
|       this.settings.disableWatcher = !!library.disableWatcher | ||||
|     } | ||||
| 
 | ||||
|     this.lastScan = library.lastScan | ||||
|     this.lastScanVersion = library.lastScanVersion | ||||
|     this.lastScanMetadataPrecedence = library.lastScanMetadataPrecedence | ||||
| 
 | ||||
|     this.createdAt = library.createdAt | ||||
|     this.lastUpdate = library.lastUpdate | ||||
|     this.cleanOldValues() // mediaType changed for v2 and icon change for v2.2.2
 | ||||
|   } | ||||
| 
 | ||||
|   cleanOldValues() { | ||||
|     const availableIcons = ['database', 'audiobookshelf', 'books-1', 'books-2', 'book-1', 'microphone-1', 'microphone-3', 'radio', 'podcast', 'rss', 'headphones', 'music', 'file-picture', 'rocket', 'power', 'star', 'heart'] | ||||
|     if (!availableIcons.includes(this.icon)) { | ||||
|       if (this.icon === 'audiobook') this.icon = 'audiobookshelf' | ||||
|       else if (this.icon === 'book') this.icon = 'books-1' | ||||
|       else if (this.icon === 'comic') this.icon = 'file-picture' | ||||
|       else this.icon = 'database' | ||||
|     } | ||||
| 
 | ||||
|     const mediaTypes = ['podcast', 'book', 'video', 'music'] | ||||
|     if (!this.mediaType || !mediaTypes.includes(this.mediaType)) { | ||||
|       this.mediaType = 'book' | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   toJSON() { | ||||
|     return { | ||||
|       id: this.id, | ||||
|       oldLibraryId: this.oldLibraryId, | ||||
|       name: this.name, | ||||
|       folders: (this.folders || []).map((f) => f.toJSON()), | ||||
|       displayOrder: this.displayOrder, | ||||
|       icon: this.icon, | ||||
|       mediaType: this.mediaType, | ||||
|       provider: this.provider, | ||||
|       settings: this.settings.toJSON(), | ||||
|       lastScan: this.lastScan, | ||||
|       lastScanVersion: this.lastScanVersion, | ||||
|       createdAt: this.createdAt, | ||||
|       lastUpdate: this.lastUpdate | ||||
|     } | ||||
|   } | ||||
| } | ||||
| module.exports = Library | ||||
| @ -1,73 +0,0 @@ | ||||
| const { BookCoverAspectRatio } = require('../../utils/constants') | ||||
| 
 | ||||
| class LibrarySettings { | ||||
|   constructor(settings) { | ||||
|     this.coverAspectRatio = BookCoverAspectRatio.SQUARE | ||||
|     this.disableWatcher = false | ||||
|     this.skipMatchingMediaWithAsin = false | ||||
|     this.skipMatchingMediaWithIsbn = false | ||||
|     this.autoScanCronExpression = null | ||||
|     this.audiobooksOnly = false | ||||
|     this.epubsAllowScriptedContent = false | ||||
|     this.hideSingleBookSeries = false // Do not show series that only have 1 book
 | ||||
|     this.onlyShowLaterBooksInContinueSeries = false // Skip showing books that are earlier than the max sequence read
 | ||||
|     this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'] | ||||
|     this.podcastSearchRegion = 'us' | ||||
| 
 | ||||
|     if (settings) { | ||||
|       this.construct(settings) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   construct(settings) { | ||||
|     this.coverAspectRatio = !isNaN(settings.coverAspectRatio) ? settings.coverAspectRatio : BookCoverAspectRatio.SQUARE | ||||
|     this.disableWatcher = !!settings.disableWatcher | ||||
|     this.skipMatchingMediaWithAsin = !!settings.skipMatchingMediaWithAsin | ||||
|     this.skipMatchingMediaWithIsbn = !!settings.skipMatchingMediaWithIsbn | ||||
|     this.autoScanCronExpression = settings.autoScanCronExpression || null | ||||
|     this.audiobooksOnly = !!settings.audiobooksOnly | ||||
|     this.epubsAllowScriptedContent = !!settings.epubsAllowScriptedContent | ||||
|     this.hideSingleBookSeries = !!settings.hideSingleBookSeries | ||||
|     this.onlyShowLaterBooksInContinueSeries = !!settings.onlyShowLaterBooksInContinueSeries | ||||
|     if (settings.metadataPrecedence) { | ||||
|       this.metadataPrecedence = [...settings.metadataPrecedence] | ||||
|     } else { | ||||
|       // Added in v2.4.5
 | ||||
|       this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'] | ||||
|     } | ||||
|     this.podcastSearchRegion = settings.podcastSearchRegion || 'us' | ||||
|   } | ||||
| 
 | ||||
|   toJSON() { | ||||
|     return { | ||||
|       coverAspectRatio: this.coverAspectRatio, | ||||
|       disableWatcher: this.disableWatcher, | ||||
|       skipMatchingMediaWithAsin: this.skipMatchingMediaWithAsin, | ||||
|       skipMatchingMediaWithIsbn: this.skipMatchingMediaWithIsbn, | ||||
|       autoScanCronExpression: this.autoScanCronExpression, | ||||
|       audiobooksOnly: this.audiobooksOnly, | ||||
|       epubsAllowScriptedContent: this.epubsAllowScriptedContent, | ||||
|       hideSingleBookSeries: this.hideSingleBookSeries, | ||||
|       onlyShowLaterBooksInContinueSeries: this.onlyShowLaterBooksInContinueSeries, | ||||
|       metadataPrecedence: [...this.metadataPrecedence], | ||||
|       podcastSearchRegion: this.podcastSearchRegion | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   update(payload) { | ||||
|     let hasUpdates = false | ||||
|     for (const key in payload) { | ||||
|       if (key === 'metadataPrecedence') { | ||||
|         if (payload[key] && Array.isArray(payload[key]) && payload[key].join() !== this[key].join()) { | ||||
|           this[key] = payload[key] | ||||
|           hasUpdates = true | ||||
|         } | ||||
|       } else if (this[key] !== payload[key]) { | ||||
|         this[key] = payload[key] | ||||
|         hasUpdates = true | ||||
|       } | ||||
|     } | ||||
|     return hasUpdates | ||||
|   } | ||||
| } | ||||
| module.exports = LibrarySettings | ||||
| @ -1,10 +1,9 @@ | ||||
| const Path = require('path') | ||||
| const uuidv4 = require("uuid").v4 | ||||
| const uuidv4 = require('uuid').v4 | ||||
| const fs = require('../libs/fsExtra') | ||||
| const date = require('../libs/dateAndTime') | ||||
| 
 | ||||
| const Logger = require('../Logger') | ||||
| const Library = require('../objects/Library') | ||||
| const { LogLevel } = require('../utils/constants') | ||||
| const { secondsToTimestamp, elapsedPretty } = require('../utils/index') | ||||
| 
 | ||||
| @ -12,7 +11,7 @@ class LibraryScan { | ||||
|   constructor() { | ||||
|     this.id = null | ||||
|     this.type = null | ||||
|     /** @type {import('../objects/Library')} */ | ||||
|     /** @type {import('../models/Library')} */ | ||||
|     this.library = null | ||||
|     this.verbose = false | ||||
| 
 | ||||
| @ -33,13 +32,21 @@ class LibraryScan { | ||||
|     this.logs = [] | ||||
|   } | ||||
| 
 | ||||
|   get libraryId() { return this.library.id } | ||||
|   get libraryName() { return this.library.name } | ||||
|   get libraryMediaType() { return this.library.mediaType } | ||||
|   get folders() { return this.library.folders } | ||||
|   get libraryId() { | ||||
|     return this.library.id | ||||
|   } | ||||
|   get libraryName() { | ||||
|     return this.library.name | ||||
|   } | ||||
|   get libraryMediaType() { | ||||
|     return this.library.mediaType | ||||
|   } | ||||
|   get libraryFolders() { | ||||
|     return this.library.libraryFolders | ||||
|   } | ||||
| 
 | ||||
|   get timestamp() { | ||||
|     return (new Date()).toISOString() | ||||
|     return new Date().toISOString() | ||||
|   } | ||||
| 
 | ||||
|   get resultStats() { | ||||
| @ -92,10 +99,15 @@ class LibraryScan { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * | ||||
|    * @param {import('../models/Library')} library | ||||
|    * @param {string} type | ||||
|    */ | ||||
|   setData(library, type = 'scan') { | ||||
|     this.id = uuidv4() | ||||
|     this.type = type | ||||
|     this.library = new Library(library.toJSON()) // clone library
 | ||||
|     this.library = library | ||||
| 
 | ||||
|     this.startedAt = Date.now() | ||||
|   } | ||||
| @ -142,7 +154,7 @@ class LibraryScan { | ||||
| 
 | ||||
|     const outputPath = Path.join(scanLogDir, this.logFilename) | ||||
|     const logLines = [JSON.stringify(this.toJSON())] | ||||
|     this.logs.forEach(l => { | ||||
|     this.logs.forEach((l) => { | ||||
|       logLines.push(JSON.stringify(l)) | ||||
|     }) | ||||
|     await fs.writeFile(outputPath, logLines.join('\n') + '\n') | ||||
|  | ||||
| @ -45,7 +45,7 @@ class LibraryScanner { | ||||
| 
 | ||||
|   /** | ||||
|    * | ||||
|    * @param {import('../objects/Library')} library | ||||
|    * @param {import('../models/Library')} library | ||||
|    * @param {boolean} [forceRescan] | ||||
|    */ | ||||
|   async scan(library, forceRescan = false) { | ||||
| @ -54,12 +54,12 @@ class LibraryScanner { | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     if (!library.folders.length) { | ||||
|     if (!library.libraryFolders.length) { | ||||
|       Logger.warn(`[LibraryScanner] Library has no folders to scan "${library.name}"`) | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     if (library.isBook && library.settings.metadataPrecedence.join() !== library.lastScanMetadataPrecedence?.join()) { | ||||
|     if (library.isBook && library.settings.metadataPrecedence.join() !== library.lastScanMetadataPrecedence.join()) { | ||||
|       const lastScanMetadataPrecedence = library.lastScanMetadataPrecedence?.join() || 'Unset' | ||||
|       Logger.info(`[LibraryScanner] Library metadata precedence changed since last scan. From [${lastScanMetadataPrecedence}] to [${library.settings.metadataPrecedence.join()}]`) | ||||
|       forceRescan = true | ||||
| @ -103,9 +103,12 @@ class LibraryScanner { | ||||
|     library.lastScan = Date.now() | ||||
|     library.lastScanVersion = packageJson.version | ||||
|     if (library.isBook) { | ||||
|       library.lastScanMetadataPrecedence = library.settings.metadataPrecedence | ||||
|       const newExtraData = library.extraData || {} | ||||
|       newExtraData.lastScanMetadataPrecedence = library.settings.metadataPrecedence | ||||
|       library.extraData = newExtraData | ||||
|       library.changed('extraData', true) | ||||
|     } | ||||
|     await Database.libraryModel.updateFromOld(library) | ||||
|     await library.save() | ||||
| 
 | ||||
|     task.setFinished(libraryScan.scanResultsString) | ||||
|     TaskManager.taskFinished(task) | ||||
| @ -124,16 +127,16 @@ class LibraryScanner { | ||||
|   async scanLibrary(libraryScan, forceRescan) { | ||||
|     // Make sure library filter data is set
 | ||||
|     //   this is used to check for existing authors & series
 | ||||
|     await libraryFilters.getFilterData(libraryScan.library.mediaType, libraryScan.libraryId) | ||||
|     await libraryFilters.getFilterData(libraryScan.libraryMediaType, libraryScan.libraryId) | ||||
| 
 | ||||
|     /** @type {LibraryItemScanData[]} */ | ||||
|     let libraryItemDataFound = [] | ||||
| 
 | ||||
|     // Scan each library folder
 | ||||
|     for (let i = 0; i < libraryScan.folders.length; i++) { | ||||
|       const folder = libraryScan.folders[i] | ||||
|     for (let i = 0; i < libraryScan.libraryFolders.length; i++) { | ||||
|       const folder = libraryScan.libraryFolders[i] | ||||
|       const itemDataFoundInFolder = await this.scanFolder(libraryScan.library, folder) | ||||
|       libraryScan.addLog(LogLevel.INFO, `${itemDataFoundInFolder.length} item data found in folder "${folder.fullPath}"`) | ||||
|       libraryScan.addLog(LogLevel.INFO, `${itemDataFoundInFolder.length} item data found in folder "${folder.path}"`) | ||||
|       libraryItemDataFound = libraryItemDataFound.concat(itemDataFoundInFolder) | ||||
|     } | ||||
| 
 | ||||
| @ -283,12 +286,12 @@ class LibraryScanner { | ||||
| 
 | ||||
|   /** | ||||
|    * Get scan data for library folder | ||||
|    * @param {import('../objects/Library')} library | ||||
|    * @param {import('../objects/Folder')} folder | ||||
|    * @param {import('../models/Library')} library | ||||
|    * @param {import('../models/LibraryFolder')} folder | ||||
|    * @returns {LibraryItemScanData[]} | ||||
|    */ | ||||
|   async scanFolder(library, folder) { | ||||
|     const folderPath = fileUtils.filePathToPOSIX(folder.fullPath) | ||||
|     const folderPath = fileUtils.filePathToPOSIX(folder.path) | ||||
| 
 | ||||
|     const pathExists = await fs.pathExists(folderPath) | ||||
|     if (!pathExists) { | ||||
|  | ||||
| @ -15,7 +15,7 @@ const CoverManager = require('../managers/CoverManager') | ||||
| const TaskManager = require('../managers/TaskManager') | ||||
| 
 | ||||
| class Scanner { | ||||
|   constructor() { } | ||||
|   constructor() {} | ||||
| 
 | ||||
|   async quickMatchLibraryItem(libraryItem, options = {}) { | ||||
|     var provider = options.provider || 'google' | ||||
| @ -25,7 +25,7 @@ class Scanner { | ||||
| 
 | ||||
|     // Set to override existing metadata if scannerPreferMatchedMetadata setting is true and
 | ||||
|     // the overrideDefaults option is not set or set to false.
 | ||||
|     if ((overrideDefaults == false) && (Database.serverSettings.scannerPreferMatchedMetadata)) { | ||||
|     if (overrideDefaults == false && Database.serverSettings.scannerPreferMatchedMetadata) { | ||||
|       options.overrideCover = true | ||||
|       options.overrideDetails = true | ||||
|     } | ||||
| @ -57,7 +57,8 @@ class Scanner { | ||||
|       } | ||||
| 
 | ||||
|       updatePayload = await this.quickMatchBookBuildUpdatePayload(libraryItem, matchData, options) | ||||
|     } else if (libraryItem.isPodcast) { // Podcast quick match
 | ||||
|     } else if (libraryItem.isPodcast) { | ||||
|       // Podcast quick match
 | ||||
|       var results = await PodcastFinder.search(searchTitle) | ||||
|       if (!results.length) { | ||||
|         return { | ||||
| @ -88,7 +89,8 @@ class Scanner { | ||||
|     } | ||||
| 
 | ||||
|     if (hasUpdated) { | ||||
|       if (libraryItem.isPodcast && libraryItem.media.metadata.feedUrl) { // Quick match all unmatched podcast episodes
 | ||||
|       if (libraryItem.isPodcast && libraryItem.media.metadata.feedUrl) { | ||||
|         // Quick match all unmatched podcast episodes
 | ||||
|         await this.quickMatchPodcastEpisodes(libraryItem, options) | ||||
|       } | ||||
| 
 | ||||
| @ -122,12 +124,16 @@ class Scanner { | ||||
|     for (const key in matchDataTransformed) { | ||||
|       if (matchDataTransformed[key]) { | ||||
|         if (key === 'genres') { | ||||
|           if ((!libraryItem.media.metadata.genres.length || options.overrideDetails)) { | ||||
|           if (!libraryItem.media.metadata.genres.length || options.overrideDetails) { | ||||
|             var genresArray = [] | ||||
|             if (Array.isArray(matchDataTransformed[key])) genresArray = [...matchDataTransformed[key]] | ||||
|             else { // Genres should always be passed in as an array but just incase handle a string
 | ||||
|             else { | ||||
|               // Genres should always be passed in as an array but just incase handle a string
 | ||||
|               Logger.warn(`[Scanner] quickMatch genres is not an array ${matchDataTransformed[key]}`) | ||||
|               genresArray = matchDataTransformed[key].split(',').map(v => v.trim()).filter(v => !!v) | ||||
|               genresArray = matchDataTransformed[key] | ||||
|                 .split(',') | ||||
|                 .map((v) => v.trim()) | ||||
|                 .filter((v) => !!v) | ||||
|             } | ||||
|             updatePayload.metadata[key] = genresArray | ||||
|           } | ||||
| @ -153,27 +159,38 @@ class Scanner { | ||||
|     for (const key in matchData) { | ||||
|       if (matchData[key] && detailKeysToUpdate.includes(key)) { | ||||
|         if (key === 'narrator') { | ||||
|           if ((!libraryItem.media.metadata.narratorName || options.overrideDetails)) { | ||||
|             updatePayload.metadata.narrators = matchData[key].split(',').map(v => v.trim()).filter(v => !!v) | ||||
|           if (!libraryItem.media.metadata.narratorName || options.overrideDetails) { | ||||
|             updatePayload.metadata.narrators = matchData[key] | ||||
|               .split(',') | ||||
|               .map((v) => v.trim()) | ||||
|               .filter((v) => !!v) | ||||
|           } | ||||
|         } else if (key === 'genres') { | ||||
|           if ((!libraryItem.media.metadata.genres.length || options.overrideDetails)) { | ||||
|           if (!libraryItem.media.metadata.genres.length || options.overrideDetails) { | ||||
|             var genresArray = [] | ||||
|             if (Array.isArray(matchData[key])) genresArray = [...matchData[key]] | ||||
|             else { // Genres should always be passed in as an array but just incase handle a string
 | ||||
|             else { | ||||
|               // Genres should always be passed in as an array but just incase handle a string
 | ||||
|               Logger.warn(`[Scanner] quickMatch genres is not an array ${matchData[key]}`) | ||||
|               genresArray = matchData[key].split(',').map(v => v.trim()).filter(v => !!v) | ||||
|               genresArray = matchData[key] | ||||
|                 .split(',') | ||||
|                 .map((v) => v.trim()) | ||||
|                 .filter((v) => !!v) | ||||
|             } | ||||
|             updatePayload.metadata[key] = genresArray | ||||
|           } | ||||
|         } else if (key === 'tags') { | ||||
|           if ((!libraryItem.media.tags.length || options.overrideDetails)) { | ||||
|           if (!libraryItem.media.tags.length || options.overrideDetails) { | ||||
|             var tagsArray = [] | ||||
|             if (Array.isArray(matchData[key])) tagsArray = [...matchData[key]] | ||||
|             else tagsArray = matchData[key].split(',').map(v => v.trim()).filter(v => !!v) | ||||
|             else | ||||
|               tagsArray = matchData[key] | ||||
|                 .split(',') | ||||
|                 .map((v) => v.trim()) | ||||
|                 .filter((v) => !!v) | ||||
|             updatePayload[key] = tagsArray | ||||
|           } | ||||
|         } else if ((!libraryItem.media.metadata[key] || options.overrideDetails)) { | ||||
|         } else if (!libraryItem.media.metadata[key] || options.overrideDetails) { | ||||
|           updatePayload.metadata[key] = matchData[key] | ||||
|         } | ||||
|       } | ||||
| @ -182,7 +199,10 @@ class Scanner { | ||||
|     // Add or set author if not set
 | ||||
|     if (matchData.author && (!libraryItem.media.metadata.authorName || options.overrideDetails)) { | ||||
|       if (!Array.isArray(matchData.author)) { | ||||
|         matchData.author = matchData.author.split(',').map(au => au.trim()).filter(au => !!au) | ||||
|         matchData.author = matchData.author | ||||
|           .split(',') | ||||
|           .map((au) => au.trim()) | ||||
|           .filter((au) => !!au) | ||||
|       } | ||||
|       const authorPayload = [] | ||||
|       for (const authorName of matchData.author) { | ||||
| @ -227,7 +247,7 @@ class Scanner { | ||||
|   } | ||||
| 
 | ||||
|   async quickMatchPodcastEpisodes(libraryItem, options = {}) { | ||||
|     const episodesToQuickMatch = libraryItem.media.episodes.filter(ep => !ep.enclosureUrl) // Only quick match episodes without enclosure
 | ||||
|     const episodesToQuickMatch = libraryItem.media.episodes.filter((ep) => !ep.enclosureUrl) // Only quick match episodes without enclosure
 | ||||
|     if (!episodesToQuickMatch.length) return false | ||||
| 
 | ||||
|     const feed = await getPodcastFeed(libraryItem.media.metadata.feedUrl) | ||||
| @ -284,7 +304,7 @@ class Scanner { | ||||
|   /** | ||||
|    * Quick match library items | ||||
|    * | ||||
|    * @param {import('../objects/Library')} library  | ||||
|    * @param {import('../models/Library')} library | ||||
|    * @param {import('../objects/LibraryItem')[]} libraryItems | ||||
|    * @param {LibraryScan} libraryScan | ||||
|    * @returns {Promise<boolean>} false if scan canceled | ||||
| @ -294,14 +314,12 @@ class Scanner { | ||||
|       const libraryItem = libraryItems[i] | ||||
| 
 | ||||
|       if (libraryItem.media.metadata.asin && library.settings.skipMatchingMediaWithAsin) { | ||||
|         Logger.debug(`[Scanner] matchLibraryItems: Skipping "${libraryItem.media.metadata.title | ||||
|           }" because it already has an ASIN (${i + 1} of ${libraryItems.length})`)
 | ||||
|         Logger.debug(`[Scanner] matchLibraryItems: Skipping "${libraryItem.media.metadata.title}" because it already has an ASIN (${i + 1} of ${libraryItems.length})`) | ||||
|         continue | ||||
|       } | ||||
| 
 | ||||
|       if (libraryItem.media.metadata.isbn && library.settings.skipMatchingMediaWithIsbn) { | ||||
|         Logger.debug(`[Scanner] matchLibraryItems: Skipping "${libraryItem.media.metadata.title | ||||
|           }" because it already has an ISBN (${i + 1} of ${libraryItems.length})`)
 | ||||
|         Logger.debug(`[Scanner] matchLibraryItems: Skipping "${libraryItem.media.metadata.title}" because it already has an ISBN (${i + 1} of ${libraryItems.length})`) | ||||
|         continue | ||||
|       } | ||||
| 
 | ||||
| @ -325,7 +343,7 @@ class Scanner { | ||||
|   /** | ||||
|    * Quick match all library items for library | ||||
|    * | ||||
|    * @param {import('../objects/Library')} library  | ||||
|    * @param {import('../models/Library')} library | ||||
|    */ | ||||
|   async matchLibraryItems(library) { | ||||
|     if (library.mediaType === 'podcast') { | ||||
| @ -360,7 +378,7 @@ class Scanner { | ||||
| 
 | ||||
|       offset += limit | ||||
|       hasMoreChunks = libraryItems.length === limit | ||||
|       let oldLibraryItems = libraryItems.map(li => Database.libraryItemModel.getOldLibraryItem(li)) | ||||
|       let oldLibraryItems = libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li)) | ||||
| 
 | ||||
|       const shouldContinue = await this.matchLibraryItemsChunk(library, oldLibraryItems, libraryScan) | ||||
|       if (!shouldContinue) { | ||||
| @ -379,7 +397,7 @@ class Scanner { | ||||
|     } | ||||
| 
 | ||||
|     delete LibraryScanner.cancelLibraryScan[libraryScan.libraryId] | ||||
|     LibraryScanner.librariesScanning = LibraryScanner.librariesScanning.filter(ls => ls.id !== library.id) | ||||
|     LibraryScanner.librariesScanning = LibraryScanner.librariesScanning.filter((ls) => ls.id !== library.id) | ||||
|     TaskManager.taskFinished(task) | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1258,7 +1258,7 @@ async function handleOldLibraryItems(ctx) { | ||||
|  */ | ||||
| async function handleOldLibraries(ctx) { | ||||
|   const oldLibraries = await oldDbFiles.loadOldData('libraries') | ||||
|   const libraries = await ctx.models.library.getAllOldLibraries() | ||||
|   const libraries = await ctx.models.library.getAllWithFolders() | ||||
| 
 | ||||
|   let librariesUpdated = 0 | ||||
|   for (const library of libraries) { | ||||
| @ -1268,13 +1268,17 @@ async function handleOldLibraries(ctx) { | ||||
|         return false | ||||
|       } | ||||
|       const folderPaths = ol.folders?.map((f) => f.fullPath) || [] | ||||
|       return folderPaths.join(',') === library.folders.map((f) => f.fullPath).join(',') | ||||
|       return folderPaths.join(',') === library.libraryFolders.map((f) => f.path).join(',') | ||||
|     }) | ||||
| 
 | ||||
|     if (matchingOldLibrary) { | ||||
|       library.oldLibraryId = matchingOldLibrary.id | ||||
|       const newExtraData = library.extraData || {} | ||||
|       newExtraData.oldLibraryId = matchingOldLibrary.id | ||||
|       library.extraData = newExtraData | ||||
|       library.changed('extraData', true) | ||||
| 
 | ||||
|       oldDbIdMap.libraries[library.oldLibraryId] = library.id | ||||
|       await ctx.models.library.updateFromOld(library) | ||||
|       await library.save() | ||||
|       librariesUpdated++ | ||||
|     } | ||||
|   } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user