mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-08 00:08:14 +01:00
Update:New API routes for library files and downloads
This commit is contained in:
parent
ea79948122
commit
019063e6f4
@ -36,10 +36,10 @@
|
||||
</div>
|
||||
|
||||
<div v-if="showLocalCovers" class="flex items-center justify-center pb-2">
|
||||
<template v-for="cover in localCovers">
|
||||
<div :key="cover.path" class="m-0.5 mb-5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover.metadata.path === coverPath ? 'border-yellow-300' : ''" @click="setCover(cover)">
|
||||
<template v-for="localCoverFile in localCovers">
|
||||
<div :key="localCoverFile.ino" class="m-0.5 mb-5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="localCoverFile.metadata.path === coverPath ? 'border-yellow-300' : ''" @click="setCover(localCoverFile)">
|
||||
<div class="h-24 bg-primary" :style="{ width: 96 / bookCoverAspectRatio + 'px' }">
|
||||
<covers-preview-cover :src="`${cover.localPath}?token=${userToken}`" :width="96 / bookCoverAspectRatio" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<covers-preview-cover :src="localCoverFile.localPath" :width="96 / bookCoverAspectRatio" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -169,8 +169,8 @@ export default {
|
||||
return this.libraryFiles
|
||||
.filter((f) => f.fileType === 'image')
|
||||
.map((file) => {
|
||||
var _file = { ...file }
|
||||
_file.localPath = `${process.env.serverUrl}/s/item/${this.libraryItemId}/${this.$encodeUriPath(file.metadata.relPath).replace(/^\//, '')}`
|
||||
const _file = { ...file }
|
||||
_file.localPath = `${process.env.serverUrl}/api/items/${this.libraryItemId}/file/${file.ino}?token=${this.userToken}`
|
||||
return _file
|
||||
})
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ export default {
|
||||
return items
|
||||
},
|
||||
downloadUrl() {
|
||||
return `${process.env.serverUrl}/s/item/${this.libraryItemId}/${this.$encodeUriPath(this.track.metadata.relPath).replace(/^\//, '')}?token=${this.userToken}`
|
||||
return `${process.env.serverUrl}/api/items/${this.libraryItemId}/file/${this.track.audioFile.ino}/download?token=${this.userToken}`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -45,7 +45,7 @@ export default {
|
||||
return this.$store.getters['user/getIsAdminOrUp']
|
||||
},
|
||||
downloadUrl() {
|
||||
return `${process.env.serverUrl}/s/item/${this.libraryItemId}/${this.$encodeUriPath(this.file.metadata.relPath).replace(/^\//, '')}?token=${this.userToken}`
|
||||
return `${process.env.serverUrl}/api/items/${this.libraryItemId}/file/${this.file.ino}/download?token=${this.userToken}`
|
||||
},
|
||||
contextMenuItems() {
|
||||
const items = []
|
||||
|
@ -162,6 +162,8 @@ class Server {
|
||||
|
||||
router.use('/api', this.authMiddleware.bind(this), this.apiRouter.router)
|
||||
router.use('/hls', this.authMiddleware.bind(this), this.hlsRouter.router)
|
||||
|
||||
// TODO: Deprecated as of 2.2.21 edge
|
||||
router.use('/s', this.authMiddleware.bind(this), this.staticRouter.router)
|
||||
|
||||
// EBook static file routes
|
||||
|
@ -6,6 +6,7 @@ const SocketAuthority = require('../SocketAuthority')
|
||||
const zipHelpers = require('../utils/zipHelpers')
|
||||
const { reqSupportsWebp, isNullOrNaN } = require('../utils/index')
|
||||
const { ScanResult } = require('../utils/constants')
|
||||
const { getAudioMimeTypeFromExtname } = require('../utils/fileUtils')
|
||||
|
||||
class LibraryItemController {
|
||||
constructor() { }
|
||||
@ -529,19 +530,45 @@ class LibraryItemController {
|
||||
res.json(toneData)
|
||||
}
|
||||
|
||||
async deleteLibraryFile(req, res) {
|
||||
const libraryFile = req.libraryItem.libraryFiles.find(lf => lf.ino === req.params.ino)
|
||||
if (!libraryFile) {
|
||||
Logger.error(`[LibraryItemController] Unable to delete library file. Not found. "${req.params.ino}"`)
|
||||
return res.sendStatus(404)
|
||||
/**
|
||||
* GET api/items/:id/file/:fileid
|
||||
*
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
async getLibraryFile(req, res) {
|
||||
const libraryFile = req.libraryFile
|
||||
|
||||
if (global.XAccel) {
|
||||
Logger.debug(`Use X-Accel to serve static file ${libraryFile.metadata.path}`)
|
||||
return res.status(204).header({ 'X-Accel-Redirect': global.XAccel + libraryFile.metadata.path }).send()
|
||||
}
|
||||
|
||||
// Express does not set the correct mimetype for m4b files so use our defined mimetypes if available
|
||||
const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(libraryFile.metadata.path))
|
||||
if (audioMimeType) {
|
||||
res.setHeader('Content-Type', audioMimeType)
|
||||
}
|
||||
res.sendFile(libraryFile.metadata.path)
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE api/items/:id/file/:fileid
|
||||
*
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
async deleteLibraryFile(req, res) {
|
||||
const libraryFile = req.libraryFile
|
||||
|
||||
Logger.info(`[LibraryItemController] User "${req.user.username}" requested file delete at "${libraryFile.metadata.path}"`)
|
||||
|
||||
await fs.remove(libraryFile.metadata.path).catch((error) => {
|
||||
Logger.error(`[LibraryItemController] Failed to delete library file at "${libraryFile.metadata.path}"`, error)
|
||||
})
|
||||
req.libraryItem.removeLibraryFile(req.params.ino)
|
||||
req.libraryItem.removeLibraryFile(req.params.fileid)
|
||||
|
||||
if (req.libraryItem.media.removeFileWithInode(req.params.ino)) {
|
||||
if (req.libraryItem.media.removeFileWithInode(req.params.fileid)) {
|
||||
// If book has no more media files then mark it as missing
|
||||
if (req.libraryItem.mediaType === 'book' && !req.libraryItem.media.hasMediaEntities) {
|
||||
req.libraryItem.setMissing()
|
||||
@ -553,6 +580,42 @@ class LibraryItemController {
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET api/items/:id/file/:fileid/download
|
||||
* Same as GET api/items/:id/file/:fileid but allows logging and restricting downloads
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
async downloadLibraryFile(req, res) {
|
||||
const libraryFile = req.libraryFile
|
||||
|
||||
if (!req.user.canDownload) {
|
||||
Logger.error(`[LibraryItemController] User without download permission attempted to download file "${libraryFile.metadata.path}"`, req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
Logger.info(`[LibraryItemController] User "${req.user.username}" requested file download at "${libraryFile.metadata.path}"`)
|
||||
|
||||
if (global.XAccel) {
|
||||
Logger.debug(`Use X-Accel to serve static file ${libraryFile.metadata.path}`)
|
||||
return res.status(204).header({ 'X-Accel-Redirect': global.XAccel + libraryFile.metadata.path }).send()
|
||||
}
|
||||
|
||||
// Express does not set the correct mimetype for m4b files so use our defined mimetypes if available
|
||||
const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(libraryFile.metadata.path))
|
||||
if (audioMimeType) {
|
||||
res.setHeader('Content-Type', audioMimeType)
|
||||
}
|
||||
|
||||
res.download(libraryFile.metadata.path, libraryFile.metadata.filename)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET api/items/:id/ebook
|
||||
*
|
||||
* @param {express.Request} req
|
||||
* @param {express.Response} res
|
||||
*/
|
||||
async getEBookFile(req, res) {
|
||||
const ebookFile = req.libraryItem.media.ebookFile
|
||||
if (!ebookFile) {
|
||||
@ -560,18 +623,33 @@ class LibraryItemController {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
const ebookFilePath = ebookFile.metadata.path
|
||||
|
||||
if (global.XAccel) {
|
||||
Logger.debug(`Use X-Accel to serve static file ${ebookFilePath}`)
|
||||
return res.status(204).header({ 'X-Accel-Redirect': global.XAccel + ebookFilePath }).send()
|
||||
}
|
||||
|
||||
res.sendFile(ebookFilePath)
|
||||
}
|
||||
|
||||
middleware(req, res, next) {
|
||||
const item = this.db.libraryItems.find(li => li.id === req.params.id)
|
||||
if (!item || !item.media) return res.sendStatus(404)
|
||||
req.libraryItem = this.db.libraryItems.find(li => li.id === req.params.id)
|
||||
if (!req.libraryItem?.media) return res.sendStatus(404)
|
||||
|
||||
// Check user can access this library item
|
||||
if (!req.user.checkCanAccessLibraryItem(item)) {
|
||||
if (!req.user.checkCanAccessLibraryItem(req.libraryItem)) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
// For library file routes, get the library file
|
||||
if (req.params.fileid) {
|
||||
req.libraryFile = req.libraryItem.libraryFiles.find(lf => lf.ino === req.params.fileid)
|
||||
if (!req.libraryFile) {
|
||||
Logger.error(`[LibraryItemController] Library file "${req.params.fileid}" does not exist for library item`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
}
|
||||
|
||||
if (req.path.includes('/play')) {
|
||||
// allow POST requests using /play and /play/:episodeId
|
||||
} else if (req.method == 'DELETE' && !req.user.canDelete) {
|
||||
@ -582,7 +660,6 @@ class LibraryItemController {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
req.libraryItem = item
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ class AudioTrack {
|
||||
this.startOffset = startOffset
|
||||
this.duration = audioFile.duration
|
||||
this.title = audioFile.metadata.filename || ''
|
||||
// TODO: Switch to /api/items/:id/file/:fileid
|
||||
this.contentUrl = Path.join(`${global.RouterBasePath}/s/item/${itemId}`, encodeUriPath(audioFile.metadata.relPath))
|
||||
this.mimeType = audioFile.mimeType
|
||||
this.codec = audioFile.codec || null
|
||||
|
@ -28,6 +28,7 @@ class VideoTrack {
|
||||
this.index = videoFile.index
|
||||
this.duration = videoFile.duration
|
||||
this.title = videoFile.metadata.filename || ''
|
||||
// TODO: Switch to /api/items/:id/file/:fileid
|
||||
this.contentUrl = Path.join(`${global.RouterBasePath}/s/item/${itemId}`, encodeUriPath(videoFile.metadata.relPath))
|
||||
this.mimeType = videoFile.mimeType
|
||||
this.codec = videoFile.codec
|
||||
|
@ -120,7 +120,9 @@ class ApiRouter {
|
||||
this.router.get('/items/:id/tone-object', LibraryItemController.middleware.bind(this), LibraryItemController.getToneMetadataObject.bind(this))
|
||||
this.router.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this))
|
||||
this.router.post('/items/:id/tone-scan/:index?', LibraryItemController.middleware.bind(this), LibraryItemController.toneScan.bind(this))
|
||||
this.router.delete('/items/:id/file/:ino', LibraryItemController.middleware.bind(this), LibraryItemController.deleteLibraryFile.bind(this))
|
||||
this.router.get('/items/:id/file/:fileid', LibraryItemController.middleware.bind(this), LibraryItemController.getLibraryFile.bind(this))
|
||||
this.router.delete('/items/:id/file/:fileid', LibraryItemController.middleware.bind(this), LibraryItemController.deleteLibraryFile.bind(this))
|
||||
this.router.get('/items/:id/file/:fileid/download', LibraryItemController.middleware.bind(this), LibraryItemController.downloadLibraryFile.bind(this))
|
||||
this.router.get('/items/:id/ebook', LibraryItemController.middleware.bind(this), LibraryItemController.getEBookFile.bind(this))
|
||||
|
||||
//
|
||||
|
@ -3,6 +3,7 @@ const Path = require('path')
|
||||
const Logger = require('../Logger')
|
||||
const { getAudioMimeTypeFromExtname } = require('../utils/fileUtils')
|
||||
|
||||
// TODO: Deprecated as of 2.2.21 edge
|
||||
class StaticRouter {
|
||||
constructor(db) {
|
||||
this.db = db
|
||||
|
Loading…
Reference in New Issue
Block a user