Skip to content

Commit

Permalink
Always sync file inodes, save http url covers in cover directory
Browse files Browse the repository at this point in the history
  • Loading branch information
advplyr committed Oct 1, 2021
1 parent 0db34dc commit 8d9d5a8
Show file tree
Hide file tree
Showing 14 changed files with 355 additions and 94 deletions.
43 changes: 33 additions & 10 deletions client/components/modals/edit-tabs/Cover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ export default {
})
.catch((error) => {
console.error('Failed', error)
this.$toast.error('Oops, something went wrong...')
if (error.response && error.response.data) {
this.$toast.error(error.response.data)
} else {
this.$toast.error('Oops, something went wrong...')
}
this.processingUpload = false
})
},
Expand Down Expand Up @@ -204,20 +208,39 @@ export default {
}
this.isProcessing = true
const updatePayload = {
book: {
cover: cover
var success = false
// Download cover from url and use
if (cover.startsWith('http:') || cover.startsWith('https:')) {
success = await this.$axios.$post(`/api/audiobook/${this.audiobook.id}/cover`, { url: cover }).catch((error) => {
console.error('Failed to download cover from url', error)
if (error.response && error.response.data) {
this.$toast.error(error.response.data)
}
return false
})
} else {
// Update local cover url
const updatePayload = {
book: {
cover: cover
}
}
success = await this.$axios.$patch(`/api/audiobook/${this.audiobook.id}`, updatePayload).catch((error) => {
console.error('Failed to update', error)
if (error.response && error.response.data) {
this.$toast.error(error.response.data)
}
return false
})
}
var updatedAudiobook = await this.$axios.$patch(`/api/audiobook/${this.audiobook.id}`, updatePayload).catch((error) => {
console.error('Failed to update', error)
return false
})
this.isProcessing = false
if (updatedAudiobook) {
if (success) {
this.$toast.success('Update Successful')
this.$emit('close')
} else {
this.imageUrl = this.book.cover || ''
}
this.isProcessing = false
},
getSearchQuery() {
var searchQuery = `provider=openlibrary&title=${this.searchTitle}`
Expand Down
2 changes: 1 addition & 1 deletion client/components/ui/FileInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
export default {
data() {
return {
inputAccept: 'image/*'
inputAccept: '.png, .jpg, .jpeg, .webp'
}
},
computed: {},
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.3.2",
"version": "1.3.3",
"description": "Audiobook manager and player",
"main": "index.js",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions client/pages/upload/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ export default {
author: null,
series: null,
acceptedAudioFormats: ['.mp3', '.m4b', '.m4a', '.flac'],
acceptedImageFormats: ['image/*'],
inputAccept: 'image/*, .mp3, .m4b, .m4a, .flac',
acceptedImageFormats: ['.png', '.jpg', '.jpeg', '.webp'],
inputAccept: '.png, .jpg, .jpeg, .webp, .mp3, .m4b, .m4a, .flac',
isDragOver: false,
showUploader: true,
validAudioFiles: [],
Expand Down
49 changes: 48 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
"version": "1.3.2",
"version": "1.3.3",
"description": "Self-hosted audiobook server for managing and playing audiobooks",
"main": "index.js",
"scripts": {
Expand Down Expand Up @@ -32,12 +32,14 @@
"express-rate-limit": "^5.3.0",
"fluent-ffmpeg": "^2.1.2",
"fs-extra": "^10.0.0",
"image-type": "^4.1.0",
"ip": "^1.1.5",
"jsonwebtoken": "^8.5.1",
"libgen": "^2.1.0",
"njodb": "^0.4.20",
"node-dir": "^0.1.17",
"podcast": "^1.3.0",
"read-chunk": "^3.1.0",
"socket.io": "^4.1.3",
"watcher": "^1.2.0"
},
Expand Down
77 changes: 18 additions & 59 deletions server/ApiController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ const Path = require('path')
const fs = require('fs-extra')
const Logger = require('./Logger')
const User = require('./objects/User')
const { isObject, isAcceptableCoverMimeType } = require('./utils/index')
const { CoverDestination } = require('./utils/constants')
const { isObject } = require('./utils/index')

class ApiController {
constructor(MetadataPath, db, scanner, auth, streamManager, rssFeeds, downloadManager, emitter, clientEmitter) {
constructor(MetadataPath, db, scanner, auth, streamManager, rssFeeds, downloadManager, coverController, emitter, clientEmitter) {
this.db = db
this.scanner = scanner
this.auth = auth
this.streamManager = streamManager
this.rssFeeds = rssFeeds
this.downloadManager = downloadManager
this.coverController = coverController
this.emitter = emitter
this.clientEmitter = clientEmitter
this.MetadataPath = MetadataPath
Expand Down Expand Up @@ -221,77 +221,36 @@ class ApiController {
Logger.warn('User attempted to upload a cover without permission', req.user)
return res.sendStatus(403)
}
if (!req.files || !req.files.cover) {
return res.status(400).send('No files were uploaded')
}

var audiobookId = req.params.id
var audiobook = this.db.audiobooks.find(ab => ab.id === audiobookId)
if (!audiobook) {
return res.status(404).send('Audiobook not found')
}

var coverFile = req.files.cover
var mimeType = coverFile.mimetype
var extname = Path.extname(coverFile.name.toLowerCase()) || '.jpg'
if (!isAcceptableCoverMimeType(mimeType)) {
return res.status(400).send('Invalid image file type: ' + mimeType)
}

var coverDestination = this.db.serverSettings ? this.db.serverSettings.coverDestination : CoverDestination.METADATA
Logger.info(`[ApiController] Cover Upload destination ${coverDestination}`)

var coverDirpath = audiobook.fullPath
var coverRelDirpath = Path.join('/local', audiobook.path)
if (coverDestination === CoverDestination.METADATA) {
coverDirpath = Path.join(this.MetadataPath, 'books', audiobookId)
coverRelDirpath = Path.join('/metadata', 'books', audiobookId)
Logger.debug(`[ApiController] storing in metadata | ${coverDirpath}`)
await fs.ensureDir(coverDirpath)
var result = null
if (req.body && req.body.url) {
Logger.debug(`[ApiController] Requesting download cover from url "${req.body.url}"`)
result = await this.coverController.downloadCoverFromUrl(audiobook, req.body.url)
} else if (req.files && req.files.cover) {
Logger.debug(`[ApiController] Handling uploaded cover`)
var coverFile = req.files.cover
result = await this.coverController.uploadCover(audiobook, coverFile)
} else {
Logger.debug(`[ApiController] storing in audiobook | ${coverRelDirpath}`)
}

var coverFilename = `cover${extname}`
var coverFullPath = Path.join(coverDirpath, coverFilename)
var coverPath = Path.join(coverRelDirpath, coverFilename)

// If current cover is a metadata cover and does not match replacement, then remove it
var currentBookCover = audiobook.book.cover
if (currentBookCover && currentBookCover.startsWith(Path.sep + 'metadata')) {
Logger.debug(`Current Book Cover is metadata ${currentBookCover}`)
if (currentBookCover !== coverPath) {
Logger.info(`[ApiController] removing old metadata cover "${currentBookCover}"`)
var oldFullBookCoverPath = Path.join(this.MetadataPath, currentBookCover.replace(Path.sep + 'metadata', ''))

// Metadata path may have changed, check if exists first
var exists = await fs.pathExists(oldFullBookCoverPath)
if (exists) {
try {
await fs.remove(oldFullBookCoverPath)
} catch (error) {
Logger.error(`[ApiController] Failed to remove old metadata book cover ${oldFullBookCoverPath}`)
}
}
}
return res.status(400).send('Invalid request no file or url')
}

var success = await coverFile.mv(coverFullPath).then(() => true).catch((error) => {
Logger.error('Failed to move cover file', path, error)
return false
})

if (!success) {
return res.status(500).send('Failed to move cover into destination')
if (result && result.error) {
return res.status(400).send(result.error)
} else if (!result || !result.cover) {
return res.status(500).send('Unknown error occurred')
}

Logger.info(`[ApiController] Uploaded audiobook cover "${coverPath}" for "${audiobook.title}"`)

audiobook.updateBookCover(coverPath)
await this.db.updateAudiobook(audiobook)
this.emitter('audiobook_updated', audiobook.toJSONMinified())
res.json({
success: true,
cover: coverPath
cover: result.cover
})
}

Expand Down
Loading

0 comments on commit 8d9d5a8

Please sign in to comment.