From 209847d98ad67118e567c767d4cd2b07c3d4427e Mon Sep 17 00:00:00 2001 From: mikiher Date: Mon, 25 Dec 2023 09:25:04 +0200 Subject: [PATCH 01/13] Add a SIGINT handler for proper server shutdown --- server/Server.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/server/Server.js b/server/Server.js index 5e8cab7606..3de8ff7f90 100644 --- a/server/Server.js +++ b/server/Server.js @@ -276,6 +276,19 @@ class Server { }) app.get('/healthcheck', (req, res) => res.sendStatus(200)) + let sigintAlreadyReceived = false + process.on('SIGINT', async () => { + if (!sigintAlreadyReceived) { + sigintAlreadyReceived = true + Logger.info('SIGINT (Ctrl+C) received. Shutting down...') + await this.stop() + Logger.info('Server stopped. Exiting.') + } else { + Logger.info('SIGINT (Ctrl+C) received again. Exiting immediately.') + } + process.exit(0) + }) + this.server.listen(this.Port, this.Host, () => { if (this.Host) Logger.info(`Listening on http://${this.Host}:${this.Port}`) else Logger.info(`Listening on port :${this.Port}`) @@ -383,6 +396,7 @@ class Server { } async stop() { + Logger.info('=== Stopping Server ===') await this.watcher.close() Logger.info('Watcher Closed') From 0d0bdce3374108dfa21a1c6d3ff6469d9c6b63f3 Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 25 Dec 2023 13:15:55 -0600 Subject: [PATCH 02/13] Fix:Fetch RSS feed request accept header #2446 --- server/utils/podcastUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/utils/podcastUtils.js b/server/utils/podcastUtils.js index 819ec91411..4e01c92b13 100644 --- a/server/utils/podcastUtils.js +++ b/server/utils/podcastUtils.js @@ -233,7 +233,7 @@ module.exports.getPodcastFeed = (feedUrl, excludeEpisodeMetadata = false) => { method: 'GET', timeout: 12000, responseType: 'arraybuffer', - headers: { Accept: 'application/rss+xml' }, + headers: { Accept: 'application/rss+xml, application/xhtml+xml, application/xml' }, httpAgent: ssrfFilter(feedUrl), httpsAgent: ssrfFilter(feedUrl) }).then(async (data) => { From 21d0d43edc387dac8d4fc9a788a6548a80cda32a Mon Sep 17 00:00:00 2001 From: mikiher Date: Wed, 27 Dec 2023 15:33:33 +0200 Subject: [PATCH 03/13] Add SocketAuthority.close() --- server/Server.js | 2 +- server/SocketAuthority.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/server/Server.js b/server/Server.js index 3de8ff7f90..bb68382660 100644 --- a/server/Server.js +++ b/server/Server.js @@ -401,7 +401,7 @@ class Server { Logger.info('Watcher Closed') return new Promise((resolve) => { - this.server.close((err) => { + SocketAuthority.close((err) => { if (err) { Logger.error('Failed to close server', err) } else { diff --git a/server/SocketAuthority.js b/server/SocketAuthority.js index da17f5df1a..b4698ef988 100644 --- a/server/SocketAuthority.js +++ b/server/SocketAuthority.js @@ -73,6 +73,15 @@ class SocketAuthority { } } + close(callback) { + Logger.info('[SocketAuthority] Shutting down') + // This will close all open socket connections, and also close the underlying http server + if (this.io) + this.io.close(callback) + else + callback() + } + initialize(Server) { this.Server = Server From 9a634e0de576d6e700f3a3a29f1394fc65347432 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 28 Dec 2023 16:32:21 -0600 Subject: [PATCH 04/13] Add JS docs for server stop --- server/Server.js | 6 +++++- server/SocketAuthority.js | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/server/Server.js b/server/Server.js index bb68382660..57b9c74acf 100644 --- a/server/Server.js +++ b/server/Server.js @@ -284,7 +284,7 @@ class Server { await this.stop() Logger.info('Server stopped. Exiting.') } else { - Logger.info('SIGINT (Ctrl+C) received again. Exiting immediately.') + Logger.info('SIGINT (Ctrl+C) received again. Exiting immediately.') } process.exit(0) }) @@ -395,6 +395,10 @@ class Server { res.sendStatus(200) } + /** + * Gracefully stop server + * Stops watcher and socket server + */ async stop() { Logger.info('=== Stopping Server ===') await this.watcher.close() diff --git a/server/SocketAuthority.js b/server/SocketAuthority.js index b4698ef988..00f0a63ecd 100644 --- a/server/SocketAuthority.js +++ b/server/SocketAuthority.js @@ -73,10 +73,15 @@ class SocketAuthority { } } + /** + * Closes the Socket.IO server and disconnect all clients + * + * @param {Function} callback + */ close(callback) { Logger.info('[SocketAuthority] Shutting down') // This will close all open socket connections, and also close the underlying http server - if (this.io) + if (this.io) this.io.close(callback) else callback() From e4effebc196226c1d4580964bbb3077bcaaab07a Mon Sep 17 00:00:00 2001 From: Jacob Southard Date: Fri, 29 Dec 2023 10:04:59 -0600 Subject: [PATCH 05/13] Add try/catch to fileutils.getFileMtimeMs --- server/utils/fileUtils.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/utils/fileUtils.js b/server/utils/fileUtils.js index ebad97dbc9..7ef5320dd7 100644 --- a/server/utils/fileUtils.js +++ b/server/utils/fileUtils.js @@ -81,7 +81,12 @@ module.exports.getFileSize = async (path) => { * @returns {Promise} epoch timestamp */ module.exports.getFileMTimeMs = async (path) => { - return (await getFileStat(path))?.mtimeMs || 0 + try { + return (await getFileStat(path))?.mtimeMs || 0 + } catch (err) { + Logger.error(`[fileUtils] Failed to getFileMtimeMs`, err) + return 0 + } } /** From 269676e8a5c3c2413c0ab80625c3a9c1ba8ce3aa Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 29 Dec 2023 17:05:35 -0600 Subject: [PATCH 06/13] Update:CORS for /cover API endpoint for use in canvas in the mobile apps --- server/Server.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/Server.js b/server/Server.js index 3de8ff7f90..070ef36fd4 100644 --- a/server/Server.js +++ b/server/Server.js @@ -136,15 +136,16 @@ class Server { /** * @temporary - * This is necessary for the ebook API endpoint in the mobile apps + * This is necessary for the ebook & cover API endpoint in the mobile apps * The mobile app ereader is using fetch api in Capacitor that is currently difficult to switch to native requests * so we have to allow cors for specific origins to the /api/items/:id/ebook endpoint + * The cover image is fetched with XMLHttpRequest in the mobile apps to load into a canvas and extract colors * @see https://ionicframework.com/docs/troubleshooting/cors * * Running in development allows cors to allow testing the mobile apps in the browser */ app.use((req, res, next) => { - if (Logger.isDev || req.path.match(/\/api\/items\/([a-z0-9-]{36})\/ebook(\/[0-9]+)?/)) { + if (Logger.isDev || req.path.match(/\/api\/items\/([a-z0-9-]{36})\/(ebook|cover)(\/[0-9]+)?/)) { const allowedOrigins = ['capacitor://localhost', 'http://localhost'] if (Logger.isDev || allowedOrigins.some(o => o === req.get('origin'))) { res.header('Access-Control-Allow-Origin', req.get('origin')) @@ -284,7 +285,7 @@ class Server { await this.stop() Logger.info('Server stopped. Exiting.') } else { - Logger.info('SIGINT (Ctrl+C) received again. Exiting immediately.') + Logger.info('SIGINT (Ctrl+C) received again. Exiting immediately.') } process.exit(0) }) From 456bb87a008e1f19dc9ed16c2990babff851ab84 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 30 Dec 2023 12:12:48 -0600 Subject: [PATCH 07/13] Update:Find one library item endpoint sequelize query split into two queries to improve performance #2073 #2075 --- server/models/LibraryItem.js | 63 ++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index b6f2f28582..ffd2f9c0dd 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -419,39 +419,40 @@ class LibraryItem extends Model { */ static async getOldById(libraryItemId) { if (!libraryItemId) return null - const libraryItem = await this.findByPk(libraryItemId, { - include: [ - { - model: this.sequelize.models.book, - include: [ - { - model: this.sequelize.models.author, - through: { - attributes: [] - } - }, - { - model: this.sequelize.models.series, - through: { - attributes: ['sequence'] - } + + const libraryItem = await this.findByPk(libraryItemId) + + if (libraryItem.mediaType === 'podcast') { + libraryItem.media = await libraryItem.getMedia({ + include: [ + { + model: this.sequelize.models.podcastEpisode + } + ] + }) + } else { + libraryItem.media = await libraryItem.getMedia({ + include: [ + { + model: this.sequelize.models.author, + through: { + attributes: [] } - ] - }, - { - model: this.sequelize.models.podcast, - include: [ - { - model: this.sequelize.models.podcastEpisode + }, + { + model: this.sequelize.models.series, + through: { + attributes: ['sequence'] } - ] - } - ], - order: [ - [this.sequelize.models.book, this.sequelize.models.author, this.sequelize.models.bookAuthor, 'createdAt', 'ASC'], - [this.sequelize.models.book, this.sequelize.models.series, 'bookSeries', 'createdAt', 'ASC'] - ] - }) + } + ], + order: [ + [this.sequelize.models.author, this.sequelize.models.bookAuthor, 'createdAt', 'ASC'], + [this.sequelize.models.series, 'bookSeries', 'createdAt', 'ASC'] + ] + }) + } + if (!libraryItem) return null return this.getOldLibraryItem(libraryItem) } From 160c83df4a0206f54cfa501c781032e5c8745cc6 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 30 Dec 2023 16:14:14 -0600 Subject: [PATCH 08/13] Update:podcastEpisodes table index added for createdAt column #2073 #2075 --- server/controllers/LibraryItemController.js | 1 - server/models/LibraryItem.js | 6 +++++- server/models/PodcastEpisode.js | 7 ++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index b0ecf446e7..f462c08196 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -49,7 +49,6 @@ class LibraryItemController { item.episodesDownloading = [this.podcastManager.currentDownload.toJSONForClient()] } } - return res.json(item) } res.json(req.libraryItem) diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index ffd2f9c0dd..67e9abfbfc 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -421,6 +421,10 @@ class LibraryItem extends Model { if (!libraryItemId) return null const libraryItem = await this.findByPk(libraryItemId) + if (!libraryItem) { + Logger.error(`[LibraryItem] Library item not found with id "${libraryItemId}"`) + return null + } if (libraryItem.mediaType === 'podcast') { libraryItem.media = await libraryItem.getMedia({ @@ -453,7 +457,7 @@ class LibraryItem extends Model { }) } - if (!libraryItem) return null + if (!libraryItem.media) return null return this.getOldLibraryItem(libraryItem) } diff --git a/server/models/PodcastEpisode.js b/server/models/PodcastEpisode.js index 55b2f9d403..2fdefb86b6 100644 --- a/server/models/PodcastEpisode.js +++ b/server/models/PodcastEpisode.js @@ -152,7 +152,12 @@ class PodcastEpisode extends Model { extraData: DataTypes.JSON }, { sequelize, - modelName: 'podcastEpisode' + modelName: 'podcastEpisode', + indexes: [ + { + fields: ['createdAt'] + } + ] }) const { podcast } = sequelize.models From 021adf31043c2d3a0595652ac12aee785dea4360 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 31 Dec 2023 14:51:01 -0600 Subject: [PATCH 09/13] Update:Podcast episode table is lazy loaded #1549 --- .../components/tables/LibraryFilesTable.vue | 4 +- ...EpisodeTableRow.vue => LazyEpisodeRow.vue} | 171 ++++++++------- ...pisodesTable.vue => LazyEpisodesTable.vue} | 202 ++++++++++++++++-- client/pages/item/_id/index.vue | 4 +- server/controllers/LibraryItemController.js | 1 + server/objects/entities/PodcastEpisode.js | 17 +- 6 files changed, 297 insertions(+), 102 deletions(-) rename client/components/tables/podcast/{EpisodeTableRow.vue => LazyEpisodeRow.vue} (55%) rename client/components/tables/podcast/{EpisodesTable.vue => LazyEpisodesTable.vue} (66%) diff --git a/client/components/tables/LibraryFilesTable.vue b/client/components/tables/LibraryFilesTable.vue index fef1ae5ae9..4160c7839b 100644 --- a/client/components/tables/LibraryFilesTable.vue +++ b/client/components/tables/LibraryFilesTable.vue @@ -12,7 +12,7 @@ -
+
@@ -70,7 +70,7 @@ export default { }, audioFiles() { if (this.libraryItem.mediaType === 'podcast') { - return this.libraryItem.media?.episodes.map((ep) => ep.audioFile) || [] + return this.libraryItem.media?.episodes.map((ep) => ep.audioFile).filter((af) => af) || [] } return this.libraryItem.media?.audioFiles || [] }, diff --git a/client/components/tables/podcast/EpisodeTableRow.vue b/client/components/tables/podcast/LazyEpisodeRow.vue similarity index 55% rename from client/components/tables/podcast/EpisodeTableRow.vue rename to client/components/tables/podcast/LazyEpisodeRow.vue index 4300b8e149..d2b106fee4 100644 --- a/client/components/tables/podcast/EpisodeTableRow.vue +++ b/client/components/tables/podcast/LazyEpisodeRow.vue @@ -1,18 +1,22 @@ diff --git a/client/pages/item/_id/index.vue b/client/pages/item/_id/index.vue index 8658a6e42e..073ec570a7 100644 --- a/client/pages/item/_id/index.vue +++ b/client/pages/item/_id/index.vue @@ -1,6 +1,6 @@
{{ $strings.LabelPath }}