From ef1cdf6ad231b5fefff8b83915cfacffd84db945 Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 24 Oct 2023 17:04:54 -0500 Subject: [PATCH 1/4] Fix:Only show authors with books for users #2250 --- server/controllers/LibraryController.js | 2 +- server/utils/queries/libraryFilters.js | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 10a77b2a..d2090270 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -621,7 +621,7 @@ class LibraryController { model: Database.bookModel, attributes: ['id', 'tags', 'explicit'], where: bookWhere, - required: false, + required: !req.user.isAdminOrUp, // Only show authors with 0 books for admin users or up through: { attributes: [] } diff --git a/server/utils/queries/libraryFilters.js b/server/utils/queries/libraryFilters.js index 6ba6ec5e..785124a9 100644 --- a/server/utils/queries/libraryFilters.js +++ b/server/utils/queries/libraryFilters.js @@ -308,6 +308,8 @@ module.exports = { async getNewestAuthors(library, user, limit) { if (library.mediaType !== 'book') return { authors: [], count: 0 } + const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(user) + const { rows: authors, count } = await Database.authorModel.findAndCountAll({ where: { libraryId: library.id, @@ -315,9 +317,15 @@ module.exports = { [Sequelize.Op.gte]: new Date(new Date() - (60 * 24 * 60 * 60 * 1000)) // 60 days ago } }, + replacements, include: { - model: Database.bookAuthorModel, - required: true // Must belong to a book + model: Database.bookModel, + attributes: ['id', 'tags', 'explicit'], + where: bookWhere, + required: true, // Must belong to a book + through: { + attributes: [] + } }, limit, distinct: true, @@ -328,7 +336,7 @@ module.exports = { return { authors: authors.map((au) => { - const numBooks = au.bookAuthors?.length || 0 + const numBooks = au.books.length || 0 return au.getOldAuthor().toJSONExpanded(numBooks) }), count From 8dc44901699763052db295321e0adbb4eeec0798 Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 25 Oct 2023 16:53:53 -0500 Subject: [PATCH 2/4] Fix:Watcher waits for files to finish transferring before scanning #1362 #2248 --- server/Watcher.js | 90 +++++++++++++++++++++++++++++++++------ server/utils/fileUtils.js | 37 +++++++++------- 2 files changed, 97 insertions(+), 30 deletions(-) diff --git a/server/Watcher.js b/server/Watcher.js index f348ce8e..99318a7e 100644 --- a/server/Watcher.js +++ b/server/Watcher.js @@ -6,7 +6,7 @@ const LibraryScanner = require('./scanner/LibraryScanner') const Task = require('./objects/Task') const TaskManager = require('./managers/TaskManager') -const { filePathToPOSIX, isSameOrSubPath } = require('./utils/fileUtils') +const { filePathToPOSIX, isSameOrSubPath, getFileMTimeMs } = require('./utils/fileUtils') /** * @typedef PendingFileUpdate @@ -29,6 +29,8 @@ class FolderWatcher extends EventEmitter { /** @type {Task} */ this.pendingTask = null + this.filesBeingAdded = new Set() + /** @type {string[]} */ this.ignoreDirs = [] /** @type {string[]} */ @@ -64,14 +66,13 @@ class FolderWatcher extends EventEmitter { }) watcher .on('add', (path) => { - this.onNewFile(library.id, path) + this.onFileAdded(library.id, filePathToPOSIX(path)) }).on('change', (path) => { // This is triggered from metadata changes, not what we want - // this.onFileUpdated(path) }).on('unlink', path => { - this.onFileRemoved(library.id, path) + this.onFileRemoved(library.id, filePathToPOSIX(path)) }).on('rename', (path, pathNext) => { - this.onRename(library.id, path, pathNext) + this.onFileRename(library.id, filePathToPOSIX(path), filePathToPOSIX(pathNext)) }).on('error', (error) => { Logger.error(`[Watcher] ${error}`) }).on('ready', () => { @@ -137,14 +138,31 @@ class FolderWatcher extends EventEmitter { return this.libraryWatchers.map(lib => lib.watcher.close()) } - onNewFile(libraryId, path) { + /** + * Watcher detected file added + * + * @param {string} libraryId + * @param {string} path + */ + onFileAdded(libraryId, path) { if (this.checkShouldIgnorePath(path)) { return } Logger.debug('[Watcher] File Added', path) this.addFileUpdate(libraryId, path, 'added') + + if (!this.filesBeingAdded.has(path)) { + this.filesBeingAdded.add(path) + this.waitForFileToAdd(path) + } } + /** + * Watcher detected file removed + * + * @param {string} libraryId + * @param {string} path + */ onFileRemoved(libraryId, path) { if (this.checkShouldIgnorePath(path)) { return @@ -153,11 +171,13 @@ class FolderWatcher extends EventEmitter { this.addFileUpdate(libraryId, path, 'deleted') } - onFileUpdated(path) { - Logger.debug('[Watcher] Updated File', path) - } - - onRename(libraryId, pathFrom, pathTo) { + /** + * Watcher detected file renamed + * + * @param {string} libraryId + * @param {string} path + */ + onFileRename(libraryId, pathFrom, pathTo) { if (this.checkShouldIgnorePath(pathTo)) { return } @@ -166,13 +186,41 @@ class FolderWatcher extends EventEmitter { } /** - * File update detected from watcher + * Get mtimeMs from an added file every second until it is no longer changing + * Times out after 180s + * + * @param {string} path + * @param {number} [lastMTimeMs=0] + * @param {number} [loop=0] + */ + async waitForFileToAdd(path, lastMTimeMs = 0, loop = 0) { + // Safety to catch infinite loop (180s) + if (loop >= 180) { + Logger.warn(`[Watcher] Waiting to add file at "${path}" timeout (loop ${loop}) - proceeding`) + return this.filesBeingAdded.delete(path) + } + + const mtimeMs = await getFileMTimeMs(path) + if (mtimeMs === lastMTimeMs) { + if (lastMTimeMs) Logger.debug(`[Watcher] File finished adding at "${path}"`) + return this.filesBeingAdded.delete(path) + } + if (lastMTimeMs % 5 === 0) { + Logger.debug(`[Watcher] Waiting to add file at "${path}". mtimeMs=${mtimeMs} lastMTimeMs=${lastMTimeMs} (loop ${loop})`) + } + // Wait 1 second + await new Promise((resolve) => setTimeout(resolve, 1000)) + this.waitForFileToAdd(path, mtimeMs, ++loop) + } + + /** + * Queue file update + * * @param {string} libraryId * @param {string} path * @param {string} type */ addFileUpdate(libraryId, path, type) { - path = filePathToPOSIX(path) if (this.pendingFilePaths.includes(path)) return // Get file library @@ -222,12 +270,26 @@ class FolderWatcher extends EventEmitter { type }) - // Notify server of update after "pendingDelay" + this.handlePendingFileUpdatesTimeout() + } + + /** + * Wait X seconds before notifying scanner that files changed + * reset timer if files are still copying + */ + handlePendingFileUpdatesTimeout() { clearTimeout(this.pendingTimeout) this.pendingTimeout = setTimeout(() => { + // Check that files are not still being added + if (this.pendingFileUpdates.some(pfu => this.filesBeingAdded.has(pfu.path))) { + Logger.debug(`[Watcher] Still waiting for pending files "${[...this.filesBeingAdded].join(', ')}"`) + return this.handlePendingFileUpdatesTimeout() + } + LibraryScanner.scanFilesChanged(this.pendingFileUpdates, this.pendingTask) this.pendingTask = null this.pendingFileUpdates = [] + this.filesBeingAdded.clear() }, this.pendingDelay) } diff --git a/server/utils/fileUtils.js b/server/utils/fileUtils.js index 19735fb7..26578f57 100644 --- a/server/utils/fileUtils.js +++ b/server/utils/fileUtils.js @@ -38,22 +38,14 @@ function isSameOrSubPath(parentPath, childPath) { } module.exports.isSameOrSubPath = isSameOrSubPath -async function getFileStat(path) { +function getFileStat(path) { try { - var stat = await fs.stat(path) - return { - size: stat.size, - atime: stat.atime, - mtime: stat.mtime, - ctime: stat.ctime, - birthtime: stat.birthtime - } + return fs.stat(path) } catch (err) { Logger.error('[fileUtils] Failed to stat', err) - return false + return null } } -module.exports.getFileStat = getFileStat async function getFileTimestampsWithIno(path) { try { @@ -72,12 +64,25 @@ async function getFileTimestampsWithIno(path) { } module.exports.getFileTimestampsWithIno = getFileTimestampsWithIno -async function getFileSize(path) { - var stat = await getFileStat(path) - if (!stat) return 0 - return stat.size || 0 +/** + * Get file size + * + * @param {string} path + * @returns {Promise} + */ +module.exports.getFileSize = async (path) => { + return (await getFileStat(path))?.size || 0 +} + +/** + * Get file mtimeMs + * + * @param {string} path + * @returns {Promise} epoch timestamp + */ +module.exports.getFileMTimeMs = async (path) => { + return (await getFileStat(path))?.mtimeMs || 0 } -module.exports.getFileSize = getFileSize /** * From 24228b442419109521f1884cbf713a1b30f1737e Mon Sep 17 00:00:00 2001 From: MxMarx Date: Thu, 26 Oct 2023 02:01:40 -0700 Subject: [PATCH 3/4] Option to change the font family in epub viewer --- client/components/readers/EpubReader.vue | 2 ++ client/components/readers/Reader.vue | 41 +++++++++++++++++------- client/strings/da.json | 1 + client/strings/de.json | 1 + client/strings/en-us.json | 1 + client/strings/es.json | 1 + client/strings/fr.json | 1 + client/strings/gu.json | 1 + client/strings/hi.json | 1 + client/strings/lt.json | 1 + client/strings/nl.json | 1 + client/strings/no.json | 1 + client/strings/pl.json | 1 + client/strings/ru.json | 1 + client/strings/zh-cn.json | 1 + 15 files changed, 45 insertions(+), 11 deletions(-) diff --git a/client/components/readers/EpubReader.vue b/client/components/readers/EpubReader.vue index fba30ec9..7cc3c33a 100644 --- a/client/components/readers/EpubReader.vue +++ b/client/components/readers/EpubReader.vue @@ -42,6 +42,7 @@ export default { rendition: null, ereaderSettings: { theme: 'dark', + font: 'serif', fontScale: 100, lineSpacing: 115, spread: 'auto' @@ -130,6 +131,7 @@ export default { const fontScale = settings.fontScale || 100 this.rendition.themes.fontSize(`${fontScale}%`) + this.rendition.themes.font(settings.font) this.rendition.spread(settings.spread || 'auto') }, prev() { diff --git a/client/components/readers/Reader.vue b/client/components/readers/Reader.vue index 120bb400..569ff84f 100644 --- a/client/components/readers/Reader.vue +++ b/client/components/readers/Reader.vue @@ -63,7 +63,13 @@

