diff --git a/client/components/modals/edit-tabs/Details.vue b/client/components/modals/edit-tabs/Details.vue index 682971f3..5d6c0c53 100644 --- a/client/components/modals/edit-tabs/Details.vue +++ b/client/components/modals/edit-tabs/Details.vue @@ -181,8 +181,8 @@ export default { this.quickMatching = true var matchOptions = { provider: this.libraryProvider, - title: details.title, - author: details.author !== this.book.author ? details.author : null + title: this.details.title, + author: this.details.author !== this.book.author ? this.details.author : null } this.$axios .$post(`/api/books/${this.audiobookId}/match`, matchOptions) diff --git a/server/ApiController.js b/server/ApiController.js index bf724f91..db166945 100644 --- a/server/ApiController.js +++ b/server/ApiController.js @@ -59,6 +59,7 @@ class ApiController { this.router.get('/libraries/:id/search', LibraryController.middleware.bind(this), LibraryController.search.bind(this)) this.router.get('/libraries/:id/stats', LibraryController.middleware.bind(this), LibraryController.stats.bind(this)) this.router.get('/libraries/:id/authors', LibraryController.middleware.bind(this), LibraryController.getAuthors.bind(this)) + this.router.post('/libraries/:id/matchbooks', LibraryController.middleware.bind(this), LibraryController.matchBooks.bind(this)) this.router.post('/libraries/order', LibraryController.reorder.bind(this)) // TEMP: Support old syntax for mobile app @@ -516,5 +517,57 @@ class ApiController { await this.cacheManager.purgeAll() res.sendStatus(200) } + + async quickMatchBook(audiobook, options = {}) { + var provider = options.provider || 'google' + var searchTitle = options.title || audiobook.book._title + var searchAuthor = options.author || audiobook.book._author + + var results = await this.bookFinder.search(provider, searchTitle, searchAuthor) + if (!results.length) { + return { + warning: `No ${provider} match found` + } + } + var matchData = results[0] + + // Update cover if not set OR overrideCover flag + var hasUpdated = false + if (matchData.cover && (!audiobook.book.cover || options.overrideCover)) { + Logger.debug(`[BookController] Updating cover "${matchData.cover}"`) + var coverResult = await this.coverController.downloadCoverFromUrl(audiobook, matchData.cover) + if (!coverResult || coverResult.error || !coverResult.cover) { + Logger.warn(`[BookController] Match cover "${matchData.cover}" failed to use: ${coverResult ? coverResult.error : 'Unknown Error'}`) + } else { + hasUpdated = true + } + } + + // Update book details if not set OR overrideDetails flag + const detailKeysToUpdate = ['title', 'subtitle', 'author', 'narrator', 'publisher', 'publishYear', 'series', 'volumeNumber', 'asin', 'isbn'] + const updatePayload = {} + for (const key in matchData) { + if (matchData[key] && detailKeysToUpdate.includes(key) && (!audiobook.book[key] || options.overrideDetails)) { + updatePayload[key] = matchData[key] + } + } + + if (Object.keys(updatePayload).length) { + Logger.debug('[BookController] Updating details', updatePayload) + if (audiobook.update({ book: updatePayload })) { + hasUpdated = true + } + } + + if (hasUpdated) { + await this.db.updateEntity('audiobook', audiobook) + this.emitter('audiobook_updated', audiobook.toJSONExpanded()) + } + + return { + updated: hasUpdated, + audiobook: audiobook.toJSONExpanded() + } + } } module.exports = ApiController \ No newline at end of file diff --git a/server/controllers/BookController.js b/server/controllers/BookController.js index 49bad422..d6e579db 100644 --- a/server/controllers/BookController.js +++ b/server/controllers/BookController.js @@ -272,55 +272,8 @@ class BookController { } var options = req.body || {} - var provider = options.provider || 'google' - var searchTitle = options.title || audiobook.book._title - var searchAuthor = options.author || audiobook.book._author - - var results = await this.bookFinder.search(provider, searchTitle, searchAuthor) - if (!results.length) { - return res.json({ - warning: `No ${provider} match found` - }) - } - var matchData = results[0] - - // Update cover if not set OR overrideCover flag - var hasUpdated = false - if (matchData.cover && (!audiobook.book.cover || options.overrideCover)) { - Logger.debug(`[BookController] Updating cover "${matchData.cover}"`) - var coverResult = await this.coverController.downloadCoverFromUrl(audiobook, matchData.cover) - if (!coverResult || coverResult.error || !coverResult.cover) { - Logger.warn(`[BookController] Match cover "${matchData.cover}" failed to use: ${coverResult ? coverResult.error : 'Unknown Error'}`) - } else { - hasUpdated = true - } - } - - // Update book details if not set OR overrideDetails flag - const detailKeysToUpdate = ['title', 'subtitle', 'author', 'narrator', 'publisher', 'publishYear', 'series', 'volumeNumber', 'asin', 'isbn'] - const updatePayload = {} - for (const key in matchData) { - if (matchData[key] && detailKeysToUpdate.includes(key) && (!audiobook.book[key] || options.overrideDetails)) { - updatePayload[key] = matchData[key] - } - } - - if (Object.keys(updatePayload).length) { - Logger.debug('[BookController] Updating details', updatePayload) - if (audiobook.update({ book: updatePayload })) { - hasUpdated = true - } - } - - if (hasUpdated) { - await this.db.updateEntity('audiobook', audiobook) - this.emitter('audiobook_updated', audiobook.toJSONExpanded()) - } - - res.json({ - updated: hasUpdated, - audiobook: audiobook.toJSONExpanded() - }) + var matchResult = await this.quickMatchBook(audiobook, options) + res.json(matchResult) } } module.exports = new BookController() \ No newline at end of file diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 15ec8e1d..93afe740 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -334,7 +334,7 @@ class LibraryController { async reorder(req, res) { if (!req.user.isRoot) { Logger.error('[ApiController] ReorderLibraries invalid user', req.user) - return res.sendStatus(401) + return res.sendStatus(403) } var orderdata = req.body @@ -470,6 +470,35 @@ class LibraryController { res.json(Object.values(authors)) } + async matchBooks(req, res) { + if (!req.user.isRoot) { + Logger.error(`[LibraryController] Non-root user attempted to match library books`, req.user) + return res.sendStatus(403) + } + res.sendStatus(200) + + const provider = req.library.provider || 'google' + var audiobooksInLibrary = this.db.audiobooks.filter(ab => ab.libraryId === req.library.id) + const resultPayload = { + library: library.toJSON(), + total: audiobooksInLibrary.length, + updated: 0 + } + + for (let i = 0; i < audiobooksInLibrary.length; i++) { + var audiobook = audiobooksInLibrary[i] + Logger.debug(`[LibraryController] matchBooks quick matching "${audiobook.title}" (${i + 1} of ${audiobooksInLibrary.length})`) + var result = await this.quickMatchBook(audiobook, { provider }) + if (result.warning) { + Logger.warn(`[LibraryController] matchBooks warning ${result.warning} for audiobook "${audiobook.title}"`) + } else if (result.updated) { + resultPayload.updated++ + } + } + + this.clientEmitter(req.usr.id, 'library-match-results', resultPayload) + } + middleware(req, res, next) { var librariesAccessible = req.user.librariesAccessible || [] if (librariesAccessible && librariesAccessible.length && !librariesAccessible.includes(req.params.id)) {