mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Merge branch 'master' into watcher-update-api
This commit is contained in:
		
						commit
						3bccd52196
					
				| @ -42,6 +42,7 @@ export default { | |||||||
|       rendition: null, |       rendition: null, | ||||||
|       ereaderSettings: { |       ereaderSettings: { | ||||||
|         theme: 'dark', |         theme: 'dark', | ||||||
|  |         font: 'serif', | ||||||
|         fontScale: 100, |         fontScale: 100, | ||||||
|         lineSpacing: 115, |         lineSpacing: 115, | ||||||
|         spread: 'auto' |         spread: 'auto' | ||||||
| @ -130,6 +131,7 @@ export default { | |||||||
| 
 | 
 | ||||||
|       const fontScale = settings.fontScale || 100 |       const fontScale = settings.fontScale || 100 | ||||||
|       this.rendition.themes.fontSize(`${fontScale}%`) |       this.rendition.themes.fontSize(`${fontScale}%`) | ||||||
|  |       this.rendition.themes.font(settings.font) | ||||||
|       this.rendition.spread(settings.spread || 'auto') |       this.rendition.spread(settings.spread || 'auto') | ||||||
|     }, |     }, | ||||||
|     prev() { |     prev() { | ||||||
|  | |||||||
| @ -63,7 +63,13 @@ | |||||||
|           <div class="w-40"> |           <div class="w-40"> | ||||||
|             <p class="text-lg">{{ $strings.LabelTheme }}:</p> |             <p class="text-lg">{{ $strings.LabelTheme }}:</p> | ||||||
|           </div> |           </div> | ||||||
|           <ui-toggle-btns v-model="ereaderSettings.theme" :items="themeItems" @input="settingsUpdated" /> |           <ui-toggle-btns v-model="ereaderSettings.theme" :items="themeItems.theme" @input="settingsUpdated" /> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex items-center mb-4"> | ||||||
|  |           <div class="w-40"> | ||||||
|  |             <p class="text-lg">{{ $strings.LabelFontFamily }}:</p> | ||||||
|  |           </div> | ||||||
|  |           <ui-toggle-btns v-model="ereaderSettings.font" :items="themeItems.font" @input="settingsUpdated" /> | ||||||
|         </div> |         </div> | ||||||
|         <div class="flex items-center mb-4"> |         <div class="flex items-center mb-4"> | ||||||
|           <div class="w-40"> |           <div class="w-40"> | ||||||
| @ -103,6 +109,7 @@ export default { | |||||||
|       showSettings: false, |       showSettings: false, | ||||||
|       ereaderSettings: { |       ereaderSettings: { | ||||||
|         theme: 'dark', |         theme: 'dark', | ||||||
|  |         font: 'serif', | ||||||
|         fontScale: 100, |         fontScale: 100, | ||||||
|         lineSpacing: 115, |         lineSpacing: 115, | ||||||
|         spread: 'auto' |         spread: 'auto' | ||||||
| @ -142,16 +149,28 @@ export default { | |||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|     themeItems() { |     themeItems() { | ||||||
|       return [ |       return { | ||||||
|         { |         theme: [ | ||||||
|           text: this.$strings.LabelThemeDark, |           { | ||||||
|           value: 'dark' |             text: this.$strings.LabelThemeDark, | ||||||
|         }, |             value: 'dark' | ||||||
|         { |           }, | ||||||
|           text: this.$strings.LabelThemeLight, |           { | ||||||
|           value: 'light' |             text: this.$strings.LabelThemeLight, | ||||||
|         } |             value: 'light' | ||||||
|       ] |           } | ||||||
|  |         ], | ||||||
|  |         font: [ | ||||||
|  |           { | ||||||
|  |             text: 'Sans', | ||||||
|  |             value: 'sans-serif', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             text: 'Serif', | ||||||
|  |             value: 'serif', | ||||||
|  |           } | ||||||
|  |         ] | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     componentName() { |     componentName() { | ||||||
|       if (this.ebookType === 'epub') return 'readers-epub-reader' |       if (this.ebookType === 'epub') return 'readers-epub-reader' | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Færdig", |   "LabelFinished": "Færdig", | ||||||
|   "LabelFolder": "Mappe", |   "LabelFolder": "Mappe", | ||||||
|   "LabelFolders": "Mapper", |   "LabelFolders": "Mapper", | ||||||
|  |   "LabelFontFamily": "Fontfamilie", | ||||||
|   "LabelFontScale": "Skriftstørrelse", |   "LabelFontScale": "Skriftstørrelse", | ||||||
|   "LabelFormat": "Format", |   "LabelFormat": "Format", | ||||||
|   "LabelGenre": "Genre", |   "LabelGenre": "Genre", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "beendet", |   "LabelFinished": "beendet", | ||||||
|   "LabelFolder": "Ordner", |   "LabelFolder": "Ordner", | ||||||
|   "LabelFolders": "Verzeichnisse", |   "LabelFolders": "Verzeichnisse", | ||||||
|  |   "LabelFontFamily": "Schriftfamilie", | ||||||
|   "LabelFontScale": "Schriftgröße", |   "LabelFontScale": "Schriftgröße", | ||||||
|   "LabelFormat": "Format", |   "LabelFormat": "Format", | ||||||
|   "LabelGenre": "Kategorie", |   "LabelGenre": "Kategorie", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Finished", |   "LabelFinished": "Finished", | ||||||
|   "LabelFolder": "Folder", |   "LabelFolder": "Folder", | ||||||
|   "LabelFolders": "Folders", |   "LabelFolders": "Folders", | ||||||
|  |   "LabelFontFamily": "Font family", | ||||||
|   "LabelFontScale": "Font scale", |   "LabelFontScale": "Font scale", | ||||||
|   "LabelFormat": "Format", |   "LabelFormat": "Format", | ||||||
|   "LabelGenre": "Genre", |   "LabelGenre": "Genre", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Terminado", |   "LabelFinished": "Terminado", | ||||||
|   "LabelFolder": "Carpeta", |   "LabelFolder": "Carpeta", | ||||||
|   "LabelFolders": "Carpetas", |   "LabelFolders": "Carpetas", | ||||||
|  |   "LabelFontFamily": "Familia tipográfica", | ||||||
|   "LabelFontScale": "Tamaño de Fuente", |   "LabelFontScale": "Tamaño de Fuente", | ||||||
|   "LabelFormat": "Formato", |   "LabelFormat": "Formato", | ||||||
|   "LabelGenre": "Genero", |   "LabelGenre": "Genero", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Fini(e)", |   "LabelFinished": "Fini(e)", | ||||||
|   "LabelFolder": "Dossier", |   "LabelFolder": "Dossier", | ||||||
|   "LabelFolders": "Dossiers", |   "LabelFolders": "Dossiers", | ||||||
|  |   "LabelFontFamily": "Famille de polices", | ||||||
|   "LabelFontScale": "Taille de la police de caractère", |   "LabelFontScale": "Taille de la police de caractère", | ||||||
|   "LabelFormat": "Format", |   "LabelFormat": "Format", | ||||||
|   "LabelGenre": "Genre", |   "LabelGenre": "Genre", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Finished", |   "LabelFinished": "Finished", | ||||||
|   "LabelFolder": "Folder", |   "LabelFolder": "Folder", | ||||||
|   "LabelFolders": "Folders", |   "LabelFolders": "Folders", | ||||||
|  |   "LabelFontFamily": "ફોન્ટ કુટુંબ", | ||||||
|   "LabelFontScale": "Font scale", |   "LabelFontScale": "Font scale", | ||||||
|   "LabelFormat": "Format", |   "LabelFormat": "Format", | ||||||
|   "LabelGenre": "Genre", |   "LabelGenre": "Genre", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Finished", |   "LabelFinished": "Finished", | ||||||
|   "LabelFolder": "Folder", |   "LabelFolder": "Folder", | ||||||
|   "LabelFolders": "Folders", |   "LabelFolders": "Folders", | ||||||
|  |   "LabelFontFamily": "फुहारा परिवार", | ||||||
|   "LabelFontScale": "Font scale", |   "LabelFontScale": "Font scale", | ||||||
|   "LabelFormat": "Format", |   "LabelFormat": "Format", | ||||||
|   "LabelGenre": "Genre", |   "LabelGenre": "Genre", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Finished", |   "LabelFinished": "Finished", | ||||||
|   "LabelFolder": "Folder", |   "LabelFolder": "Folder", | ||||||
|   "LabelFolders": "Folderi", |   "LabelFolders": "Folderi", | ||||||
|  |   "LabelFontFamily": "Font family", | ||||||
|   "LabelFontScale": "Font scale", |   "LabelFontScale": "Font scale", | ||||||
|   "LabelFormat": "Format", |   "LabelFormat": "Format", | ||||||
|   "LabelGenre": "Genre", |   "LabelGenre": "Genre", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Finita", |   "LabelFinished": "Finita", | ||||||
|   "LabelFolder": "Cartella", |   "LabelFolder": "Cartella", | ||||||
|   "LabelFolders": "Cartelle", |   "LabelFolders": "Cartelle", | ||||||
|  |   "LabelFontFamily": "Font family", | ||||||
|   "LabelFontScale": "Dimensione Font", |   "LabelFontScale": "Dimensione Font", | ||||||
|   "LabelFormat": "Formato", |   "LabelFormat": "Formato", | ||||||
|   "LabelGenre": "Genere", |   "LabelGenre": "Genere", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Baigta", |   "LabelFinished": "Baigta", | ||||||
|   "LabelFolder": "Aplankas", |   "LabelFolder": "Aplankas", | ||||||
|   "LabelFolders": "Aplankai", |   "LabelFolders": "Aplankai", | ||||||
|  |   "LabelFontFamily": "Famiglia di font", | ||||||
|   "LabelFontScale": "Šrifto mastelis", |   "LabelFontScale": "Šrifto mastelis", | ||||||
|   "LabelFormat": "Formatas", |   "LabelFormat": "Formatas", | ||||||
|   "LabelGenre": "Žanras", |   "LabelGenre": "Žanras", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Voltooid", |   "LabelFinished": "Voltooid", | ||||||
|   "LabelFolder": "Map", |   "LabelFolder": "Map", | ||||||
|   "LabelFolders": "Mappen", |   "LabelFolders": "Mappen", | ||||||
|  |   "LabelFontFamily": "Lettertypefamilie", | ||||||
|   "LabelFontScale": "Lettertype schaal", |   "LabelFontScale": "Lettertype schaal", | ||||||
|   "LabelFormat": "Formaat", |   "LabelFormat": "Formaat", | ||||||
|   "LabelGenre": "Genre", |   "LabelGenre": "Genre", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Fullført", |   "LabelFinished": "Fullført", | ||||||
|   "LabelFolder": "Mappe", |   "LabelFolder": "Mappe", | ||||||
|   "LabelFolders": "Mapper", |   "LabelFolders": "Mapper", | ||||||
|  |   "LabelFontFamily": "Fontfamilie", | ||||||
|   "LabelFontScale": "Font størrelse", |   "LabelFontScale": "Font størrelse", | ||||||
|   "LabelFormat": "Format", |   "LabelFormat": "Format", | ||||||
|   "LabelGenre": "Sjanger", |   "LabelGenre": "Sjanger", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Zakończone", |   "LabelFinished": "Zakończone", | ||||||
|   "LabelFolder": "Folder", |   "LabelFolder": "Folder", | ||||||
|   "LabelFolders": "Foldery", |   "LabelFolders": "Foldery", | ||||||
|  |   "LabelFontFamily": "Rodzina czcionek", | ||||||
|   "LabelFontScale": "Font scale", |   "LabelFontScale": "Font scale", | ||||||
|   "LabelFormat": "Format", |   "LabelFormat": "Format", | ||||||
|   "LabelGenre": "Gatunek", |   "LabelGenre": "Gatunek", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "Закончен", |   "LabelFinished": "Закончен", | ||||||
|   "LabelFolder": "Папка", |   "LabelFolder": "Папка", | ||||||
|   "LabelFolders": "Папки", |   "LabelFolders": "Папки", | ||||||
|  |   "LabelFontFamily": "Семейство шрифтов", | ||||||
|   "LabelFontScale": "Масштаб шрифта", |   "LabelFontScale": "Масштаб шрифта", | ||||||
|   "LabelFormat": "Формат", |   "LabelFormat": "Формат", | ||||||
|   "LabelGenre": "Жанр", |   "LabelGenre": "Жанр", | ||||||
|  | |||||||
| @ -260,6 +260,7 @@ | |||||||
|   "LabelFinished": "已听完", |   "LabelFinished": "已听完", | ||||||
|   "LabelFolder": "文件夹", |   "LabelFolder": "文件夹", | ||||||
|   "LabelFolders": "文件夹", |   "LabelFolders": "文件夹", | ||||||
|  |   "LabelFontFamily": "字体系列", | ||||||
|   "LabelFontScale": "字体比例", |   "LabelFontScale": "字体比例", | ||||||
|   "LabelFormat": "编码格式", |   "LabelFormat": "编码格式", | ||||||
|   "LabelGenre": "流派", |   "LabelGenre": "流派", | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ const LibraryScanner = require('./scanner/LibraryScanner') | |||||||
| const Task = require('./objects/Task') | const Task = require('./objects/Task') | ||||||
| const TaskManager = require('./managers/TaskManager') | const TaskManager = require('./managers/TaskManager') | ||||||
| 
 | 
 | ||||||
| const { filePathToPOSIX, isSameOrSubPath } = require('./utils/fileUtils') | const { filePathToPOSIX, isSameOrSubPath, getFileMTimeMs } = require('./utils/fileUtils') | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @typedef PendingFileUpdate |  * @typedef PendingFileUpdate | ||||||
| @ -29,6 +29,8 @@ class FolderWatcher extends EventEmitter { | |||||||
|     /** @type {Task} */ |     /** @type {Task} */ | ||||||
|     this.pendingTask = null |     this.pendingTask = null | ||||||
| 
 | 
 | ||||||
|  |     this.filesBeingAdded = new Set() | ||||||
|  | 
 | ||||||
|     /** @type {string[]} */ |     /** @type {string[]} */ | ||||||
|     this.ignoreDirs = [] |     this.ignoreDirs = [] | ||||||
|     /** @type {string[]} */ |     /** @type {string[]} */ | ||||||
| @ -64,14 +66,13 @@ class FolderWatcher extends EventEmitter { | |||||||
|     }) |     }) | ||||||
|     watcher |     watcher | ||||||
|       .on('add', (path) => { |       .on('add', (path) => { | ||||||
|         this.onNewFile(library.id, path) |         this.onFileAdded(library.id, filePathToPOSIX(path)) | ||||||
|       }).on('change', (path) => { |       }).on('change', (path) => { | ||||||
|         // This is triggered from metadata changes, not what we want
 |         // This is triggered from metadata changes, not what we want
 | ||||||
|         // this.onFileUpdated(path)
 |  | ||||||
|       }).on('unlink', path => { |       }).on('unlink', path => { | ||||||
|         this.onFileRemoved(library.id, path) |         this.onFileRemoved(library.id, filePathToPOSIX(path)) | ||||||
|       }).on('rename', (path, pathNext) => { |       }).on('rename', (path, pathNext) => { | ||||||
|         this.onRename(library.id, path, pathNext) |         this.onFileRename(library.id, filePathToPOSIX(path), filePathToPOSIX(pathNext)) | ||||||
|       }).on('error', (error) => { |       }).on('error', (error) => { | ||||||
|         Logger.error(`[Watcher] ${error}`) |         Logger.error(`[Watcher] ${error}`) | ||||||
|       }).on('ready', () => { |       }).on('ready', () => { | ||||||
| @ -137,14 +138,31 @@ class FolderWatcher extends EventEmitter { | |||||||
|     return this.libraryWatchers.map(lib => lib.watcher.close()) |     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)) { |     if (this.checkShouldIgnorePath(path)) { | ||||||
|       return |       return | ||||||
|     } |     } | ||||||
|     Logger.debug('[Watcher] File Added', path) |     Logger.debug('[Watcher] File Added', path) | ||||||
|     this.addFileUpdate(libraryId, path, 'added') |     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) { |   onFileRemoved(libraryId, path) { | ||||||
|     if (this.checkShouldIgnorePath(path)) { |     if (this.checkShouldIgnorePath(path)) { | ||||||
|       return |       return | ||||||
| @ -153,11 +171,13 @@ class FolderWatcher extends EventEmitter { | |||||||
|     this.addFileUpdate(libraryId, path, 'deleted') |     this.addFileUpdate(libraryId, path, 'deleted') | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   onFileUpdated(path) { |   /** | ||||||
|     Logger.debug('[Watcher] Updated File', path) |    * Watcher detected file renamed | ||||||
|   } |    *  | ||||||
| 
 |    * @param {string} libraryId  | ||||||
|   onRename(libraryId, pathFrom, pathTo) { |    * @param {string} path  | ||||||
|  |    */ | ||||||
|  |   onFileRename(libraryId, pathFrom, pathTo) { | ||||||
|     if (this.checkShouldIgnorePath(pathTo)) { |     if (this.checkShouldIgnorePath(pathTo)) { | ||||||
|       return |       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} libraryId  | ||||||
|    * @param {string} path  |    * @param {string} path  | ||||||
|    * @param {string} type  |    * @param {string} type  | ||||||
|    */ |    */ | ||||||
|   addFileUpdate(libraryId, path, type) { |   addFileUpdate(libraryId, path, type) { | ||||||
|     path = filePathToPOSIX(path) |  | ||||||
|     if (this.pendingFilePaths.includes(path)) return |     if (this.pendingFilePaths.includes(path)) return | ||||||
| 
 | 
 | ||||||
|     // Get file library
 |     // Get file library
 | ||||||
| @ -222,12 +270,26 @@ class FolderWatcher extends EventEmitter { | |||||||
|       type |       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) |     clearTimeout(this.pendingTimeout) | ||||||
|     this.pendingTimeout = setTimeout(() => { |     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) |       LibraryScanner.scanFilesChanged(this.pendingFileUpdates, this.pendingTask) | ||||||
|       this.pendingTask = null |       this.pendingTask = null | ||||||
|       this.pendingFileUpdates = [] |       this.pendingFileUpdates = [] | ||||||
|  |       this.filesBeingAdded.clear() | ||||||
|     }, this.pendingDelay) |     }, this.pendingDelay) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -621,7 +621,7 @@ class LibraryController { | |||||||
|         model: Database.bookModel, |         model: Database.bookModel, | ||||||
|         attributes: ['id', 'tags', 'explicit'], |         attributes: ['id', 'tags', 'explicit'], | ||||||
|         where: bookWhere, |         where: bookWhere, | ||||||
|         required: false, |         required: !req.user.isAdminOrUp, // Only show authors with 0 books for admin users or up
 | ||||||
|         through: { |         through: { | ||||||
|           attributes: [] |           attributes: [] | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -38,22 +38,14 @@ function isSameOrSubPath(parentPath, childPath) { | |||||||
| } | } | ||||||
| module.exports.isSameOrSubPath = isSameOrSubPath | module.exports.isSameOrSubPath = isSameOrSubPath | ||||||
| 
 | 
 | ||||||
| async function getFileStat(path) { | function getFileStat(path) { | ||||||
|   try { |   try { | ||||||
|     var stat = await fs.stat(path) |     return fs.stat(path) | ||||||
|     return { |  | ||||||
|       size: stat.size, |  | ||||||
|       atime: stat.atime, |  | ||||||
|       mtime: stat.mtime, |  | ||||||
|       ctime: stat.ctime, |  | ||||||
|       birthtime: stat.birthtime |  | ||||||
|     } |  | ||||||
|   } catch (err) { |   } catch (err) { | ||||||
|     Logger.error('[fileUtils] Failed to stat', err) |     Logger.error('[fileUtils] Failed to stat', err) | ||||||
|     return false |     return null | ||||||
|   } |   } | ||||||
| } | } | ||||||
| module.exports.getFileStat = getFileStat |  | ||||||
| 
 | 
 | ||||||
| async function getFileTimestampsWithIno(path) { | async function getFileTimestampsWithIno(path) { | ||||||
|   try { |   try { | ||||||
| @ -72,12 +64,25 @@ async function getFileTimestampsWithIno(path) { | |||||||
| } | } | ||||||
| module.exports.getFileTimestampsWithIno = getFileTimestampsWithIno | module.exports.getFileTimestampsWithIno = getFileTimestampsWithIno | ||||||
| 
 | 
 | ||||||
| async function getFileSize(path) { | /** | ||||||
|   var stat = await getFileStat(path) |  * Get file size | ||||||
|   if (!stat) return 0 |  *  | ||||||
|   return stat.size || 0 |  * @param {string} path  | ||||||
|  |  * @returns {Promise<number>} | ||||||
|  |  */ | ||||||
|  | module.exports.getFileSize = async (path) => { | ||||||
|  |   return (await getFileStat(path))?.size || 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Get file mtimeMs | ||||||
|  |  *  | ||||||
|  |  * @param {string} path  | ||||||
|  |  * @returns {Promise<number>} epoch timestamp | ||||||
|  |  */ | ||||||
|  | module.exports.getFileMTimeMs = async (path) => { | ||||||
|  |   return (await getFileStat(path))?.mtimeMs || 0 | ||||||
| } | } | ||||||
| module.exports.getFileSize = getFileSize |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  *  |  *  | ||||||
|  | |||||||
| @ -308,6 +308,8 @@ module.exports = { | |||||||
|   async getNewestAuthors(library, user, limit) { |   async getNewestAuthors(library, user, limit) { | ||||||
|     if (library.mediaType !== 'book') return { authors: [], count: 0 } |     if (library.mediaType !== 'book') return { authors: [], count: 0 } | ||||||
| 
 | 
 | ||||||
|  |     const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(user) | ||||||
|  | 
 | ||||||
|     const { rows: authors, count } = await Database.authorModel.findAndCountAll({ |     const { rows: authors, count } = await Database.authorModel.findAndCountAll({ | ||||||
|       where: { |       where: { | ||||||
|         libraryId: library.id, |         libraryId: library.id, | ||||||
| @ -315,9 +317,15 @@ module.exports = { | |||||||
|           [Sequelize.Op.gte]: new Date(new Date() - (60 * 24 * 60 * 60 * 1000)) // 60 days ago
 |           [Sequelize.Op.gte]: new Date(new Date() - (60 * 24 * 60 * 60 * 1000)) // 60 days ago
 | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|  |       replacements, | ||||||
|       include: { |       include: { | ||||||
|         model: Database.bookAuthorModel, |         model: Database.bookModel, | ||||||
|         required: true // Must belong to a book
 |         attributes: ['id', 'tags', 'explicit'], | ||||||
|  |         where: bookWhere, | ||||||
|  |         required: true, // Must belong to a book
 | ||||||
|  |         through: { | ||||||
|  |           attributes: [] | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|       limit, |       limit, | ||||||
|       distinct: true, |       distinct: true, | ||||||
| @ -328,7 +336,7 @@ module.exports = { | |||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|       authors: authors.map((au) => { |       authors: authors.map((au) => { | ||||||
|         const numBooks = au.bookAuthors?.length || 0 |         const numBooks = au.books.length || 0 | ||||||
|         return au.getOldAuthor().toJSONExpanded(numBooks) |         return au.getOldAuthor().toJSONExpanded(numBooks) | ||||||
|       }), |       }), | ||||||
|       count |       count | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user