audiobookshelf/server/managers/CacheManager.js
2022-05-15 11:19:04 -05:00

149 lines
4.3 KiB
JavaScript

const Path = require('path')
const fs = require('fs-extra')
const stream = require('stream')
const filePerms = require('../utils/filePerms')
const Logger = require('../Logger')
const { resizeImage } = require('../utils/ffmpegHelpers')
class CacheManager {
constructor() {
this.CachePath = Path.join(global.MetadataPath, 'cache')
this.CoverCachePath = Path.join(this.CachePath, 'covers')
this.ImageCachePath = Path.join(this.CachePath, 'images')
this.cachePathsExist = false
}
async ensureCachePaths() { // Creates cache paths if necessary and sets owner and permissions
if (this.cachePathsExist) return
var pathsCreated = false
if (!(await fs.pathExists(this.CachePath))) {
await fs.mkdir(this.CachePath)
pathsCreated = true
}
if (!(await fs.pathExists(this.CoverCachePath))) {
await fs.mkdir(this.CoverCachePath)
pathsCreated = true
}
if (!(await fs.pathExists(this.ImageCachePath))) {
await fs.mkdir(this.ImageCachePath)
pathsCreated = true
}
if (pathsCreated) {
await filePerms.setDefault(this.CachePath)
}
this.cachePathsExist = true
}
async handleCoverCache(res, libraryItem, options = {}) {
const format = options.format || 'webp'
const width = options.width || 400
const height = options.height || null
res.type(`image/${format}`)
var path = Path.join(this.CoverCachePath, `${libraryItem.id}_${width}${height ? `x${height}` : ''}`) + '.' + format
// Cache exists
if (await fs.pathExists(path)) {
const r = fs.createReadStream(path)
const ps = new stream.PassThrough()
stream.pipeline(r, ps, (err) => {
if (err) {
console.log(err)
return res.sendStatus(400)
}
})
return ps.pipe(res)
}
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)
// Set owner and permissions of cache image
await filePerms.setDefault(path)
var readStream = fs.createReadStream(writtenFile)
readStream.pipe(res)
}
purgeCoverCache(libraryItemId) {
return this.purgeEntityCache(libraryItemId, this.CoverCachePath)
}
purgeImageCache(entityId) {
return this.purgeEntityCache(entityId, this.ImageCachePath)
}
async purgeEntityCache(entityId, cachePath) {
return Promise.all((await fs.readdir(cachePath)).reduce((promises, file) => {
if (file.startsWith(entityId)) {
Logger.debug(`[CacheManager] Going to purge ${file}`);
promises.push(this.removeCache(Path.join(cachePath, file)))
}
return promises
}, []))
}
removeCache(path) {
if (!path) return false
return fs.pathExists(path).then((exists) => {
if (!exists) return false
return fs.unlink(path).then(() => true).catch((err) => {
Logger.error(`[CacheManager] Failed to remove cache "${path}"`, err)
return false
})
})
}
async purgeAll() {
if (await fs.pathExists(this.CachePath)) {
await fs.remove(this.CachePath).catch((error) => {
Logger.error(`[CacheManager] Failed to remove cache dir "${this.CachePath}"`, error)
})
}
await this.ensureCachePaths()
}
async handleAuthorCache(res, author, options = {}) {
const format = options.format || 'webp'
const width = options.width || 400
const height = options.height || null
res.type(`image/${format}`)
var path = Path.join(this.ImageCachePath, `${author.id}_${width}${height ? `x${height}` : ''}`) + '.' + format
// Cache exists
if (await fs.pathExists(path)) {
const r = fs.createReadStream(path)
const ps = new stream.PassThrough()
stream.pipeline(r, ps, (err) => {
if (err) {
console.log(err)
return res.sendStatus(400)
}
})
return ps.pipe(res)
}
let writtenFile = await resizeImage(author.imagePath, path, width, height)
if (!writtenFile) return res.sendStatus(400)
// Set owner and permissions of cache image
await filePerms.setDefault(path)
var readStream = fs.createReadStream(writtenFile)
readStream.pipe(res)
}
}
module.exports = CacheManager