diff --git a/server/Database.js b/server/Database.js index e63ae988..d8ef7df2 100644 --- a/server/Database.js +++ b/server/Database.js @@ -15,9 +15,6 @@ class Database { this.isNew = false // New absdatabase.sqlite created this.hasRootUser = false // Used to show initialization page in web ui - // Temporarily using format of old DB - // TODO: below data should be loaded from the DB as needed - this.libraryItems = [] this.settings = [] // Cached library filter data @@ -255,8 +252,6 @@ class Database { await dbMigration.migrate(this.models) } - const startTime = Date.now() - const settingsData = await this.models.setting.getOldSettings() this.settings = settingsData.settings this.emailSettings = settingsData.emailSettings @@ -272,16 +267,9 @@ class Database { await dbMigration.migrationPatch2(this) } - Logger.info(`[Database] Loading db data...`) - - this.libraryItems = await this.models.libraryItem.loadAllLibraryItems() - Logger.info(`[Database] Loaded ${this.libraryItems.length} library items`) - // Set if root user has been created this.hasRootUser = await this.models.user.getHasRootUser() - Logger.info(`[Database] Db data loaded in ${((Date.now() - startTime) / 1000).toFixed(2)}s`) - if (packageJson.version !== this.serverSettings.version) { Logger.info(`[Database] Server upgrade detected from ${this.serverSettings.version} to ${packageJson.version}`) this.serverSettings.version = packageJson.version @@ -380,20 +368,10 @@ class Database { return this.models.playlistMediaItem.bulkCreate(playlistMediaItems) } - getLibraryItem(libraryItemId) { - if (!this.sequelize || !libraryItemId) return false - - // Temp support for old library item ids from mobile - if (libraryItemId.startsWith('li_')) return this.libraryItems.find(li => li.oldLibraryItemId === libraryItemId) - - return this.libraryItems.find(li => li.id === libraryItemId) - } - async createLibraryItem(oldLibraryItem) { if (!this.sequelize) return false await oldLibraryItem.saveMetadata() await this.models.libraryItem.fullCreateFromOld(oldLibraryItem) - this.libraryItems.push(oldLibraryItem) } async updateLibraryItem(oldLibraryItem) { @@ -420,14 +398,12 @@ class Database { for (const oldLibraryItem of oldLibraryItems) { await oldLibraryItem.saveMetadata() await this.models.libraryItem.fullCreateFromOld(oldLibraryItem) - this.libraryItems.push(oldLibraryItem) } } async removeLibraryItem(libraryItemId) { if (!this.sequelize) return false await this.models.libraryItem.removeById(libraryItemId) - this.libraryItems = this.libraryItems.filter(li => li.id !== libraryItemId) } async createFeed(oldFeed) { diff --git a/server/Server.js b/server/Server.js index 9bb22b39..6afab875 100644 --- a/server/Server.js +++ b/server/Server.js @@ -76,7 +76,7 @@ class Server { this.audioMetadataManager = new AudioMetadataMangaer(this.taskManager) this.rssFeedManager = new RssFeedManager() - this.scanner = new Scanner(this.coverManager, this.taskManager) + this.scanner = new Scanner(this.coverManager) this.cronManager = new CronManager(this.podcastManager) // Routers diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js index 3b91105c..f6f62de1 100644 --- a/server/controllers/AuthorController.js +++ b/server/controllers/AuthorController.js @@ -5,6 +5,7 @@ const { createNewSortInstance } = require('../libs/fastSort') const Logger = require('../Logger') const SocketAuthority = require('../SocketAuthority') const Database = require('../Database') +const AuthorFinder = require('../finders/AuthorFinder') const { reqSupportsWebp } = require('../utils/index') @@ -70,7 +71,7 @@ class AuthorController { await this.cacheManager.purgeImageCache(req.author.id) // Purge cache await this.coverManager.removeFile(req.author.imagePath) } else if (payload.imagePath.startsWith('http')) { // Check if image path is a url - const imageData = await this.authorFinder.saveAuthorImage(req.author.id, payload.imagePath) + const imageData = await AuthorFinder.saveAuthorImage(req.author.id, payload.imagePath) if (imageData) { if (req.author.imagePath) { await this.cacheManager.purgeImageCache(req.author.id) // Purge cache @@ -168,9 +169,9 @@ class AuthorController { let authorData = null const region = req.body.region || 'us' if (req.body.asin) { - authorData = await this.authorFinder.findAuthorByASIN(req.body.asin, region) + authorData = await AuthorFinder.findAuthorByASIN(req.body.asin, region) } else { - authorData = await this.authorFinder.findAuthorByName(req.body.q, region) + authorData = await AuthorFinder.findAuthorByName(req.body.q, region) } if (!authorData) { return res.status(404).send('Author not found') @@ -187,7 +188,7 @@ class AuthorController { if (authorData.image && (!req.author.imagePath || hasUpdates)) { this.cacheManager.purgeImageCache(req.author.id) - const imageData = await this.authorFinder.saveAuthorImage(req.author.id, authorData.image) + const imageData = await AuthorFinder.saveAuthorImage(req.author.id, authorData.image) if (imageData) { req.author.imagePath = imageData.path hasUpdates = true diff --git a/server/controllers/EmailController.js b/server/controllers/EmailController.js index ada0f5df..fefc23b6 100644 --- a/server/controllers/EmailController.js +++ b/server/controllers/EmailController.js @@ -54,7 +54,7 @@ class EmailController { async sendEBookToDevice(req, res) { Logger.debug(`[EmailController] Send ebook to device request for libraryItemId=${req.body.libraryItemId}, deviceName=${req.body.deviceName}`) - const libraryItem = Database.getLibraryItem(req.body.libraryItemId) + const libraryItem = await Database.libraryItemModel.getOldById(req.body.libraryItemId) if (!libraryItem) { return res.status(404).send('Library item not found') } diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index d396790b..89654b1c 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -411,7 +411,9 @@ class LibraryItemController { return res.sendStatus(400) } - const libraryItems = req.body.libraryItemIds.map(lid => Database.getLibraryItem(lid)).filter(li => li) + const libraryItems = await Database.libraryItemModel.getAllOldLibraryItems({ + id: req.body.libraryItemIds + }) if (!libraryItems?.length) { return res.sendStatus(400) } diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index 4c6b3ab4..529877a2 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -193,7 +193,8 @@ class MeController { Logger.error(`[MeController] syncLocalMediaProgress invalid local media progress object`, localProgress) continue } - const libraryItem = Database.getLibraryItem(localProgress.libraryItemId) + + const libraryItem = await Database.libraryItemModel.getOldById(localProgress.libraryItemId) if (!libraryItem) { Logger.error(`[MeController] syncLocalMediaProgress invalid local media progress object no library item`, localProgress) continue @@ -245,13 +246,15 @@ class MeController { } // GET: api/me/items-in-progress - getAllLibraryItemsInProgress(req, res) { + async getAllLibraryItemsInProgress(req, res) { const limit = !isNaN(req.query.limit) ? Number(req.query.limit) || 25 : 25 let itemsInProgress = [] + // TODO: More efficient to do this in a single query for (const mediaProgress of req.user.mediaProgress) { if (!mediaProgress.isFinished && (mediaProgress.progress > 0 || mediaProgress.ebookProgress > 0)) { - const libraryItem = Database.getLibraryItem(mediaProgress.libraryItemId) + + const libraryItem = await Database.libraryItemModel.getOldById(mediaProgress.libraryItemId) if (libraryItem) { if (mediaProgress.episodeId && libraryItem.mediaType === 'podcast') { const episode = libraryItem.media.episodes.find(ep => ep.id === mediaProgress.episodeId) diff --git a/server/controllers/SearchController.js b/server/controllers/SearchController.js index cc803720..2749016c 100644 --- a/server/controllers/SearchController.js +++ b/server/controllers/SearchController.js @@ -1,4 +1,8 @@ const Logger = require("../Logger") +const BookFinder = require('../finders/BookFinder') +const PodcastFinder = require('../finders/PodcastFinder') +const AuthorFinder = require('../finders/AuthorFinder') +const MusicFinder = require('../finders/MusicFinder') class SearchController { constructor() { } @@ -7,7 +11,7 @@ class SearchController { const provider = req.query.provider || 'google' const title = req.query.title || '' const author = req.query.author || '' - const results = await this.bookFinder.search(provider, title, author) + const results = await BookFinder.search(provider, title, author) res.json(results) } @@ -21,8 +25,8 @@ class SearchController { } let results = null - if (podcast) results = await this.podcastFinder.findCovers(query.title) - else results = await this.bookFinder.findCovers(query.provider || 'google', query.title, query.author || null) + if (podcast) results = await PodcastFinder.findCovers(query.title) + else results = await BookFinder.findCovers(query.provider || 'google', query.title, query.author || null) res.json({ results }) @@ -30,20 +34,20 @@ class SearchController { async findPodcasts(req, res) { const term = req.query.term - const results = await this.podcastFinder.search(term) + const results = await PodcastFinder.search(term) res.json(results) } async findAuthor(req, res) { const query = req.query.q - const author = await this.authorFinder.findAuthorByName(query) + const author = await AuthorFinder.findAuthorByName(query) res.json(author) } async findChapters(req, res) { const asin = req.query.asin const region = (req.query.region || 'us').toLowerCase() - const chapterData = await this.bookFinder.findChapters(asin, region) + const chapterData = await BookFinder.findChapters(asin, region) if (!chapterData) { return res.json({ error: 'Chapters not found' }) } @@ -51,7 +55,7 @@ class SearchController { } async findMusicTrack(req, res) { - const tracks = await this.musicFinder.searchTrack(req.query || {}) + const tracks = await MusicFinder.searchTrack(req.query || {}) res.json({ tracks }) diff --git a/server/controllers/SessionController.js b/server/controllers/SessionController.js index 928095a6..85baeb27 100644 --- a/server/controllers/SessionController.js +++ b/server/controllers/SessionController.js @@ -62,9 +62,9 @@ class SessionController { }) } - getOpenSession(req, res) { - var libraryItem = Database.getLibraryItem(req.session.libraryItemId) - var sessionForClient = req.session.toJSONForClient(libraryItem) + async getOpenSession(req, res) { + const libraryItem = await Database.libraryItemModel.getOldById(req.session.libraryItemId) + const sessionForClient = req.session.toJSONForClient(libraryItem) res.json(sessionForClient) } diff --git a/server/controllers/ToolsController.js b/server/controllers/ToolsController.js index 9561e892..3f81d116 100644 --- a/server/controllers/ToolsController.js +++ b/server/controllers/ToolsController.js @@ -66,7 +66,7 @@ class ToolsController { const libraryItems = [] for (const libraryItemId of libraryItemIds) { - const libraryItem = Database.getLibraryItem(libraryItemId) + const libraryItem = await Database.libraryItemModel.getOldById(libraryItemId) if (!libraryItem) { Logger.error(`[ToolsController] Batch embed metadata library item (${libraryItemId}) not found`) return res.sendStatus(404) diff --git a/server/finders/AuthorFinder.js b/server/finders/AuthorFinder.js index 18fb2223..fcc22cad 100644 --- a/server/finders/AuthorFinder.js +++ b/server/finders/AuthorFinder.js @@ -8,8 +8,6 @@ const filePerms = require('../utils/filePerms') class AuthorFinder { constructor() { - this.AuthorPath = Path.join(global.MetadataPath, 'authors') - this.audnexus = new Audnexus() } @@ -37,7 +35,7 @@ class AuthorFinder { } async saveAuthorImage(authorId, url) { - var authorDir = this.AuthorPath + var authorDir = Path.join(global.MetadataPath, 'authors') var relAuthorDir = Path.posix.join('/metadata', 'authors') if (!await fs.pathExists(authorDir)) { @@ -61,4 +59,4 @@ class AuthorFinder { } } } -module.exports = AuthorFinder \ No newline at end of file +module.exports = new AuthorFinder() \ No newline at end of file diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index 9124e2bd..6452bff0 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -253,4 +253,4 @@ class BookFinder { return this.audnexus.getChaptersByASIN(asin, region) } } -module.exports = BookFinder \ No newline at end of file +module.exports = new BookFinder() \ No newline at end of file diff --git a/server/finders/MusicFinder.js b/server/finders/MusicFinder.js index 938cae83..3569576f 100644 --- a/server/finders/MusicFinder.js +++ b/server/finders/MusicFinder.js @@ -9,4 +9,4 @@ class MusicFinder { return this.musicBrainz.searchTrack(options) } } -module.exports = MusicFinder \ No newline at end of file +module.exports = new MusicFinder() \ No newline at end of file diff --git a/server/finders/PodcastFinder.js b/server/finders/PodcastFinder.js index e0a204bd..52fec15c 100644 --- a/server/finders/PodcastFinder.js +++ b/server/finders/PodcastFinder.js @@ -22,4 +22,4 @@ class PodcastFinder { return results.map(r => r.cover).filter(r => r) } } -module.exports = PodcastFinder \ No newline at end of file +module.exports = new PodcastFinder() \ No newline at end of file diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js index a64acc18..fba813d2 100644 --- a/server/managers/PlaybackSessionManager.js +++ b/server/managers/PlaybackSessionManager.js @@ -93,7 +93,7 @@ class PlaybackSessionManager { } async syncLocalSession(user, sessionJson, deviceInfo) { - const libraryItem = Database.getLibraryItem(sessionJson.libraryItemId) + const libraryItem = await Database.libraryItemModel.getOldById(sessionJson.libraryItemId) const episode = (sessionJson.episodeId && libraryItem && libraryItem.isPodcast) ? libraryItem.media.getEpisode(sessionJson.episodeId) : null if (!libraryItem || (libraryItem.isPodcast && !episode)) { Logger.error(`[PlaybackSessionManager] syncLocalSession: Media item not found for session "${sessionJson.displayTitle}" (${sessionJson.id})`) diff --git a/server/managers/RssFeedManager.js b/server/managers/RssFeedManager.js index 0c0821ae..7eb1cce7 100644 --- a/server/managers/RssFeedManager.js +++ b/server/managers/RssFeedManager.js @@ -87,7 +87,7 @@ class RssFeedManager { // Check if feed needs to be updated if (feed.entityType === 'libraryItem') { - const libraryItem = Database.getLibraryItem(feed.entityId) + const libraryItem = await Database.libraryItemModel.getOldById(feed.entityId) let mostRecentlyUpdatedAt = libraryItem.updatedAt if (libraryItem.isPodcast) { diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index fa288cde..77782939 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -118,12 +118,13 @@ class LibraryItem extends Model { * @param {number} limit * @returns {Promise[]>} LibraryItem */ - static getLibraryItemsIncrement(offset, limit) { + static getLibraryItemsIncrement(offset, limit, where = null) { return this.findAll({ benchmark: true, logging: (sql, timeMs) => { console.log(`[Query] Elapsed ${timeMs}ms.`) }, + where, include: [ { model: this.sequelize.models.book, diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index a52fbb9b..18eef019 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -29,11 +29,6 @@ const ToolsController = require('../controllers/ToolsController') const RSSFeedController = require('../controllers/RSSFeedController') const MiscController = require('../controllers/MiscController') -const BookFinder = require('../finders/BookFinder') -const AuthorFinder = require('../finders/AuthorFinder') -const PodcastFinder = require('../finders/PodcastFinder') -const MusicFinder = require('../finders/MusicFinder') - const Author = require('../objects/entities/Author') const Series = require('../objects/entities/Series') @@ -55,11 +50,6 @@ class ApiRouter { this.emailManager = Server.emailManager this.taskManager = Server.taskManager - this.bookFinder = new BookFinder() - this.authorFinder = new AuthorFinder() - this.podcastFinder = new PodcastFinder() - this.musicFinder = new MusicFinder() - this.router = express() this.router.disable('x-powered-by') this.init() diff --git a/server/scanner/BookScanner.js b/server/scanner/BookScanner.js index ff3b773c..b19e8bf6 100644 --- a/server/scanner/BookScanner.js +++ b/server/scanner/BookScanner.js @@ -16,6 +16,7 @@ const CoverManager = require('../managers/CoverManager') const LibraryFile = require('../objects/files/LibraryFile') const SocketAuthority = require('../SocketAuthority') const fsExtra = require("../libs/fsExtra") +// const BookFinder = require('../finders/BookFinder') /** * Metadata for books pulled from files @@ -1049,5 +1050,32 @@ class BookScanner { scanLogger.addLog(LogLevel.INFO, `Removed ${bookSeriesToRemove.length} series`) } } + + // async searchForCover(libraryItem, libraryScan = null) { + // const options = { + // titleDistance: 2, + // authorDistance: 2 + // } + // const scannerCoverProvider = Database.serverSettings.scannerCoverProvider + // const results = await BookFinder.findCovers(scannerCoverProvider, libraryItem.media.metadata.title, libraryItem.media.metadata.authorName, options) + // if (results.length) { + // if (libraryScan) libraryScan.addLog(LogLevel.DEBUG, `Found best cover for "${libraryItem.media.metadata.title}"`) + // else Logger.debug(`[Scanner] Found best cover for "${libraryItem.media.metadata.title}"`) + + // // If the first cover result fails, attempt to download the second + // for (let i = 0; i < results.length && i < 2; i++) { + + // // Downloads and updates the book cover + // const result = await this.coverManager.downloadCoverFromUrl(libraryItem, results[i]) + + // if (result.error) { + // Logger.error(`[Scanner] Failed to download cover from url "${results[i]}" | Attempt ${i + 1}`, result.error) + // } else { + // return true + // } + // } + // } + // return false + // } } module.exports = new BookScanner() \ No newline at end of file diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index 74702307..0ecad33e 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -3,7 +3,6 @@ const SocketAuthority = require('../SocketAuthority') const Database = require('../Database') // Utils -const { LogLevel } = require('../utils/constants') const { findMatchingEpisodesInFeed, getPodcastFeed } = require('../utils/podcastUtils') const BookFinder = require('../finders/BookFinder') @@ -11,44 +10,11 @@ const PodcastFinder = require('../finders/PodcastFinder') const LibraryScan = require('./LibraryScan') const Author = require('../objects/entities/Author') const Series = require('../objects/entities/Series') +const LibraryScanner = require('./LibraryScanner') class Scanner { - constructor(coverManager, taskManager) { + constructor(coverManager) { this.coverManager = coverManager - this.taskManager = taskManager - - this.cancelLibraryScan = {} - this.librariesScanning = [] - - this.bookFinder = new BookFinder() - this.podcastFinder = new PodcastFinder() - } - - async searchForCover(libraryItem, libraryScan = null) { - const options = { - titleDistance: 2, - authorDistance: 2 - } - const scannerCoverProvider = Database.serverSettings.scannerCoverProvider - const results = await this.bookFinder.findCovers(scannerCoverProvider, libraryItem.media.metadata.title, libraryItem.media.metadata.authorName, options) - if (results.length) { - if (libraryScan) libraryScan.addLog(LogLevel.DEBUG, `Found best cover for "${libraryItem.media.metadata.title}"`) - else Logger.debug(`[Scanner] Found best cover for "${libraryItem.media.metadata.title}"`) - - // If the first cover result fails, attempt to download the second - for (let i = 0; i < results.length && i < 2; i++) { - - // Downloads and updates the book cover - const result = await this.coverManager.downloadCoverFromUrl(libraryItem, results[i]) - - if (result.error) { - Logger.error(`[Scanner] Failed to download cover from url "${results[i]}" | Attempt ${i + 1}`, result.error) - } else { - return true - } - } - } - return false } async quickMatchLibraryItem(libraryItem, options = {}) { @@ -71,7 +37,7 @@ class Scanner { var searchISBN = options.isbn || libraryItem.media.metadata.isbn var searchASIN = options.asin || libraryItem.media.metadata.asin - var results = await this.bookFinder.search(provider, searchTitle, searchAuthor, searchISBN, searchASIN) + var results = await BookFinder.search(provider, searchTitle, searchAuthor, searchISBN, searchASIN) if (!results.length) { return { warning: `No ${provider} match found` @@ -92,7 +58,7 @@ class Scanner { updatePayload = await this.quickMatchBookBuildUpdatePayload(libraryItem, matchData, options) } else if (libraryItem.isPodcast) { // Podcast quick match - var results = await this.podcastFinder.search(searchTitle) + var results = await PodcastFinder.search(searchTitle) if (!results.length) { return { warning: `No ${provider} match found` @@ -315,62 +281,80 @@ class Scanner { return false } - async matchLibraryItems(library) { - if (library.mediaType === 'podcast') { - Logger.error(`[Scanner] matchLibraryItems: Match all not supported for podcasts yet`) - return - } - - const itemsInLibrary = Database.libraryItems.filter(li => li.libraryId === library.id) - if (!itemsInLibrary.length) { - Logger.error(`[Scanner] matchLibraryItems: Library has no items ${library.id}`) - return - } - - const provider = library.provider - - var libraryScan = new LibraryScan() - libraryScan.setData(library, null, 'match') - this.librariesScanning.push(libraryScan.getScanEmitData) - SocketAuthority.emitter('scan_start', libraryScan.getScanEmitData) - - Logger.info(`[Scanner] matchLibraryItems: Starting library match scan ${libraryScan.id} for ${libraryScan.libraryName}`) - - for (let i = 0; i < itemsInLibrary.length; i++) { - var libraryItem = itemsInLibrary[i] + async matchLibraryItemsChunk(library, libraryItems, libraryScan) { + for (let i = 0; i < libraryItems.length; i++) { + 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 ${itemsInLibrary.length})`) - continue; + }" 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 ${itemsInLibrary.length})`) - continue; + }" because it already has an ISBN (${i + 1} of ${libraryItems.length})`) + continue } - Logger.debug(`[Scanner] matchLibraryItems: Quick matching "${libraryItem.media.metadata.title}" (${i + 1} of ${itemsInLibrary.length})`) - var result = await this.quickMatchLibraryItem(libraryItem, { provider }) + Logger.debug(`[Scanner] matchLibraryItems: Quick matching "${libraryItem.media.metadata.title}" (${i + 1} of ${libraryItems.length})`) + const result = await this.quickMatchLibraryItem(libraryItem, { provider: library.provider }) if (result.warning) { Logger.warn(`[Scanner] matchLibraryItems: Match warning ${result.warning} for library item "${libraryItem.media.metadata.title}"`) } else if (result.updated) { libraryScan.resultsUpdated++ } - if (this.cancelLibraryScan[libraryScan.libraryId]) { + if (LibraryScanner.cancelLibraryScan[libraryScan.libraryId]) { Logger.info(`[Scanner] matchLibraryItems: Library match scan canceled for "${libraryScan.libraryName}"`) - delete this.cancelLibraryScan[libraryScan.libraryId] - var scanData = libraryScan.getScanEmitData - scanData.results = null - SocketAuthority.emitter('scan_complete', scanData) - this.librariesScanning = this.librariesScanning.filter(ls => ls.id !== library.id) - return + return false } } - this.librariesScanning = this.librariesScanning.filter(ls => ls.id !== library.id) + return true + } + + async matchLibraryItems(library) { + if (library.mediaType === 'podcast') { + Logger.error(`[Scanner] matchLibraryItems: Match all not supported for podcasts yet`) + return + } + + if (LibraryScanner.isLibraryScanning(library.id)) { + Logger.error(`[Scanner] Library "${library.name}" is already scanning`) + return + } + + const limit = 100 + let offset = 0 + + const libraryScan = new LibraryScan() + libraryScan.setData(library, null, 'match') + LibraryScanner.librariesScanning.push(libraryScan.getScanEmitData) + SocketAuthority.emitter('scan_start', libraryScan.getScanEmitData) + + Logger.info(`[Scanner] matchLibraryItems: Starting library match scan ${libraryScan.id} for ${libraryScan.libraryName}`) + + let hasMoreChunks = true + while (hasMoreChunks) { + const libraryItems = await Database.libraryItemModel.getLibraryItemsIncrement(offset, limit, { libraryId: library.id }) + if (!libraryItems.length) { + Logger.error(`[Scanner] matchLibraryItems: Library has no items ${library.id}`) + SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData) + return + } + offset += limit + hasMoreChunks = libraryItems.length < limit + let oldLibraryItems = libraryItems.map(li => Database.libraryItemModel.getOldLibraryItem(li)) + + const shouldContinue = await this.matchLibraryItemsChunk(library, oldLibraryItems, libraryScan) + if (!shouldContinue) { + break + } + } + + delete LibraryScanner.cancelLibraryScan[libraryScan.libraryId] + LibraryScanner.librariesScanning = LibraryScanner.librariesScanning.filter(ls => ls.id !== library.id) SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData) } }