@@ -105,7 +107,10 @@ export default {
showLocalCovers: false,
previewUpload: null,
selectedFile: null,
- provider: 'google'
+ provider: 'google',
+ currentSearchRequestId: null,
+ searchInProgress: false,
+ socketListenersActive: false
}
},
watch: {
@@ -129,7 +134,7 @@ export default {
},
providers() {
if (this.isPodcast) return this.$store.state.scanners.podcastProviders
- return [{ text: 'All', value: 'all' }, ...this.$store.state.scanners.providers, ...this.$store.state.scanners.coverOnlyProviders]
+ return [{ text: 'Best', value: 'best' }, ...this.$store.state.scanners.providers, ...this.$store.state.scanners.coverOnlyProviders, { text: 'All', value: 'all' }]
},
searchTitleLabel() {
if (this.provider.startsWith('audible')) return this.$strings.LabelSearchTitleOrASIN
@@ -186,6 +191,9 @@ export default {
_file.localPath = `${process.env.serverUrl}/api/items/${this.libraryItemId}/file/${file.ino}?token=${this.userToken}`
return _file
})
+ },
+ socket() {
+ return this.$root.socket
}
},
methods: {
@@ -235,7 +243,19 @@ export default {
this.searchTitle = this.mediaMetadata.title || ''
this.searchAuthor = this.mediaMetadata.authorName || ''
if (this.isPodcast) this.provider = 'itunes'
- else this.provider = localStorage.getItem('book-cover-provider') || localStorage.getItem('book-provider') || 'google'
+ else {
+ // Migrate from 'all' to 'best' (only once)
+ const migrationKey = 'book-cover-provider-migrated'
+ const currentProvider = localStorage.getItem('book-cover-provider') || localStorage.getItem('book-provider') || 'google'
+
+ if (!localStorage.getItem(migrationKey) && currentProvider === 'all') {
+ localStorage.setItem('book-cover-provider', 'best')
+ localStorage.setItem(migrationKey, 'true')
+ this.provider = 'best'
+ } else {
+ this.provider = currentProvider
+ }
+ }
},
removeCover() {
if (!this.coverPath) {
@@ -291,22 +311,116 @@ export default {
console.error('PersistProvider', error)
}
},
+ generateRequestId() {
+ return `cover-search-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
+ },
+ addSocketListeners() {
+ if (!this.socket || this.socketListenersActive) return
+
+ this.socket.on('cover_search_result', this.handleSearchResult)
+ this.socket.on('cover_search_complete', this.handleSearchComplete)
+ this.socket.on('cover_search_error', this.handleSearchError)
+ this.socket.on('cover_search_provider_error', this.handleProviderError)
+ this.socket.on('cover_search_cancelled', this.handleSearchCancelled)
+ this.socket.on('disconnect', this.handleSocketDisconnect)
+ this.socketListenersActive = true
+ },
+ removeSocketListeners() {
+ if (!this.socket || !this.socketListenersActive) return
+
+ this.socket.off('cover_search_result', this.handleSearchResult)
+ this.socket.off('cover_search_complete', this.handleSearchComplete)
+ this.socket.off('cover_search_error', this.handleSearchError)
+ this.socket.off('cover_search_provider_error', this.handleProviderError)
+ this.socket.off('cover_search_cancelled', this.handleSearchCancelled)
+ this.socket.off('disconnect', this.handleSocketDisconnect)
+ this.socketListenersActive = false
+ },
+ handleSearchResult(data) {
+ if (data.requestId !== this.currentSearchRequestId) return
+
+ // Add new covers to the list (avoiding duplicates)
+ const newCovers = data.covers.filter((cover) => !this.coversFound.includes(cover))
+ this.coversFound.push(...newCovers)
+ },
+ handleSearchComplete(data) {
+ if (data.requestId !== this.currentSearchRequestId) return
+
+ this.searchInProgress = false
+ this.currentSearchRequestId = null
+ },
+ handleSearchError(data) {
+ if (data.requestId !== this.currentSearchRequestId) return
+
+ console.error('[Cover Search] Search error:', data.error)
+ this.$toast.error(this.$strings.ToastCoverSearchFailed)
+ this.searchInProgress = false
+ this.currentSearchRequestId = null
+ },
+ handleProviderError(data) {
+ if (data.requestId !== this.currentSearchRequestId) return
+
+ console.warn(`[Cover Search] Provider ${data.provider} failed:`, data.error)
+ },
+ handleSearchCancelled(data) {
+ if (data.requestId !== this.currentSearchRequestId) return
+
+ this.searchInProgress = false
+ this.currentSearchRequestId = null
+ },
+ handleSocketDisconnect() {
+ // If we were in the middle of a search, cancel it (server can't send results anymore)
+ if (this.searchInProgress && this.currentSearchRequestId) {
+ this.searchInProgress = false
+ this.currentSearchRequestId = null
+ }
+ },
+ cancelCurrentSearch() {
+ if (!this.currentSearchRequestId || !this.socket?.connected) {
+ console.error('[Cover Search] Socket not connected')
+ this.$toast.error(this.$strings.ToastConnectionNotAvailable)
+ return
+ }
+
+ this.socket.emit('cancel_cover_search', this.currentSearchRequestId)
+ this.currentSearchRequestId = null
+ this.searchInProgress = false
+ },
async submitSearchForm() {
+ if (!this.socket?.connected) {
+ console.error('[Cover Search] Socket not connected')
+ this.$toast.error(this.$strings.ToastConnectionNotAvailable)
+ return
+ }
+
+ // Cancel any existing search
+ if (this.searchInProgress) {
+ this.cancelCurrentSearch()
+ }
+
// Store provider in local storage
this.persistProvider()
- this.isProcessing = true
- const searchQuery = this.getSearchQuery()
- const results = await this.$axios
- .$get(`/api/search/covers?${searchQuery}`)
- .then((res) => res.results)
- .catch((error) => {
- console.error('Failed', error)
- return []
- })
- this.coversFound = results
- this.isProcessing = false
+ // Setup socket listeners if not already done
+ this.addSocketListeners()
+
+ // Clear previous results
+ this.coversFound = []
this.hasSearched = true
+ this.searchInProgress = true
+
+ // Generate unique request ID
+ const requestId = this.generateRequestId()
+ this.currentSearchRequestId = requestId
+
+ // Emit search request via WebSocket
+ this.socket.emit('search_covers', {
+ requestId,
+ title: this.searchTitle,
+ author: this.searchAuthor || '',
+ provider: this.provider,
+ podcast: this.isPodcast
+ })
},
setCover(coverFile) {
this.isProcessing = true
@@ -320,6 +434,18 @@ export default {
this.isProcessing = false
})
}
+ },
+ mounted() {
+ // Setup socket listeners when component is mounted
+ this.addSocketListeners()
+ },
+ beforeDestroy() {
+ // Cancel any ongoing search when component is destroyed
+ if (this.searchInProgress) {
+ this.cancelCurrentSearch()
+ }
+ // Remove socket listeners
+ this.removeSocketListeners()
}
}
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index 42832e37a..83acb5a69 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -1026,6 +1026,8 @@
"ToastCollectionItemsAddFailed": "Item(s) added to collection failed",
"ToastCollectionRemoveSuccess": "Collection removed",
"ToastCollectionUpdateSuccess": "Collection updated",
+ "ToastConnectionNotAvailable": "Connection not available. Please try again later",
+ "ToastCoverSearchFailed": "Cover search failed",
"ToastCoverUpdateFailed": "Cover update failed",
"ToastDateTimeInvalidOrIncomplete": "Date and time is invalid or incomplete",
"ToastDeleteFileFailed": "Failed to delete file",
diff --git a/server/SocketAuthority.js b/server/SocketAuthority.js
index da31ba4a0..ad55f6605 100644
--- a/server/SocketAuthority.js
+++ b/server/SocketAuthority.js
@@ -2,6 +2,7 @@ const SocketIO = require('socket.io')
const Logger = require('./Logger')
const Database = require('./Database')
const TokenManager = require('./auth/TokenManager')
+const CoverSearchManager = require('./managers/CoverSearchManager')
/**
* @typedef SocketClient
@@ -180,6 +181,10 @@ class SocketAuthority {
// Scanning
socket.on('cancel_scan', (libraryId) => this.cancelScan(libraryId))
+ // Cover search streaming
+ socket.on('search_covers', (payload) => this.handleCoverSearch(socket, payload))
+ socket.on('cancel_cover_search', (requestId) => this.handleCancelCoverSearch(socket, requestId))
+
// Logs
socket.on('set_log_listener', (level) => Logger.addSocketListener(socket, level))
socket.on('remove_log_listener', () => Logger.removeSocketListener(socket.id))
@@ -200,6 +205,10 @@ class SocketAuthority {
const disconnectTime = Date.now() - _client.connected_at
Logger.info(`[SocketAuthority] Socket ${socket.id} disconnected from client "${_client.user.username}" after ${disconnectTime}ms (Reason: ${reason})`)
+
+ // Cancel any active cover searches for this socket
+ this.cancelSocketCoverSearches(socket.id)
+
delete this.clients[socket.id]
}
})
@@ -300,5 +309,100 @@ class SocketAuthority {
Logger.debug('[SocketAuthority] Cancel scan', id)
this.Server.cancelLibraryScan(id)
}
+
+ /**
+ * Handle cover search request via WebSocket
+ * @param {SocketIO.Socket} socket
+ * @param {Object} payload
+ */
+ async handleCoverSearch(socket, payload) {
+ const client = this.clients[socket.id]
+ if (!client?.user) {
+ Logger.error('[SocketAuthority] Unauthorized cover search request')
+ socket.emit('cover_search_error', {
+ requestId: payload.requestId,
+ error: 'Unauthorized'
+ })
+ return
+ }
+
+ const { requestId, title, author, provider, podcast } = payload
+
+ if (!requestId || !title) {
+ Logger.error('[SocketAuthority] Invalid cover search request')
+ socket.emit('cover_search_error', {
+ requestId,
+ error: 'Invalid request parameters'
+ })
+ return
+ }
+
+ Logger.info(`[SocketAuthority] User ${client.user.username} initiated cover search ${requestId}`)
+
+ // Callback for streaming results to client
+ const onResult = (result) => {
+ socket.emit('cover_search_result', {
+ requestId,
+ provider: result.provider,
+ covers: result.covers,
+ total: result.total
+ })
+ }
+
+ // Callback when search completes
+ const onComplete = () => {
+ Logger.info(`[SocketAuthority] Cover search ${requestId} completed`)
+ socket.emit('cover_search_complete', { requestId })
+ }
+
+ // Callback for provider errors
+ const onError = (provider, errorMessage) => {
+ socket.emit('cover_search_provider_error', {
+ requestId,
+ provider,
+ error: errorMessage
+ })
+ }
+
+ // Start the search
+ CoverSearchManager.startSearch(requestId, { title, author, provider, podcast }, onResult, onComplete, onError).catch((error) => {
+ Logger.error(`[SocketAuthority] Cover search ${requestId} failed:`, error)
+ socket.emit('cover_search_error', {
+ requestId,
+ error: error.message
+ })
+ })
+ }
+
+ /**
+ * Handle cancel cover search request
+ * @param {SocketIO.Socket} socket
+ * @param {string} requestId
+ */
+ handleCancelCoverSearch(socket, requestId) {
+ const client = this.clients[socket.id]
+ if (!client?.user) {
+ Logger.error('[SocketAuthority] Unauthorized cancel cover search request')
+ return
+ }
+
+ Logger.info(`[SocketAuthority] User ${client.user.username} cancelled cover search ${requestId}`)
+
+ const cancelled = CoverSearchManager.cancelSearch(requestId)
+ if (cancelled) {
+ socket.emit('cover_search_cancelled', { requestId })
+ }
+ }
+
+ /**
+ * Cancel all cover searches associated with a socket (called on disconnect)
+ * @param {string} socketId
+ */
+ cancelSocketCoverSearches(socketId) {
+ // Get all active search request IDs and cancel those that might belong to this socket
+ // Since we don't track socket-to-request mapping, we log this for debugging
+ // The client will handle reconnection gracefully
+ Logger.debug(`[SocketAuthority] Socket ${socketId} disconnected, any active searches will timeout`)
+ }
}
module.exports = new SocketAuthority()
diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js
index 2d7b57f14..a6a6b07e6 100644
--- a/server/finders/BookFinder.js
+++ b/server/finders/BookFinder.js
@@ -11,7 +11,7 @@ const { levenshteinDistance, levenshteinSimilarity, escapeRegExp, isValidASIN }
const htmlSanitizer = require('../utils/htmlSanitizer')
class BookFinder {
- #providerResponseTimeout = 30000
+ #providerResponseTimeout = 10000
constructor() {
this.openLibrary = new OpenLibrary()
@@ -608,6 +608,14 @@ class BookFinder {
Logger.debug(`[BookFinder] Found ${providerResults.length} covers from ${providerString}`)
searchResults.push(...providerResults)
}
+ } else if (provider === 'best') {
+ // Best providers: google, fantlab, and audible.com
+ const bestProviders = ['google', 'fantlab', 'audible']
+ for (const providerString of bestProviders) {
+ const providerResults = await this.search(null, providerString, title, author, options)
+ Logger.debug(`[BookFinder] Found ${providerResults.length} covers from ${providerString}`)
+ searchResults.push(...providerResults)
+ }
} else {
searchResults = await this.search(null, provider, title, author, options)
}
diff --git a/server/managers/CoverSearchManager.js b/server/managers/CoverSearchManager.js
new file mode 100644
index 000000000..ddcaa23db
--- /dev/null
+++ b/server/managers/CoverSearchManager.js
@@ -0,0 +1,251 @@
+const { setMaxListeners } = require('events')
+const Logger = require('../Logger')
+const BookFinder = require('../finders/BookFinder')
+const PodcastFinder = require('../finders/PodcastFinder')
+
+/**
+ * Manager for handling streaming cover search across multiple providers
+ */
+class CoverSearchManager {
+ constructor() {
+ /** @type {Map} Map of requestId to AbortController */
+ this.activeSearches = new Map()
+
+ // Default timeout for each provider search
+ this.providerTimeout = 10000 // 10 seconds
+
+ // Set to 0 to disable the max listeners limit
+ // We need one listener per provider (15+) and may have multiple concurrent searches
+ this.maxListeners = 0
+ }
+
+ /**
+ * Start a streaming cover search
+ * @param {string} requestId - Unique identifier for this search request
+ * @param {Object} searchParams - Search parameters
+ * @param {string} searchParams.title - Title to search for
+ * @param {string} searchParams.author - Author to search for (optional)
+ * @param {string} searchParams.provider - Provider to search (or 'all')
+ * @param {boolean} searchParams.podcast - Whether this is a podcast search
+ * @param {Function} onResult - Callback for each result chunk
+ * @param {Function} onComplete - Callback when search completes
+ * @param {Function} onError - Callback for errors
+ */
+ async startSearch(requestId, searchParams, onResult, onComplete, onError) {
+ if (this.activeSearches.has(requestId)) {
+ Logger.warn(`[CoverSearchManager] Search with requestId ${requestId} already exists`)
+ return
+ }
+
+ const abortController = new AbortController()
+
+ // Increase max listeners on this signal to accommodate parallel provider searches
+ // AbortSignal is an EventTarget, so we use the events module's setMaxListeners
+ setMaxListeners(this.maxListeners, abortController.signal)
+
+ this.activeSearches.set(requestId, abortController)
+
+ Logger.info(`[CoverSearchManager] Starting search ${requestId} with params:`, searchParams)
+
+ try {
+ const { title, author, provider, podcast } = searchParams
+
+ if (podcast) {
+ await this.searchPodcastCovers(requestId, title, abortController.signal, onResult, onError)
+ } else {
+ await this.searchBookCovers(requestId, provider, title, author, abortController.signal, onResult, onError)
+ }
+
+ if (!abortController.signal.aborted) {
+ onComplete()
+ }
+ } catch (error) {
+ if (error.name === 'AbortError') {
+ Logger.info(`[CoverSearchManager] Search ${requestId} was cancelled`)
+ } else {
+ Logger.error(`[CoverSearchManager] Search ${requestId} failed:`, error)
+ onError(error.message)
+ }
+ } finally {
+ this.activeSearches.delete(requestId)
+ }
+ }
+
+ /**
+ * Cancel an active search
+ * @param {string} requestId - Request ID to cancel
+ */
+ cancelSearch(requestId) {
+ const abortController = this.activeSearches.get(requestId)
+ if (abortController) {
+ Logger.info(`[CoverSearchManager] Cancelling search ${requestId}`)
+ abortController.abort()
+ this.activeSearches.delete(requestId)
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Search for podcast covers
+ */
+ async searchPodcastCovers(requestId, title, signal, onResult, onError) {
+ try {
+ const results = await this.executeWithTimeout(() => PodcastFinder.findCovers(title), this.providerTimeout, signal)
+
+ if (signal.aborted) return
+
+ const covers = this.extractCoversFromResults(results)
+ if (covers.length > 0) {
+ onResult({
+ provider: 'itunes',
+ covers,
+ total: covers.length
+ })
+ }
+ } catch (error) {
+ if (error.name !== 'AbortError') {
+ Logger.error(`[CoverSearchManager] Podcast search failed:`, error)
+ onError('itunes', error.message)
+ }
+ }
+ }
+
+ /**
+ * Search for book covers across providers
+ */
+ async searchBookCovers(requestId, provider, title, author, signal, onResult, onError) {
+ let providers = []
+
+ if (provider === 'all') {
+ providers = [...BookFinder.providers]
+ } else if (provider === 'best') {
+ // Best providers: google, fantlab, and audible.com
+ providers = ['google', 'fantlab', 'audible']
+ } else {
+ providers = [provider]
+ }
+
+ Logger.debug(`[CoverSearchManager] Searching ${providers.length} providers in parallel`)
+
+ // Search all providers in parallel
+ const searchPromises = providers.map(async (providerName) => {
+ if (signal.aborted) return
+
+ try {
+ const searchResults = await this.executeWithTimeout(() => BookFinder.search(null, providerName, title, author || ''), this.providerTimeout, signal)
+
+ if (signal.aborted) return
+
+ const covers = this.extractCoversFromResults(searchResults)
+
+ Logger.debug(`[CoverSearchManager] Found ${covers.length} covers from ${providerName}`)
+
+ if (covers.length > 0) {
+ onResult({
+ provider: providerName,
+ covers,
+ total: covers.length
+ })
+ }
+ } catch (error) {
+ if (error.name !== 'AbortError') {
+ Logger.warn(`[CoverSearchManager] Provider ${providerName} failed:`, error.message)
+ onError(providerName, error.message)
+ }
+ }
+ })
+
+ await Promise.allSettled(searchPromises)
+ }
+
+ /**
+ * Execute a promise with timeout and abort signal
+ */
+ async executeWithTimeout(fn, timeout, signal) {
+ return new Promise(async (resolve, reject) => {
+ let abortHandler = null
+ let timeoutId = null
+
+ // Cleanup function to ensure we always remove listeners
+ const cleanup = () => {
+ if (timeoutId) {
+ clearTimeout(timeoutId)
+ timeoutId = null
+ }
+ if (abortHandler) {
+ signal.removeEventListener('abort', abortHandler)
+ abortHandler = null
+ }
+ }
+
+ // Set up timeout
+ timeoutId = setTimeout(() => {
+ cleanup()
+ const error = new Error('Provider timeout')
+ error.name = 'TimeoutError'
+ reject(error)
+ }, timeout)
+
+ // Check if already aborted
+ if (signal.aborted) {
+ cleanup()
+ const error = new Error('Search cancelled')
+ error.name = 'AbortError'
+ reject(error)
+ return
+ }
+
+ // Set up abort handler
+ abortHandler = () => {
+ cleanup()
+ const error = new Error('Search cancelled')
+ error.name = 'AbortError'
+ reject(error)
+ }
+ signal.addEventListener('abort', abortHandler)
+
+ try {
+ const result = await fn()
+ cleanup()
+ resolve(result)
+ } catch (error) {
+ cleanup()
+ reject(error)
+ }
+ })
+ }
+
+ /**
+ * Extract cover URLs from search results
+ */
+ extractCoversFromResults(results) {
+ const covers = []
+ if (!Array.isArray(results)) return covers
+
+ results.forEach((result) => {
+ if (result.covers && Array.isArray(result.covers)) {
+ covers.push(...result.covers)
+ }
+ if (result.cover) {
+ covers.push(result.cover)
+ }
+ })
+
+ // Remove duplicates
+ return [...new Set(covers)]
+ }
+
+ /**
+ * Cancel all active searches (cleanup on server shutdown)
+ */
+ cancelAllSearches() {
+ Logger.info(`[CoverSearchManager] Cancelling ${this.activeSearches.size} active searches`)
+ for (const [requestId, abortController] of this.activeSearches.entries()) {
+ abortController.abort()
+ }
+ this.activeSearches.clear()
+ }
+}
+
+module.exports = new CoverSearchManager()
diff --git a/server/providers/Audible.js b/server/providers/Audible.js
index 18879e912..2c12ffc1a 100644
--- a/server/providers/Audible.js
+++ b/server/providers/Audible.js
@@ -3,7 +3,7 @@ const Logger = require('../Logger')
const { isValidASIN } = require('../utils/index')
class Audible {
- #responseTimeout = 30000
+ #responseTimeout = 10000
constructor() {
this.regionMap = {
@@ -106,7 +106,7 @@ class Audible {
return res.data
})
.catch((error) => {
- Logger.error('[Audible] ASIN search error', error)
+ Logger.error('[Audible] ASIN search error', error.message)
return null
})
}
@@ -158,7 +158,7 @@ class Audible {
return Promise.all(res.data.products.map((result) => this.asinSearch(result.asin, region, timeout)))
})
.catch((error) => {
- Logger.error('[Audible] query search error', error)
+ Logger.error('[Audible] query search error', error.message)
return []
})
}
diff --git a/server/providers/AudiobookCovers.js b/server/providers/AudiobookCovers.js
index 8e284fea2..9bd1f367c 100644
--- a/server/providers/AudiobookCovers.js
+++ b/server/providers/AudiobookCovers.js
@@ -2,7 +2,7 @@ const axios = require('axios')
const Logger = require('../Logger')
class AudiobookCovers {
- #responseTimeout = 30000
+ #responseTimeout = 10000
constructor() {}
@@ -24,7 +24,7 @@ class AudiobookCovers {
})
.then((res) => res?.data || [])
.catch((error) => {
- Logger.error('[AudiobookCovers] Cover search error', error)
+ Logger.error('[AudiobookCovers] Cover search error', error.message)
return []
})
return items.map((item) => ({ cover: item.versions.png.original }))
diff --git a/server/providers/Audnexus.js b/server/providers/Audnexus.js
index 4f11a2a36..9a76dc861 100644
--- a/server/providers/Audnexus.js
+++ b/server/providers/Audnexus.js
@@ -55,7 +55,7 @@ class Audnexus {
return this._processRequest(this.limiter(() => axios.get(authorRequestUrl)))
.then((res) => res.data || [])
.catch((error) => {
- Logger.error(`[Audnexus] Author ASIN request failed for ${name}`, error)
+ Logger.error(`[Audnexus] Author ASIN request failed for ${name}`, error.message)
return []
})
}
@@ -82,7 +82,7 @@ class Audnexus {
return this._processRequest(this.limiter(() => axios.get(authorRequestUrl.toString())))
.then((res) => res.data)
.catch((error) => {
- Logger.error(`[Audnexus] Author request failed for ${asin}`, error)
+ Logger.error(`[Audnexus] Author request failed for ${asin}`, error.message)
return null
})
}
@@ -158,7 +158,7 @@ class Audnexus {
return this._processRequest(this.limiter(() => axios.get(chaptersRequestUrl.toString())))
.then((res) => res.data)
.catch((error) => {
- Logger.error(`[Audnexus] Chapter ASIN request failed for ${asin}/${region}`, error)
+ Logger.error(`[Audnexus] Chapter ASIN request failed for ${asin}/${region}`, error.message)
return null
})
}
diff --git a/server/providers/CustomProviderAdapter.js b/server/providers/CustomProviderAdapter.js
index a5fed3939..c079a1280 100644
--- a/server/providers/CustomProviderAdapter.js
+++ b/server/providers/CustomProviderAdapter.js
@@ -4,7 +4,7 @@ const Logger = require('../Logger')
const htmlSanitizer = require('../utils/htmlSanitizer')
class CustomProviderAdapter {
- #responseTimeout = 30000
+ #responseTimeout = 10000
constructor() {}
@@ -61,7 +61,7 @@ class CustomProviderAdapter {
return res.data.matches
})
.catch((error) => {
- Logger.error('[CustomMetadataProvider] Search error', error)
+ Logger.error('[CustomMetadataProvider] Search error', error.message)
return []
})
diff --git a/server/providers/FantLab.js b/server/providers/FantLab.js
index f33934ca2..d25be1c80 100644
--- a/server/providers/FantLab.js
+++ b/server/providers/FantLab.js
@@ -2,7 +2,7 @@ const axios = require('axios')
const Logger = require('../Logger')
class FantLab {
- #responseTimeout = 30000
+ #responseTimeout = 10000
// 7 - other
// 11 - essay
// 12 - article
@@ -48,7 +48,7 @@ class FantLab {
return res.data || []
})
.catch((error) => {
- Logger.error('[FantLab] search error', error)
+ Logger.error('[FantLab] search error', error.message)
return []
})
@@ -77,7 +77,7 @@ class FantLab {
return resp.data || null
})
.catch((error) => {
- Logger.error(`[FantLab] work info request for url "${url}" error`, error)
+ Logger.error(`[FantLab] work info request for url "${url}" error`, error.message)
return null
})
@@ -193,7 +193,7 @@ class FantLab {
return resp.data || null
})
.catch((error) => {
- Logger.error(`[FantLab] search cover from edition with url "${url}" error`, error)
+ Logger.error(`[FantLab] search cover from edition with url "${url}" error`, error.message)
return null
})
diff --git a/server/providers/GoogleBooks.js b/server/providers/GoogleBooks.js
index 76a3dceaa..3aec4c849 100644
--- a/server/providers/GoogleBooks.js
+++ b/server/providers/GoogleBooks.js
@@ -2,7 +2,7 @@ const axios = require('axios')
const Logger = require('../Logger')
class GoogleBooks {
- #responseTimeout = 30000
+ #responseTimeout = 10000
constructor() {}
@@ -67,7 +67,7 @@ class GoogleBooks {
return res.data.items
})
.catch((error) => {
- Logger.error('[GoogleBooks] Volume search error', error)
+ Logger.error('[GoogleBooks] Volume search error', error.message)
return []
})
return items.map((item) => this.cleanResult(item))
diff --git a/server/providers/OpenLibrary.js b/server/providers/OpenLibrary.js
index 10d033786..fb73dc294 100644
--- a/server/providers/OpenLibrary.js
+++ b/server/providers/OpenLibrary.js
@@ -1,7 +1,7 @@
const axios = require('axios').default
class OpenLibrary {
- #responseTimeout = 30000
+ #responseTimeout = 10000
constructor() {
this.baseUrl = 'https://openlibrary.org'
@@ -23,7 +23,7 @@ class OpenLibrary {
return res.data
})
.catch((error) => {
- console.error('Failed', error)
+ console.error('Failed', error.message)
return null
})
}
diff --git a/server/providers/iTunes.js b/server/providers/iTunes.js
index 57a47d0d1..87695c58c 100644
--- a/server/providers/iTunes.js
+++ b/server/providers/iTunes.js
@@ -28,7 +28,7 @@ const htmlSanitizer = require('../utils/htmlSanitizer')
*/
class iTunes {
- #responseTimeout = 30000
+ #responseTimeout = 10000
constructor() {}
@@ -63,7 +63,7 @@ class iTunes {
return response.data.results || []
})
.catch((error) => {
- Logger.error(`[iTunes] search request error`, error)
+ Logger.error(`[iTunes] search request error`, error.message)
return []
})
}