mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:Library match all books #359
This commit is contained in:
		
							parent
							
								
									c953c3dee0
								
							
						
					
					
						commit
						11be49a535
					
				@ -9,8 +9,11 @@
 | 
				
			|||||||
    </svg>
 | 
					    </svg>
 | 
				
			||||||
    <p class="text-xl font-book pl-4 hover:underline cursor-pointer" @click.stop="$emit('click', library)">{{ library.name }}</p>
 | 
					    <p class="text-xl font-book pl-4 hover:underline cursor-pointer" @click.stop="$emit('click', library)">{{ library.name }}</p>
 | 
				
			||||||
    <div class="flex-grow" />
 | 
					    <div class="flex-grow" />
 | 
				
			||||||
    <ui-btn v-show="isHovering && !libraryScan && canScan" small color="bg" @click.stop="scan">Scan</ui-btn>
 | 
					    <ui-btn v-show="isHovering && !libraryScan && canScan" small color="success" @click.stop="scan">Scan</ui-btn>
 | 
				
			||||||
    <ui-btn v-show="isHovering && !libraryScan && canScan" small color="bg" class="ml-2" @click.stop="forceScan">Force Re-Scan</ui-btn>
 | 
					    <ui-btn v-show="isHovering && !libraryScan && canScan" small color="bg" class="ml-2" @click.stop="forceScan">Force Re-Scan</ui-btn>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ui-btn v-show="isHovering && !libraryScan && canScan" small color="bg" class="ml-2" @click.stop="matchAll">Match Books</ui-btn>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <span v-show="isHovering && !libraryScan && showEdit && canEdit" class="material-icons text-xl text-gray-300 hover:text-gray-50 ml-4 cursor-pointer" @click.stop="editClick">edit</span>
 | 
					    <span v-show="isHovering && !libraryScan && showEdit && canEdit" class="material-icons text-xl text-gray-300 hover:text-gray-50 ml-4 cursor-pointer" @click.stop="editClick">edit</span>
 | 
				
			||||||
    <span v-show="!libraryScan && isHovering && showEdit && canDelete && !isDeleting" class="material-icons text-xl text-gray-300 ml-3" :class="isMain ? 'text-opacity-5 cursor-not-allowed' : 'hover:text-gray-50 cursor-pointer'" @click.stop="deleteClick">delete</span>
 | 
					    <span v-show="!libraryScan && isHovering && showEdit && canDelete && !isDeleting" class="material-icons text-xl text-gray-300 ml-3" :class="isMain ? 'text-opacity-5 cursor-not-allowed' : 'hover:text-gray-50 cursor-pointer'" @click.stop="deleteClick">delete</span>
 | 
				
			||||||
    <div v-show="isDeleting" class="text-xl text-gray-300 ml-3 animate-spin">
 | 
					    <div v-show="isDeleting" class="text-xl text-gray-300 ml-3 animate-spin">
 | 
				
			||||||
