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