@@ -214,7 +214,7 @@ export default {
return this.title
},
displayAuthor() {
- if (this.orderBy === 'media.metadata.authorLF') return this.authorLF
+ if (this.orderBy === 'media.metadata.authorNameLF') return this.authorLF
return this.author
},
displaySortLine() {
@@ -226,13 +226,13 @@ export default {
return null
},
userProgress() {
- return this.store.getters['user/getUserAudiobook'](this.libraryItemId)
+ return this.store.getters['user/getUserLibraryItemProgress'](this.libraryItemId)
},
userProgressPercent() {
return this.userProgress ? this.userProgress.progress || 0 : 0
},
- userIsRead() {
- return this.userProgress ? !!this.userProgress.isRead : false
+ itemIsFinished() {
+ return this.userProgress ? !!this.userProgress.isFinished : false
},
showError() {
return this.hasMissingParts || this.hasInvalidParts || this.isMissing || this.isInvalid
@@ -302,7 +302,7 @@ export default {
var items = [
{
func: 'toggleRead',
- text: `Mark as ${this.userIsRead ? 'Not Read' : 'Read'}`
+ text: `Mark as ${this.itemIsFinished ? 'Not Read' : 'Read'}`
},
{
func: 'openCollections',
@@ -401,7 +401,7 @@ export default {
toggleRead() {
// More menu func
var updatePayload = {
- isRead: !this.userIsRead
+ isFinished: !this.itemIsFinished
}
this.isProcessingReadUpdate = true
var toast = this.$toast || this.$nuxt.$toast
@@ -410,12 +410,12 @@ export default {
.$patch(`/api/me/audiobook/${this.libraryItemId}`, updatePayload)
.then(() => {
this.isProcessingReadUpdate = false
- toast.success(`"${this.title}" Marked as ${updatePayload.isRead ? 'Read' : 'Not Read'}`)
+ toast.success(`"${this.title}" Marked as ${updatePayload.isFinished ? 'Read' : 'Not Read'}`)
})
.catch((error) => {
console.error('Failed', error)
this.isProcessingReadUpdate = false
- toast.error(`Failed to mark as ${updatePayload.isRead ? 'Read' : 'Not Read'}`)
+ toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Read' : 'Not Read'}`)
})
},
itemScanComplete(result) {
diff --git a/client/components/controls/FilterSelect.vue b/client/components/controls/FilterSelect.vue
index 041c465f..5908f388 100644
--- a/client/components/controls/FilterSelect.vue
+++ b/client/components/controls/FilterSelect.vue
@@ -139,7 +139,18 @@ export default {
if (!this.selected) return ''
var parts = this.selected.split('.')
if (parts.length > 1) {
- return this.$decode(parts[1])
+ var decoded = this.$decode(parts[1])
+ if (decoded.startsWith('aut_')) {
+ var author = this.authors.find((au) => au.id == decoded)
+ if (author) return author.name
+ return ''
+ }
+ if (decoded.startsWith('ser_')) {
+ var series = this.series.find((se) => se.id == decoded)
+ if (series) return series.name
+ return ''
+ }
+ return decoded
}
var _sel = this.items.find((i) => i.value === this.selected)
if (!_sel) return ''
@@ -176,7 +187,7 @@ export default {
} else {
return {
text: item.name,
- value: item.id
+ value: this.$encode(item.id)
}
}
})
diff --git a/client/components/controls/OrderSelect.vue b/client/components/controls/OrderSelect.vue
index f963038c..4a6599a6 100644
--- a/client/components/controls/OrderSelect.vue
+++ b/client/components/controls/OrderSelect.vue
@@ -38,20 +38,20 @@ export default {
},
{
text: 'Author (First Last)',
- value: 'media.metadata.author'
+ value: 'media.metadata.authorName'
},
{
text: 'Author (Last, First)',
- value: 'media.metadata.authorLF'
+ value: 'media.metadata.authorNameLF'
},
{
text: 'Added At',
value: 'addedAt'
},
- {
- text: 'Duration',
- value: 'media.duration'
- },
+ // {
+ // text: 'Duration',
+ // value: 'media.duration'
+ // },
{
text: 'Size',
value: 'size'
diff --git a/client/components/modals/item/EditModal.vue b/client/components/modals/item/EditModal.vue
index 7ab2c38f..923ec0d9 100644
--- a/client/components/modals/item/EditModal.vue
+++ b/client/components/modals/item/EditModal.vue
@@ -52,11 +52,11 @@ export default {
title: 'Files',
component: 'modals-item-tabs-files'
},
- {
- id: 'download',
- title: 'Download',
- component: 'modals-item-tabs-download'
- },
+ // {
+ // id: 'download',
+ // title: 'Download',
+ // component: 'modals-item-tabs-download'
+ // },
{
id: 'match',
title: 'Match',
diff --git a/client/components/modals/item/tabs/Chapters.vue b/client/components/modals/item/tabs/Chapters.vue
index e7f5a636..d9470847 100644
--- a/client/components/modals/item/tabs/Chapters.vue
+++ b/client/components/modals/item/tabs/Chapters.vue
@@ -1,30 +1,36 @@
-
No Chapters
-
-
- Id |
- Title |
- Start |
- End |
-
-
-
-
- {{ chapter.id }}
- |
-
- {{ chapter.title }}
- |
-
- {{ $secondsToTimestamp(chapter.start) }}
- |
-
- {{ $secondsToTimestamp(chapter.end) }}
- |
-
-
-
+
No Audiobooks
+
+
+
+
Audiobook Chapters ({{ audiobook.name }})
+
+
No Chapters
+
+
+ Id |
+ Title |
+ Start |
+ End |
+
+
+
+ {{ chapter.id }}
+ |
+
+ {{ chapter.title }}
+ |
+
+ {{ $secondsToTimestamp(chapter.start) }}
+ |
+
+ {{ $secondsToTimestamp(chapter.end) }}
+ |
+
+
+
+
@@ -37,23 +43,16 @@ export default {
}
},
data() {
- return {
- chapters: []
+ return {}
+ },
+ computed: {
+ media() {
+ return this.libraryItem ? this.libraryItem.media || {} : {}
+ },
+ audiobooks() {
+ return this.media.audiobooks || []
}
},
- watch: {
- libraryItem: {
- immediate: true,
- handler(newVal) {
- if (newVal) this.init()
- }
- }
- },
- computed: {},
- methods: {
- init() {
- this.chapters = this.libraryItem.chapters || []
- }
- }
+ methods: {}
}
\ No newline at end of file
diff --git a/client/components/modals/item/tabs/Files.vue b/client/components/modals/item/tabs/Files.vue
index df17b441..ab3a2665 100644
--- a/client/components/modals/item/tabs/Files.vue
+++ b/client/components/modals/item/tabs/Files.vue
@@ -1,47 +1,8 @@
-
-
-
-
Audio Tracks
-
- {{ tracks.length }}
-
-
-
Full Path
-
- Manage Tracks
-
-
-
-
- # |
- Filename |
- Size |
- Duration |
- Download |
-
-
-
-
- {{ track.index }}
- |
- {{ showFullPath ? track.metadata.path : track.metadata.filename }} |
-
- {{ $bytesPretty(track.metadata.size) }}
- |
-
- {{ $secondsToTimestamp(track.duration) }}
- |
-
- download
- |
-
-
-
-
-
No Audio Tracks
-
+
+
+
@@ -91,8 +52,11 @@ export default {
showDownload() {
return this.userCanDownload && !this.isMissing
},
- hasTracks() {
- return this.tracks.length
+ audiobooks() {
+ return this.media.audiobooks || []
+ },
+ ebooks() {
+ return this.media.ebooks || []
}
},
methods: {
diff --git a/client/components/tables/AudioFilesTable.vue b/client/components/tables/AudioFilesTable.vue
index 909e78ea..b1201bd1 100644
--- a/client/components/tables/AudioFilesTable.vue
+++ b/client/components/tables/AudioFilesTable.vue
@@ -4,7 +4,7 @@
Other Audio Files
{{ files.length }}
-
+
Manage Tracks
diff --git a/client/components/tables/TracksTable.vue b/client/components/tables/TracksTable.vue
index 69b46639..f05e4415 100644
--- a/client/components/tables/TracksTable.vue
+++ b/client/components/tables/TracksTable.vue
@@ -1,14 +1,14 @@
-
Audio Tracks
+
{{ title }}
{{ tracks.length }}
Full Path
-
+
Manage Tracks
@@ -38,7 +38,7 @@
{{ $secondsToTimestamp(track.duration) }}
- download
+ download
|
@@ -51,11 +51,15 @@
\ No newline at end of file
diff --git a/client/pages/item/_id/edit.vue b/client/pages/audiobook/_id/edit.vue
similarity index 93%
rename from client/pages/item/_id/edit.vue
rename to client/pages/audiobook/_id/edit.vue
index a2384714..322b7237 100644
--- a/client/pages/item/_id/edit.vue
+++ b/client/pages/audiobook/_id/edit.vue
@@ -95,17 +95,19 @@ export default {
if (!store.getters['user/getUserCanUpdate']) {
return redirect('/?error=unauthorized')
}
- var libraryItem = await app.$axios.$get(`/api/items/${params.id}?extended=1`).catch((error) => {
+ var payload = await app.$axios.$get(`/api/audiobooks/${params.id}/item?expanded=1`).catch((error) => {
console.error('Failed', error)
return false
})
- if (!libraryItem) {
- console.error('No item...', params.id)
+ if (!payload) {
+ console.error('Not found...', params.id)
return redirect('/')
}
+ const audiobook = payload.audiobook
return {
- libraryItem,
- files: libraryItem.media.audioFiles ? libraryItem.media.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : []
+ audiobook,
+ libraryItem: payload.libraryItem,
+ files: audiobook.audioFiles ? audiobook.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : []
}
},
data() {
@@ -128,7 +130,7 @@ export default {
return this.media.metadata || []
},
audioFiles() {
- return this.media.audioFiles || []
+ return this.audiobook.audioFiles || []
},
numExcluded() {
var count = 0
@@ -138,7 +140,7 @@ export default {
return count
},
missingParts() {
- return this.media.missingParts || []
+ return this.audiobook.missingParts || []
},
libraryItemId() {
return this.libraryItem.id
@@ -150,7 +152,7 @@ export default {
return this.mediaMetadata.authorName || 'Unknown'
},
tracks() {
- return this.media.tracks
+ return this.audiobook.tracks
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
@@ -216,7 +218,7 @@ export default {
this.saving = true
this.$axios
- .$patch(`/api/items/${this.libraryItemId}/tracks`, { orderedFileData })
+ .$patch(`/api/audiobooks/${this.audiobook.id}/tracks`, { orderedFileData })
.then((data) => {
console.log('Finished patching files', data)
this.saving = false
diff --git a/client/pages/item/_id/index.vue b/client/pages/item/_id/index.vue
index 22e48175..c99e06ae 100644
--- a/client/pages/item/_id/index.vue
+++ b/client/pages/item/_id/index.vue
@@ -117,7 +117,7 @@
{{ isMissing ? 'Missing' : 'Incomplete' }}
-
+
auto_stories
Read
@@ -143,26 +143,9 @@
{{ description }}
-
-
- Missing Parts ({{ missingParts.length }})
-
-
{{ missingPartChunks.join(', ') }}
-
-
-
-
- Invalid Parts ({{ invalidParts.length }})
-
-
-
{{ part.filename }}: {{ part.error }}
-
-
-
-
-
-
-
+
+
+
@@ -219,37 +202,6 @@ export default {
showExperimentalFeatures() {
return this.$store.state.showExperimentalFeatures
},
- missingPartChunks() {
- if (this.missingParts === 1) return this.missingParts[0]
- var chunks = []
-
- var currentIndex = this.missingParts[0]
- var currentChunk = [this.missingParts[0]]
-
- for (let i = 1; i < this.missingParts.length; i++) {
- var partIndex = this.missingParts[i]
- if (currentIndex === partIndex - 1) {
- currentChunk.push(partIndex)
- currentIndex = partIndex
- } else {
- // console.log('Chunk ended', currentChunk.join(', '), currentIndex, partIndex)
- if (currentChunk.length === 0) {
- console.error('How is current chunk 0?', currentChunk.join(', '))
- }
- chunks.push(currentChunk)
- currentChunk = [partIndex]
- currentIndex = partIndex
- }
- }
- if (currentChunk.length) {
- chunks.push(currentChunk)
- }
- chunks = chunks.map((chunk) => {
- if (chunk.length === 1) return chunk[0]
- else return `${chunk[0]}-${chunk[chunk.length - 1]}`
- })
- return chunks
- },
isMissing() {
return this.libraryItem.isMissing
},
@@ -257,13 +209,7 @@ export default {
return this.libraryItem.isInvalid
},
showPlayButton() {
- return !this.isMissing && !this.isInvalid && this.tracks.length
- },
- missingParts() {
- return this.libraryItem.missingParts || []
- },
- invalidParts() {
- return this.libraryItem.invalidParts || []
+ return !this.isMissing && !this.isInvalid && this.audiobooks.length
},
libraryId() {
return this.libraryItem.libraryId
@@ -280,6 +226,9 @@ export default {
mediaMetadata() {
return this.media.metadata || {}
},
+ audiobooks() {
+ return this.media.audiobooks || []
+ },
title() {
return this.mediaMetadata.title || 'No Title'
},
@@ -341,14 +290,11 @@ export default {
return this.media.audioFiles || []
},
ebooks() {
- return this.media.ebookFiles
+ return this.media.ebooks || []
},
showExperimentalReadAlert() {
return !this.tracks.length && this.ebooks.length && !this.showExperimentalFeatures
},
- numEbooks() {
- return this.media.numEbooks
- },
description() {
return this.mediaMetadata.description || ''
},
diff --git a/client/store/user.js b/client/store/user.js
index de1f2f6a..a9b141a9 100644
--- a/client/store/user.js
+++ b/client/store/user.js
@@ -22,8 +22,9 @@ export const getters = {
getToken: (state) => {
return state.user ? state.user.token : null
},
- getUserAudiobook: (state) => (audiobookId) => {
- return state.user && state.user.audiobooks ? state.user.audiobooks[audiobookId] || null : null
+ getUserLibraryItemProgress: (state) => (libraryItemId) => {
+ if (!state.user.libraryItemProgress) return null
+ return state.user.libraryItemProgress.find(li => li.id == libraryItemId)
},
getUserSetting: (state) => (key) => {
return state.settings ? state.settings[key] : null
diff --git a/server/ApiController.js b/server/ApiController.js
index 51b06d12..ae03bc06 100644
--- a/server/ApiController.js
+++ b/server/ApiController.js
@@ -16,6 +16,7 @@ const BackupController = require('./controllers/BackupController')
const LibraryItemController = require('./controllers/LibraryItemController')
const SeriesController = require('./controllers/SeriesController')
const AuthorController = require('./controllers/AuthorController')
+const AudiobookController = require('./controllers/AudiobookController')
const BookFinder = require('./finders/BookFinder')
const AuthorFinder = require('./finders/AuthorFinder')
@@ -70,6 +71,13 @@ class ApiController {
this.router.post('/libraries/order', LibraryController.reorder.bind(this))
+ //
+ // Audiobook Routes
+ //
+ this.router.get('/audiobooks/:id', AudiobookController.middleware.bind(this), AudiobookController.findOne.bind(this))
+ this.router.get('/audiobooks/:id/item', AudiobookController.middleware.bind(this), AudiobookController.findWithItem.bind(this))
+ this.router.patch('/audiobooks/:id/tracks', AudiobookController.middleware.bind(this), AudiobookController.updateTracks.bind(this))
+
//
// Item Routes
//
@@ -84,7 +92,6 @@ class ApiController {
this.router.patch('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.updateCover.bind(this))
this.router.delete('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.removeCover.bind(this))
this.router.post('/items/:id/match', LibraryItemController.middleware.bind(this), LibraryItemController.match.bind(this))
- this.router.patch('/items/:id/tracks', LibraryItemController.middleware.bind(this), LibraryItemController.updateTracks.bind(this))
this.router.get('/items/:id/play', LibraryItemController.middleware.bind(this), LibraryItemController.startPlaybackSession.bind(this))
this.router.post('/items/batch/delete', LibraryItemController.batchDelete.bind(this))
diff --git a/server/CacheManager.js b/server/CacheManager.js
index f84052bb..1421830b 100644
--- a/server/CacheManager.js
+++ b/server/CacheManager.js
@@ -36,6 +36,10 @@ class CacheManager {
// Write cache
await fs.ensureDir(this.CoverCachePath)
+ if (!libraryItem.media.coverPath || !await fs.pathExists(libraryItem.media.coverPath)) {
+ return res.sendStatus(404)
+ }
+
let writtenFile = await resizeImage(libraryItem.media.coverPath, path, width, height)
if (!writtenFile) return res.sendStatus(400)
diff --git a/server/Server.js b/server/Server.js
index 890181aa..55aa9e46 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -219,7 +219,7 @@ class Server {
// Client dynamic routes
app.get('/item/:id', (req, res) => res.sendFile(Path.join(distPath, 'index.html')))
- app.get('/item/:id/edit', (req, res) => res.sendFile(Path.join(distPath, 'index.html')))
+ app.get('/audiobook/:id/edit', (req, res) => res.sendFile(Path.join(distPath, 'index.html')))
app.get('/library/:library', (req, res) => res.sendFile(Path.join(distPath, 'index.html')))
app.get('/library/:library/search', (req, res) => res.sendFile(Path.join(distPath, 'index.html')))
app.get('/library/:library/bookshelf/:id?', (req, res) => res.sendFile(Path.join(distPath, 'index.html')))
diff --git a/server/controllers/AudiobookController.js b/server/controllers/AudiobookController.js
new file mode 100644
index 00000000..71e3a481
--- /dev/null
+++ b/server/controllers/AudiobookController.js
@@ -0,0 +1,57 @@
+const Logger = require('../Logger')
+
+class AudiobookController {
+ constructor() { }
+
+ async findOne(req, res) {
+ if (req.query.expanded == 1) return res.json(req.audiobook.toJSONExpanded())
+ return res.json(req.audiobook)
+ }
+
+ async findWithItem(req, res) {
+ if (req.query.expanded == 1) {
+ return res.json({
+ libraryItem: req.libraryItem.toJSONExpanded(),
+ audiobook: req.audiobook.toJSONExpanded()
+ })
+ }
+ res.json({
+ libraryItem: req.libraryItem.toJSON(),
+ audiobook: req.audiobook.toJSON()
+ })
+ }
+
+ // PATCH: api/audiobooks/:id/tracks
+ async updateTracks(req, res) {
+ var libraryItem = req.libraryItem
+ var audiobook = req.audiobook
+ var orderedFileData = req.body.orderedFileData
+ audiobook.updateAudioTracks(orderedFileData)
+ await this.db.updateLibraryItem(libraryItem)
+ this.emitter('item_updated', libraryItem.toJSONExpanded())
+ res.json(libraryItem.toJSON())
+ }
+
+ middleware(req, res, next) {
+ var audiobook = null
+ var libraryItem = this.db.libraryItems.find(li => {
+ if (li.mediaType != 'book') return false
+ audiobook = li.media.getAudiobookById(req.params.id)
+ return !!audiobook
+ })
+ if (!audiobook) return res.sendStatus(404)
+
+ if (req.method == 'DELETE' && !req.user.canDelete) {
+ Logger.warn(`[AudiobookController] User attempted to delete without permission`, req.user)
+ return res.sendStatus(403)
+ } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
+ Logger.warn('[AudiobookController] User attempted to update without permission', req.user)
+ return res.sendStatus(403)
+ }
+
+ req.libraryItem = libraryItem
+ req.audiobook = audiobook
+ next()
+ }
+}
+module.exports = new AudiobookController()
\ No newline at end of file
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index 50dfe75a..02694e52 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -146,7 +146,7 @@ class LibraryController {
if (payload.sortBy) {
var sortKey = payload.sortBy
- // old sort key
+ // old sort key TODO: should be mutated in dbMigration
if (sortKey.startsWith('book.')) {
sortKey = sortKey.replace('book.', 'media.metadata.')
}
@@ -263,26 +263,26 @@ class LibraryController {
var limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
var minified = req.query.minified === '1'
- var booksWithUserAb = libraryHelpers.getBooksWithUserAudiobook(req.user, libraryItems)
+ var itemsWithUserProgress = libraryHelpers.getItemsWithUserProgress(req.user, libraryItems)
var categories = [
{
id: 'continue-reading',
label: 'Continue Reading',
- type: 'books',
- entities: libraryHelpers.getBooksMostRecentlyRead(booksWithUserAb, limitPerShelf, minified)
+ type: req.library.mediaType,
+ entities: libraryHelpers.getItemsMostRecentlyListened(itemsWithUserProgress, limitPerShelf, minified)
},
{
id: 'recently-added',
label: 'Recently Added',
- type: 'books',
- entities: libraryHelpers.getBooksMostRecentlyAdded(libraryItems, limitPerShelf, minified)
+ type: req.library.mediaType,
+ entities: libraryHelpers.getItemsMostRecentlyAdded(libraryItems, limitPerShelf, minified)
},
{
id: 'read-again',
label: 'Read Again',
- type: 'books',
- entities: libraryHelpers.getBooksMostRecentlyFinished(booksWithUserAb, limitPerShelf, minified)
+ type: req.library.mediaType,
+ entities: libraryHelpers.getItemsMostRecentlyFinished(itemsWithUserProgress, limitPerShelf, minified)
}
].filter(cats => { // Remove categories with no items
return cats.entities.length
@@ -299,7 +299,7 @@ class LibraryController {
var limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
var minified = req.query.minified === '1'
- var booksWithUserAb = libraryHelpers.getBooksWithUserAudiobook(req.user, books)
+ var booksWithUserAb = libraryHelpers.getItemsWithUserProgress(req.user, books)
var series = libraryHelpers.getSeriesFromBooks(books, minified)
var seriesWithUserAb = libraryHelpers.getSeriesWithProgressFromBooks(req.user, books)
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index b234ef32..5ec64a4c 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -156,17 +156,6 @@ class LibraryItemController {
res.json(matchResult)
}
- // PATCH: api/items/:id/tracks
- async updateTracks(req, res) {
- var libraryItem = req.libraryItem
- var orderedFileData = req.body.orderedFileData
- Logger.info(`Updating item tracks called ${libraryItem.id}`)
- libraryItem.media.updateAudioTracks(orderedFileData)
- await this.db.updateLibraryItem(libraryItem)
- this.emitter('item_updated', libraryItem.toJSONExpanded())
- res.json(libraryItem.toJSON())
- }
-
// POST: api/items/batch/delete
async batchDelete(req, res) {
if (!req.user.canDelete) {
diff --git a/server/objects/LibraryItem.js b/server/objects/LibraryItem.js
index 3466f6eb..f763feae 100644
--- a/server/objects/LibraryItem.js
+++ b/server/objects/LibraryItem.js
@@ -106,7 +106,8 @@ class LibraryItem {
isInvalid: !!this.isInvalid,
mediaType: this.mediaType,
media: this.media.toJSONMinified(),
- numFiles: this.libraryFiles.length
+ numFiles: this.libraryFiles.length,
+ size: this.size
}
}
diff --git a/server/objects/legacy/Book.js b/server/objects/legacy/Book.js
index 092d04df..9281dd27 100644
--- a/server/objects/legacy/Book.js
+++ b/server/objects/legacy/Book.js
@@ -137,7 +137,7 @@ class Book {
return hasUpdated
}
try {
- var { authorLF, authorFL } = parseAuthors(author)
+ var { authorLF, authorFL } = parseAuthors.parse(author)
var hasUpdated = authorLF !== this.authorLF || authorFL !== this.authorFL
this.authorFL = authorFL || null
this.authorLF = authorLF || null
@@ -155,7 +155,7 @@ class Book {
return hasUpdated
}
try {
- var { authorFL } = parseAuthors(narrator)
+ var { authorFL } = parseAuthors.parse(narrator)
var hasUpdated = authorFL !== this.narratorFL
this.narratorFL = authorFL || null
return hasUpdated
diff --git a/server/objects/mediaTypes/Book.js b/server/objects/mediaTypes/Book.js
index 054f98dc..127b53fb 100644
--- a/server/objects/mediaTypes/Book.js
+++ b/server/objects/mediaTypes/Book.js
@@ -116,6 +116,10 @@ class Book {
return true
}
+ getAudiobookById(audiobookId) {
+ return this.audiobooks.find(ab => ab.id === audiobookId)
+ }
+
removeFileWithInode(inode) {
var audiobookWithIno = this.audiobooks.find(ab => ab.findFileWithInode(inode))
if (audiobookWithIno) {
diff --git a/server/objects/metadata/BookMetadata.js b/server/objects/metadata/BookMetadata.js
index c7a64551..c2988395 100644
--- a/server/objects/metadata/BookMetadata.js
+++ b/server/objects/metadata/BookMetadata.js
@@ -76,6 +76,7 @@ class BookMetadata {
language: this.language,
explicit: this.explicit,
authorName: this.authorName,
+ authorNameLF: this.authorNameLF,
narratorName: this.narratorName
}
}
@@ -95,6 +96,10 @@ class BookMetadata {
if (!this.authors.length) return ''
return this.authors.map(au => au.name).join(', ')
}
+ get authorNameLF() { // Last, First
+ if (!this.authors.length) return ''
+ return this.authors.map(au => parseNameString.nameToLastFirst(au.name)).join(', ')
+ }
get seriesName() {
if (!this.series.length) return ''
return this.series.map(se => {
@@ -243,13 +248,13 @@ class BookMetadata {
// Returns array of names in First Last format
parseNarratorsTag(narratorsTag) {
- var parsed = parseNameString(narratorsTag)
+ var parsed = parseNameString.parse(narratorsTag)
return parsed ? parsed.names : []
}
// Return array of authors minified with placeholder id
parseAuthorsTag(authorsTag) {
- var parsed = parseNameString(authorsTag)
+ var parsed = parseNameString.parse(authorsTag)
if (!parsed) return []
return (parsed.names || []).map((au) => {
return {
diff --git a/server/objects/user/LibraryItemProgress.js b/server/objects/user/LibraryItemProgress.js
index 9455a8dd..f9df5ed4 100644
--- a/server/objects/user/LibraryItemProgress.js
+++ b/server/objects/user/LibraryItemProgress.js
@@ -3,12 +3,12 @@ const Logger = require('../../Logger')
class LibraryItemProgress {
constructor(progress) {
this.id = null // Same as library item id
- this.libararyItemId = null
+ this.libraryItemId = null
this.totalDuration = null // seconds
this.progress = null // 0 to 1
this.currentTime = null // seconds
- this.isRead = false
+ this.isFinished = false
this.lastUpdate = null
this.startedAt = null
@@ -22,11 +22,11 @@ class LibraryItemProgress {
toJSON() {
return {
id: this.id,
- libararyItemId: this.libararyItemId,
+ libraryItemId: this.libraryItemId,
totalDuration: this.totalDuration,
progress: this.progress,
currentTime: this.currentTime,
- isRead: this.isRead,
+ isFinished: this.isFinished,
lastUpdate: this.lastUpdate,
startedAt: this.startedAt,
finishedAt: this.finishedAt
@@ -35,11 +35,11 @@ class LibraryItemProgress {
construct(progress) {
this.id = progress.id
- this.libararyItemId = progress.libararyItemId
+ this.libraryItemId = progress.libraryItemId
this.totalDuration = progress.totalDuration
this.progress = progress.progress
this.currentTime = progress.currentTime
- this.isRead = !!progress.isRead
+ this.isFinished = !!progress.isFinished
this.lastUpdate = progress.lastUpdate
this.startedAt = progress.startedAt
this.finishedAt = progress.finishedAt || null
@@ -59,11 +59,11 @@ class LibraryItemProgress {
// If has < 10 seconds remaining mark as read
var timeRemaining = this.totalDuration - this.currentTime
if (timeRemaining < 10) {
- this.isRead = true
+ this.isFinished = true
this.progress = 1
this.finishedAt = Date.now()
} else {
- this.isRead = false
+ this.isFinished = false
this.finishedAt = null
}
}
@@ -72,7 +72,7 @@ class LibraryItemProgress {
var hasUpdates = false
for (const key in payload) {
if (this[key] !== undefined && payload[key] !== this[key]) {
- if (key === 'isRead') {
+ if (key === 'isFinished') {
if (!payload[key]) { // Updating to Not Read - Reset progress and current time
this.finishedAt = null
this.progress = 0
diff --git a/server/objects/user/User.js b/server/objects/user/User.js
index e4ce5238..0b637259 100644
--- a/server/objects/user/User.js
+++ b/server/objects/user/User.js
@@ -1,5 +1,4 @@
const Logger = require('../../Logger')
-const { isObject } = require('../../utils')
const AudioBookmark = require('./AudioBookmark')
const LibraryItemProgress = require('./LibraryItemProgress')
diff --git a/server/utils/dbMigration.js b/server/utils/dbMigration.js
index 846bf06d..965487db 100644
--- a/server/utils/dbMigration.js
+++ b/server/utils/dbMigration.js
@@ -245,6 +245,27 @@ async function migrateLibraryItems(db) {
var libraryItems = audiobooks.map((ab) => makeLibraryItemFromOldAb(ab))
+ // User library item progress was using the auidobook ID when migrated
+ // now that library items are created the LibraryItemProgress objects
+ // need the library item id to be set
+ for (const user of db.users) {
+ if (user.libraryItemProgress.length) {
+ user.libraryItemProgress = user.libraryItemProgress.map(lip => {
+ var audiobookId = lip.id
+ var libraryItemWithAudiobook = libraryItems.find(li => li.media.getAudiobookById && !!li.media.getAudiobookById(audiobookId))
+ if (!libraryItemWithAudiobook) {
+ Logger.error('[dbMigration] Failed to find library item with audiobook id', audiobookId)
+ return null
+ }
+ lip.id = libraryItemWithAudiobook.id
+ lip.libraryItemId = libraryItemWithAudiobook.id
+ return lip
+ }).filter(lip => !!lip)
+ await db.updateEntity('user', user)
+ Logger.debug(`>>> User ${user.username} with ${user.libraryItemProgress.length} progress entries were updated`)
+ }
+ }
+
Logger.info(`>>> ${libraryItems.length} Library Items made`)
await db.insertEntities('libraryItem', libraryItems)
if (authorsToAdd.length) {
@@ -286,8 +307,9 @@ function cleanUserObject(db, userObj) {
var userAudiobookData = new UserAudiobookData(userObj.audiobooks[audiobookId]) // Legacy object
var liProgress = new LibraryItemProgress() // New Progress Object
- liProgress.id = userAudiobookData.audiobookId
+ liProgress.id = userAudiobookData.audiobookId // This ID is INCORRECT, will be updated when library item is created
liProgress.libraryItemId = userAudiobookData.audiobookId
+ liProgress.isFinished = !!userAudiobookData.isRead
Object.keys(liProgress.toJSON()).forEach((key) => {
if (userAudiobookData[key] !== undefined) {
liProgress[key] = userAudiobookData[key]
diff --git a/server/utils/libraryHelpers.js b/server/utils/libraryHelpers.js
index 85e98f7d..4bbc2843 100644
--- a/server/utils/libraryHelpers.js
+++ b/server/utils/libraryHelpers.js
@@ -194,21 +194,21 @@ module.exports = {
})
},
- getBooksWithUserAudiobook(user, books) {
- return books.map(book => {
+ getItemsWithUserProgress(user, libraryItems) {
+ return libraryItems.map(li => {
return {
- userAudiobook: user.getLibraryItemProgress(book.id),
- book
+ userProgress: user.getLibraryItemProgress(li.id),
+ libraryItem: li
}
- }).filter(b => !!b.userAudiobook)
+ }).filter(b => !!b.userProgress)
},
- getBooksMostRecentlyRead(booksWithUserAb, limit, minified = false) {
- var booksWithProgress = booksWithUserAb.filter((data) => data.userAudiobook && data.userAudiobook.progress > 0 && !data.userAudiobook.isRead)
- booksWithProgress.sort((a, b) => {
- return b.userAudiobook.lastUpdate - a.userAudiobook.lastUpdate
+ getItemsMostRecentlyListened(itemsWithUserProgress, limit, minified = false) {
+ var itemsInProgress = itemsWithUserProgress.filter((data) => data.userProgress && data.userProgress.progress > 0 && !data.userProgress.isFinished)
+ itemsInProgress.sort((a, b) => {
+ return b.userProgress.lastUpdate - a.userProgress.lastUpdate
})
- return booksWithProgress.map(b => minified ? b.book.toJSONMinified() : b.book.toJSONExpanded()).slice(0, limit)
+ return itemsInProgress.map(b => minified ? b.libraryItem.toJSONMinified() : b.libraryItem.toJSONExpanded()).slice(0, limit)
},
getBooksNextInSeries(seriesWithUserAb, limit, minified = false) {
@@ -223,17 +223,17 @@ module.exports = {
return booksNextInSeries.sort((a, b) => { return b.DateLastReadSeries - a.DateLastReadSeries }).map(b => minified ? b.book.toJSONMinified() : b.book.toJSONExpanded()).slice(0, limit)
},
- getBooksMostRecentlyAdded(books, limit, minified = false) {
- var booksSortedByAddedAt = sort(books).desc(book => book.addedAt)
- return booksSortedByAddedAt.map(b => minified ? b.toJSONMinified() : b.toJSONExpanded()).slice(0, limit)
+ getItemsMostRecentlyAdded(libraryItems, limit, minified = false) {
+ var itemsSortedByAddedAt = sort(libraryItems).desc(li => li.addedAt)
+ return itemsSortedByAddedAt.map(b => minified ? b.toJSONMinified() : b.toJSONExpanded()).slice(0, limit)
},
- getBooksMostRecentlyFinished(booksWithUserAb, limit, minified = false) {
- var booksRead = booksWithUserAb.filter((data) => data.userAudiobook && data.userAudiobook.isRead)
- booksRead.sort((a, b) => {
- return b.userAudiobook.finishedAt - a.userAudiobook.finishedAt
+ getItemsMostRecentlyFinished(itemsWithUserProgress, limit, minified = false) {
+ var itemsFinished = itemsWithUserProgress.filter((data) => data.userProgress && data.userProgress.isFinished)
+ itemsFinished.sort((a, b) => {
+ return b.userProgress.finishedAt - a.userProgress.finishedAt
})
- return booksRead.map(b => minified ? b.book.toJSONMinified() : b.book.toJSONExpanded()).slice(0, limit)
+ return itemsFinished.map(i => minified ? i.libraryItem.toJSONMinified() : i.libraryItem.toJSONExpanded()).slice(0, limit)
},
getSeriesMostRecentlyAdded(series, limit) {
diff --git a/server/utils/parseNameString.js b/server/utils/parseNameString.js
index 7a407393..18212e30 100644
--- a/server/utils/parseNameString.js
+++ b/server/utils/parseNameString.js
@@ -27,7 +27,16 @@ function checkIsALastName(name) {
return false
}
-module.exports = (nameString) => {
+// Handle name already in First Last format and return Last, First
+module.exports.nameToLastFirst = (firstLast) => {
+ var nameObj = parseName(firstLast)
+ if (!nameObj.last_name) return nameObj.first_name
+ else if (!nameObj.first_name) return nameObj.last_name
+ return `${nameObj.last_name}, ${nameObj.first_name}`
+}
+
+// Handle any name string
+module.exports.parse = (nameString) => {
if (!nameString) return null
var splitNames = []