diff --git a/client/components/app/Appbar.vue b/client/components/app/Appbar.vue
index cc417739..842d559f 100644
--- a/client/components/app/Appbar.vue
+++ b/client/components/app/Appbar.vue
@@ -49,6 +49,9 @@
{{ numLibraryItemsSelected }} Selected
+
+
+
@@ -210,7 +213,10 @@ export default {
},
setBookshelfTotalEntities(totalEntities) {
this.totalEntities = totalEntities
- }
+ },
+ batchAutoMatchClick() {
+ this.$store.commit('globals/setShowBatchQuickMatchModal', true)
+ },
},
mounted() {
this.$eventBus.$on('bookshelf-total-entities', this.setBookshelfTotalEntities)
diff --git a/client/components/modals/BatchQuickMatchModel.vue b/client/components/modals/BatchQuickMatchModel.vue
new file mode 100644
index 00000000..a80beefa
--- /dev/null
+++ b/client/components/modals/BatchQuickMatchModel.vue
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
Quick Match {{ selectedBookIds.length }} Books
+
+
+
+
+
Quick Match will attempt to add missing covers and metadata for the selected books. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.
+
+
+
+
+ Update Covers
+ info_outlined
+
+
+
+
+
+
+
+ Update Details
+ info_outlined
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/layouts/default.vue b/client/layouts/default.vue
index 3e4202f2..1a23cde7 100644
--- a/client/layouts/default.vue
+++ b/client/layouts/default.vue
@@ -15,6 +15,7 @@
+
@@ -358,6 +359,18 @@ export default {
// Force refresh
location.reload()
},
+ batchQuickMatchComplete(result) {
+ var success = result.success || false
+ var toast = 'Batch quick match complete!\n' + result.updates + ' Updated'
+ if (result.unmatched && (result.unmatched > 0)) {
+ toast += '\n' + result.unmatched + ' with no matches'
+ }
+ if (success) {
+ this.$toast.success(toast)
+ } else {
+ this.$toast.info(toast)
+ }
+ },
initializeSocket() {
this.socket = this.$nuxtSocket({
name: process.env.NODE_ENV === 'development' ? 'dev' : 'prod',
@@ -429,6 +442,8 @@ export default {
this.socket.on('rss_feed_closed', this.rssFeedClosed)
this.socket.on('backup_applied', this.backupApplied)
+
+ this.socket.on('batch_quickmatch_complete', this.batchQuickMatchComplete)
},
showUpdateToast(versionData) {
var ignoreVersion = localStorage.getItem('ignoreVersion')
diff --git a/client/store/globals.js b/client/store/globals.js
index 21a31d5a..9e837f00 100644
--- a/client/store/globals.js
+++ b/client/store/globals.js
@@ -14,6 +14,7 @@ export const state = () => ({
selectedAuthor: null,
isCasting: false, // Actively casting
isChromecastInitialized: false, // Script loaded
+ showBatchQuickMatchModal: false,
dateFormats: [
{
text: 'MM/DD/YYYY',
@@ -108,5 +109,8 @@ export const mutations = {
},
setCasting(state, val) {
state.isCasting = val
+ },
+ setShowBatchQuickMatchModal(state, val) {
+ state.showBatchQuickMatchModal = val
}
}
\ No newline at end of file
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index 328e75a5..acbc6926 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -305,6 +305,42 @@ class LibraryItemController {
res.json(libraryItems)
}
+ // POST: api/items/batch/quickmatch
+ async batchQuickMatch(req, res) {
+ if (!req.user.isAdminOrUp) {
+ Logger.warn('User other than admin attempted to batch quick match library items', req.user)
+ return res.sendStatus(403)
+ }
+
+ var itemsUpdated = 0
+ var itemsUnmatched = 0
+
+ var matchData = req.body
+ var options = matchData.options || {}
+ var items = matchData.libraryItemIds
+ if (!items || !items.length) {
+ return res.sendStatus(500)
+ }
+ res.sendStatus(200)
+
+ for (let i = 0; i < items.length; i++) {
+ var libraryItem = this.db.libraryItems.find(_li => _li.id === items[i])
+ var matchResult = await this.scanner.quickMatchLibraryItem(libraryItem, options)
+ if (matchResult.updated) {
+ itemsUpdated++
+ } else if (matchResult.warning) {
+ itemsUnmatched++
+ }
+ }
+
+ var result = {
+ success: itemsUpdated > 0,
+ updates: itemsUpdated,
+ unmatched: itemsUnmatched
+ }
+ this.clientEmitter(req.user.id, 'batch_quickmatch_complete', result)
+ }
+
// DELETE: api/items/all
async deleteAll(req, res) {
if (!req.user.isAdminOrUp) {
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index 9f612cbe..a689db36 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -103,6 +103,7 @@ class ApiRouter {
this.router.post('/items/batch/delete', LibraryItemController.batchDelete.bind(this))
this.router.post('/items/batch/update', LibraryItemController.batchUpdate.bind(this))
this.router.post('/items/batch/get', LibraryItemController.batchGet.bind(this))
+ this.router.post('/items/batch/quickmatch', LibraryItemController.batchQuickMatch.bind(this))
//
// User Routes
diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js
index acfcdd9e..21d95058 100644
--- a/server/scanner/Scanner.js
+++ b/server/scanner/Scanner.js
@@ -675,9 +675,11 @@ class Scanner {
var provider = options.provider || 'google'
var searchTitle = options.title || libraryItem.media.metadata.title
var searchAuthor = options.author || libraryItem.media.metadata.authorName
+ var overrideDefaults = options.overrideDefaults || false
- // Set to override existing metadata if scannerPreferMatchedMetadata setting is true
- if (this.db.serverSettings.scannerPreferMatchedMetadata) {
+ // Set to override existing metadata if scannerPreferMatchedMetadata setting is true and
+ // the overrideDefaults option is not set or set to false.
+ if ((overrideDefaults == false) && (this.db.serverSettings.scannerPreferMatchedMetadata)) {
options.overrideCover = true
options.overrideDetails = true
}