diff --git a/server/Watcher.js b/server/Watcher.js index 32788e27..83c45234 100644 --- a/server/Watcher.js +++ b/server/Watcher.js @@ -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 = [] diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 9d9ed2ee..59e8c181 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -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') diff --git a/server/managers/CronManager.js b/server/managers/CronManager.js index 09ff4d95..7a8c9bd0 100644 --- a/server/managers/CronManager.js +++ b/server/managers/CronManager.js @@ -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({ diff --git a/server/models/Library.js b/server/models/Library.js index 3ebd32df..972aa264 100644 --- a/server/models/Library.js +++ b/server/models/Library.js @@ -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} - */ - 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} - */ - 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} 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 diff --git a/server/objects/Folder.js b/server/objects/Folder.js deleted file mode 100644 index 9ca6b214..00000000 --- a/server/objects/Folder.js +++ /dev/null @@ -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 \ No newline at end of file diff --git a/server/objects/Library.js b/server/objects/Library.js deleted file mode 100644 index a16e7b01..00000000 --- a/server/objects/Library.js +++ /dev/null @@ -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 diff --git a/server/objects/settings/LibrarySettings.js b/server/objects/settings/LibrarySettings.js deleted file mode 100644 index 4369c0ff..00000000 --- a/server/objects/settings/LibrarySettings.js +++ /dev/null @@ -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 diff --git a/server/scanner/LibraryScan.js b/server/scanner/LibraryScan.js index ddf3c66b..5ae5c06a 100644 --- a/server/scanner/LibraryScan.js +++ b/server/scanner/LibraryScan.js @@ -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,17 +99,22 @@ 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() } /** - * - * @param {string} error + * + * @param {string} error */ setComplete(error = null) { this.finishedAt = 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') @@ -150,4 +162,4 @@ class LibraryScan { Logger.info(`[LibraryScan] Scan log saved "${outputPath}"`) } } -module.exports = LibraryScan \ No newline at end of file +module.exports = LibraryScan diff --git a/server/scanner/LibraryScanner.js b/server/scanner/LibraryScanner.js index bbdde328..75d18df0 100644 --- a/server/scanner/LibraryScanner.js +++ b/server/scanner/LibraryScanner.js @@ -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) { diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index e0bcf4fd..4d67248c 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -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' @@ -23,9 +23,9 @@ class Scanner { var searchAuthor = options.author || libraryItem.media.metadata.authorName var overrideDefaults = options.overrideDefaults || false - // Set to override existing metadata if scannerPreferMatchedMetadata setting is true and + // 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) @@ -283,10 +303,10 @@ class Scanner { /** * Quick match library items - * - * @param {import('../objects/Library')} library - * @param {import('../objects/LibraryItem')[]} libraryItems - * @param {LibraryScan} libraryScan + * + * @param {import('../models/Library')} library + * @param {import('../objects/LibraryItem')[]} libraryItems + * @param {LibraryScan} libraryScan * @returns {Promise} false if scan canceled */ async matchLibraryItemsChunk(library, libraryItems, libraryScan) { @@ -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 } @@ -324,8 +342,8 @@ 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) } } diff --git a/server/utils/migrations/dbMigration.js b/server/utils/migrations/dbMigration.js index cbb8edea..8337f5aa 100644 --- a/server/utils/migrations/dbMigration.js +++ b/server/utils/migrations/dbMigration.js @@ -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++ } }