From ac746f199b1fe51ba33cc70d9efe8bcdc6fea778 Mon Sep 17 00:00:00 2001 From: mikiher Date: Thu, 14 Sep 2023 21:32:20 +0000 Subject: [PATCH 01/39] Fuzzy Matching V1 --- server/finders/BookFinder.js | 128 +++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 22 deletions(-) diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index 6452bff0..307249df 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -60,13 +60,13 @@ class BookFinder { // Remove single quotes (i.e. "Ender's Game" becomes "Enders Game") cleaned = cleaned.replace(/'/g, '') cleaned = this.replaceAccentedChars(cleaned) - return cleaned.toLowerCase() + return cleaned } cleanAuthorForCompares(author) { if (!author) return '' var cleaned = this.replaceAccentedChars(author) - return cleaned.toLowerCase() + return cleaned } filterSearchResults(books, title, author, maxTitleDistance, maxAuthorDistance) { @@ -181,12 +181,113 @@ class BookFinder { return books } + addTitleCandidate(title, candidates) { + // Main variant + const cleanTitle = this.cleanTitleForCompares(title).trim() + if (!cleanTitle) return + candidates.add(cleanTitle) + + let candidate = cleanTitle + + // Remove subtitle + candidate = candidate.replace(/([,:;_]| by ).*/g, "").trim() + if (candidate) + candidates.add(candidate) + + // Remove preceding/trailing numbers + candidate = candidate.replace(/^\d+ | \d+$/g, "").trim() + if (candidate) + candidates.add(candidate) + + // Remove bitrate + candidate = candidate.replace(/(^| )\d+k(bps)?( |$)/, " ").trim() + if (candidate) + candidates.add(candidate) + + // Remove edition + candidate = candidate.replace(/ (2nd|3rd|\d+th)\s+ed(\.|ition)?/, "").trim() + if (candidate) + candidates.add(candidate) + } + async search(provider, title, author, isbn, asin, options = {}) { var books = [] - var maxTitleDistance = !isNaN(options.titleDistance) ? Number(options.titleDistance) : 4 - var maxAuthorDistance = !isNaN(options.authorDistance) ? Number(options.authorDistance) : 4 + const maxTitleDistance = !isNaN(options.titleDistance) ? Number(options.titleDistance) : 4 + const maxAuthorDistance = !isNaN(options.authorDistance) ? Number(options.authorDistance) : 4 + const maxFuzzySearches = 5 + var numFuzzySearches = 0 + + if (!title) + return books + + books = await this.runSearch(title, author, provider, asin, maxTitleDistance, maxAuthorDistance) + + if (!books.length && maxFuzzySearches > 0) { + // normalize title and author + title = title.trim().toLowerCase() + author = author.trim().toLowerCase() + + // Now run up to maxFuzzySearches fuzzy searches + var candidates = new Set() + var cleanedAuthor = this.cleanAuthorForCompares(author) + this.addTitleCandidate(title, candidates) + + // remove parentheses and their contents, and replace with a separator + const cleanTitle = title.replace(/\[.*?\]|\(.*?\)|{.*?}/g, " - ") + // Split title into hypen-separated parts + const titleParts = cleanTitle.split(/ - | -|- /) + for (const titlePart of titleParts) { + this.addTitleCandidate(titlePart, candidates) + } + // We already searched for original title + if (author == cleanedAuthor) candidates.delete(title) + if (candidates.size > 0) { + candidates = [...candidates] + candidates.sort((a, b) => { + // Candidates that include the author are likely low quality + const includesAuthorDiff = !b.includes(cleanedAuthor) - !a.includes(cleanedAuthor) + if (includesAuthorDiff) return includesAuthorDiff + // Candidates that include only digits are also likely low quality + const onlyDigits = /^\d+$/ + const includesOnlyDigitsDiff = !onlyDigits.test(b) - !onlyDigits.test(a) + if (includesOnlyDigitsDiff) return includesOnlyDigitsDiff + // Start with longer candidaets, as they are likely more specific + const lengthDiff = b.length - a.length + if (lengthDiff) return lengthDiff + return b.localeCompare(a) + }) + Logger.debug(`[BookFinder] Found ${candidates.length} fuzzy title candidates`) + Logger.debug(candidates) + for (const candidate of candidates) { + if (++numFuzzySearches > maxFuzzySearches) return books + books = await this.runSearch(candidate, cleanedAuthor, provider, asin, maxTitleDistance, maxAuthorDistance) + if (books.length) break + } + if (!books.length) { + // Now try searching without the author + for (const candidate of candidates) { + if (++numFuzzySearches > maxFuzzySearches) return books + books = await this.runSearch(candidate, '', provider, asin, maxTitleDistance, maxAuthorDistance) + if (books.length) break + } + } + } + } + + if (provider === 'openlibrary') { + books.sort((a, b) => { + return a.totalDistance - b.totalDistance + }) + } + + return books + } + + async runSearch(title, author, provider, asin, maxTitleDistance, maxAuthorDistance) { Logger.debug(`Book Search: title: "${title}", author: "${author || ''}", provider: ${provider}`) + var books = [] + if (provider === 'google') { books = await this.getGoogleBooksResults(title, author) } else if (provider.startsWith('audible')) { @@ -203,23 +304,6 @@ class BookFinder { else { books = await this.getGoogleBooksResults(title, author) } - - if (!books.length && !options.currentlyTryingCleaned) { - var cleanedTitle = this.cleanTitleForCompares(title) - var cleanedAuthor = this.cleanAuthorForCompares(author) - if (cleanedTitle == title && cleanedAuthor == author) return books - - Logger.debug(`Book Search, no matches.. checking cleaned title and author`) - options.currentlyTryingCleaned = true - return this.search(provider, cleanedTitle, cleanedAuthor, isbn, asin, options) - } - - if (provider === 'openlibrary') { - books.sort((a, b) => { - return a.totalDistance - b.totalDistance - }) - } - return books } @@ -253,4 +337,4 @@ class BookFinder { return this.audnexus.getChaptersByASIN(asin, region) } } -module.exports = new BookFinder() \ No newline at end of file +module.exports = new BookFinder() From b1524d245e193d4803215145c7fac1dad673a823 Mon Sep 17 00:00:00 2001 From: Selfhost Alt Date: Thu, 14 Sep 2023 22:52:43 -0700 Subject: [PATCH 02/39] Add ability to enable DEV logs of Sqlite queries --- server/Database.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/server/Database.js b/server/Database.js index a959d0a9..09f6dfc7 100644 --- a/server/Database.js +++ b/server/Database.js @@ -166,10 +166,25 @@ class Database { */ async connect() { Logger.info(`[Database] Initializing db at "${this.dbPath}"`) + + let logging = false; + let benchmark = false; + if (process.env.QUERY_LOGGING === "log") { + // Setting QUERY_LOGGING=log will log all Sequelize queries before they run + Logger.info(`[Database] Query logging enabled"`) + logging = (query) => Logger.dev(`Running the following query: ${query}`) + } else if (process.env.QUERY_LOGGING === "benchmark") { + // Setting QUERY_LOGGING=benchmark will log all Sequelize queries and their execution times, after they run + Logger.info(`[Database] Query benchmarking enabled"`) + logging = (query, time) => Logger.dev(`Ran the following query in ${time}ms: ${query}`) + benchmark = true; + } + this.sequelize = new Sequelize({ dialect: 'sqlite', storage: this.dbPath, - logging: false, + logging: logging, + benchmark: benchmark, transactionType: 'IMMEDIATE' }) From 71762ef8371c45b8cb9a61f09c12ea531743c632 Mon Sep 17 00:00:00 2001 From: Selfhost Alt Date: Thu, 14 Sep 2023 23:01:40 -0700 Subject: [PATCH 03/39] Newline before printing query --- server/Database.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/Database.js b/server/Database.js index 09f6dfc7..1d7ae9ab 100644 --- a/server/Database.js +++ b/server/Database.js @@ -172,11 +172,11 @@ class Database { if (process.env.QUERY_LOGGING === "log") { // Setting QUERY_LOGGING=log will log all Sequelize queries before they run Logger.info(`[Database] Query logging enabled"`) - logging = (query) => Logger.dev(`Running the following query: ${query}`) + logging = (query) => Logger.dev(`Running the following query:\n ${query}`) } else if (process.env.QUERY_LOGGING === "benchmark") { // Setting QUERY_LOGGING=benchmark will log all Sequelize queries and their execution times, after they run Logger.info(`[Database] Query benchmarking enabled"`) - logging = (query, time) => Logger.dev(`Ran the following query in ${time}ms: ${query}`) + logging = (query, time) => Logger.dev(`Ran the following query in ${time}ms:\n ${query}`) benchmark = true; } From b668c6e37a2bf62784a7ba6ca17c15ab3511104a Mon Sep 17 00:00:00 2001 From: Selfhost Alt Date: Thu, 14 Sep 2023 23:04:47 -0700 Subject: [PATCH 04/39] Remove stray quote --- server/Database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/Database.js b/server/Database.js index 1d7ae9ab..82cda3fc 100644 --- a/server/Database.js +++ b/server/Database.js @@ -171,7 +171,7 @@ class Database { let benchmark = false; if (process.env.QUERY_LOGGING === "log") { // Setting QUERY_LOGGING=log will log all Sequelize queries before they run - Logger.info(`[Database] Query logging enabled"`) + Logger.info(`[Database] Query logging enabled`) logging = (query) => Logger.dev(`Running the following query:\n ${query}`) } else if (process.env.QUERY_LOGGING === "benchmark") { // Setting QUERY_LOGGING=benchmark will log all Sequelize queries and their execution times, after they run From 67bbe2151383803cc762991574c640cbefafd8f4 Mon Sep 17 00:00:00 2001 From: mikiher Date: Fri, 15 Sep 2023 09:24:19 +0000 Subject: [PATCH 05/39] Make quick-match more conservative --- server/finders/BookFinder.js | 2 +- server/scanner/Scanner.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index 307249df..18865f2b 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -214,7 +214,7 @@ class BookFinder { var books = [] const maxTitleDistance = !isNaN(options.titleDistance) ? Number(options.titleDistance) : 4 const maxAuthorDistance = !isNaN(options.authorDistance) ? Number(options.authorDistance) : 4 - const maxFuzzySearches = 5 + const maxFuzzySearches = !isNaN(options.maxFuzzySearches) ? Number(options.maxFuzzySearches) : 5 var numFuzzySearches = 0 if (!title) diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index ee1fcc37..d2037f8b 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -36,7 +36,7 @@ class Scanner { var searchISBN = options.isbn || libraryItem.media.metadata.isbn var searchASIN = options.asin || libraryItem.media.metadata.asin - var results = await BookFinder.search(provider, searchTitle, searchAuthor, searchISBN, searchASIN) + var results = await BookFinder.search(provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 }) if (!results.length) { return { warning: `No ${provider} match found` From 9382055bf223688ee7f327f0529329cc29fd6363 Mon Sep 17 00:00:00 2001 From: Hallo951 <40667862+Hallo951@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:39:46 +0200 Subject: [PATCH 06/39] Update de.json --- client/strings/de.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/strings/de.json b/client/strings/de.json index 4d1ca73e..d60ede89 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -398,9 +398,9 @@ "LabelSettingsDisableWatcher": "Überwachung deaktivieren", "LabelSettingsDisableWatcherForLibrary": "Ordnerüberwachung für die Bibliothek deaktivieren", "LabelSettingsDisableWatcherHelp": "Deaktiviert das automatische Hinzufügen/Aktualisieren von Elementen, wenn Dateiänderungen erkannt werden. *Erfordert einen Server-Neustart", - "LabelSettingsEnableWatcher": "Enable Watcher", - "LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library", - "LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart", + "LabelSettingsEnableWatcher": "Überwachung aktivieren", + "LabelSettingsEnableWatcherForLibrary": "Ordnerüberwachung für die Bibliothek aktivieren", + "LabelSettingsEnableWatcherHelp": "Aktiviert das automatische Hinzufügen/Aktualisieren von Elementen, wenn Dateiänderungen erkannt werden. *Erfordert einen Server-Neustart", "LabelSettingsExperimentalFeatures": "Experimentelle Funktionen", "LabelSettingsExperimentalFeaturesHelp": "Funktionen welche sich in der Entwicklung befinden, benötigen Ihr Feedback und Ihre Hilfe beim Testen. Klicken Sie hier, um die Github-Diskussion zu öffnen.", "LabelSettingsFindCovers": "Suche Titelbilder", From 9519f6418da8fc07ae42ef1477c604cb181e5539 Mon Sep 17 00:00:00 2001 From: mfcar Date: Tue, 19 Sep 2023 22:37:57 +0100 Subject: [PATCH 07/39] Now, whenever someone requests a backup file, it will automatically suggest a default file name for the downloaded file. --- server/controllers/BackupController.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/controllers/BackupController.js b/server/controllers/BackupController.js index f33624ac..c578c63f 100644 --- a/server/controllers/BackupController.js +++ b/server/controllers/BackupController.js @@ -42,6 +42,9 @@ class BackupController { Logger.debug(`Use X-Accel to serve static file ${encodedURI}`) return res.status(204).header({ 'X-Accel-Redirect': encodedURI }).send() } + + res.setHeader('Content-disposition', 'attachment; filename=' + req.backup.filename) + res.sendFile(req.backup.fullPath) } @@ -65,4 +68,4 @@ class BackupController { next() } } -module.exports = new BackupController() \ No newline at end of file +module.exports = new BackupController() From 6eab985b1ef09ee5dbb256473db0810455a1d577 Mon Sep 17 00:00:00 2001 From: mikiher Date: Wed, 20 Sep 2023 11:25:21 +0000 Subject: [PATCH 08/39] Use encodeURIComponent for text inputs --- client/components/modals/item/tabs/Match.vue | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/components/modals/item/tabs/Match.vue b/client/components/modals/item/tabs/Match.vue index 58c52df8..0c5d67eb 100644 --- a/client/components/modals/item/tabs/Match.vue +++ b/client/components/modals/item/tabs/Match.vue @@ -205,7 +205,7 @@ export default { processing: Boolean, libraryItem: { type: Object, - default: () => {} + default: () => { } } }, data() { @@ -305,7 +305,7 @@ export default { const filterData = this.$store.state.libraries.filterData || {} const currentGenres = filterData.genres || [] const selectedMatchGenres = this.selectedMatch.genres || [] - return [...new Set([...currentGenres ,...selectedMatchGenres])] + return [...new Set([...currentGenres, ...selectedMatchGenres])] } }, methods: { @@ -325,9 +325,9 @@ export default { } }, getSearchQuery() { - if (this.isPodcast) return `term=${this.searchTitle}` - var searchQuery = `provider=${this.provider}&fallbackTitleOnly=1&title=${this.searchTitle}` - if (this.searchAuthor) searchQuery += `&author=${this.searchAuthor}` + if (this.isPodcast) return `term=${encodeURIComponent(this.searchTitle)}` + var searchQuery = `provider=${this.provider}&fallbackTitleOnly=1&title=${encodeURIComponent(this.searchTitle)}` + if (this.searchAuthor) searchQuery += `&author=${encodeURIComponent(this.searchAuthor)}` return searchQuery }, submitSearch() { @@ -580,6 +580,7 @@ export default { .matchListWrapper { height: calc(100% - 124px); } + @media (min-width: 768px) { .matchListWrapper { height: calc(100% - 80px); From bfa87a2131edcde4da7a3f92a8621fd5fb190616 Mon Sep 17 00:00:00 2001 From: mfcar Date: Wed, 20 Sep 2023 22:33:58 +0100 Subject: [PATCH 09/39] Add a way to see the backup location --- client/pages/config/backups.vue | 17 +++++++++++++++++ client/strings/en-us.json | 3 ++- server/controllers/BackupController.js | 6 ++++++ server/managers/BackupManager.js | 4 ++++ server/routers/ApiRouter.js | 1 + 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/client/pages/config/backups.vue b/client/pages/config/backups.vue index ba6ff2be..e02575b0 100644 --- a/client/pages/config/backups.vue +++ b/client/pages/config/backups.vue @@ -134,6 +134,23 @@ export default { this.enableBackups = !!this.newServerSettings.backupSchedule this.maxBackupSize = this.newServerSettings.maxBackupSize || 1 this.cronExpression = this.newServerSettings.backupSchedule || '30 1 * * *' + + this.loadBackupLocation() + }, + loadBackupLocation() { + this.processing = true + this.$axios + .$get('/api/backups/location') + .then((data) => { + this.backupLocation = data.backupLocation + }) + .catch((error) => { + console.error('Failed to load backup location', error) + this.$toast.error('Failed to load backup location') + }) + .finally(() => { + this.processing = false + }) } }, mounted() { diff --git a/client/strings/en-us.json b/client/strings/en-us.json index 75606da2..1bbecb48 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -188,6 +188,7 @@ "LabelBackToUser": "Back to User", "LabelBackupsEnableAutomaticBackups": "Enable automatic backups", "LabelBackupsEnableAutomaticBackupsHelp": "Backups saved in /metadata/backups", + "LabelBackupLocation": "Backup Location", "LabelBackupsMaxBackupSize": "Maximum backup size (in GB)", "LabelBackupsMaxBackupSizeHelp": "As a safeguard against misconfiguration, backups will fail if they exceed the configured size.", "LabelBackupsNumberToKeep": "Number of backups to keep", @@ -711,4 +712,4 @@ "ToastSocketFailedToConnect": "Socket failed to connect", "ToastUserDeleteFailed": "Failed to delete user", "ToastUserDeleteSuccess": "User deleted" -} \ No newline at end of file +} diff --git a/server/controllers/BackupController.js b/server/controllers/BackupController.js index c578c63f..d4e818b6 100644 --- a/server/controllers/BackupController.js +++ b/server/controllers/BackupController.js @@ -10,6 +10,12 @@ class BackupController { }) } + getBackupLocation(req, res) { + res.json({ + backupLocation: this.backupManager.backupLocation + }) + } + create(req, res) { this.backupManager.requestCreateBackup(res) } diff --git a/server/managers/BackupManager.js b/server/managers/BackupManager.js index e19b1db6..50d6d19c 100644 --- a/server/managers/BackupManager.js +++ b/server/managers/BackupManager.js @@ -26,6 +26,10 @@ class BackupManager { this.backups = [] } + get backupLocation() { + return this.BackupPath + } + get backupSchedule() { return global.ServerSettings.backupSchedule } diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 71d9429e..2346f114 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -187,6 +187,7 @@ class ApiRouter { this.router.get('/backups/:id/download', BackupController.middleware.bind(this), BackupController.download.bind(this)) this.router.get('/backups/:id/apply', BackupController.middleware.bind(this), BackupController.apply.bind(this)) this.router.post('/backups/upload', BackupController.middleware.bind(this), BackupController.upload.bind(this)) + this.router.get('/backups/location', BackupController.middleware.bind(this), BackupController.getBackupLocation.bind(this)) // // File System Routes From 944f5950caac93743444460810e3ff177c7ee622 Mon Sep 17 00:00:00 2001 From: mfcar Date: Wed, 20 Sep 2023 22:36:30 +0100 Subject: [PATCH 10/39] File missing --- client/pages/config/backups.vue | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/client/pages/config/backups.vue b/client/pages/config/backups.vue index e02575b0..50114d34 100644 --- a/client/pages/config/backups.vue +++ b/client/pages/config/backups.vue @@ -18,13 +18,21 @@ edit -
+
event
{{ $strings.LabelNextBackupDate }}:
{{ nextBackupDate }}
+ +
+ folder +
+ {{ $strings.LabelBackupLocation }}: +
+
{{ backupLocation }}
+
@@ -65,7 +73,8 @@ export default { maxBackupSize: 1, cronExpression: '', newServerSettings: {}, - showCronBuilder: false + showCronBuilder: false, + backupLocation: '' } }, watch: { From 5e976c08afcd320914970aa1d3fafdc86be851b0 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 21 Sep 2023 16:57:48 -0500 Subject: [PATCH 11/39] Update cover API endpoint to only load necessary data from DB #2073 --- server/controllers/LibraryItemController.js | 44 +++++++++++++++++---- server/managers/CacheManager.js | 10 ++--- server/objects/user/User.js | 12 ++++++ server/routers/ApiRouter.js | 2 +- 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index fa6d5f2f..498398f7 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -225,15 +225,45 @@ class LibraryItemController { res.sendStatus(200) } - // GET api/items/:id/cover + /** + * GET: api/items/:id/cover + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ async getCover(req, res) { - const { query: { width, height, format, raw }, libraryItem } = req + const { query: { width, height, format, raw } } = req + + const libraryItem = await Database.libraryItemModel.findByPk(req.params.id, { + attributes: ['id', 'mediaType', 'mediaId', 'libraryId'], + include: [ + { + model: Database.bookModel, + attributes: ['id', 'coverPath', 'tags', 'explicit'] + }, + { + model: Database.podcastModel, + attributes: ['id', 'coverPath', 'tags', 'explicit'] + } + ] + }) + if (!libraryItem) { + Logger.warn(`[LibraryItemController] getCover: Library item "${req.params.id}" does not exist`) + return res.sendStatus(404) + } + + // Check if user can access this library item + if (!req.user.checkCanAccessLibraryItemWithData(libraryItem.libraryId, libraryItem.media.explicit, libraryItem.media.tags)) { + return res.sendStatus(403) + } + + // Check if library item media has a cover path + if (!libraryItem.media.coverPath || !await fs.pathExists(libraryItem.media.coverPath)) { + Logger.debug(`[LibraryItemController] getCover: Library item "${req.params.id}" has no cover path`) + return res.sendStatus(404) + } if (raw) { // any value - if (!libraryItem.media.coverPath || !await fs.pathExists(libraryItem.media.coverPath)) { - return res.sendStatus(404) - } - if (global.XAccel) { const encodedURI = encodeUriPath(global.XAccel + libraryItem.media.coverPath) Logger.debug(`Use X-Accel to serve static file ${encodedURI}`) @@ -247,7 +277,7 @@ class LibraryItemController { height: height ? parseInt(height) : null, width: width ? parseInt(width) : null } - return CacheManager.handleCoverCache(res, libraryItem, options) + return CacheManager.handleCoverCache(res, libraryItem.id, libraryItem.media.coverPath, options) } // GET: api/items/:id/stream diff --git a/server/managers/CacheManager.js b/server/managers/CacheManager.js index d7baee3b..c273d389 100644 --- a/server/managers/CacheManager.js +++ b/server/managers/CacheManager.js @@ -39,14 +39,14 @@ class CacheManager { } } - async handleCoverCache(res, libraryItem, options = {}) { + async handleCoverCache(res, libraryItemId, coverPath, options = {}) { const format = options.format || 'webp' const width = options.width || 400 const height = options.height || null res.type(`image/${format}`) - const path = Path.join(this.CoverCachePath, `${libraryItem.id}_${width}${height ? `x${height}` : ''}`) + '.' + format + const path = Path.join(this.CoverCachePath, `${libraryItemId}_${width}${height ? `x${height}` : ''}`) + '.' + format // Cache exists if (await fs.pathExists(path)) { @@ -67,11 +67,7 @@ class CacheManager { return ps.pipe(res) } - if (!libraryItem.media.coverPath || !await fs.pathExists(libraryItem.media.coverPath)) { - return res.sendStatus(500) - } - - const writtenFile = await resizeImage(libraryItem.media.coverPath, path, width, height) + const writtenFile = await resizeImage(coverPath, path, width, height) if (!writtenFile) return res.sendStatus(500) if (global.XAccel) { diff --git a/server/objects/user/User.js b/server/objects/user/User.js index 4bfcb105..1ed74bb2 100644 --- a/server/objects/user/User.js +++ b/server/objects/user/User.js @@ -326,6 +326,18 @@ class User { return this.checkCanAccessLibraryItemWithTags(libraryItem.media.tags) } + /** + * Checks if a user can access a library item + * @param {string} libraryId + * @param {boolean} explicit + * @param {string[]} tags + */ + checkCanAccessLibraryItemWithData(libraryId, explicit, tags) { + if (!this.checkCanAccessLibrary(libraryId)) return false + if (explicit && !this.canAccessExplicitContent) return false + return this.checkCanAccessLibraryItemWithTags(tags) + } + findBookmark(libraryItemId, time) { return this.bookmarks.find(bm => bm.libraryItemId === libraryItemId && bm.time == time) } diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 71d9429e..74d8aa56 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -99,7 +99,7 @@ class ApiRouter { this.router.delete('/items/:id', LibraryItemController.middleware.bind(this), LibraryItemController.delete.bind(this)) this.router.get('/items/:id/download', LibraryItemController.middleware.bind(this), LibraryItemController.download.bind(this)) this.router.patch('/items/:id/media', LibraryItemController.middleware.bind(this), LibraryItemController.updateMedia.bind(this)) - this.router.get('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.getCover.bind(this)) + this.router.get('/items/:id/cover', LibraryItemController.getCover.bind(this)) this.router.post('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.uploadCover.bind(this)) this.router.patch('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.updateCover.bind(this)) this.router.delete('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.removeCover.bind(this)) From 452d59dcf6fca4586a1b298e36628b7896a826c8 Mon Sep 17 00:00:00 2001 From: Jon Erling Hustadnes Date: Fri, 22 Sep 2023 12:31:38 +0200 Subject: [PATCH 12/39] copy of en-us.json to no.json --- client/strings/no.json | 714 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 714 insertions(+) create mode 100644 client/strings/no.json diff --git a/client/strings/no.json b/client/strings/no.json new file mode 100644 index 00000000..75606da2 --- /dev/null +++ b/client/strings/no.json @@ -0,0 +1,714 @@ +{ + "ButtonAdd": "Add", + "ButtonAddChapters": "Add Chapters", + "ButtonAddPodcasts": "Add Podcasts", + "ButtonAddYourFirstLibrary": "Add your first library", + "ButtonApply": "Apply", + "ButtonApplyChapters": "Apply Chapters", + "ButtonAuthors": "Authors", + "ButtonBrowseForFolder": "Browse for Folder", + "ButtonCancel": "Cancel", + "ButtonCancelEncode": "Cancel Encode", + "ButtonChangeRootPassword": "Change Root Password", + "ButtonCheckAndDownloadNewEpisodes": "Check & Download New Episodes", + "ButtonChooseAFolder": "Choose a folder", + "ButtonChooseFiles": "Choose files", + "ButtonClearFilter": "Clear Filter", + "ButtonCloseFeed": "Close Feed", + "ButtonCollections": "Collections", + "ButtonConfigureScanner": "Configure Scanner", + "ButtonCreate": "Create", + "ButtonCreateBackup": "Create Backup", + "ButtonDelete": "Delete", + "ButtonDownloadQueue": "Queue", + "ButtonEdit": "Edit", + "ButtonEditChapters": "Edit Chapters", + "ButtonEditPodcast": "Edit Podcast", + "ButtonForceReScan": "Force Re-Scan", + "ButtonFullPath": "Full Path", + "ButtonHide": "Hide", + "ButtonHome": "Home", + "ButtonIssues": "Issues", + "ButtonLatest": "Latest", + "ButtonLibrary": "Library", + "ButtonLogout": "Logout", + "ButtonLookup": "Lookup", + "ButtonManageTracks": "Manage Tracks", + "ButtonMapChapterTitles": "Map Chapter Titles", + "ButtonMatchAllAuthors": "Match All Authors", + "ButtonMatchBooks": "Match Books", + "ButtonNevermind": "Nevermind", + "ButtonOk": "Ok", + "ButtonOpenFeed": "Open Feed", + "ButtonOpenManager": "Open Manager", + "ButtonPlay": "Play", + "ButtonPlaying": "Playing", + "ButtonPlaylists": "Playlists", + "ButtonPurgeAllCache": "Purge All Cache", + "ButtonPurgeItemsCache": "Purge Items Cache", + "ButtonPurgeMediaProgress": "Purge Media Progress", + "ButtonQueueAddItem": "Add to queue", + "ButtonQueueRemoveItem": "Remove from queue", + "ButtonQuickMatch": "Quick Match", + "ButtonRead": "Read", + "ButtonRemove": "Remove", + "ButtonRemoveAll": "Remove All", + "ButtonRemoveAllLibraryItems": "Remove All Library Items", + "ButtonRemoveFromContinueListening": "Remove from Continue Listening", + "ButtonRemoveFromContinueReading": "Remove from Continue Reading", + "ButtonRemoveSeriesFromContinueSeries": "Remove Series from Continue Series", + "ButtonReScan": "Re-Scan", + "ButtonReset": "Reset", + "ButtonRestore": "Restore", + "ButtonSave": "Save", + "ButtonSaveAndClose": "Save & Close", + "ButtonSaveTracklist": "Save Tracklist", + "ButtonScan": "Scan", + "ButtonScanLibrary": "Scan Library", + "ButtonSearch": "Search", + "ButtonSelectFolderPath": "Select Folder Path", + "ButtonSeries": "Series", + "ButtonSetChaptersFromTracks": "Set chapters from tracks", + "ButtonShiftTimes": "Shift Times", + "ButtonShow": "Show", + "ButtonStartM4BEncode": "Start M4B Encode", + "ButtonStartMetadataEmbed": "Start Metadata Embed", + "ButtonSubmit": "Submit", + "ButtonTest": "Test", + "ButtonUpload": "Upload", + "ButtonUploadBackup": "Upload Backup", + "ButtonUploadCover": "Upload Cover", + "ButtonUploadOPMLFile": "Upload OPML File", + "ButtonUserDelete": "Delete user {0}", + "ButtonUserEdit": "Edit user {0}", + "ButtonViewAll": "View All", + "ButtonYes": "Yes", + "HeaderAccount": "Account", + "HeaderAdvanced": "Advanced", + "HeaderAppriseNotificationSettings": "Apprise Notification Settings", + "HeaderAudiobookTools": "Audiobook File Management Tools", + "HeaderAudioTracks": "Audio Tracks", + "HeaderBackups": "Backups", + "HeaderChangePassword": "Change Password", + "HeaderChapters": "Chapters", + "HeaderChooseAFolder": "Choose a Folder", + "HeaderCollection": "Collection", + "HeaderCollectionItems": "Collection Items", + "HeaderCover": "Cover", + "HeaderCurrentDownloads": "Current Downloads", + "HeaderDetails": "Details", + "HeaderDownloadQueue": "Download Queue", + "HeaderEbookFiles": "Ebook Files", + "HeaderEmail": "Email", + "HeaderEmailSettings": "Email Settings", + "HeaderEpisodes": "Episodes", + "HeaderEreaderDevices": "Ereader Devices", + "HeaderEreaderSettings": "Ereader Settings", + "HeaderFiles": "Files", + "HeaderFindChapters": "Find Chapters", + "HeaderIgnoredFiles": "Ignored Files", + "HeaderItemFiles": "Item Files", + "HeaderItemMetadataUtils": "Item Metadata Utils", + "HeaderLastListeningSession": "Last Listening Session", + "HeaderLatestEpisodes": "Latest episodes", + "HeaderLibraries": "Libraries", + "HeaderLibraryFiles": "Library Files", + "HeaderLibraryStats": "Library Stats", + "HeaderListeningSessions": "Listening Sessions", + "HeaderListeningStats": "Listening Stats", + "HeaderLogin": "Login", + "HeaderLogs": "Logs", + "HeaderManageGenres": "Manage Genres", + "HeaderManageTags": "Manage Tags", + "HeaderMapDetails": "Map details", + "HeaderMatch": "Match", + "HeaderMetadataToEmbed": "Metadata to embed", + "HeaderNewAccount": "New Account", + "HeaderNewLibrary": "New Library", + "HeaderNotifications": "Notifications", + "HeaderOpenRSSFeed": "Open RSS Feed", + "HeaderOtherFiles": "Other Files", + "HeaderPermissions": "Permissions", + "HeaderPlayerQueue": "Player Queue", + "HeaderPlaylist": "Playlist", + "HeaderPlaylistItems": "Playlist Items", + "HeaderPodcastsToAdd": "Podcasts to Add", + "HeaderPreviewCover": "Preview Cover", + "HeaderRemoveEpisode": "Remove Episode", + "HeaderRemoveEpisodes": "Remove {0} Episodes", + "HeaderRSSFeedGeneral": "RSS Details", + "HeaderRSSFeedIsOpen": "RSS Feed is Open", + "HeaderRSSFeeds": "RSS Feeds", + "HeaderSavedMediaProgress": "Saved Media Progress", + "HeaderSchedule": "Schedule", + "HeaderScheduleLibraryScans": "Schedule Automatic Library Scans", + "HeaderSession": "Session", + "HeaderSetBackupSchedule": "Set Backup Schedule", + "HeaderSettings": "Settings", + "HeaderSettingsDisplay": "Display", + "HeaderSettingsExperimental": "Experimental Features", + "HeaderSettingsGeneral": "General", + "HeaderSettingsScanner": "Scanner", + "HeaderSleepTimer": "Sleep Timer", + "HeaderStatsLargestItems": "Largest Items", + "HeaderStatsLongestItems": "Longest Items (hrs)", + "HeaderStatsMinutesListeningChart": "Minutes Listening (last 7 days)", + "HeaderStatsRecentSessions": "Recent Sessions", + "HeaderStatsTop10Authors": "Top 10 Authors", + "HeaderStatsTop5Genres": "Top 5 Genres", + "HeaderTableOfContents": "Table of Contents", + "HeaderTools": "Tools", + "HeaderUpdateAccount": "Update Account", + "HeaderUpdateAuthor": "Update Author", + "HeaderUpdateDetails": "Update Details", + "HeaderUpdateLibrary": "Update Library", + "HeaderUsers": "Users", + "HeaderYourStats": "Your Stats", + "LabelAbridged": "Abridged", + "LabelAccountType": "Account Type", + "LabelAccountTypeAdmin": "Admin", + "LabelAccountTypeGuest": "Guest", + "LabelAccountTypeUser": "User", + "LabelActivity": "Activity", + "LabelAdded": "Added", + "LabelAddedAt": "Added At", + "LabelAddToCollection": "Add to Collection", + "LabelAddToCollectionBatch": "Add {0} Books to Collection", + "LabelAddToPlaylist": "Add to Playlist", + "LabelAddToPlaylistBatch": "Add {0} Items to Playlist", + "LabelAll": "All", + "LabelAllUsers": "All Users", + "LabelAlreadyInYourLibrary": "Already in your library", + "LabelAppend": "Append", + "LabelAuthor": "Author", + "LabelAuthorFirstLast": "Author (First Last)", + "LabelAuthorLastFirst": "Author (Last, First)", + "LabelAuthors": "Authors", + "LabelAutoDownloadEpisodes": "Auto Download Episodes", + "LabelBackToUser": "Back to User", + "LabelBackupsEnableAutomaticBackups": "Enable automatic backups", + "LabelBackupsEnableAutomaticBackupsHelp": "Backups saved in /metadata/backups", + "LabelBackupsMaxBackupSize": "Maximum backup size (in GB)", + "LabelBackupsMaxBackupSizeHelp": "As a safeguard against misconfiguration, backups will fail if they exceed the configured size.", + "LabelBackupsNumberToKeep": "Number of backups to keep", + "LabelBackupsNumberToKeepHelp": "Only 1 backup will be removed at a time so if you already have more backups than this you should manually remove them.", + "LabelBitrate": "Bitrate", + "LabelBooks": "Books", + "LabelChangePassword": "Change Password", + "LabelChannels": "Channels", + "LabelChapters": "Chapters", + "LabelChaptersFound": "chapters found", + "LabelChapterTitle": "Chapter Title", + "LabelClosePlayer": "Close player", + "LabelCodec": "Codec", + "LabelCollapseSeries": "Collapse Series", + "LabelCollection": "Collection", + "LabelCollections": "Collections", + "LabelComplete": "Complete", + "LabelConfirmPassword": "Confirm Password", + "LabelContinueListening": "Continue Listening", + "LabelContinueReading": "Continue Reading", + "LabelContinueSeries": "Continue Series", + "LabelCover": "Cover", + "LabelCoverImageURL": "Cover Image URL", + "LabelCreatedAt": "Created At", + "LabelCronExpression": "Cron Expression", + "LabelCurrent": "Current", + "LabelCurrently": "Currently:", + "LabelCustomCronExpression": "Custom Cron Expression:", + "LabelDatetime": "Datetime", + "LabelDescription": "Description", + "LabelDeselectAll": "Deselect All", + "LabelDevice": "Device", + "LabelDeviceInfo": "Device Info", + "LabelDirectory": "Directory", + "LabelDiscFromFilename": "Disc from Filename", + "LabelDiscFromMetadata": "Disc from Metadata", + "LabelDiscover": "Discover", + "LabelDownload": "Download", + "LabelDownloadNEpisodes": "Download {0} episodes", + "LabelDuration": "Duration", + "LabelDurationFound": "Duration found:", + "LabelEbook": "Ebook", + "LabelEbooks": "Ebooks", + "LabelEdit": "Edit", + "LabelEmail": "Email", + "LabelEmailSettingsFromAddress": "From Address", + "LabelEmailSettingsSecure": "Secure", + "LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)", + "LabelEmailSettingsTestAddress": "Test Address", + "LabelEmbeddedCover": "Embedded Cover", + "LabelEnable": "Enable", + "LabelEnd": "End", + "LabelEpisode": "Episode", + "LabelEpisodeTitle": "Episode Title", + "LabelEpisodeType": "Episode Type", + "LabelExample": "Example", + "LabelExplicit": "Explicit", + "LabelFeedURL": "Feed URL", + "LabelFile": "File", + "LabelFileBirthtime": "File Birthtime", + "LabelFileModified": "File Modified", + "LabelFilename": "Filename", + "LabelFilterByUser": "Filter by User", + "LabelFindEpisodes": "Find Episodes", + "LabelFinished": "Finished", + "LabelFolder": "Folder", + "LabelFolders": "Folders", + "LabelFontScale": "Font scale", + "LabelFormat": "Format", + "LabelGenre": "Genre", + "LabelGenres": "Genres", + "LabelHardDeleteFile": "Hard delete file", + "LabelHasEbook": "Has ebook", + "LabelHasSupplementaryEbook": "Has supplementary ebook", + "LabelHost": "Host", + "LabelHour": "Hour", + "LabelIcon": "Icon", + "LabelIncludeInTracklist": "Include in Tracklist", + "LabelIncomplete": "Incomplete", + "LabelInProgress": "In Progress", + "LabelInterval": "Interval", + "LabelIntervalCustomDailyWeekly": "Custom daily/weekly", + "LabelIntervalEvery12Hours": "Every 12 hours", + "LabelIntervalEvery15Minutes": "Every 15 minutes", + "LabelIntervalEvery2Hours": "Every 2 hours", + "LabelIntervalEvery30Minutes": "Every 30 minutes", + "LabelIntervalEvery6Hours": "Every 6 hours", + "LabelIntervalEveryDay": "Every day", + "LabelIntervalEveryHour": "Every hour", + "LabelInvalidParts": "Invalid Parts", + "LabelInvert": "Invert", + "LabelItem": "Item", + "LabelLanguage": "Language", + "LabelLanguageDefaultServer": "Default Server Language", + "LabelLastBookAdded": "Last Book Added", + "LabelLastBookUpdated": "Last Book Updated", + "LabelLastSeen": "Last Seen", + "LabelLastTime": "Last Time", + "LabelLastUpdate": "Last Update", + "LabelLayout": "Layout", + "LabelLayoutSinglePage": "Single page", + "LabelLayoutSplitPage": "Split page", + "LabelLess": "Less", + "LabelLibrariesAccessibleToUser": "Libraries Accessible to User", + "LabelLibrary": "Library", + "LabelLibraryItem": "Library Item", + "LabelLibraryName": "Library Name", + "LabelLimit": "Limit", + "LabelLineSpacing": "Line spacing", + "LabelListenAgain": "Listen Again", + "LabelLogLevelDebug": "Debug", + "LabelLogLevelInfo": "Info", + "LabelLogLevelWarn": "Warn", + "LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date", + "LabelMediaPlayer": "Media Player", + "LabelMediaType": "Media Type", + "LabelMetadataProvider": "Metadata Provider", + "LabelMetaTag": "Meta Tag", + "LabelMetaTags": "Meta Tags", + "LabelMinute": "Minute", + "LabelMissing": "Missing", + "LabelMissingParts": "Missing Parts", + "LabelMore": "More", + "LabelMoreInfo": "More Info", + "LabelName": "Name", + "LabelNarrator": "Narrator", + "LabelNarrators": "Narrators", + "LabelNew": "New", + "LabelNewestAuthors": "Newest Authors", + "LabelNewestEpisodes": "Newest Episodes", + "LabelNewPassword": "New Password", + "LabelNextBackupDate": "Next backup date", + "LabelNextScheduledRun": "Next scheduled run", + "LabelNoEpisodesSelected": "No episodes selected", + "LabelNotes": "Notes", + "LabelNotFinished": "Not Finished", + "LabelNotificationAppriseURL": "Apprise URL(s)", + "LabelNotificationAvailableVariables": "Available variables", + "LabelNotificationBodyTemplate": "Body Template", + "LabelNotificationEvent": "Notification Event", + "LabelNotificationsMaxFailedAttempts": "Max failed attempts", + "LabelNotificationsMaxFailedAttemptsHelp": "Notifications are disabled once they fail to send this many times", + "LabelNotificationsMaxQueueSize": "Max queue size for notification events", + "LabelNotificationsMaxQueueSizeHelp": "Events are limited to firing 1 per second. Events will be ignored if the queue is at max size. This prevents notification spamming.", + "LabelNotificationTitleTemplate": "Title Template", + "LabelNotStarted": "Not Started", + "LabelNumberOfBooks": "Number of Books", + "LabelNumberOfEpisodes": "# of Episodes", + "LabelOpenRSSFeed": "Open RSS Feed", + "LabelOverwrite": "Overwrite", + "LabelPassword": "Password", + "LabelPath": "Path", + "LabelPermissionsAccessAllLibraries": "Can Access All Libraries", + "LabelPermissionsAccessAllTags": "Can Access All Tags", + "LabelPermissionsAccessExplicitContent": "Can Access Explicit Content", + "LabelPermissionsDelete": "Can Delete", + "LabelPermissionsDownload": "Can Download", + "LabelPermissionsUpdate": "Can Update", + "LabelPermissionsUpload": "Can Upload", + "LabelPhotoPathURL": "Photo Path/URL", + "LabelPlaylists": "Playlists", + "LabelPlayMethod": "Play Method", + "LabelPodcast": "Podcast", + "LabelPodcasts": "Podcasts", + "LabelPodcastType": "Podcast Type", + "LabelPort": "Port", + "LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)", + "LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories", + "LabelPrimaryEbook": "Primary ebook", + "LabelProgress": "Progress", + "LabelProvider": "Provider", + "LabelPubDate": "Pub Date", + "LabelPublisher": "Publisher", + "LabelPublishYear": "Publish Year", + "LabelRead": "Read", + "LabelReadAgain": "Read Again", + "LabelReadEbookWithoutProgress": "Read ebook without keeping progress", + "LabelRecentlyAdded": "Recently Added", + "LabelRecentSeries": "Recent Series", + "LabelRecommended": "Recommended", + "LabelRegion": "Region", + "LabelReleaseDate": "Release Date", + "LabelRemoveCover": "Remove cover", + "LabelRSSFeedCustomOwnerEmail": "Custom owner Email", + "LabelRSSFeedCustomOwnerName": "Custom owner Name", + "LabelRSSFeedOpen": "RSS Feed Open", + "LabelRSSFeedPreventIndexing": "Prevent Indexing", + "LabelRSSFeedSlug": "RSS Feed Slug", + "LabelRSSFeedURL": "RSS Feed URL", + "LabelSearchTerm": "Search Term", + "LabelSearchTitle": "Search Title", + "LabelSearchTitleOrASIN": "Search Title or ASIN", + "LabelSeason": "Season", + "LabelSelectAllEpisodes": "Select all episodes", + "LabelSelectEpisodesShowing": "Select {0} episodes showing", + "LabelSendEbookToDevice": "Send Ebook to...", + "LabelSequence": "Sequence", + "LabelSeries": "Series", + "LabelSeriesName": "Series Name", + "LabelSeriesProgress": "Series Progress", + "LabelSetEbookAsPrimary": "Set as primary", + "LabelSetEbookAsSupplementary": "Set as supplementary", + "LabelSettingsAudiobooksOnly": "Audiobooks only", + "LabelSettingsAudiobooksOnlyHelp": "Enabling this setting will ignore ebook files unless they are inside an audiobook folder in which case they will be set as supplementary ebooks", + "LabelSettingsBookshelfViewHelp": "Skeumorphic design with wooden shelves", + "LabelSettingsChromecastSupport": "Chromecast support", + "LabelSettingsDateFormat": "Date Format", + "LabelSettingsDisableWatcher": "Disable Watcher", + "LabelSettingsDisableWatcherForLibrary": "Disable folder watcher for library", + "LabelSettingsDisableWatcherHelp": "Disables the automatic adding/updating of items when file changes are detected. *Requires server restart", + "LabelSettingsEnableWatcher": "Enable Watcher", + "LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library", + "LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart", + "LabelSettingsExperimentalFeatures": "Experimental features", + "LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.", + "LabelSettingsFindCovers": "Find covers", + "LabelSettingsFindCoversHelp": "If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover.
Note: This will extend scan time", + "LabelSettingsHideSingleBookSeries": "Hide single book series", + "LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.", + "LabelSettingsHomePageBookshelfView": "Home page use bookshelf view", + "LabelSettingsLibraryBookshelfView": "Library use bookshelf view", + "LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters", + "LabelSettingsOverdriveMediaMarkersHelp": "MP3 files from Overdrive come with chapter timings embedded as custom metadata. Enabling this will use these tags for chapter timings automatically", + "LabelSettingsParseSubtitles": "Parse subtitles", + "LabelSettingsParseSubtitlesHelp": "Extract subtitles from audiobook folder names.
Subtitle must be seperated by \" - \"
i.e. \"Book Title - A Subtitle Here\" has the subtitle \"A Subtitle Here\"", + "LabelSettingsPreferAudioMetadata": "Prefer audio metadata", + "LabelSettingsPreferAudioMetadataHelp": "Audio file ID3 meta tags will be used for book details over folder names", + "LabelSettingsPreferMatchedMetadata": "Prefer matched metadata", + "LabelSettingsPreferMatchedMetadataHelp": "Matched data will overide item details when using Quick Match. By default Quick Match will only fill in missing details.", + "LabelSettingsPreferOPFMetadata": "Prefer OPF metadata", + "LabelSettingsPreferOPFMetadataHelp": "OPF file metadata will be used for book details over folder names", + "LabelSettingsSkipMatchingBooksWithASIN": "Skip matching books that already have an ASIN", + "LabelSettingsSkipMatchingBooksWithISBN": "Skip matching books that already have an ISBN", + "LabelSettingsSortingIgnorePrefixes": "Ignore prefixes when sorting", + "LabelSettingsSortingIgnorePrefixesHelp": "i.e. for prefix \"the\" book title \"The Book Title\" would sort as \"Book Title, The\"", + "LabelSettingsSquareBookCovers": "Use square book covers", + "LabelSettingsSquareBookCoversHelp": "Prefer to use square covers over standard 1.6:1 book covers", + "LabelSettingsStoreCoversWithItem": "Store covers with item", + "LabelSettingsStoreCoversWithItemHelp": "By default covers are stored in /metadata/items, enabling this setting will store covers in your library item folder. Only one file named \"cover\" will be kept", + "LabelSettingsStoreMetadataWithItem": "Store metadata with item", + "LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders. Uses .abs file extension", + "LabelSettingsTimeFormat": "Time Format", + "LabelShowAll": "Show All", + "LabelSize": "Size", + "LabelSleepTimer": "Sleep timer", + "LabelSlug": "Slug", + "LabelStart": "Start", + "LabelStarted": "Started", + "LabelStartedAt": "Started At", + "LabelStartTime": "Start Time", + "LabelStatsAudioTracks": "Audio Tracks", + "LabelStatsAuthors": "Authors", + "LabelStatsBestDay": "Best Day", + "LabelStatsDailyAverage": "Daily Average", + "LabelStatsDays": "Days", + "LabelStatsDaysListened": "Days Listened", + "LabelStatsHours": "Hours", + "LabelStatsInARow": "in a row", + "LabelStatsItemsFinished": "Items Finished", + "LabelStatsItemsInLibrary": "Items in Library", + "LabelStatsMinutes": "minutes", + "LabelStatsMinutesListening": "Minutes Listening", + "LabelStatsOverallDays": "Overall Days", + "LabelStatsOverallHours": "Overall Hours", + "LabelStatsWeekListening": "Week Listening", + "LabelSubtitle": "Subtitle", + "LabelSupportedFileTypes": "Supported File Types", + "LabelTag": "Tag", + "LabelTags": "Tags", + "LabelTagsAccessibleToUser": "Tags Accessible to User", + "LabelTagsNotAccessibleToUser": "Tags not Accessible to User", + "LabelTasks": "Tasks Running", + "LabelTheme": "Theme", + "LabelThemeDark": "Dark", + "LabelThemeLight": "Light", + "LabelTimeBase": "Time Base", + "LabelTimeListened": "Time Listened", + "LabelTimeListenedToday": "Time Listened Today", + "LabelTimeRemaining": "{0} remaining", + "LabelTimeToShift": "Time to shift in seconds", + "LabelTitle": "Title", + "LabelToolsEmbedMetadata": "Embed Metadata", + "LabelToolsEmbedMetadataDescription": "Embed metadata into audio files including cover image and chapters.", + "LabelToolsMakeM4b": "Make M4B Audiobook File", + "LabelToolsMakeM4bDescription": "Generate a .M4B audiobook file with embedded metadata, cover image, and chapters.", + "LabelToolsSplitM4b": "Split M4B to MP3's", + "LabelToolsSplitM4bDescription": "Create MP3's from an M4B split by chapters with embedded metadata, cover image, and chapters.", + "LabelTotalDuration": "Total Duration", + "LabelTotalTimeListened": "Total Time Listened", + "LabelTrackFromFilename": "Track from Filename", + "LabelTrackFromMetadata": "Track from Metadata", + "LabelTracks": "Tracks", + "LabelTracksMultiTrack": "Multi-track", + "LabelTracksNone": "No tracks", + "LabelTracksSingleTrack": "Single-track", + "LabelType": "Type", + "LabelUnabridged": "Unabridged", + "LabelUnknown": "Unknown", + "LabelUpdateCover": "Update Cover", + "LabelUpdateCoverHelp": "Allow overwriting of existing covers for the selected books when a match is located", + "LabelUpdatedAt": "Updated At", + "LabelUpdateDetails": "Update Details", + "LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located", + "LabelUploaderDragAndDrop": "Drag & drop files or folders", + "LabelUploaderDropFiles": "Drop files", + "LabelUseChapterTrack": "Use chapter track", + "LabelUseFullTrack": "Use full track", + "LabelUser": "User", + "LabelUsername": "Username", + "LabelValue": "Value", + "LabelVersion": "Version", + "LabelViewBookmarks": "View bookmarks", + "LabelViewChapters": "View chapters", + "LabelViewQueue": "View player queue", + "LabelVolume": "Volume", + "LabelWeekdaysToRun": "Weekdays to run", + "LabelYourAudiobookDuration": "Your audiobook duration", + "LabelYourBookmarks": "Your Bookmarks", + "LabelYourPlaylists": "Your Playlists", + "LabelYourProgress": "Your Progress", + "MessageAddToPlayerQueue": "Add to player queue", + "MessageAppriseDescription": "To use this feature you will need to have an instance of Apprise API running or an api that will handle those same requests.
The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at http://192.168.1.1:8337 then you would put http://192.168.1.1:8337/notify.", + "MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in /metadata/items & /metadata/authors. Backups do not include any files stored in your library folders.", + "MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.", + "MessageBookshelfNoCollections": "You haven't made any collections yet", + "MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"", + "MessageBookshelfNoRSSFeeds": "No RSS feeds are open", + "MessageBookshelfNoSeries": "You have no series", + "MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook", + "MessageChapterErrorFirstNotZero": "First chapter must start at 0", + "MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration", + "MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time", + "MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook", + "MessageCheckingCron": "Checking cron...", + "MessageConfirmCloseFeed": "Are you sure you want to close this feed?", + "MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?", + "MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?", + "MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?", + "MessageConfirmDeleteSession": "Are you sure you want to delete this session?", + "MessageConfirmForceReScan": "Are you sure you want to force re-scan?", + "MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?", + "MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?", + "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?", + "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?", + "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?", + "MessageConfirmRemoveCollection": "Are you sure you want to remove collection \"{0}\"?", + "MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?", + "MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?", + "MessageConfirmRemoveNarrator": "Are you sure you want to remove narrator \"{0}\"?", + "MessageConfirmRemovePlaylist": "Are you sure you want to remove your playlist \"{0}\"?", + "MessageConfirmRenameGenre": "Are you sure you want to rename genre \"{0}\" to \"{1}\" for all items?", + "MessageConfirmRenameGenreMergeNote": "Note: This genre already exists so they will be merged.", + "MessageConfirmRenameGenreWarning": "Warning! A similar genre with a different casing already exists \"{0}\".", + "MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?", + "MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.", + "MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".", + "MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?", + "MessageDownloadingEpisode": "Downloading episode", + "MessageDragFilesIntoTrackOrder": "Drag files into correct track order", + "MessageEmbedFinished": "Embed Finished!", + "MessageEpisodesQueuedForDownload": "{0} Episode(s) queued for download", + "MessageFeedURLWillBe": "Feed URL will be {0}", + "MessageFetching": "Fetching...", + "MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.", + "MessageImportantNotice": "Important Notice!", + "MessageInsertChapterBelow": "Insert chapter below", + "MessageItemsSelected": "{0} Items Selected", + "MessageItemsUpdated": "{0} Items Updated", + "MessageJoinUsOn": "Join us on", + "MessageListeningSessionsInTheLastYear": "{0} listening sessions in the last year", + "MessageLoading": "Loading...", + "MessageLoadingFolders": "Loading folders...", + "MessageM4BFailed": "M4B Failed!", + "MessageM4BFinished": "M4B Finished!", + "MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps", + "MessageMarkAllEpisodesFinished": "Mark all episodes finished", + "MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished", + "MessageMarkAsFinished": "Mark as Finished", + "MessageMarkAsNotFinished": "Mark as Not Finished", + "MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.", + "MessageNoAudioTracks": "No audio tracks", + "MessageNoAuthors": "No Authors", + "MessageNoBackups": "No Backups", + "MessageNoBookmarks": "No Bookmarks", + "MessageNoChapters": "No Chapters", + "MessageNoCollections": "No Collections", + "MessageNoCoversFound": "No Covers Found", + "MessageNoDescription": "No description", + "MessageNoDownloadsInProgress": "No downloads currently in progress", + "MessageNoDownloadsQueued": "No downloads queued", + "MessageNoEpisodeMatchesFound": "No episode matches found", + "MessageNoEpisodes": "No Episodes", + "MessageNoFoldersAvailable": "No Folders Available", + "MessageNoGenres": "No Genres", + "MessageNoIssues": "No Issues", + "MessageNoItems": "No Items", + "MessageNoItemsFound": "No items found", + "MessageNoListeningSessions": "No Listening Sessions", + "MessageNoLogs": "No Logs", + "MessageNoMediaProgress": "No Media Progress", + "MessageNoNotifications": "No Notifications", + "MessageNoPodcastsFound": "No podcasts found", + "MessageNoResults": "No Results", + "MessageNoSearchResultsFor": "No search results for \"{0}\"", + "MessageNoSeries": "No Series", + "MessageNoTags": "No Tags", + "MessageNoTasksRunning": "No Tasks Running", + "MessageNotYetImplemented": "Not yet implemented", + "MessageNoUpdateNecessary": "No update necessary", + "MessageNoUpdatesWereNecessary": "No updates were necessary", + "MessageNoUserPlaylists": "You have no playlists", + "MessageOr": "or", + "MessagePauseChapter": "Pause chapter playback", + "MessagePlayChapter": "Listen to beginning of chapter", + "MessagePlaylistCreateFromCollection": "Create playlist from collection", + "MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching", + "MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.", + "MessageRemoveChapter": "Remove chapter", + "MessageRemoveEpisodes": "Remove {0} episode(s)", + "MessageRemoveFromPlayerQueue": "Remove from player queue", + "MessageRemoveUserWarning": "Are you sure you want to permanently delete user \"{0}\"?", + "MessageReportBugsAndContribute": "Report bugs, request features, and contribute on", + "MessageResetChaptersConfirm": "Are you sure you want to reset chapters and undo the changes you made?", + "MessageRestoreBackupConfirm": "Are you sure you want to restore the backup created on", + "MessageRestoreBackupWarning": "Restoring a backup will overwrite the entire database located at /config and cover images in /metadata/items & /metadata/authors.

Backups do not modify any files in your library folders. If you have enabled server settings to store cover art and metadata in your library folders then those are not backed up or overwritten.

All clients using your server will be automatically refreshed.", + "MessageSearchResultsFor": "Search results for", + "MessageServerCouldNotBeReached": "Server could not be reached", + "MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name", + "MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?", + "MessageThinking": "Thinking...", + "MessageUploaderItemFailed": "Failed to upload", + "MessageUploaderItemSuccess": "Successfully Uploaded!", + "MessageUploading": "Uploading...", + "MessageValidCronExpression": "Valid cron expression", + "MessageWatcherIsDisabledGlobally": "Watcher is disabled globally in server settings", + "MessageXLibraryIsEmpty": "{0} Library is empty!", + "MessageYourAudiobookDurationIsLonger": "Your audiobook duration is longer than the duration found", + "MessageYourAudiobookDurationIsShorter": "Your audiobook duration is shorter than duration found", + "NoteChangeRootPassword": "Root user is the only user that can have an empty password", + "NoteChapterEditorTimes": "Note: First chapter start time must remain at 0:00 and the last chapter start time cannot exceed this audiobooks duration.", + "NoteFolderPicker": "Note: folders already mapped will not be shown", + "NoteFolderPickerDebian": "Note: Folder picker for the debian install is not fully implemented. You should enter the path to your library directly.", + "NoteRSSFeedPodcastAppsHttps": "Warning: Most podcast apps will require the RSS feed URL is using HTTPS", + "NoteRSSFeedPodcastAppsPubDate": "Warning: 1 or more of your episodes do not have a Pub Date. Some podcast apps require this.", + "NoteUploaderFoldersWithMediaFiles": "Folders with media files will be handled as separate library items.", + "NoteUploaderOnlyAudioFiles": "If uploading only audio files then each audio file will be handled as a separate audiobook.", + "NoteUploaderUnsupportedFiles": "Unsupported files are ignored. When choosing or dropping a folder, other files that are not in an item folder are ignored.", + "PlaceholderNewCollection": "New collection name", + "PlaceholderNewFolderPath": "New folder path", + "PlaceholderNewPlaylist": "New playlist name", + "PlaceholderSearch": "Search..", + "PlaceholderSearchEpisode": "Search episode..", + "ToastAccountUpdateFailed": "Failed to update account", + "ToastAccountUpdateSuccess": "Account updated", + "ToastAuthorImageRemoveFailed": "Failed to remove image", + "ToastAuthorImageRemoveSuccess": "Author image removed", + "ToastAuthorUpdateFailed": "Failed to update author", + "ToastAuthorUpdateMerged": "Author merged", + "ToastAuthorUpdateSuccess": "Author updated", + "ToastAuthorUpdateSuccessNoImageFound": "Author updated (no image found)", + "ToastBackupCreateFailed": "Failed to create backup", + "ToastBackupCreateSuccess": "Backup created", + "ToastBackupDeleteFailed": "Failed to delete backup", + "ToastBackupDeleteSuccess": "Backup deleted", + "ToastBackupRestoreFailed": "Failed to restore backup", + "ToastBackupUploadFailed": "Failed to upload backup", + "ToastBackupUploadSuccess": "Backup uploaded", + "ToastBatchUpdateFailed": "Batch update failed", + "ToastBatchUpdateSuccess": "Batch update success", + "ToastBookmarkCreateFailed": "Failed to create bookmark", + "ToastBookmarkCreateSuccess": "Bookmark added", + "ToastBookmarkRemoveFailed": "Failed to remove bookmark", + "ToastBookmarkRemoveSuccess": "Bookmark removed", + "ToastBookmarkUpdateFailed": "Failed to update bookmark", + "ToastBookmarkUpdateSuccess": "Bookmark updated", + "ToastChaptersHaveErrors": "Chapters have errors", + "ToastChaptersMustHaveTitles": "Chapters must have titles", + "ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection", + "ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection", + "ToastCollectionRemoveFailed": "Failed to remove collection", + "ToastCollectionRemoveSuccess": "Collection removed", + "ToastCollectionUpdateFailed": "Failed to update collection", + "ToastCollectionUpdateSuccess": "Collection updated", + "ToastItemCoverUpdateFailed": "Failed to update item cover", + "ToastItemCoverUpdateSuccess": "Item cover updated", + "ToastItemDetailsUpdateFailed": "Failed to update item details", + "ToastItemDetailsUpdateSuccess": "Item details updated", + "ToastItemDetailsUpdateUnneeded": "No updates needed for item details", + "ToastItemMarkedAsFinishedFailed": "Failed to mark as Finished", + "ToastItemMarkedAsFinishedSuccess": "Item marked as Finished", + "ToastItemMarkedAsNotFinishedFailed": "Failed to mark as Not Finished", + "ToastItemMarkedAsNotFinishedSuccess": "Item marked as Not Finished", + "ToastLibraryCreateFailed": "Failed to create library", + "ToastLibraryCreateSuccess": "Library \"{0}\" created", + "ToastLibraryDeleteFailed": "Failed to delete library", + "ToastLibraryDeleteSuccess": "Library deleted", + "ToastLibraryScanFailedToStart": "Failed to start scan", + "ToastLibraryScanStarted": "Library scan started", + "ToastLibraryUpdateFailed": "Failed to update library", + "ToastLibraryUpdateSuccess": "Library \"{0}\" updated", + "ToastPlaylistCreateFailed": "Failed to create playlist", + "ToastPlaylistCreateSuccess": "Playlist created", + "ToastPlaylistRemoveFailed": "Failed to remove playlist", + "ToastPlaylistRemoveSuccess": "Playlist removed", + "ToastPlaylistUpdateFailed": "Failed to update playlist", + "ToastPlaylistUpdateSuccess": "Playlist updated", + "ToastPodcastCreateFailed": "Failed to create podcast", + "ToastPodcastCreateSuccess": "Podcast created successfully", + "ToastRemoveItemFromCollectionFailed": "Failed to remove item from collection", + "ToastRemoveItemFromCollectionSuccess": "Item removed from collection", + "ToastRSSFeedCloseFailed": "Failed to close RSS feed", + "ToastRSSFeedCloseSuccess": "RSS feed closed", + "ToastSendEbookToDeviceFailed": "Failed to send ebook to device", + "ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"", + "ToastSeriesUpdateFailed": "Series update failed", + "ToastSeriesUpdateSuccess": "Series update success", + "ToastSessionDeleteFailed": "Failed to delete session", + "ToastSessionDeleteSuccess": "Session deleted", + "ToastSocketConnected": "Socket connected", + "ToastSocketDisconnected": "Socket disconnected", + "ToastSocketFailedToConnect": "Socket failed to connect", + "ToastUserDeleteFailed": "Failed to delete user", + "ToastUserDeleteSuccess": "User deleted" +} \ No newline at end of file From 61c48602e86abade6b186f0af6d2d9326f0f24c4 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 22 Sep 2023 16:03:41 -0500 Subject: [PATCH 13/39] Add jsdocs to BookFinder search functions --- server/finders/BookFinder.js | 45 +++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index 18865f2b..96735cc9 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -52,21 +52,19 @@ class BookFinder { cleanTitleForCompares(title) { if (!title) return '' // Remove subtitle if there (i.e. "Cool Book: Coolest Ever" becomes "Cool Book") - var stripped = this.stripSubtitle(title) + let stripped = this.stripSubtitle(title) // Remove text in paranthesis (i.e. "Ender's Game (Ender's Saga)" becomes "Ender's Game") - var cleaned = stripped.replace(/ *\([^)]*\) */g, "") + let cleaned = stripped.replace(/ *\([^)]*\) */g, "") // Remove single quotes (i.e. "Ender's Game" becomes "Enders Game") cleaned = cleaned.replace(/'/g, '') - cleaned = this.replaceAccentedChars(cleaned) - return cleaned + return this.replaceAccentedChars(cleaned) } cleanAuthorForCompares(author) { if (!author) return '' - var cleaned = this.replaceAccentedChars(author) - return cleaned + return this.replaceAccentedChars(author) } filterSearchResults(books, title, author, maxTitleDistance, maxAuthorDistance) { @@ -210,12 +208,23 @@ class BookFinder { candidates.add(candidate) } + /** + * Search for books including fuzzy searches + * + * @param {string} provider + * @param {string} title + * @param {string} author + * @param {string} isbn + * @param {string} asin + * @param {{titleDistance:number, authorDistance:number, maxFuzzySearches:number}} options + * @returns {Promise} + */ async search(provider, title, author, isbn, asin, options = {}) { - var books = [] + let books = [] const maxTitleDistance = !isNaN(options.titleDistance) ? Number(options.titleDistance) : 4 const maxAuthorDistance = !isNaN(options.authorDistance) ? Number(options.authorDistance) : 4 const maxFuzzySearches = !isNaN(options.maxFuzzySearches) ? Number(options.maxFuzzySearches) : 5 - var numFuzzySearches = 0 + let numFuzzySearches = 0 if (!title) return books @@ -228,8 +237,8 @@ class BookFinder { author = author.trim().toLowerCase() // Now run up to maxFuzzySearches fuzzy searches - var candidates = new Set() - var cleanedAuthor = this.cleanAuthorForCompares(author) + let candidates = new Set() + let cleanedAuthor = this.cleanAuthorForCompares(author) this.addTitleCandidate(title, candidates) // remove parentheses and their contents, and replace with a separator @@ -256,8 +265,7 @@ class BookFinder { if (lengthDiff) return lengthDiff return b.localeCompare(a) }) - Logger.debug(`[BookFinder] Found ${candidates.length} fuzzy title candidates`) - Logger.debug(candidates) + Logger.debug(`[BookFinder] Found ${candidates.length} fuzzy title candidates`, candidates) for (const candidate of candidates) { if (++numFuzzySearches > maxFuzzySearches) return books books = await this.runSearch(candidate, cleanedAuthor, provider, asin, maxTitleDistance, maxAuthorDistance) @@ -283,10 +291,21 @@ class BookFinder { return books } + /** + * Search for books + * + * @param {string} title + * @param {string} author + * @param {string} provider + * @param {string} asin only used for audible providers + * @param {number} maxTitleDistance only used for openlibrary provider + * @param {number} maxAuthorDistance only used for openlibrary provider + * @returns {Promise} + */ async runSearch(title, author, provider, asin, maxTitleDistance, maxAuthorDistance) { Logger.debug(`Book Search: title: "${title}", author: "${author || ''}", provider: ${provider}`) - var books = [] + let books = [] if (provider === 'google') { books = await this.getGoogleBooksResults(title, author) From b64ecc7c6fab4cfa6d6770433b8f9d744e71e64c Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 22 Sep 2023 16:14:00 -0500 Subject: [PATCH 14/39] Update server/Database.js --- server/Database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/Database.js b/server/Database.js index 82cda3fc..b6823ce5 100644 --- a/server/Database.js +++ b/server/Database.js @@ -177,7 +177,7 @@ class Database { // Setting QUERY_LOGGING=benchmark will log all Sequelize queries and their execution times, after they run Logger.info(`[Database] Query benchmarking enabled"`) logging = (query, time) => Logger.dev(`Ran the following query in ${time}ms:\n ${query}`) - benchmark = true; + benchmark = true } this.sequelize = new Sequelize({ From 1ab34fa77f0b6b93599c39129f4e893672981b7a Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 22 Sep 2023 16:14:12 -0500 Subject: [PATCH 15/39] Update server/Database.js --- server/Database.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/Database.js b/server/Database.js index b6823ce5..f56de5ba 100644 --- a/server/Database.js +++ b/server/Database.js @@ -167,8 +167,8 @@ class Database { async connect() { Logger.info(`[Database] Initializing db at "${this.dbPath}"`) - let logging = false; - let benchmark = false; + let logging = false + let benchmark = false if (process.env.QUERY_LOGGING === "log") { // Setting QUERY_LOGGING=log will log all Sequelize queries before they run Logger.info(`[Database] Query logging enabled`) From f37ab53effc759e9435efaa90169460da54cc955 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 22 Sep 2023 16:49:01 -0500 Subject: [PATCH 16/39] Update get all backups api endpoint to return backupLocation, display location above backup settings --- client/components/tables/BackupsTable.vue | 1 + client/pages/config/backups.vue | 36 +++++++++-------------- server/controllers/BackupController.js | 7 +---- server/routers/ApiRouter.js | 1 - 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/client/components/tables/BackupsTable.vue b/client/components/tables/BackupsTable.vue index a216f463..08692a4d 100644 --- a/client/components/tables/BackupsTable.vue +++ b/client/components/tables/BackupsTable.vue @@ -164,6 +164,7 @@ export default { this.$axios .$get('/api/backups') .then((data) => { + this.$emit('loaded', data.backupLocation) this.setBackups(data.backups || []) }) .catch((error) => { diff --git a/client/pages/config/backups.vue b/client/pages/config/backups.vue index 50114d34..61543e65 100644 --- a/client/pages/config/backups.vue +++ b/client/pages/config/backups.vue @@ -1,6 +1,12 @@