@ -59,6 +62,18 @@ export default {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
 | 
					    matchAll() {
 | 
				
			||||||
 | 
					      this.$axios
 | 
				
			||||||
 | 
					        .$post(`/api/libraries/${this.library.id}/matchbooks`)
 | 
				
			||||||
 | 
					        .then(() => {
 | 
				
			||||||
 | 
					          console.log('Starting scan for matches')
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch((error) => {
 | 
				
			||||||
 | 
					          console.error('Failed', error)
 | 
				
			||||||
 | 
					          var errorMsg = err.response ? err.response.data : ''
 | 
				
			||||||
 | 
					          this.$toast.error(errorMsg || 'Match all failed')
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    editClick() {
 | 
					    editClick() {
 | 
				
			||||||
      this.$emit('edit', this.library)
 | 
					      this.$emit('edit', this.library)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,8 @@
 | 
				
			|||||||
    <modals-edit-library-modal v-model="showLibraryModal" :library="selectedLibrary" />
 | 
					    <modals-edit-library-modal v-model="showLibraryModal" :library="selectedLibrary" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <p class="text-xs mt-4 text-gray-200">*<strong>Force Re-Scan</strong> will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be probed/parsed and used for book details.</p>
 | 
					    <p class="text-xs mt-4 text-gray-200">*<strong>Force Re-Scan</strong> will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be probed/parsed and used for book details.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <p class="text-xs mt-4 text-gray-200">**<strong>Match Books</strong> will attempt to match books in library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.</p>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -205,7 +205,7 @@ export default {
 | 
				
			|||||||
    scanComplete(data) {
 | 
					    scanComplete(data) {
 | 
				
			||||||
      console.log('Scan complete received', data)
 | 
					      console.log('Scan complete received', data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var message = `Scan "${data.name}" complete!`
 | 
					      var message = `${data.type === 'match' ? 'Match' : 'Scan'} "${data.name}" complete!`
 | 
				
			||||||
      if (data.results) {
 | 
					      if (data.results) {
 | 
				
			||||||
        var scanResultMsgs = []
 | 
					        var scanResultMsgs = []
 | 
				
			||||||
        var results = data.results
 | 
					        var results = data.results
 | 
				
			||||||
@ -216,7 +216,7 @@ export default {
 | 
				
			|||||||
        if (!scanResultMsgs.length) message += '\nEverything was up to date'
 | 
					        if (!scanResultMsgs.length) message += '\nEverything was up to date'
 | 
				
			||||||
        else message += '\n' + scanResultMsgs.join('\n')
 | 
					        else message += '\n' + scanResultMsgs.join('\n')
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        message = `Scan "${data.name}" was canceled`
 | 
					        message = `${data.type === 'match' ? 'Match' : 'Scan'} "${data.name}" was canceled`
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var existingScan = this.$store.getters['scanners/getLibraryScan'](data.id)
 | 
					      var existingScan = this.$store.getters['scanners/getLibraryScan'](data.id)
 | 
				
			||||||
@ -232,7 +232,7 @@ export default {
 | 
				
			|||||||
      this.$root.socket.emit('cancel_scan', id)
 | 
					      this.$root.socket.emit('cancel_scan', id)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    scanStart(data) {
 | 
					    scanStart(data) {
 | 
				
			||||||
      data.toastId = this.$toast(`Scanning "${data.name}"...`, { timeout: false, type: 'info', draggable: false, closeOnClick: false, closeButton: CloseButton, closeButtonClassName: 'cancel-scan-btn', showCloseButtonOnHover: false, position: 'bottom-center', onClose: () => this.onScanToastCancel(data.id) })
 | 
					      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, position: 'bottom-center', onClose: () => this.onScanToastCancel(data.id) })
 | 
				
			||||||
      this.$store.commit('scanners/addUpdate', data)
 | 
					      this.$store.commit('scanners/addUpdate', data)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    scanProgress(data) {
 | 
					    scanProgress(data) {
 | 
				
			||||||
 | 
				
			|||||||
@ -18,9 +18,10 @@ const AuthorFinder = require('./AuthorFinder')
 | 
				
			|||||||
const FileSystemController = require('./controllers/FileSystemController')
 | 
					const FileSystemController = require('./controllers/FileSystemController')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ApiController {
 | 
					class ApiController {
 | 
				
			||||||
  constructor(MetadataPath, db, auth, streamManager, rssFeeds, downloadManager, coverController, backupManager, watcher, cacheManager, emitter, clientEmitter) {
 | 
					  constructor(MetadataPath, db, auth, scanner, streamManager, rssFeeds, downloadManager, coverController, backupManager, watcher, cacheManager, emitter, clientEmitter) {
 | 
				
			||||||
    this.db = db
 | 
					    this.db = db
 | 
				
			||||||
    this.auth = auth
 | 
					    this.auth = auth
 | 
				
			||||||
 | 
					    this.scanner = scanner
 | 
				
			||||||
    this.streamManager = streamManager
 | 
					    this.streamManager = streamManager
 | 
				
			||||||
    this.rssFeeds = rssFeeds
 | 
					    this.rssFeeds = rssFeeds
 | 
				
			||||||
    this.downloadManager = downloadManager
 | 
					    this.downloadManager = downloadManager
 | 
				
			||||||
@ -517,57 +518,5 @@ class ApiController {
 | 
				
			|||||||
    await this.cacheManager.purgeAll()
 | 
					    await this.cacheManager.purgeAll()
 | 
				
			||||||
    res.sendStatus(200)
 | 
					    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
 | 
					module.exports = ApiController
 | 
				
			||||||
@ -55,7 +55,7 @@ class Server {
 | 
				
			|||||||
    this.streamManager = new StreamManager(this.db, this.MetadataPath, this.emitter.bind(this), this.clientEmitter.bind(this))
 | 
					    this.streamManager = new StreamManager(this.db, this.MetadataPath, this.emitter.bind(this), this.clientEmitter.bind(this))
 | 
				
			||||||
    this.rssFeeds = new RssFeeds(this.Port, this.db)
 | 
					    this.rssFeeds = new RssFeeds(this.Port, this.db)
 | 
				
			||||||
    this.downloadManager = new DownloadManager(this.db, this.MetadataPath, this.AudiobookPath, this.Uid, this.Gid)
 | 
					    this.downloadManager = new DownloadManager(this.db, this.MetadataPath, this.AudiobookPath, this.Uid, this.Gid)
 | 
				
			||||||
    this.apiController = new ApiController(this.MetadataPath, this.db, this.auth, this.streamManager, this.rssFeeds, this.downloadManager, this.coverController, this.backupManager, this.watcher, this.cacheManager, this.emitter.bind(this), this.clientEmitter.bind(this))
 | 
					    this.apiController = new ApiController(this.MetadataPath, this.db, this.auth, this.scanner, this.streamManager, this.rssFeeds, this.downloadManager, this.coverController, this.backupManager, this.watcher, this.cacheManager, this.emitter.bind(this), this.clientEmitter.bind(this))
 | 
				
			||||||
    this.hlsController = new HlsController(this.db, this.auth, this.streamManager, this.emitter.bind(this), this.streamManager.StreamsPath)
 | 
					    this.hlsController = new HlsController(this.db, this.auth, this.streamManager, this.emitter.bind(this), this.streamManager.StreamsPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Logger.logManager = this.logManager
 | 
					    Logger.logManager = this.logManager
 | 
				
			||||||
 | 
				
			|||||||
@ -272,7 +272,7 @@ class BookController {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var options = req.body || {}
 | 
					    var options = req.body || {}
 | 
				
			||||||
    var matchResult = await this.quickMatchBook(audiobook, options)
 | 
					    var matchResult = await this.scanner.quickMatchBook(audiobook, options)
 | 
				
			||||||
    res.json(matchResult)
 | 
					    res.json(matchResult)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -475,28 +475,8 @@ class LibraryController {
 | 
				
			|||||||
      Logger.error(`[LibraryController] Non-root user attempted to match library books`, req.user)
 | 
					      Logger.error(`[LibraryController] Non-root user attempted to match library books`, req.user)
 | 
				
			||||||
      return res.sendStatus(403)
 | 
					      return res.sendStatus(403)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    this.scanner.matchLibraryBooks(req.library)
 | 
				
			||||||
    res.sendStatus(200)
 | 
					    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) {
 | 
					  middleware(req, res, next) {
 | 
				
			||||||
 | 
				
			|||||||
@ -26,8 +26,10 @@ class Audible {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getBestImageLink(images) {
 | 
					    getBestImageLink(images) {
 | 
				
			||||||
        var keys = Object.keys(images);
 | 
					        if (!images) return null
 | 
				
			||||||
        return images[keys[keys.length - 1]];
 | 
					        var keys = Object.keys(images)
 | 
				
			||||||
 | 
					        if (!keys.length) return null
 | 
				
			||||||
 | 
					        return images[keys[keys.length - 1]]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getPrimarySeries(series, publication_name) {
 | 
					    getPrimarySeries(series, publication_name) {
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@ const { getId, secondsToTimestamp } = require('../utils/index')
 | 
				
			|||||||
class LibraryScan {
 | 
					class LibraryScan {
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    this.id = null
 | 
					    this.id = null
 | 
				
			||||||
 | 
					    this.type = null
 | 
				
			||||||
    this.libraryId = null
 | 
					    this.libraryId = null
 | 
				
			||||||
    this.libraryName = null
 | 
					    this.libraryName = null
 | 
				
			||||||
    this.folders = null
 | 
					    this.folders = null
 | 
				
			||||||
@ -46,6 +47,7 @@ class LibraryScan {
 | 
				
			|||||||
  get getScanEmitData() {
 | 
					  get getScanEmitData() {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      id: this.libraryId,
 | 
					      id: this.libraryId,
 | 
				
			||||||
 | 
					      type: this.type,
 | 
				
			||||||
      name: this.libraryName,
 | 
					      name: this.libraryName,
 | 
				
			||||||
      results: {
 | 
					      results: {
 | 
				
			||||||
        added: this.resultsAdded,
 | 
					        added: this.resultsAdded,
 | 
				
			||||||
@ -64,10 +66,11 @@ class LibraryScan {
 | 
				
			|||||||
  toJSON() {
 | 
					  toJSON() {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      id: this.id,
 | 
					      id: this.id,
 | 
				
			||||||
 | 
					      type: this.type,
 | 
				
			||||||
      libraryId: this.libraryId,
 | 
					      libraryId: this.libraryId,
 | 
				
			||||||
      libraryName: this.libraryName,
 | 
					      libraryName: this.libraryName,
 | 
				
			||||||
      folders: this.folders.map(f => f.toJSON()),
 | 
					      folders: this.folders.map(f => f.toJSON()),
 | 
				
			||||||
      scanOptions: this.scanOptions.toJSON(),
 | 
					      scanOptions: this.scanOptions ? this.scanOptions.toJSON() : null,
 | 
				
			||||||
      startedAt: this.startedAt,
 | 
					      startedAt: this.startedAt,
 | 
				
			||||||
      finishedAt: this.finishedAt,
 | 
					      finishedAt: this.finishedAt,
 | 
				
			||||||
      elapsed: this.elapsed,
 | 
					      elapsed: this.elapsed,
 | 
				
			||||||
@ -77,8 +80,9 @@ class LibraryScan {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setData(library, scanOptions) {
 | 
					  setData(library, scanOptions, type = 'scan') {
 | 
				
			||||||
    this.id = getId('lscan')
 | 
					    this.id = getId('lscan')
 | 
				
			||||||
 | 
					    this.type = type
 | 
				
			||||||
    this.libraryId = library.id
 | 
					    this.libraryId = library.id
 | 
				
			||||||
    this.libraryName = library.name
 | 
					    this.libraryName = library.name
 | 
				
			||||||
    this.folders = library.folders.map(folder => new Folder(folder.toJSON()))
 | 
					    this.folders = library.folders.map(folder => new Folder(folder.toJSON()))
 | 
				
			||||||
 | 
				
			|||||||
@ -625,5 +625,101 @@ class Scanner {
 | 
				
			|||||||
      Logger.info(`[Scanner] Updated ${audiobooksUpdated} audiobook IDs`)
 | 
					      Logger.info(`[Scanner] Updated ${audiobooksUpdated} audiobook IDs`)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async matchLibraryBooks(library) {
 | 
				
			||||||
 | 
					    if (this.isLibraryScanning(library.id)) {
 | 
				
			||||||
 | 
					      Logger.error(`[Scanner] Already scanning ${library.id}`)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const provider = library.provider || 'google'
 | 
				
			||||||
 | 
					    var audiobooksInLibrary = this.db.audiobooks.filter(ab => ab.libraryId === library.id)
 | 
				
			||||||
 | 
					    if (!audiobooksInLibrary.length) {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var libraryScan = new LibraryScan()
 | 
				
			||||||
 | 
					    libraryScan.setData(library, null, 'match')
 | 
				
			||||||
 | 
					    this.librariesScanning.push(libraryScan.getScanEmitData)
 | 
				
			||||||
 | 
					    this.emitter('scan_start', libraryScan.getScanEmitData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Logger.info(`[Scanner] Starting library match books scan ${libraryScan.id} for ${libraryScan.libraryName}`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let i = 0; i < audiobooksInLibrary.length; i++) {
 | 
				
			||||||
 | 
					      var audiobook = audiobooksInLibrary[i]
 | 
				
			||||||
 | 
					      Logger.debug(`[Scanner] Quick matching "${audiobook.title}" (${i + 1} of ${audiobooksInLibrary.length})`)
 | 
				
			||||||
 | 
					      var result = await this.quickMatchBook(audiobook, { provider })
 | 
				
			||||||
 | 
					      if (result.warning) {
 | 
				
			||||||
 | 
					        Logger.warn(`[Scanner] Match warning ${result.warning} for audiobook "${audiobook.title}"`)
 | 
				
			||||||
 | 
					      } else if (result.updated) {
 | 
				
			||||||
 | 
					        libraryScan.resultsUpdated++
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.cancelLibraryScan[libraryScan.libraryId]) {
 | 
				
			||||||
 | 
					        Logger.info(`[Scanner] Library match scan canceled for "${libraryScan.libraryName}"`)
 | 
				
			||||||
 | 
					        delete this.cancelLibraryScan[libraryScan.libraryId]
 | 
				
			||||||
 | 
					        var scanData = libraryScan.getScanEmitData
 | 
				
			||||||
 | 
					        scanData.results = false
 | 
				
			||||||
 | 
					        this.emitter('scan_complete', scanData)
 | 
				
			||||||
 | 
					        this.librariesScanning = this.librariesScanning.filter(ls => ls.id !== library.id)
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.librariesScanning = this.librariesScanning.filter(ls => ls.id !== library.id)
 | 
				
			||||||
 | 
					    this.emitter('scan_complete', libraryScan.getScanEmitData)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
module.exports = Scanner
 | 
					module.exports = Scanner
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user