From 5ca12eee19a8d64d0a2f029465e228da15ca799c Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 13 Feb 2025 18:07:59 -0600 Subject: [PATCH 1/6] Fix count cache by stringify Symbols #3979 --- .../utils/queries/libraryItemsBookFilters.js | 5 +-- server/utils/stringifySequelizeQuery.js | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 server/utils/stringifySequelizeQuery.js diff --git a/server/utils/queries/libraryItemsBookFilters.js b/server/utils/queries/libraryItemsBookFilters.js index 65ab1fef..d446a5e9 100644 --- a/server/utils/queries/libraryItemsBookFilters.js +++ b/server/utils/queries/libraryItemsBookFilters.js @@ -5,7 +5,7 @@ const authorFilters = require('./authorFilters') const ShareManager = require('../../managers/ShareManager') const { profile } = require('../profiler') - +const stringifySequelizeQuery = require('../stringifySequelizeQuery') const countCache = new Map() module.exports = { @@ -345,7 +345,7 @@ module.exports = { }, async findAndCountAll(findOptions, limit, offset) { - const findOptionsKey = JSON.stringify(findOptions) + const findOptionsKey = stringifySequelizeQuery(findOptions) Logger.debug(`[LibraryItemsBookFilters] findOptionsKey: ${findOptionsKey}`) findOptions.limit = limit || null @@ -353,6 +353,7 @@ module.exports = { if (countCache.has(findOptionsKey)) { const rows = await Database.bookModel.findAll(findOptions) + return { rows, count: countCache.get(findOptionsKey) } } else { const result = await Database.bookModel.findAndCountAll(findOptions) diff --git a/server/utils/stringifySequelizeQuery.js b/server/utils/stringifySequelizeQuery.js new file mode 100644 index 00000000..a41e0650 --- /dev/null +++ b/server/utils/stringifySequelizeQuery.js @@ -0,0 +1,34 @@ +function stringifySequelizeQuery(findOptions) { + // Helper function to handle symbols in nested objects + function handleSymbols(obj) { + if (!obj || typeof obj !== 'object') return obj + + if (Array.isArray(obj)) { + return obj.map(handleSymbols) + } + + const newObj = {} + for (const [key, value] of Object.entries(obj)) { + // Handle Symbol keys from Object.getOwnPropertySymbols + Object.getOwnPropertySymbols(obj).forEach((sym) => { + newObj[`__Op.${sym.toString()}`] = handleSymbols(obj[sym]) + }) + + // Handle regular keys + if (typeof key === 'string') { + if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Symbol.prototype) { + // Handle Symbol values + newObj[key] = `__Op.${value.toString()}` + } else { + // Recursively handle nested objects + newObj[key] = handleSymbols(value) + } + } + } + return newObj + } + + const sanitizedOptions = handleSymbols(findOptions) + return JSON.stringify(sanitizedOptions) +} +module.exports = stringifySequelizeQuery From c4d99a118fc0ebd613bf58dd73324a4776e942f8 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 14 Feb 2025 16:24:39 -0600 Subject: [PATCH 2/6] Fix chapter end sleep timer sometimes not stopping #3969 --- .../components/app/MediaPlayerContainer.vue | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/client/components/app/MediaPlayerContainer.vue b/client/components/app/MediaPlayerContainer.vue index cf22d322..8aa6188b 100644 --- a/client/components/app/MediaPlayerContainer.vue +++ b/client/components/app/MediaPlayerContainer.vue @@ -85,7 +85,8 @@ export default { displayTitle: null, currentPlaybackRate: 1, syncFailedToast: null, - coverAspectRatio: 1 + coverAspectRatio: 1, + lastChapterId: null } }, computed: { @@ -236,12 +237,16 @@ export default { } }, 1000) }, - checkChapterEnd(time) { + checkChapterEnd() { if (!this.currentChapter) return - const chapterEndTime = this.currentChapter.end - const tolerance = 0.75 - if (time >= chapterEndTime - tolerance) { - this.sleepTimerEnd() + + // Track chapter transitions by comparing current chapter with last chapter + if (this.lastChapterId !== this.currentChapter.id) { + // Chapter changed - if we had a previous chapter, this means we crossed a boundary + if (this.lastChapterId) { + this.sleepTimerEnd() + } + this.lastChapterId = this.currentChapter.id } }, sleepTimerEnd() { @@ -301,7 +306,7 @@ export default { } if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) { - this.checkChapterEnd(time) + this.checkChapterEnd() } }, setDuration(duration) { From d9b206fe1cfdd6f6714917d68e4b1bf726e968b9 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 14 Feb 2025 16:56:37 -0600 Subject: [PATCH 3/6] Fix server crash when quick match all updates existing series sequence #3961 --- server/models/LibraryItem.js | 2 +- server/scanner/Scanner.js | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index ace6af43..5d23bc8f 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -103,7 +103,7 @@ class LibraryItem extends Model { { model: this.sequelize.models.series, through: { - attributes: ['sequence', 'createdAt'] + attributes: ['id', 'sequence', 'createdAt'] } } ] diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index 1a2a7aaf..c5c62532 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -48,13 +48,7 @@ class Scanner { let updatePayload = {} let hasUpdated = false - let existingAuthors = [] // Used for checking if authors or series are now empty - let existingSeries = [] - if (libraryItem.isBook) { - existingAuthors = libraryItem.media.authors.map((a) => a.id) - existingSeries = libraryItem.media.series.map((s) => s.id) - const searchISBN = options.isbn || libraryItem.media.isbn const searchASIN = options.asin || libraryItem.media.asin From 8ee5646d790d5bf5a1fef42079b686b93f40a9d1 Mon Sep 17 00:00:00 2001 From: mikiher Date: Sat, 15 Feb 2025 23:57:27 +0200 Subject: [PATCH 4/6] fix stringifySequelizeQuery and add tests --- server/utils/stringifySequelizeQuery.js | 49 +++++++---------- .../utils/stringifySequeslizeQuery.test.js | 52 +++++++++++++++++++ 2 files changed, 72 insertions(+), 29 deletions(-) create mode 100644 test/server/utils/stringifySequeslizeQuery.test.js diff --git a/server/utils/stringifySequelizeQuery.js b/server/utils/stringifySequelizeQuery.js index a41e0650..44d8b12d 100644 --- a/server/utils/stringifySequelizeQuery.js +++ b/server/utils/stringifySequelizeQuery.js @@ -1,34 +1,25 @@ function stringifySequelizeQuery(findOptions) { - // Helper function to handle symbols in nested objects - function handleSymbols(obj) { - if (!obj || typeof obj !== 'object') return obj - - if (Array.isArray(obj)) { - return obj.map(handleSymbols) - } - - const newObj = {} - for (const [key, value] of Object.entries(obj)) { - // Handle Symbol keys from Object.getOwnPropertySymbols - Object.getOwnPropertySymbols(obj).forEach((sym) => { - newObj[`__Op.${sym.toString()}`] = handleSymbols(obj[sym]) - }) - - // Handle regular keys - if (typeof key === 'string') { - if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Symbol.prototype) { - // Handle Symbol values - newObj[key] = `__Op.${value.toString()}` - } else { - // Recursively handle nested objects - newObj[key] = handleSymbols(value) - } - } - } - return newObj + function isClass(func) { + return typeof func === 'function' && /^class\s/.test(func.toString()) } - const sanitizedOptions = handleSymbols(findOptions) - return JSON.stringify(sanitizedOptions) + function replacer(key, value) { + if (typeof value === 'object' && value !== null) { + const symbols = Object.getOwnPropertySymbols(value).reduce((acc, sym) => { + acc[sym.toString()] = value[sym] + return acc + }, {}) + + return { ...value, ...symbols } + } + + if (isClass(value)) { + return `${value.name}` + } + + return value + } + + return JSON.stringify(findOptions, replacer) } module.exports = stringifySequelizeQuery diff --git a/test/server/utils/stringifySequeslizeQuery.test.js b/test/server/utils/stringifySequeslizeQuery.test.js new file mode 100644 index 00000000..764acfd2 --- /dev/null +++ b/test/server/utils/stringifySequeslizeQuery.test.js @@ -0,0 +1,52 @@ +const { expect } = require('chai') +const stringifySequelizeQuery = require('../../../server/utils/stringifySequelizeQuery') +const Sequelize = require('sequelize') + +class DummyClass {} + +describe('stringifySequelizeQuery', () => { + it('should stringify a sequelize query containing an op', () => { + const query = { + where: { + name: 'John', + age: { + [Sequelize.Op.gt]: 20 + } + } + } + + const result = stringifySequelizeQuery(query) + expect(result).to.equal('{"where":{"name":"John","age":{"Symbol(gt)":20}}}') + }) + + it('should stringify a sequelize query containing a literal', () => { + const query = { + order: [[Sequelize.literal('libraryItem.title'), 'ASC']] + } + + const result = stringifySequelizeQuery(query) + expect(result).to.equal('{"order":{"0":{"0":{"val":"libraryItem.title"},"1":"ASC"}}}') + }) + + it('should stringify a sequelize query containing a class', () => { + const query = { + include: [ + { + model: DummyClass + } + ] + } + + const result = stringifySequelizeQuery(query) + expect(result).to.equal('{"include":{"0":{"model":"DummyClass"}}}') + }) + + it('should ignore non-class functions', () => { + const query = { + logging: (query) => console.log(query) + } + + const result = stringifySequelizeQuery(query) + expect(result).to.equal('{}') + }) +}) From 6a7418ad41305c7fbdee12d1513c0c32183b020e Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 15 Feb 2025 17:55:56 -0600 Subject: [PATCH 5/6] Fix:Edit book cover tab local images overflowing #3986 --- client/components/modals/item/tabs/Cover.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/components/modals/item/tabs/Cover.vue b/client/components/modals/item/tabs/Cover.vue index 69c11119..5b371fc4 100644 --- a/client/components/modals/item/tabs/Cover.vue +++ b/client/components/modals/item/tabs/Cover.vue @@ -1,7 +1,7 @@