{{ $strings.LabelTheme }}:

- + + +
+
+

{{ $strings.LabelFontFamily }}:

+
+
@@ -103,6 +109,7 @@ export default { showSettings: false, ereaderSettings: { theme: 'dark', + font: 'serif', fontScale: 100, lineSpacing: 115, spread: 'auto' @@ -142,16 +149,28 @@ export default { ] }, themeItems() { - return [ - { - text: this.$strings.LabelThemeDark, - value: 'dark' - }, - { - text: this.$strings.LabelThemeLight, - value: 'light' - } - ] + return { + theme: [ + { + text: this.$strings.LabelThemeDark, + value: 'dark' + }, + { + text: this.$strings.LabelThemeLight, + value: 'light' + } + ], + font: [ + { + text: 'Sans', + value: 'sans-serif', + }, + { + text: 'Serif', + value: 'serif', + } + ] + } }, componentName() { if (this.ebookType === 'epub') return 'readers-epub-reader' diff --git a/client/strings/da.json b/client/strings/da.json index adf138a1..3197cc3c 100644 --- a/client/strings/da.json +++ b/client/strings/da.json @@ -260,6 +260,7 @@ "LabelFinished": "Færdig", "LabelFolder": "Mappe", "LabelFolders": "Mapper", + "LabelFontFamily": "Fontfamilie", "LabelFontScale": "Skriftstørrelse", "LabelFormat": "Format", "LabelGenre": "Genre", diff --git a/client/strings/de.json b/client/strings/de.json index a072a549..942cad8b 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -260,6 +260,7 @@ "LabelFinished": "beendet", "LabelFolder": "Ordner", "LabelFolders": "Verzeichnisse", + "LabelFontFamily": "Schriftfamilie", "LabelFontScale": "Schriftgröße", "LabelFormat": "Format", "LabelGenre": "Kategorie", diff --git a/client/strings/en-us.json b/client/strings/en-us.json index 24d07726..9e69aa4e 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -260,6 +260,7 @@ "LabelFinished": "Finished", "LabelFolder": "Folder", "LabelFolders": "Folders", + "LabelFontFamily": "Font family", "LabelFontScale": "Font scale", "LabelFormat": "Format", "LabelGenre": "Genre", diff --git a/client/strings/es.json b/client/strings/es.json index 4b37139d..b04815ab 100644 --- a/client/strings/es.json +++ b/client/strings/es.json @@ -260,6 +260,7 @@ "LabelFinished": "Terminado", "LabelFolder": "Carpeta", "LabelFolders": "Carpetas", + "LabelFontFamily": "Familia tipográfica", "LabelFontScale": "Tamaño de Fuente", "LabelFormat": "Formato", "LabelGenre": "Genero", diff --git a/client/strings/fr.json b/client/strings/fr.json index 28bdf743..11fa1468 100644 --- a/client/strings/fr.json +++ b/client/strings/fr.json @@ -260,6 +260,7 @@ "LabelFinished": "Fini(e)", "LabelFolder": "Dossier", "LabelFolders": "Dossiers", + "LabelFontFamily": "Famille de polices", "LabelFontScale": "Taille de la police de caractère", "LabelFormat": "Format", "LabelGenre": "Genre", diff --git a/client/strings/gu.json b/client/strings/gu.json index 8593a95d..b3de487a 100644 --- a/client/strings/gu.json +++ b/client/strings/gu.json @@ -260,6 +260,7 @@ "LabelFinished": "Finished", "LabelFolder": "Folder", "LabelFolders": "Folders", + "LabelFontFamily": "ફોન્ટ કુટુંબ", "LabelFontScale": "Font scale", "LabelFormat": "Format", "LabelGenre": "Genre", diff --git a/client/strings/hi.json b/client/strings/hi.json index 82d25986..d05c1e85 100644 --- a/client/strings/hi.json +++ b/client/strings/hi.json @@ -260,6 +260,7 @@ "LabelFinished": "Finished", "LabelFolder": "Folder", "LabelFolders": "Folders", + "LabelFontFamily": "फुहारा परिवार", "LabelFontScale": "Font scale", "LabelFormat": "Format", "LabelGenre": "Genre", diff --git a/client/strings/lt.json b/client/strings/lt.json index dee54e12..0623a7ab 100644 --- a/client/strings/lt.json +++ b/client/strings/lt.json @@ -260,6 +260,7 @@ "LabelFinished": "Baigta", "LabelFolder": "Aplankas", "LabelFolders": "Aplankai", + "LabelFontFamily": "Famiglia di font", "LabelFontScale": "Šrifto mastelis", "LabelFormat": "Formatas", "LabelGenre": "Žanras", diff --git a/client/strings/nl.json b/client/strings/nl.json index 62696dce..659e3ec5 100644 --- a/client/strings/nl.json +++ b/client/strings/nl.json @@ -260,6 +260,7 @@ "LabelFinished": "Voltooid", "LabelFolder": "Map", "LabelFolders": "Mappen", + "LabelFontFamily": "Lettertypefamilie", "LabelFontScale": "Lettertype schaal", "LabelFormat": "Formaat", "LabelGenre": "Genre", diff --git a/client/strings/no.json b/client/strings/no.json index dc7685ee..5bf537f2 100644 --- a/client/strings/no.json +++ b/client/strings/no.json @@ -260,6 +260,7 @@ "LabelFinished": "Fullført", "LabelFolder": "Mappe", "LabelFolders": "Mapper", + "LabelFontFamily": "Fontfamilie", "LabelFontScale": "Font størrelse", "LabelFormat": "Format", "LabelGenre": "Sjanger", diff --git a/client/strings/pl.json b/client/strings/pl.json index c4fb50f8..16a0970b 100644 --- a/client/strings/pl.json +++ b/client/strings/pl.json @@ -260,6 +260,7 @@ "LabelFinished": "Zakończone", "LabelFolder": "Folder", "LabelFolders": "Foldery", + "LabelFontFamily": "Rodzina czcionek", "LabelFontScale": "Font scale", "LabelFormat": "Format", "LabelGenre": "Gatunek", diff --git a/client/strings/ru.json b/client/strings/ru.json index 69868bca..478ac33a 100644 --- a/client/strings/ru.json +++ b/client/strings/ru.json @@ -260,6 +260,7 @@ "LabelFinished": "Закончен", "LabelFolder": "Папка", "LabelFolders": "Папки", + "LabelFontFamily": "Семейство шрифтов", "LabelFontScale": "Масштаб шрифта", "LabelFormat": "Формат", "LabelGenre": "Жанр", diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json index 219e861a..ded2c9e2 100644 --- a/client/strings/zh-cn.json +++ b/client/strings/zh-cn.json @@ -260,6 +260,7 @@ "LabelFinished": "已听完", "LabelFolder": "文件夹", "LabelFolders": "文件夹", + "LabelFontFamily": "字体系列", "LabelFontScale": "字体比例", "LabelFormat": "编码格式", "LabelGenre": "流派", From 0c23da7b028dbea3978949ad3863360c2883e8c7 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 26 Oct 2023 16:31:47 -0500 Subject: [PATCH 4/4] Add missing translations --- client/strings/hr.json | 1 + client/strings/it.json | 1 + 2 files changed, 2 insertions(+) diff --git a/client/strings/hr.json b/client/strings/hr.json index e9a323ee..32213095 100644 --- a/client/strings/hr.json +++ b/client/strings/hr.json @@ -260,6 +260,7 @@ "LabelFinished": "Finished", "LabelFolder": "Folder", "LabelFolders": "Folderi", + "LabelFontFamily": "Font family", "LabelFontScale": "Font scale", "LabelFormat": "Format", "LabelGenre": "Genre", diff --git a/client/strings/it.json b/client/strings/it.json index f73b3ffc..8de1f4ec 100644 --- a/client/strings/it.json +++ b/client/strings/it.json @@ -260,6 +260,7 @@ "LabelFinished": "Finita", "LabelFolder": "Cartella", "LabelFolders": "Cartelle", + "LabelFontFamily": "Font family", "LabelFontScale": "Dimensione Font", "LabelFormat": "Formato", "LabelGenre": "Genere",