diff --git a/client/components/cards/ItemTaskRunningCard.vue b/client/components/cards/ItemTaskRunningCard.vue
index c9de1a87..d284c505 100644
--- a/client/components/cards/ItemTaskRunningCard.vue
+++ b/client/components/cards/ItemTaskRunningCard.vue
@@ -12,7 +12,7 @@
{{ failedMessage }}
Canceling...
- {{ this.$strings.ButtonCancel }}
+ {{ this.$strings.ButtonCancel }}
@@ -81,6 +81,9 @@ export default {
}
return ''
+ },
+ isLibraryScan() {
+ return this.action === 'library-scan' || this.action === 'library-match-all'
}
},
methods: {
diff --git a/client/components/modals/item/tabs/Details.vue b/client/components/modals/item/tabs/Details.vue
index 14fe68a7..62f08c92 100644
--- a/client/components/modals/item/tabs/Details.vue
+++ b/client/components/modals/item/tabs/Details.vue
@@ -11,8 +11,8 @@
{{ $strings.ButtonQuickMatch }}
-
- {{ $strings.ButtonReScan }}
+
+ {{ $strings.ButtonReScan }}
@@ -80,9 +80,9 @@ export default {
libraryProvider() {
return this.$store.getters['libraries/getLibraryProvider'](this.libraryId) || 'google'
},
- libraryScan() {
+ isLibraryScanning() {
if (!this.libraryId) return null
- return this.$store.getters['scanners/getLibraryScan'](this.libraryId)
+ return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.libraryId)
}
},
methods: {
diff --git a/client/components/tables/library/LibrariesTable.vue b/client/components/tables/library/LibrariesTable.vue
index 598b12b7..faf8d69d 100644
--- a/client/components/tables/library/LibrariesTable.vue
+++ b/client/components/tables/library/LibrariesTable.vue
@@ -42,13 +42,10 @@ export default {
return this.$store.getters['libraries/getCurrentLibrary']
},
currentLibraryId() {
- return this.currentLibrary ? this.currentLibrary.id : null
+ return this.currentLibrary?.id || null
},
libraries() {
return this.$store.getters['libraries/getSortedLibraries']()
- },
- libraryScans() {
- return this.$store.state.scanners.libraryScans
}
},
methods: {
diff --git a/client/layouts/default.vue b/client/layouts/default.vue
index 1f2acbd3..4f5e0fea 100644
--- a/client/layouts/default.vue
+++ b/client/layouts/default.vue
@@ -212,54 +212,6 @@ export default {
this.libraryItemAdded(ab)
})
},
- scanComplete(data) {
- console.log('Scan complete received', data)
-
- let message = `${data.type === 'match' ? 'Match' : 'Scan'} "${data.name}" complete!`
- let toastType = 'success'
- if (data.error) {
- message = `${data.type === 'match' ? 'Match' : 'Scan'} "${data.name}" finished with error:\n${data.error}`
- toastType = 'error'
- } else if (data.results) {
- var scanResultMsgs = []
- var results = data.results
- if (results.added) scanResultMsgs.push(`${results.added} added`)
- if (results.updated) scanResultMsgs.push(`${results.updated} updated`)
- if (results.removed) scanResultMsgs.push(`${results.removed} removed`)
- if (results.missing) scanResultMsgs.push(`${results.missing} missing`)
- if (!scanResultMsgs.length) message += '\nEverything was up to date'
- else message += '\n' + scanResultMsgs.join('\n')
- } else {
- message = `${data.type === 'match' ? 'Match' : 'Scan'} "${data.name}" was canceled`
- }
-
- var existingScan = this.$store.getters['scanners/getLibraryScan'](data.id)
- if (existingScan && !isNaN(existingScan.toastId)) {
- this.$toast.update(existingScan.toastId, { content: message, options: { timeout: 5000, type: toastType, closeButton: false, onClose: () => null } }, true)
- } else {
- this.$toast[toastType](message, { timeout: 5000 })
- }
-
- this.$store.commit('scanners/remove', data)
- },
- onScanToastCancel(id) {
- this.$root.socket.emit('cancel_scan', id)
- },
- scanStart(data) {
- data.toastId = this.$toast(`${data.type === 'match' ? 'Matching' : 'Scanning'} "${data.name}"...`, { timeout: false, type: 'info', draggable: false, closeOnClick: false, closeButton: CloseButton, closeButtonClassName: 'cancel-scan-btn', showCloseButtonOnHover: false, onClose: () => this.onScanToastCancel(data.id) })
- this.$store.commit('scanners/addUpdate', data)
- },
- scanProgress(data) {
- var existingScan = this.$store.getters['scanners/getLibraryScan'](data.id)
- if (existingScan && !isNaN(existingScan.toastId)) {
- data.toastId = existingScan.toastId
- this.$toast.update(existingScan.toastId, { content: `Scanning "${existingScan.name}"... ${data.progress.progress || 0}%`, options: { timeout: false } }, true)
- } else {
- data.toastId = this.$toast(`Scanning "${data.name}"...`, { timeout: false, type: 'info', draggable: false, closeOnClick: false, closeButton: CloseButton, closeButtonClassName: 'cancel-scan-btn', showCloseButtonOnHover: false, onClose: () => this.onScanToastCancel(data.id) })
- }
-
- this.$store.commit('scanners/addUpdate', data)
- },
taskStarted(task) {
console.log('Task started', task)
this.$store.commit('tasks/addUpdateTask', task)
@@ -442,11 +394,6 @@ export default {
this.socket.on('playlist_updated', this.playlistUpdated)
this.socket.on('playlist_removed', this.playlistRemoved)
- // Scan Listeners
- this.socket.on('scan_start', this.scanStart)
- this.socket.on('scan_complete', this.scanComplete)
- this.socket.on('scan_progress', this.scanProgress)
-
// Task Listeners
this.socket.on('task_started', this.taskStarted)
this.socket.on('task_finished', this.taskFinished)
diff --git a/client/store/scanners.js b/client/store/scanners.js
index de154c3d..ccdc1791 100644
--- a/client/store/scanners.js
+++ b/client/store/scanners.js
@@ -1,5 +1,4 @@
export const state = () => ({
- libraryScans: [],
providers: [
{
text: 'Google Books',
@@ -72,26 +71,8 @@ export const state = () => ({
]
})
-export const getters = {
- getLibraryScan: state => id => {
- return state.libraryScans.find(ls => ls.id === id)
- }
-}
+export const getters = {}
-export const actions = {
+export const actions = {}
-}
-
-export const mutations = {
- addUpdate(state, data) {
- const index = state.libraryScans.findIndex(lib => lib.id === data.id)
- if (index >= 0) {
- state.libraryScans.splice(index, 1, data)
- } else {
- state.libraryScans.push(data)
- }
- },
- remove(state, data) {
- state.libraryScans = state.libraryScans.filter(scan => scan.id !== data.id)
- }
-}
\ No newline at end of file
+export const mutations = {}
\ No newline at end of file
diff --git a/client/store/tasks.js b/client/store/tasks.js
index 9277d412..96e7e5b8 100644
--- a/client/store/tasks.js
+++ b/client/store/tasks.js
@@ -9,7 +9,8 @@ export const getters = {
return state.tasks.filter(t => t.data?.libraryItemId === libraryItemId)
},
getRunningLibraryScanTask: (state) => (libraryId) => {
- return state.tasks.find(t => t.data?.libraryId === libraryId && !t.isFinished)
+ const libraryScanActions = ['library-scan', 'library-match-all']
+ return state.tasks.find(t => libraryScanActions.includes(t.action) && t.data?.libraryId === libraryId && !t.isFinished)
}
}
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index 9c593ff2..10a77b2a 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -775,6 +775,13 @@ class LibraryController {
})
}
+ /**
+ * GET: /api/libraries/:id/matchall
+ * Quick match all library items. Book libraries only.
+ *
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ */
async matchAll(req, res) {
if (!req.user.isAdminOrUp) {
Logger.error(`[LibraryController] Non-root user attempted to match library items`, req.user)
diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js
index d17dbd15..b6f2f285 100644
--- a/server/models/LibraryItem.js
+++ b/server/models/LibraryItem.js
@@ -69,7 +69,7 @@ class LibraryItem extends Model {
*
* @param {number} offset
* @param {number} limit
- * @returns {Promise[]>} LibraryItem
+ * @returns {Promise} LibraryItem
*/
static getLibraryItemsIncrement(offset, limit, where = null) {
return this.findAll({
diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js
index ebce3607..616baf29 100644
--- a/server/scanner/Scanner.js
+++ b/server/scanner/Scanner.js
@@ -12,6 +12,7 @@ const Author = require('../objects/entities/Author')
const Series = require('../objects/entities/Series')
const LibraryScanner = require('./LibraryScanner')
const CoverManager = require('../managers/CoverManager')
+const TaskManager = require('../managers/TaskManager')
class Scanner {
constructor() { }
@@ -280,6 +281,14 @@ class Scanner {
return false
}
+ /**
+ * Quick match library items
+ *
+ * @param {import('../objects/Library')} library
+ * @param {import('../objects/LibraryItem')[]} libraryItems
+ * @param {LibraryScan} libraryScan
+ * @returns {Promise} false if scan canceled
+ */
async matchLibraryItemsChunk(library, libraryItems, libraryScan) {
for (let i = 0; i < libraryItems.length; i++) {
const libraryItem = libraryItems[i]
@@ -313,6 +322,11 @@ class Scanner {
return true
}
+ /**
+ * Quick match all library items for library
+ *
+ * @param {import('../objects/Library')} library
+ */
async matchLibraryItems(library) {
if (library.mediaType === 'podcast') {
Logger.error(`[Scanner] matchLibraryItems: Match all not supported for podcasts yet`)
@@ -330,11 +344,14 @@ class Scanner {
const libraryScan = new LibraryScan()
libraryScan.setData(library, 'match')
LibraryScanner.librariesScanning.push(libraryScan.getScanEmitData)
- SocketAuthority.emitter('scan_start', libraryScan.getScanEmitData)
-
+ const taskData = {
+ libraryId: library.id
+ }
+ const task = TaskManager.createAndAddTask('library-match-all', `Matching books in "${library.name}"`, null, true, taskData)
Logger.info(`[Scanner] matchLibraryItems: Starting library match scan ${libraryScan.id} for ${libraryScan.libraryName}`)
let hasMoreChunks = true
+ let isCanceled = false
while (hasMoreChunks) {
const libraryItems = await Database.libraryItemModel.getLibraryItemsIncrement(offset, limit, { libraryId: library.id })
if (!libraryItems.length) {
@@ -347,6 +364,7 @@ class Scanner {
const shouldContinue = await this.matchLibraryItemsChunk(library, oldLibraryItems, libraryScan)
if (!shouldContinue) {
+ isCanceled = true
break
}
}
@@ -354,13 +372,15 @@ class Scanner {
if (offset === 0) {
Logger.error(`[Scanner] matchLibraryItems: Library has no items ${library.id}`)
libraryScan.setComplete('Library has no items')
+ task.setFailed(libraryScan.error)
} else {
libraryScan.setComplete()
+ task.setFinished(isCanceled ? 'Canceled' : libraryScan.scanResultsString)
}
delete LibraryScanner.cancelLibraryScan[libraryScan.libraryId]
LibraryScanner.librariesScanning = LibraryScanner.librariesScanning.filter(ls => ls.id !== library.id)
- SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData)
+ TaskManager.taskFinished(task)
}
}
module.exports = new Scanner()