From 2a30cc428f2dc1769ee08884e5ff76efefc0bc44 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 10 Mar 2022 18:45:02 -0600 Subject: [PATCH] New api routes, updating web client pages, audiobooks to libraryItem migration --- .../components/app/BookShelfCategorized.vue | 3 +- client/components/app/LazyBookshelf.vue | 2 +- client/components/cards/LazyBookCard.vue | 95 ++-- client/components/controls/OrderSelect.vue | 15 +- client/components/covers/BookCover.vue | 33 +- client/components/covers/GroupCover.vue | 2 +- client/components/covers/HoverBookCover.vue | 2 +- client/components/modals/EditModal.vue | 44 +- .../components/modals/edit-tabs/Chapters.vue | 6 +- client/components/modals/edit-tabs/Cover.vue | 53 +- .../components/modals/edit-tabs/Details.vue | 77 +-- .../components/modals/edit-tabs/Download.vue | 27 +- client/components/modals/edit-tabs/Files.vue | 43 +- client/components/modals/edit-tabs/Match.vue | 20 +- client/components/modals/edit-tabs/Tracks.vue | 110 ---- ...erFilesTable.vue => LibraryFilesTable.vue} | 42 +- client/components/tables/TracksTable.vue | 33 +- client/nuxt.config.js | 1 - client/pages/audiobook/_id/index.vue | 2 +- client/pages/item/_id/index.vue | 475 ++++++++++++++++++ client/players/CastPlayer.js | 2 +- client/plugins/chromecast.js | 2 +- client/store/audiobooks.js | 20 +- client/store/index.js | 21 +- client/store/libraries.js | 2 +- docs/LibraryItemModelDemo.js | 5 +- server/ApiController.js | 19 +- server/CacheManager.js | 6 +- server/Server.js | 57 +-- server/controllers/LibraryController.js | 120 ++++- server/controllers/LibraryItemController.js | 37 ++ server/finders/AuthorFinder.js | 2 +- server/objects/AudioFileMetadata.js | 129 ----- server/objects/LibraryItem.js | 74 ++- server/objects/entities/Book.js | 45 +- server/objects/entities/Podcast.js | 36 +- server/objects/files/LibraryFile.js | 13 +- server/objects/{ => legacy}/AudioFile.js | 4 +- server/objects/{ => legacy}/AudioTrack.js | 2 +- server/objects/{ => legacy}/Audiobook.js | 14 +- server/objects/{ => legacy}/AudiobookFile.js | 0 server/objects/{ => legacy}/Author.js | 4 +- server/objects/{ => legacy}/Book.js | 4 +- server/objects/metadata/BookMetadata.js | 50 +- server/objects/metadata/FileMetadata.js | 5 + server/objects/metadata/PodcastMetadata.js | 7 + server/scanner/AudioProbeData.js | 2 +- server/scanner/Scanner.js | 2 +- server/utils/dbMigration.js | 8 +- server/utils/globals.js | 4 +- server/utils/libraryHelpers.js | 98 +++- 51 files changed, 1225 insertions(+), 654 deletions(-) delete mode 100644 client/components/modals/edit-tabs/Tracks.vue rename client/components/tables/{OtherFilesTable.vue => LibraryFilesTable.vue} (65%) create mode 100644 client/pages/item/_id/index.vue create mode 100644 server/controllers/LibraryItemController.js delete mode 100644 server/objects/AudioFileMetadata.js rename server/objects/{ => legacy}/AudioFile.js (98%) rename server/objects/{ => legacy}/AudioTrack.js (98%) rename server/objects/{ => legacy}/Audiobook.js (98%) rename server/objects/{ => legacy}/AudiobookFile.js (100%) rename server/objects/{ => legacy}/Author.js (94%) rename server/objects/{ => legacy}/Book.js (99%) diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index e2119bf8..13a21c08 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -85,8 +85,9 @@ export default { }, async fetchCategories() { var categories = await this.$axios - .$get(`/api/libraries/${this.currentLibraryId}/categories?minified=1`) + .$get(`/api/libraries/${this.currentLibraryId}/personalized?minified=1`) .then((data) => { + console.log('Personalized data', data) return data }) .catch((error) => { diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index d3fdb388..ea692428 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -239,7 +239,7 @@ export default { this.currentSFQueryString = this.buildSearchParams() } - var entityPath = this.entityName === 'books' || this.entityName === 'series-books' ? `books/all` : this.entityName + var entityPath = this.entityName === 'books' || this.entityName === 'series-books' ? `items` : this.entityName var sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : '' var fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1` diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index 169fabd1..2a9cd2c0 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -8,7 +8,7 @@

- #{{ volumeNumber }} {{ displayTitle }} + {{ displayTitle }}

{{ displayAuthor }}

{{ displaySortLine }}

@@ -21,7 +21,7 @@

{{ title }}

- +
@@ -136,42 +136,45 @@ export default { showExperimentalFeatures() { return this.store.state.showExperimentalFeatures }, - _audiobook() { + _libraryItem() { return this.audiobook || {} }, - book() { - return this._audiobook.book || {} + media() { + return this._libraryItem.media || {} + }, + mediaMetadata() { + return this.media.metadata || {} }, placeholderUrl() { return '/book_placeholder.jpg' }, bookCoverSrc() { - return this.store.getters['audiobooks/getBookCoverSrc'](this._audiobook, this.placeholderUrl) + return this.store.getters['audiobooks/getLibraryItemCoverSrc'](this._libraryItem, this.placeholderUrl) }, - audiobookId() { - return this._audiobook.id + libraryItemId() { + return this._libraryItem.id }, series() { - return this.book.series + return this.mediaMetadata.series }, libraryId() { - return this._audiobook.libraryId + return this._libraryItem.libraryId }, hasEbook() { - return this._audiobook.numEbooks + return this.media.numEbooks }, hasTracks() { - return this._audiobook.numTracks + return this.media.numTracks }, processingBatch() { return this.store.state.processingBatch }, booksInSeries() { // Only added to audiobook object when collapseSeries is enabled - return this._audiobook.booksInSeries + return this._libraryItem.booksInSeries }, hasCover() { - return !!this.book.cover + return !!this.media.coverPath }, squareAspectRatio() { return this.bookCoverAspectRatio === 1 @@ -181,43 +184,49 @@ export default { return this.width / baseSize }, title() { - return this.book.title || '' + return this.mediaMetadata.title || '' }, playIconFontSize() { return Math.max(2, 3 * this.sizeMultiplier) }, - author() { - return this.book.author + authors() { + return this.mediaMetadata.authors || [] }, - authorFL() { - return this.book.authorFL || this.author + author() { + return this.authors.map((au) => au.name).join(', ') }, authorLF() { - return this.book.authorLF || this.author + return this.authors + .map((au) => { + var parts = au.name.split(' ') + if (parts.length === 1) return parts[0] + return `${parts[1]}, ${parts[0]}` + }) + .join(', ') }, volumeNumber() { - return this.book.volumeNumber || null + return this.mediaMetadata.volumeNumber || null }, displayTitle() { - if (this.orderBy === 'book.title' && this.sortingIgnorePrefix && this.title.toLowerCase().startsWith('the ')) { + if (this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix && this.title.toLowerCase().startsWith('the ')) { return this.title.substr(4) + ', The' } return this.title }, displayAuthor() { - if (this.orderBy === 'book.authorLF') return this.authorLF - return this.authorFL + if (this.orderBy === 'media.metadata.authorLF') return this.authorLF + return this.author }, displaySortLine() { - if (this.orderBy === 'mtimeMs') return 'Modified ' + this.$formatDate(this._audiobook.mtimeMs) - if (this.orderBy === 'birthtimeMs') return 'Born ' + this.$formatDate(this._audiobook.birthtimeMs) - if (this.orderBy === 'addedAt') return 'Added ' + this.$formatDate(this._audiobook.addedAt) - if (this.orderBy === 'duration') return 'Duration: ' + this.$elapsedPrettyExtended(this._audiobook.duration, false) - if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._audiobook.size) + if (this.orderBy === 'mtimeMs') return 'Modified ' + this.$formatDate(this._libraryItem.mtimeMs) + if (this.orderBy === 'birthtimeMs') return 'Born ' + this.$formatDate(this._libraryItem.birthtimeMs) + if (this.orderBy === 'addedAt') return 'Added ' + this.$formatDate(this._libraryItem.addedAt) + if (this.orderBy === 'duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false) + if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size) return null }, userProgress() { - return this.store.getters['user/getUserAudiobook'](this.audiobookId) + return this.store.getters['user/getUserAudiobook'](this.libraryItemId) }, userProgressPercent() { return this.userProgress ? this.userProgress.progress || 0 : 0 @@ -229,7 +238,7 @@ export default { return this.hasMissingParts || this.hasInvalidParts || this.isMissing || this.isInvalid }, isStreaming() { - return this.store.getters['getAudiobookIdStreaming'] === this.audiobookId + return this.store.getters['getlibraryItemIdStreaming'] === this.libraryItemId }, showReadButton() { return !this.isSelectionMode && this.showExperimentalFeatures && !this.showPlayButton && this.hasEbook @@ -241,16 +250,16 @@ export default { return !this.isSelectionMode && this.showExperimentalFeatures && this.hasEbook }, isMissing() { - return this._audiobook.isMissing + return this._libraryItem.isMissing }, isInvalid() { - return this._audiobook.isInvalid + return this._libraryItem.isInvalid }, hasMissingParts() { - return this._audiobook.hasMissingParts + return this._libraryItem.hasMissingParts }, hasInvalidParts() { - return this._audiobook.hasInvalidParts + return this._libraryItem.hasInvalidParts }, errorText() { if (this.isMissing) return 'Audiobook directory is missing!' @@ -349,11 +358,11 @@ export default { return this.title }, authorCleaned() { - if (!this.authorFL) return '' - if (this.authorFL.length > 30) { - return this.authorFL.slice(0, 27) + '...' + if (!this.author) return '' + if (this.author.length > 30) { + return this.author.slice(0, 27) + '...' } - return this.authorFL + return this.author }, isAlternativeBookshelfView() { var constants = this.$constants || this.$nuxt.$constants @@ -382,7 +391,7 @@ export default { var router = this.$router || this.$nuxt.$router if (router) { if (this.booksInSeries) router.push(`/library/${this.libraryId}/series/${this.$encode(this.series)}`) - else router.push(`/audiobook/${this.audiobookId}`) + else router.push(`/item/${this.libraryItemId}`) } } }, @@ -398,7 +407,7 @@ export default { var toast = this.$toast || this.$nuxt.$toast var axios = this.$axios || this.$nuxt.$axios axios - .$patch(`/api/me/audiobook/${this.audiobookId}`, updatePayload) + .$patch(`/api/me/audiobook/${this.libraryItemId}`, updatePayload) .then(() => { this.isProcessingReadUpdate = false toast.success(`"${this.title}" Marked as ${updatePayload.isRead ? 'Read' : 'Not Read'}`) @@ -425,7 +434,7 @@ export default { rescan() { this.rescanning = true this._socket.once('audiobook_scan_complete', this.audiobookScanComplete) - this._socket.emit('scan_audiobook', this.audiobookId) + this._socket.emit('scan_libraryItem', this.libraryItemId) }, showEditModalTracks() { // More menu func @@ -503,7 +512,7 @@ export default { }, play() { var eventBus = this.$eventBus || this.$nuxt.$eventBus - eventBus.$emit('play-audiobook', this.audiobookId) + eventBus.$emit('play-audiobook', this.libraryItemId) }, mouseover() { this.isHovering = true diff --git a/client/components/controls/OrderSelect.vue b/client/components/controls/OrderSelect.vue index a230fad8..faf3e8ab 100644 --- a/client/components/controls/OrderSelect.vue +++ b/client/components/controls/OrderSelect.vue @@ -34,27 +34,23 @@ export default { items: [ { text: 'Title', - value: 'book.title' + value: 'media.metadata.title' }, { text: 'Author (First Last)', - value: 'book.authorFL' + value: 'media.metadata.author' }, { text: 'Author (Last, First)', - value: 'book.authorLF' + value: 'media.metadata.authorLF' }, { text: 'Added At', value: 'addedAt' }, - { - text: 'Volume #', - value: 'book.volumeNumber' - }, { text: 'Duration', - value: 'duration' + value: 'media.duration' }, { text: 'Size', @@ -89,7 +85,8 @@ export default { } }, selectedText() { - var _selected = this.selected === 'book.author' ? 'book.authorFL' : this.selected + var _selected = this.selected + if (this.selected.startsWith('book.')) _selected = _selected.replace('book.', 'media.metadata.') var _sel = this.items.find((i) => i.value === _selected) if (!_sel) return '' return _sel.text diff --git a/client/components/covers/BookCover.vue b/client/components/covers/BookCover.vue index 0dce1307..ddbefbb1 100644 --- a/client/components/covers/BookCover.vue +++ b/client/components/covers/BookCover.vue @@ -5,8 +5,8 @@
- -
+ +

{{ title }}

@@ -44,11 +44,10 @@ \ No newline at end of file diff --git a/client/components/tables/OtherFilesTable.vue b/client/components/tables/LibraryFilesTable.vue similarity index 65% rename from client/components/tables/OtherFilesTable.vue rename to client/components/tables/LibraryFilesTable.vue index 2b334e59..92aa907c 100644 --- a/client/components/tables/OtherFilesTable.vue +++ b/client/components/tables/LibraryFilesTable.vue @@ -1,14 +1,11 @@