Skip to content

Commit

Permalink
Fix:Quick match not removing empty series/authors #3743
Browse files Browse the repository at this point in the history
  • Loading branch information
advplyr committed Dec 22, 2024
1 parent 7eb315a commit 5fa2630
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 31 deletions.
7 changes: 3 additions & 4 deletions client/components/modals/BatchQuickMatchModel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ export default {
options: {
provider: undefined,
overrideDetails: true,
overrideCover: true,
overrideDefaults: true
overrideCover: true
}
}
},
Expand Down Expand Up @@ -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
}
},
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/LibraryController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
35 changes: 30 additions & 5 deletions server/controllers/LibraryItemController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}
Expand All @@ -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) {
Expand Down
1 change: 0 additions & 1 deletion server/managers/RssFeedManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
84 changes: 64 additions & 20 deletions server/scanner/Scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

const searchISBN = options.isbn || libraryItem.media.metadata.isbn
const searchASIN = options.asin || libraryItem.media.metadata.asin

var results = await BookFinder.search(libraryItem, provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 })
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)) {
Expand All @@ -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)) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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]

Expand All @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 5fa2630

Please sign in to comment.