mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Merge branch 'master' into plugin-implementation-demo
This commit is contained in:
		
						commit
						a762e6ca03
					
				| @ -54,8 +54,7 @@ export default { | ||||
|       options: { | ||||
|         provider: undefined, | ||||
|         overrideDetails: true, | ||||
|         overrideCover: true, | ||||
|         overrideDefaults: true | ||||
|         overrideCover: true | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
| @ -99,8 +98,8 @@ export default { | ||||
|     init() { | ||||
|       // If we don't have a set provider (first open of dialog) or we've switched library, set | ||||
|       // the selected provider to the current library default provider | ||||
|       if (!this.options.provider || this.options.lastUsedLibrary != this.currentLibraryId) { | ||||
|         this.options.lastUsedLibrary = this.currentLibraryId | ||||
|       if (!this.options.provider || this.lastUsedLibrary != this.currentLibraryId) { | ||||
|         this.lastUsedLibrary = this.currentLibraryId | ||||
|         this.options.provider = this.libraryProvider | ||||
|       } | ||||
|     }, | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <button :aria-labelledby="labeledBy" role="checkbox" type="button" class="border rounded-full border-black-100 flex items-center cursor-pointer justify-start" :style="{ width: buttonWidth + 'px' }" :aria-checked="toggleValue" :class="className" @click="clickToggle"> | ||||
|     <button :aria-labelledby="labeledBy" :aria-label="label" role="checkbox" type="button" class="border rounded-full border-black-100 flex items-center cursor-pointer justify-start" :style="{ width: buttonWidth + 'px' }" :aria-checked="toggleValue" :class="className" @click="clickToggle"> | ||||
|       <span class="rounded-full border border-black-50 shadow transform transition-transform duration-100" :style="{ width: cursorHeightWidth + 'px', height: cursorHeightWidth + 'px' }" :class="switchClassName"></span> | ||||
|     </button> | ||||
|   </div> | ||||
| @ -20,6 +20,7 @@ export default { | ||||
|     }, | ||||
|     disabled: Boolean, | ||||
|     labeledBy: String, | ||||
|     label: String, | ||||
|     size: { | ||||
|       type: String, | ||||
|       default: 'md' | ||||
|  | ||||
| @ -6,9 +6,9 @@ | ||||
|           <div class="pt-4"> | ||||
|             <h2 class="font-semibold">{{ $strings.HeaderSettingsGeneral }}</h2> | ||||
|           </div> | ||||
|           <div class="flex items-end py-2"> | ||||
|             <ui-toggle-switch labeledBy="settings-store-cover-with-items" v-model="newServerSettings.storeCoverWithItem" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('storeCoverWithItem', val)" /> | ||||
|             <ui-tooltip :text="$strings.LabelSettingsStoreCoversWithItemHelp"> | ||||
|           <div role="article" :aria-label="$strings.LabelSettingsStoreCoversWithItemHelp" class="flex items-end py-2"> | ||||
|             <ui-toggle-switch :label="$strings.LabelSettingsStoreCoversWithItem" v-model="newServerSettings.storeCoverWithItem" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('storeCoverWithItem', val)" /> | ||||
|             <ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsStoreCoversWithItemHelp"> | ||||
|               <p class="pl-4"> | ||||
|                 <span id="settings-store-cover-with-items">{{ $strings.LabelSettingsStoreCoversWithItem }}</span> | ||||
|                 <span class="material-symbols icon-text">info</span> | ||||
| @ -16,9 +16,9 @@ | ||||
|             </ui-tooltip> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex items-center py-2"> | ||||
|             <ui-toggle-switch labeledBy="settings-store-metadata-with-items" v-model="newServerSettings.storeMetadataWithItem" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('storeMetadataWithItem', val)" /> | ||||
|             <ui-tooltip :text="$strings.LabelSettingsStoreMetadataWithItemHelp"> | ||||
|           <div role="article" :aria-label="$strings.LabelSettingsStoreMetadataWithItemHelp" class="flex items-center py-2"> | ||||
|             <ui-toggle-switch :label="$strings.LabelSettingsStoreMetadataWithItem" v-model="newServerSettings.storeMetadataWithItem" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('storeMetadataWithItem', val)" /> | ||||
|             <ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsStoreMetadataWithItemHelp"> | ||||
|               <p class="pl-4"> | ||||
|                 <span id="settings-store-metadata-with-items">{{ $strings.LabelSettingsStoreMetadataWithItem }}</span> | ||||
|                 <span class="material-symbols icon-text">info</span> | ||||
| @ -26,9 +26,9 @@ | ||||
|             </ui-tooltip> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex items-center py-2"> | ||||
|             <ui-toggle-switch labeledBy="settings-sorting-ignore-prefixes" v-model="newServerSettings.sortingIgnorePrefix" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('sortingIgnorePrefix', val)" /> | ||||
|             <ui-tooltip :text="$strings.LabelSettingsSortingIgnorePrefixesHelp"> | ||||
|           <div role="article" :aria-label="$strings.LabelSettingsSortingIgnorePrefixesHelp" class="flex items-center py-2"> | ||||
|             <ui-toggle-switch :label="$strings.LabelSettingsSortingIgnorePrefixes" v-model="newServerSettings.sortingIgnorePrefix" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('sortingIgnorePrefix', val)" /> | ||||
|             <ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsSortingIgnorePrefixesHelp"> | ||||
|               <p class="pl-4"> | ||||
|                 <span id="settings-sorting-ignore-prefixes">{{ $strings.LabelSettingsSortingIgnorePrefixes }}</span> | ||||
|                 <span class="material-symbols icon-text">info</span> | ||||
| @ -46,9 +46,9 @@ | ||||
|             <h2 class="font-semibold">{{ $strings.HeaderSettingsScanner }}</h2> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex items-center py-2"> | ||||
|             <ui-toggle-switch labeledBy="settings-parse-subtitles" v-model="newServerSettings.scannerParseSubtitle" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerParseSubtitle', val)" /> | ||||
|             <ui-tooltip :text="$strings.LabelSettingsParseSubtitlesHelp"> | ||||
|           <div role="article" :aria-label="$strings.LabelSettingsParseSubtitlesHelp" class="flex items-center py-2"> | ||||
|             <ui-toggle-switch :label="$strings.LabelSettingsParseSubtitles" v-model="newServerSettings.scannerParseSubtitle" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerParseSubtitle', val)" /> | ||||
|             <ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsParseSubtitlesHelp"> | ||||
|               <p class="pl-4"> | ||||
|                 <span id="settings-parse-subtitles">{{ $strings.LabelSettingsParseSubtitles }}</span> | ||||
|                 <span class="material-symbols icon-text">info</span> | ||||
| @ -56,9 +56,9 @@ | ||||
|             </ui-tooltip> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex items-center py-2"> | ||||
|             <ui-toggle-switch labeledBy="settings-find-covers" v-model="newServerSettings.scannerFindCovers" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerFindCovers', val)" /> | ||||
|             <ui-tooltip :text="$strings.LabelSettingsFindCoversHelp"> | ||||
|           <div role="article" :aria-label="$strings.LabelSettingsFindCoversHelp" class="flex items-center py-2"> | ||||
|             <ui-toggle-switch :label="$strings.LabelSettingsFindCovers" v-model="newServerSettings.scannerFindCovers" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerFindCovers', val)" /> | ||||
|             <ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsFindCoversHelp"> | ||||
|               <p class="pl-4"> | ||||
|                 <span id="settings-find-covers">{{ $strings.LabelSettingsFindCovers }}</span> | ||||
|                 <span class="material-symbols icon-text">info</span> | ||||
| @ -70,9 +70,9 @@ | ||||
|             <ui-dropdown v-model="newServerSettings.scannerCoverProvider" small :items="providers" label="Cover Provider" @input="updateScannerCoverProvider" :disabled="updatingServerSettings" /> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex items-center py-2"> | ||||
|             <ui-toggle-switch labeledBy="settings-prefer-matched-metadata" v-model="newServerSettings.scannerPreferMatchedMetadata" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerPreferMatchedMetadata', val)" /> | ||||
|             <ui-tooltip :text="$strings.LabelSettingsPreferMatchedMetadataHelp"> | ||||
|           <div role="article" :aria-label="$strings.LabelSettingsPreferMatchedMetadataHelp" class="flex items-center py-2"> | ||||
|             <ui-toggle-switch :label="$strings.LabelSettingsPreferMatchedMetadata" v-model="newServerSettings.scannerPreferMatchedMetadata" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerPreferMatchedMetadata', val)" /> | ||||
|             <ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsPreferMatchedMetadataHelp"> | ||||
|               <p class="pl-4"> | ||||
|                 <span id="settings-prefer-matched-metadata">{{ $strings.LabelSettingsPreferMatchedMetadata }}</span> | ||||
|                 <span class="material-symbols icon-text">info</span> | ||||
| @ -80,9 +80,9 @@ | ||||
|             </ui-tooltip> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex items-center py-2"> | ||||
|             <ui-toggle-switch labeledBy="settings-disable-watcher" v-model="scannerEnableWatcher" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerDisableWatcher', !val)" /> | ||||
|             <ui-tooltip :text="$strings.LabelSettingsEnableWatcherHelp"> | ||||
|           <div role="article" :aria-label="$strings.LabelSettingsEnableWatcherHelp" class="flex items-center py-2"> | ||||
|             <ui-toggle-switch :label="$strings.LabelSettingsEnableWatcher" v-model="scannerEnableWatcher" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerDisableWatcher', !val)" /> | ||||
|             <ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsEnableWatcherHelp"> | ||||
|               <p class="pl-4"> | ||||
|                 <span id="settings-disable-watcher">{{ $strings.LabelSettingsEnableWatcher }}</span> | ||||
|                 <span class="material-symbols icon-text">info</span> | ||||
| @ -95,13 +95,13 @@ | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex items-center py-2"> | ||||
|             <ui-toggle-switch labeledBy="settings-chromecast-support" v-model="newServerSettings.chromecastEnabled" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('chromecastEnabled', val)" /> | ||||
|             <p class="pl-4" id="settings-chromecast-support">{{ $strings.LabelSettingsChromecastSupport }}</p> | ||||
|             <ui-toggle-switch v-model="newServerSettings.chromecastEnabled" :label="$strings.LabelSettingsChromecastSupport" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('chromecastEnabled', val)" /> | ||||
|             <p aria-hidden="true" class="pl-4">{{ $strings.LabelSettingsChromecastSupport }}</p> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex items-center py-2 mb-2"> | ||||
|             <ui-toggle-switch labeledBy="settings-allow-iframe" v-model="newServerSettings.allowIframe" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('allowIframe', val)" /> | ||||
|             <p class="pl-4" id="settings-allow-iframe">{{ $strings.LabelSettingsAllowIframe }}</p> | ||||
|             <ui-toggle-switch v-model="newServerSettings.allowIframe" :label="$strings.LabelSettingsAllowIframe" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('allowIframe', val)" /> | ||||
|             <p aria-hidden="true" class="pl-4">{{ $strings.LabelSettingsAllowIframe }}</p> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|  | ||||
| @ -190,7 +190,9 @@ class FolderWatcher extends EventEmitter { | ||||
|       return | ||||
|     } | ||||
|     Logger.debug('[Watcher] File Added', path) | ||||
|     this.addFileUpdate(libraryId, path, 'added') | ||||
|     if (!this.addFileUpdate(libraryId, path, 'added')) { | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     if (!this.filesBeingAdded.has(path)) { | ||||
|       this.filesBeingAdded.add(path) | ||||
| @ -261,22 +263,23 @@ class FolderWatcher extends EventEmitter { | ||||
|    * @param {string} libraryId | ||||
|    * @param {string} path | ||||
|    * @param {string} type | ||||
|    * @returns {boolean} - If file was added to pending updates | ||||
|    */ | ||||
|   addFileUpdate(libraryId, path, type) { | ||||
|     if (this.pendingFilePaths.includes(path)) return | ||||
|     if (this.pendingFilePaths.includes(path)) return false | ||||
| 
 | ||||
|     // Get file library
 | ||||
|     const libwatcher = this.libraryWatchers.find((lw) => lw.id === libraryId) | ||||
|     if (!libwatcher) { | ||||
|       Logger.error(`[Watcher] Invalid library id from watcher ${libraryId}`) | ||||
|       return | ||||
|       return false | ||||
|     } | ||||
| 
 | ||||
|     // Get file folder
 | ||||
|     const folder = libwatcher.libraryFolders.find((fold) => isSameOrSubPath(fold.path, path)) | ||||
|     if (!folder) { | ||||
|       Logger.error(`[Watcher] New file folder not found in library "${libwatcher.name}" with path "${path}"`) | ||||
|       return | ||||
|       return false | ||||
|     } | ||||
| 
 | ||||
|     const folderPath = filePathToPOSIX(folder.path) | ||||
| @ -285,14 +288,14 @@ class FolderWatcher extends EventEmitter { | ||||
| 
 | ||||
|     if (Path.extname(relPath).toLowerCase() === '.part') { | ||||
|       Logger.debug(`[Watcher] Ignoring .part file "${relPath}"`) | ||||
|       return | ||||
|       return false | ||||
|     } | ||||
| 
 | ||||
|     // Ignore files/folders starting with "."
 | ||||
|     const hasDotPath = relPath.split('/').find((p) => p.startsWith('.')) | ||||
|     if (hasDotPath) { | ||||
|       Logger.debug(`[Watcher] Ignoring dot path "${relPath}" | Piece "${hasDotPath}"`) | ||||
|       return | ||||
|       return false | ||||
|     } | ||||
| 
 | ||||
|     Logger.debug(`[Watcher] Modified file in library "${libwatcher.name}" and folder "${folder.id}" with relPath "${relPath}"`) | ||||
| @ -318,6 +321,7 @@ class FolderWatcher extends EventEmitter { | ||||
|     }) | ||||
| 
 | ||||
|     this.handlePendingFileUpdatesTimeout() | ||||
|     return true | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | ||||
| @ -1217,7 +1217,7 @@ class LibraryController { | ||||
|       Logger.error(`[LibraryController] Non-root user "${req.user.username}" attempted to match library items`) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
|     Scanner.matchLibraryItems(req.library) | ||||
|     Scanner.matchLibraryItems(this, req.library) | ||||
|     res.sendStatus(200) | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -456,10 +456,24 @@ class LibraryItemController { | ||||
|    * @param {Response} res | ||||
|    */ | ||||
|   async match(req, res) { | ||||
|     var libraryItem = req.libraryItem | ||||
|     const libraryItem = req.libraryItem | ||||
|     const reqBody = req.body || {} | ||||
| 
 | ||||
|     var options = req.body || {} | ||||
|     var matchResult = await Scanner.quickMatchLibraryItem(libraryItem, options) | ||||
|     const options = {} | ||||
|     const matchOptions = ['provider', 'title', 'author', 'isbn', 'asin'] | ||||
|     for (const key of matchOptions) { | ||||
|       if (reqBody[key] && typeof reqBody[key] === 'string') { | ||||
|         options[key] = reqBody[key] | ||||
|       } | ||||
|     } | ||||
|     if (reqBody.overrideCover !== undefined) { | ||||
|       options.overrideCover = !!reqBody.overrideCover | ||||
|     } | ||||
|     if (reqBody.overrideDetails !== undefined) { | ||||
|       options.overrideDetails = !!reqBody.overrideDetails | ||||
|     } | ||||
| 
 | ||||
|     var matchResult = await Scanner.quickMatchLibraryItem(this, libraryItem, options) | ||||
|     res.json(matchResult) | ||||
|   } | ||||
| 
 | ||||
| @ -642,7 +656,6 @@ class LibraryItemController { | ||||
|     let itemsUpdated = 0 | ||||
|     let itemsUnmatched = 0 | ||||
| 
 | ||||
|     const options = req.body.options || {} | ||||
|     if (!req.body.libraryItemIds?.length) { | ||||
|       return res.sendStatus(400) | ||||
|     } | ||||
| @ -656,8 +669,20 @@ class LibraryItemController { | ||||
| 
 | ||||
|     res.sendStatus(200) | ||||
| 
 | ||||
|     const reqBodyOptions = req.body.options || {} | ||||
|     const options = {} | ||||
|     if (reqBodyOptions.provider && typeof reqBodyOptions.provider === 'string') { | ||||
|       options.provider = reqBodyOptions.provider | ||||
|     } | ||||
|     if (reqBodyOptions.overrideCover !== undefined) { | ||||
|       options.overrideCover = !!reqBodyOptions.overrideCover | ||||
|     } | ||||
|     if (reqBodyOptions.overrideDetails !== undefined) { | ||||
|       options.overrideDetails = !!reqBodyOptions.overrideDetails | ||||
|     } | ||||
| 
 | ||||
|     for (const libraryItem of libraryItems) { | ||||
|       const matchResult = await Scanner.quickMatchLibraryItem(libraryItem, options) | ||||
|       const matchResult = await Scanner.quickMatchLibraryItem(this, libraryItem, options) | ||||
|       if (matchResult.updated) { | ||||
|         itemsUpdated++ | ||||
|       } else if (matchResult.warning) { | ||||
|  | ||||
| @ -342,7 +342,6 @@ class RssFeedManager { | ||||
|       } | ||||
|     }) | ||||
|     if (!feed) { | ||||
|       Logger.warn(`[RssFeedManager] closeFeedForEntityId: Feed not found for entity id ${entityId}`) | ||||
|       return false | ||||
|     } | ||||
|     return this.handleCloseFeed(feed) | ||||
|  | ||||
| @ -13,36 +13,58 @@ const LibraryScanner = require('./LibraryScanner') | ||||
| const CoverManager = require('../managers/CoverManager') | ||||
| const TaskManager = require('../managers/TaskManager') | ||||
| 
 | ||||
| /** | ||||
|  * @typedef QuickMatchOptions | ||||
|  * @property {string} [provider] | ||||
|  * @property {string} [title] | ||||
|  * @property {string} [author] | ||||
|  * @property {string} [isbn] - This override is currently unused in Abs clients | ||||
|  * @property {string} [asin] - This override is currently unused in Abs clients | ||||
|  * @property {boolean} [overrideCover] | ||||
|  * @property {boolean} [overrideDetails] | ||||
|  */ | ||||
| 
 | ||||
| class Scanner { | ||||
|   constructor() {} | ||||
| 
 | ||||
|   async quickMatchLibraryItem(libraryItem, options = {}) { | ||||
|     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 | ||||
|   /** | ||||
|    * | ||||
|    * @param {import('../routers/ApiRouter')} apiRouterCtx | ||||
|    * @param {import('../objects/LibraryItem')} libraryItem | ||||
|    * @param {QuickMatchOptions} options | ||||
|    * @returns {Promise<{updated: boolean, libraryItem: import('../objects/LibraryItem')}>} | ||||
|    */ | ||||
|   async quickMatchLibraryItem(apiRouterCtx, libraryItem, options = {}) { | ||||
|     const provider = options.provider || 'google' | ||||
|     const searchTitle = options.title || libraryItem.media.metadata.title | ||||
|     const searchAuthor = options.author || libraryItem.media.metadata.authorName | ||||
| 
 | ||||
|     // Set to override existing metadata if scannerPreferMatchedMetadata setting is true and
 | ||||
|     // the overrideDefaults option is not set or set to false.
 | ||||
|     if (overrideDefaults == false && Database.serverSettings.scannerPreferMatchedMetadata) { | ||||
|     // If overrideCover and overrideDetails is not sent in options than use the server setting to determine if we should override
 | ||||
|     if (options.overrideCover === undefined && options.overrideDetails === undefined && Database.serverSettings.scannerPreferMatchedMetadata) { | ||||
|       options.overrideCover = true | ||||
|       options.overrideDetails = true | ||||
|     } | ||||
| 
 | ||||
|     var updatePayload = {} | ||||
|     var hasUpdated = false | ||||
|     let updatePayload = {} | ||||
|     let hasUpdated = false | ||||
| 
 | ||||
|     let existingAuthors = [] // Used for checking if authors or series are now empty
 | ||||
|     let existingSeries = [] | ||||
| 
 | ||||
|     if (libraryItem.isBook) { | ||||
|       var searchISBN = options.isbn || libraryItem.media.metadata.isbn | ||||
|       var searchASIN = options.asin || libraryItem.media.metadata.asin | ||||
|       existingAuthors = libraryItem.media.metadata.authors.map((a) => a.id) | ||||
|       existingSeries = libraryItem.media.metadata.series.map((s) => s.id) | ||||
| 
 | ||||
|       var results = await BookFinder.search(libraryItem, provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 }) | ||||
|       const searchISBN = options.isbn || libraryItem.media.metadata.isbn | ||||
|       const searchASIN = options.asin || libraryItem.media.metadata.asin | ||||
| 
 | ||||
|       const results = await BookFinder.search(libraryItem, provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 }) | ||||
|       if (!results.length) { | ||||
|         return { | ||||
|           warning: `No ${provider} match found` | ||||
|         } | ||||
|       } | ||||
|       var matchData = results[0] | ||||
|       const matchData = results[0] | ||||
| 
 | ||||
|       // Update cover if not set OR overrideCover flag
 | ||||
|       if (matchData.cover && (!libraryItem.media.coverPath || options.overrideCover)) { | ||||
| @ -58,13 +80,13 @@ class Scanner { | ||||
|       updatePayload = await this.quickMatchBookBuildUpdatePayload(libraryItem, matchData, options) | ||||
|     } else if (libraryItem.isPodcast) { | ||||
|       // Podcast quick match
 | ||||
|       var results = await PodcastFinder.search(searchTitle) | ||||
|       const results = await PodcastFinder.search(searchTitle) | ||||
|       if (!results.length) { | ||||
|         return { | ||||
|           warning: `No ${provider} match found` | ||||
|         } | ||||
|       } | ||||
|       var matchData = results[0] | ||||
|       const matchData = results[0] | ||||
| 
 | ||||
|       // Update cover if not set OR overrideCover flag
 | ||||
|       if (matchData.cover && (!libraryItem.media.coverPath || options.overrideCover)) { | ||||
| @ -95,6 +117,19 @@ class Scanner { | ||||
| 
 | ||||
|       await Database.updateLibraryItem(libraryItem) | ||||
|       SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) | ||||
| 
 | ||||
|       // Check if any authors or series are now empty and should be removed
 | ||||
|       if (libraryItem.isBook) { | ||||
|         const authorsRemoved = existingAuthors.filter((aid) => !libraryItem.media.metadata.authors.find((au) => au.id === aid)) | ||||
|         const seriesRemoved = existingSeries.filter((sid) => !libraryItem.media.metadata.series.find((se) => se.id === sid)) | ||||
| 
 | ||||
|         if (authorsRemoved.length) { | ||||
|           await apiRouterCtx.checkRemoveAuthorsWithNoBooks(authorsRemoved) | ||||
|         } | ||||
|         if (seriesRemoved.length) { | ||||
|           await apiRouterCtx.checkRemoveEmptySeries(seriesRemoved) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
| @ -149,6 +184,13 @@ class Scanner { | ||||
|     return updatePayload | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * | ||||
|    * @param {import('../objects/LibraryItem')} libraryItem | ||||
|    * @param {*} matchData | ||||
|    * @param {QuickMatchOptions} options | ||||
|    * @returns | ||||
|    */ | ||||
|   async quickMatchBookBuildUpdatePayload(libraryItem, matchData, options) { | ||||
|     // Update media metadata if not set OR overrideDetails flag
 | ||||
|     const detailKeysToUpdate = ['title', 'subtitle', 'description', 'narrator', 'publisher', 'publishedYear', 'genres', 'tags', 'language', 'explicit', 'abridged', 'asin', 'isbn'] | ||||
| @ -307,12 +349,13 @@ class Scanner { | ||||
|   /** | ||||
|    * Quick match library items | ||||
|    * | ||||
|    * @param {import('../routers/ApiRouter')} apiRouterCtx | ||||
|    * @param {import('../models/Library')} library | ||||
|    * @param {import('../objects/LibraryItem')[]} libraryItems | ||||
|    * @param {LibraryScan} libraryScan | ||||
|    * @returns {Promise<boolean>} false if scan canceled | ||||
|    */ | ||||
|   async matchLibraryItemsChunk(library, libraryItems, libraryScan) { | ||||
|   async matchLibraryItemsChunk(apiRouterCtx, library, libraryItems, libraryScan) { | ||||
|     for (let i = 0; i < libraryItems.length; i++) { | ||||
|       const libraryItem = libraryItems[i] | ||||
| 
 | ||||
| @ -327,7 +370,7 @@ class Scanner { | ||||
|       } | ||||
| 
 | ||||
|       Logger.debug(`[Scanner] matchLibraryItems: Quick matching "${libraryItem.media.metadata.title}" (${i + 1} of ${libraryItems.length})`) | ||||
|       const result = await this.quickMatchLibraryItem(libraryItem, { provider: library.provider }) | ||||
|       const result = await this.quickMatchLibraryItem(apiRouterCtx, 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) { | ||||
| @ -346,9 +389,10 @@ class Scanner { | ||||
|   /** | ||||
|    * Quick match all library items for library | ||||
|    * | ||||
|    * @param {import('../routers/ApiRouter')} apiRouterCtx | ||||
|    * @param {import('../models/Library')} library | ||||
|    */ | ||||
|   async matchLibraryItems(library) { | ||||
|   async matchLibraryItems(apiRouterCtx, library) { | ||||
|     if (library.mediaType === 'podcast') { | ||||
|       Logger.error(`[Scanner] matchLibraryItems: Match all not supported for podcasts yet`) | ||||
|       return | ||||
| @ -388,7 +432,7 @@ class Scanner { | ||||
|       hasMoreChunks = libraryItems.length === limit | ||||
|       let oldLibraryItems = libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li)) | ||||
| 
 | ||||
|       const shouldContinue = await this.matchLibraryItemsChunk(library, oldLibraryItems, libraryScan) | ||||
|       const shouldContinue = await this.matchLibraryItemsChunk(apiRouterCtx, library, oldLibraryItems, libraryScan) | ||||
|       if (!shouldContinue) { | ||||
|         isCanceled = true | ||||
|         break | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user