diff --git a/server/models/Book.js b/server/models/Book.js index d9f2ff13..c42e744d 100644 --- a/server/models/Book.js +++ b/server/models/Book.js @@ -258,7 +258,8 @@ class Book extends Model { } get includedAudioFiles() { - return this.audioFiles.filter((af) => !af.exclude) + const audioFiles = Array.isArray(this.audioFiles) ? this.audioFiles : [] + return audioFiles.filter((af) => !af.exclude) } get hasMediaFiles() { @@ -328,10 +329,18 @@ class Book extends Model { * @returns {number} */ get size() { + const computedSize = Number(this.dataValues?.computedMediaSize) + if (Number.isFinite(computedSize)) { + return computedSize + } + let total = 0 - this.audioFiles.forEach((af) => (total += af.metadata.size)) + const audioFiles = Array.isArray(this.audioFiles) ? this.audioFiles : [] + audioFiles.forEach((af) => { + total += Number(af?.metadata?.size) || 0 + }) if (this.ebookFile) { - total += this.ebookFile.metadata.size + total += Number(this.ebookFile?.metadata?.size) || 0 } return total } @@ -655,14 +664,18 @@ class Book extends Model { throw new Error(`[Book] Cannot convert to old JSON because series are not loaded`) } + const computedNumTracks = Number(this.dataValues?.computedNumTracks) + const computedNumAudioFiles = Number(this.dataValues?.computedNumAudioFiles) + const computedNumChapters = Number(this.dataValues?.computedNumChapters) + return { id: this.id, metadata: this.oldMetadataToJSONMinified(), coverPath: this.coverPath, tags: [...(this.tags || [])], - numTracks: this.includedAudioFiles.length, - numAudioFiles: this.audioFiles?.length || 0, - numChapters: this.chapters?.length || 0, + numTracks: Number.isFinite(computedNumTracks) ? computedNumTracks : this.includedAudioFiles.length, + numAudioFiles: Number.isFinite(computedNumAudioFiles) ? computedNumAudioFiles : this.audioFiles?.length || 0, + numChapters: Number.isFinite(computedNumChapters) ? computedNumChapters : this.chapters?.length || 0, duration: this.duration, size: this.size, ebookFormat: this.ebookFile?.ebookFormat diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index b33fa585..c22038dd 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -491,11 +491,7 @@ class LibraryItem extends Model { } Logger.debug(`Loaded ${newestAuthorsPayload.authors.length} of ${newestAuthorsPayload.count} authors for "Newest Authors" in ${newestAuthorsResult.elapsedSeconds}s`) } else if (library.isPodcast) { - const [newestEpisodesResult, mostRecentResult, mediaFinishedResult] = await Promise.all([ - timed(() => libraryFilters.getNewestPodcastEpisodes(library, user, limit)), - timed(() => libraryFilters.getLibraryItemsMostRecentlyAdded(library, user, include, limit)), - timed(() => libraryFilters.getMediaFinished(library, user, include, limit)) - ]) + const [newestEpisodesResult, mostRecentResult, mediaFinishedResult] = await Promise.all([timed(() => libraryFilters.getNewestPodcastEpisodes(library, user, limit)), timed(() => libraryFilters.getLibraryItemsMostRecentlyAdded(library, user, include, limit)), timed(() => libraryFilters.getMediaFinished(library, user, include, limit))]) const newestEpisodesPayload = newestEpisodesResult.payload // "Newest Episodes" shelf @@ -1009,6 +1005,9 @@ class LibraryItem extends Model { throw new Error(`[LibraryItem] Cannot convert to old JSON without media for library item "${this.id}"`) } + const computedNumFiles = Number(this.dataValues?.computedNumFiles) + const numFiles = Number.isFinite(computedNumFiles) ? computedNumFiles : this.libraryFiles?.length || 0 + return { id: this.id, ino: this.ino, @@ -1027,7 +1026,7 @@ class LibraryItem extends Model { isInvalid: !!this.isInvalid, mediaType: this.mediaType, media: this.media.toOldJSONMinified(), - numFiles: this.libraryFiles.length, + numFiles, size: this.size } } diff --git a/server/utils/queries/seriesFilters.js b/server/utils/queries/seriesFilters.js index ed71e5b3..6656b039 100644 --- a/server/utils/queries/seriesFilters.js +++ b/server/utils/queries/seriesFilters.js @@ -158,10 +158,55 @@ module.exports = { model: Database.bookSeriesModel, include: { model: Database.bookModel, + attributes: [ + 'id', + 'title', + 'subtitle', + 'publishedYear', + 'publishedDate', + 'publisher', + 'description', + 'isbn', + 'asin', + 'language', + 'explicit', + 'abridged', + 'coverPath', + 'duration', + 'narrators', + 'ebookFile', + 'tags', + 'genres', + [ + Sequelize.literal(`CASE + WHEN json_valid(audioFiles) THEN ( + SELECT count(*) + FROM json_each(audioFiles) af + WHERE COALESCE(json_extract(af.value, '$.exclude'), 0) = 0 + ) + ELSE 0 + END`), + 'computedNumTracks' + ], + [Sequelize.literal('CASE WHEN json_valid(audioFiles) THEN json_array_length(audioFiles) ELSE 0 END'), 'computedNumAudioFiles'], + [Sequelize.literal('CASE WHEN json_valid(chapters) THEN json_array_length(chapters) ELSE 0 END'), 'computedNumChapters'], + [ + Sequelize.literal(`( + COALESCE(( + SELECT SUM(COALESCE(CAST(json_extract(af.value, '$.metadata.size') AS INTEGER), 0)) + FROM json_each(audioFiles) af + WHERE json_valid(audioFiles) + ), 0) + + COALESCE(CAST(json_extract(ebookFile, '$.metadata.size') AS INTEGER), 0) + )`), + 'computedMediaSize' + ] + ], where: userPermissionBookWhere.bookWhere, include: [ { - model: Database.libraryItemModel + model: Database.libraryItemModel, + attributes: ['id', 'ino', 'extraData', 'libraryId', 'libraryFolderId', 'path', 'relPath', 'isFile', 'mtime', 'ctime', 'birthtime', 'createdAt', 'updatedAt', 'isMissing', 'isInvalid', 'mediaType', 'size', [Sequelize.literal('CASE WHEN json_valid(libraryFiles) THEN json_array_length(libraryFiles) ELSE 0 END'), 'computedNumFiles']] }, { model: Database.authorModel