Skip to content

Commit

Permalink
Missing audiobooks flagged not deleted, fix close progress loop on st…
Browse files Browse the repository at this point in the history
…ream errors, clickable download toast, consolidate duplicate track error log, improved scanner to ignore non-audio files
  • Loading branch information
advplyr committed Sep 17, 2021
1 parent 0851a1e commit db01db3
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 60 deletions.
8 changes: 6 additions & 2 deletions client/components/cards/BookCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<cards-book-cover :audiobook="audiobook" :author-override="authorFormat" :width="width" />

<div v-show="isHovering || isSelectionMode" class="absolute top-0 left-0 w-full h-full bg-black rounded" :class="overlayWrapperClasslist">
<div v-show="!isSelectionMode" class="h-full flex items-center justify-center">
<div v-show="!isSelectionMode && !isMissing" class="h-full flex items-center justify-center">
<div class="hover:text-gray-200 hover:scale-110 transform duration-200" @click.stop.prevent="play">
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
</div>
Expand Down Expand Up @@ -132,7 +132,10 @@ export default {
return this.userProgress ? !!this.userProgress.isRead : false
},
showError() {
return this.hasMissingParts || this.hasInvalidParts
return this.hasMissingParts || this.hasInvalidParts || this.isMissing
},
isMissing() {
return this.audiobook.isMissing
},
hasMissingParts() {
return this.audiobook.hasMissingParts
Expand All @@ -141,6 +144,7 @@ export default {
return this.audiobook.hasInvalidParts
},
errorText() {
if (this.isMissing) return 'Audiobook directory is missing!'
var txt = ''
if (this.hasMissingParts) {
txt = `${this.hasMissingParts} missing parts.`
Expand Down
4 changes: 4 additions & 0 deletions client/components/modals/EditModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export default {
availableTabs() {
if (!this.userCanUpdate && !this.userCanDownload) return []
return this.tabs.filter((tab) => {
if (tab.id === 'download' && this.isMissing) return false
if ((tab.id === 'download' || tab.id === 'tracks') && this.userCanDownload) return true
if (tab.id !== 'download' && tab.id !== 'tracks' && this.userCanUpdate) return true
return false
Expand All @@ -122,6 +123,9 @@ export default {
var _tab = this.tabs.find((t) => t.id === this.selectedTab)
return _tab ? _tab.component : ''
},
isMissing() {
return this.selectedAudiobook.isMissing
},
selectedAudiobook() {
return this.$store.state.selectedAudiobook || {}
},
Expand Down
10 changes: 8 additions & 2 deletions client/components/modals/edit-tabs/Tracks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<th class="text-left">Filename</th>
<th class="text-left">Size</th>
<th class="text-left">Duration</th>
<th v-if="userCanDownload" class="text-center">Download</th>
<th v-if="showDownload" class="text-center">Download</th>
</tr>
<template v-for="track in tracks">
<tr :key="track.index">
Expand All @@ -27,7 +27,7 @@
<td class="font-mono">
{{ $secondsToTimestamp(track.duration) }}
</td>
<td v-if="userCanDownload" class="font-mono text-center">
<td v-if="showDownload" class="font-mono text-center">
<a :href="`/local/${track.path}`" download><span class="material-icons icon-text">download</span></a>
</td>
</tr>
Expand Down Expand Up @@ -64,6 +64,12 @@ export default {
},
userCanDownload() {
return this.$store.getters['user/getUserCanDownload']
},
isMissing() {
return this.audiobook.isMissing
},
showDownload() {
return this.userCanDownload && !this.isMissing
}
},
methods: {
Expand Down
5 changes: 4 additions & 1 deletion client/components/ui/IconBtn.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<button class="icon-btn rounded-md border border-gray-600 flex items-center justify-center h-9 w-9 relative" :class="className" @click="clickBtn">
<button class="icon-btn rounded-md border border-gray-600 flex items-center justify-center h-9 w-9 relative" :disabled="disabled" :class="className" @click="clickBtn">
<span class="material-icons icon-text">{{ icon }}</span>
</button>
</template>
Expand Down Expand Up @@ -39,6 +39,9 @@ export default {
</script>

<style>
button.icon-btn:disabled {
cursor: not-allowed;
}
button.icon-btn::before {
content: '';
position: absolute;
Expand Down
9 changes: 8 additions & 1 deletion client/components/ui/Tooltip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export default {
direction: {
type: String,
default: 'right'
}
},
disabled: Boolean
},
data() {
return {
Expand All @@ -25,6 +26,11 @@ export default {
watch: {
text() {
this.updateText()
},
disabled(newVal) {
if (newVal && this.isShowing) {
this.hideTooltip()
}
}
},
methods: {
Expand Down Expand Up @@ -81,6 +87,7 @@ export default {
tooltip.style.left = left + 'px'
},
showTooltip() {
if (this.disabled) return
if (!this.tooltip) {
this.createTooltip()
}
Expand Down
26 changes: 13 additions & 13 deletions client/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export default {
if (results.added) scanResultMsgs.push(`${results.added} added`)
if (results.updated) scanResultMsgs.push(`${results.updated} updated`)
if (results.removed) scanResultMsgs.push(`${results.removed} removed`)
if (results.missing) scanResultMsgs.push(`${results.missing} missing`)
if (!scanResultMsgs.length) this.$toast.success('Scan Finished\nEverything was up to date')
else this.$toast.success('Scan Finished\n' + scanResultMsgs.join('\n'))
}
Expand All @@ -128,19 +129,18 @@ export default {
}
},
downloadToastClick(download) {
console.log('Downlaod ready toast click', download)
// if (!download || !download.audiobookId) {
// return console.error('Invalid download object', download)
// }
// var audiobook = this.$store.getters['audiobooks/getAudiobook'](download.audiobookId)
// if (!audiobook) {
// return console.error('Audiobook not found for download', download)
// }
// this.$store.commit('showEditModalOnTab', { audiobook, tab: 'download' })
if (!download || !download.audiobookId) {
return console.error('Invalid download object', download)
}
var audiobook = this.$store.getters['audiobooks/getAudiobook'](download.audiobookId)
if (!audiobook) {
return console.error('Audiobook not found for download', download)
}
this.$store.commit('showEditModalOnTab', { audiobook, tab: 'download' })
},
downloadStarted(download) {
download.status = this.$constants.DownloadStatus.PENDING
download.toastId = this.$toast(`Preparing download "${download.filename}"`, { timeout: false, draggable: false, closeOnClick: false, onClick: this.downloadToastClick })
download.toastId = this.$toast(`Preparing download "${download.filename}"`, { timeout: false, draggable: false, closeOnClick: false, onClick: () => this.downloadToastClick(download) })
this.$store.commit('downloads/addUpdateDownload', download)
},
downloadReady(download) {
Expand All @@ -149,7 +149,7 @@ export default {
if (existingDownload && existingDownload.toastId !== undefined) {
download.toastId = existingDownload.toastId
this.$toast.update(existingDownload.toastId, { content: `Download "${download.filename}" is ready!`, options: { timeout: 5000, type: 'success', onClick: this.downloadToastClick } }, true)
this.$toast.update(existingDownload.toastId, { content: `Download "${download.filename}" is ready!`, options: { timeout: 5000, type: 'success', onClick: () => this.downloadToastClick(download) } }, true)
} else {
this.$toast.success(`Download "${download.filename}" is ready!`)
}
Expand All @@ -163,7 +163,7 @@ export default {
if (existingDownload && existingDownload.toastId !== undefined) {
download.toastId = existingDownload.toastId
this.$toast.update(existingDownload.toastId, { content: `Download "${download.filename}" ${failedMsg}`, options: { timeout: 5000, type: 'error', onClick: this.downloadToastClick } }, true)
this.$toast.update(existingDownload.toastId, { content: `Download "${download.filename}" ${failedMsg}`, options: { timeout: 5000, type: 'error', onClick: () => this.downloadToastClick(download) } }, true)
} else {
console.warn('Download failed no existing download', existingDownload)
this.$toast.error(`Download "${download.filename}" ${failedMsg}`)
Expand All @@ -174,7 +174,7 @@ export default {
var existingDownload = this.$store.getters['downloads/getDownload'](download.id)
if (existingDownload && existingDownload.toastId !== undefined) {
download.toastId = existingDownload.toastId
this.$toast.update(existingDownload.toastId, { content: `Download "${download.filename}" was terminated`, options: { timeout: 5000, type: 'error', onClick: this.downloadToastClick } }, true)
this.$toast.update(existingDownload.toastId, { content: `Download "${download.filename}" was terminated`, options: { timeout: 5000, type: 'error', onClick: () => this.downloadToastClick(download) } }, true)
} else {
console.warn('Download killed no existing download found', existingDownload)
this.$toast.error(`Download "${download.filename}" was terminated`)
Expand Down
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
"version": "1.1.11",
"version": "1.1.12",
"description": "Audiobook manager and player",
"main": "index.js",
"scripts": {
Expand Down
13 changes: 10 additions & 3 deletions client/pages/audiobook/_id/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,21 @@
</div>

<div class="flex items-center pt-4">
<ui-btn :disabled="streaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="startStream">
<ui-btn v-if="!isMissing" :disabled="streaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="startStream">
<span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
{{ streaming ? 'Streaming' : 'Play' }}
</ui-btn>
<ui-btn v-else color="error" :padding-x="4" small class="flex items-center h-9 mr-2">
<span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">error</span>
Missing
</ui-btn>

<ui-tooltip v-if="userCanUpdate" text="Edit" direction="top">
<ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
</ui-tooltip>

<ui-tooltip v-if="userCanDownload" text="Download" direction="top">
<ui-icon-btn icon="download" class="mx-0.5" @click="downloadClick" />
<ui-tooltip v-if="userCanDownload" :disabled="isMissing" text="Download" direction="top">
<ui-icon-btn icon="download" :disabled="isMissing" class="mx-0.5" @click="downloadClick" />
</ui-tooltip>

<ui-tooltip :text="isRead ? 'Mark as Not Read' : 'Mark as Read'" direction="top">
Expand Down Expand Up @@ -152,6 +156,9 @@ export default {
})
return chunks
},
isMissing() {
return this.audiobook.isMissing
},
missingParts() {
return this.audiobook.missingParts || []
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
"version": "1.1.11",
"version": "1.1.12",
"description": "Self-hosted audiobook server for managing and playing audiobooks.",
"main": "index.js",
"scripts": {
Expand Down
60 changes: 35 additions & 25 deletions server/Scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const { comparePaths, getIno } = require('./utils/index')
const { secondsToTimestamp } = require('./utils/fileUtils')
const { ScanResult } = require('./utils/constants')


class Scanner {
constructor(AUDIOBOOK_PATH, METADATA_PATH, db, emitter) {
this.AudiobookPath = AUDIOBOOK_PATH
Expand Down Expand Up @@ -71,6 +70,7 @@ class Scanner {
if (existingAudiobook) {

// REMOVE: No valid audio files
// TODO: Label as incomplete, do not actually delete
if (!audiobookData.audioFiles.length) {
Logger.error(`[Scanner] "${existingAudiobook.title}" no valid audio files found - removing audiobook`)

Expand Down Expand Up @@ -109,8 +109,8 @@ class Scanner {
await audioFileScanner.scanAudioFiles(existingAudiobook, newAudioFiles)
}


// REMOVE: No valid audio tracks
// TODO: Label as incomplete, do not actually delete
if (!existingAudiobook.tracks.length) {
Logger.error(`[Scanner] "${existingAudiobook.title}" has no valid tracks after update - removing audiobook`)

Expand All @@ -135,6 +135,12 @@ class Scanner {
hasUpdates = true
}

if (existingAudiobook.isMissing) {
existingAudiobook.isMissing = false
hasUpdates = true
Logger.info(`[Scanner] "${existingAudiobook.title}" was missing but now it is found`)
}

if (hasUpdates) {
existingAudiobook.setChapters()

Expand Down Expand Up @@ -173,23 +179,24 @@ class Scanner {
}

async scan() {
// TODO: This temporary fix from pre-release should be removed soon, including the "fixRelativePath" and "checkUpdateInos"
// TEMP - fix relative file paths
// TEMP - update ino for each audiobook
if (this.audiobooks.length) {
for (let i = 0; i < this.audiobooks.length; i++) {
var ab = this.audiobooks[i]
var shouldUpdate = ab.fixRelativePath(this.AudiobookPath) || !ab.ino

// Update ino if an audio file has the same ino as the audiobook
var shouldUpdateIno = !ab.ino || (ab.audioFiles || []).find(abf => abf.ino === ab.ino)
if (shouldUpdateIno) {
await ab.checkUpdateInos()
}
if (shouldUpdate) {
await this.db.updateAudiobook(ab)
}
}
}
// if (this.audiobooks.length) {
// for (let i = 0; i < this.audiobooks.length; i++) {
// var ab = this.audiobooks[i]
// var shouldUpdate = ab.fixRelativePath(this.AudiobookPath) || !ab.ino

// // Update ino if an audio file has the same ino as the audiobook
// var shouldUpdateIno = !ab.ino || (ab.audioFiles || []).find(abf => abf.ino === ab.ino)
// if (shouldUpdateIno) {
// await ab.checkUpdateInos()
// }
// if (shouldUpdate) {
// await this.db.updateAudiobook(ab)
// }
// }
// }

const scanStart = Date.now()
var audiobookDataFound = await scanRootDir(this.AudiobookPath, this.db.serverSettings)
Expand All @@ -205,18 +212,21 @@ class Scanner {
var scanResults = {
removed: 0,
updated: 0,
added: 0
added: 0,
missing: 0
}

// Check for removed audiobooks
for (let i = 0; i < this.audiobooks.length; i++) {
var dataFound = audiobookDataFound.find(abd => abd.ino === this.audiobooks[i].ino)
var audiobook = this.audiobooks[i]
var dataFound = audiobookDataFound.find(abd => abd.ino === audiobook.ino)
if (!dataFound) {
Logger.info(`[Scanner] Removing audiobook "${this.audiobooks[i].title}" - no longer in dir`)
var audiobookJSON = this.audiobooks[i].toJSONMinified()
await this.db.removeEntity('audiobook', this.audiobooks[i].id)
scanResults.removed++
this.emitter('audiobook_removed', audiobookJSON)
Logger.info(`[Scanner] Audiobook "${audiobook.title}" is missing`)
audiobook.isMissing = true
audiobook.lastUpdate = Date.now()
scanResults.missing++
await this.db.updateAudiobook(audiobook)
this.emitter('audiobook_updated', audiobook.toJSONMinified())
}
if (this.cancelScan) {
this.cancelScan = false
Expand Down Expand Up @@ -247,7 +257,7 @@ class Scanner {
}
}
const scanElapsed = Math.floor((Date.now() - scanStart) / 1000)
Logger.info(`[Scanned] Finished | ${scanResults.added} added | ${scanResults.updated} updated | ${scanResults.removed} removed | elapsed: ${secondsToTimestamp(scanElapsed)}`)
Logger.info(`[Scanned] Finished | ${scanResults.added} added | ${scanResults.updated} updated | ${scanResults.removed} removed | ${scanResults.missing} missing | elapsed: ${secondsToTimestamp(scanElapsed)}`)
return scanResults
}

Expand Down
1 change: 0 additions & 1 deletion server/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ class Server {
res.json({ success: true })
})


// Used in development to set-up streams without authentication
if (process.env.NODE_ENV !== 'production') {
app.use('/test-hls', this.hlsController.router)
Expand Down
Loading

0 comments on commit db01db3

Please sign in to comment.