mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-23 20:29:37 +00:00
Merge branch 'master' into server/respond-with-objects
This commit is contained in:
commit
5c31687a0f
36 changed files with 322 additions and 247 deletions
|
|
@ -109,7 +109,7 @@ class Auth {
|
|||
Logger.error('JWT Verify Token Failed', err)
|
||||
return resolve(null)
|
||||
}
|
||||
var user = this.users.find(u => u.id === payload.userId && u.username === payload.username)
|
||||
const user = this.users.find(u => u.id === payload.userId && u.username === payload.username)
|
||||
resolve(user || null)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -31,9 +31,13 @@ class SocketAuthority {
|
|||
}
|
||||
|
||||
// Emits event to all authorized clients
|
||||
emitter(evt, data) {
|
||||
// optional filter function to only send event to specific users
|
||||
// TODO: validate that filter is actually a function
|
||||
emitter(evt, data, filter = null) {
|
||||
for (const socketId in this.clients) {
|
||||
if (this.clients[socketId].user) {
|
||||
if (filter && !filter(this.clients[socketId].user)) continue
|
||||
|
||||
this.clients[socketId].socket.emit(evt, data)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class LibraryController {
|
|||
constructor() { }
|
||||
|
||||
async create(req, res) {
|
||||
var newLibraryPayload = {
|
||||
const newLibraryPayload = {
|
||||
...req.body
|
||||
}
|
||||
if (!newLibraryPayload.name || !newLibraryPayload.folders || !newLibraryPayload.folders.length) {
|
||||
|
|
@ -26,9 +26,9 @@ class LibraryController {
|
|||
f.fullPath = Path.resolve(f.fullPath)
|
||||
return f
|
||||
})
|
||||
for (var folder of newLibraryPayload.folders) {
|
||||
for (const folder of newLibraryPayload.folders) {
|
||||
try {
|
||||
var direxists = await fs.pathExists(folder.fullPath)
|
||||
const direxists = await fs.pathExists(folder.fullPath)
|
||||
if (!direxists) { // If folder does not exist try to make it and set file permissions/owner
|
||||
await fs.mkdir(folder.fullPath)
|
||||
await filePerms.setDefault(folder.fullPath)
|
||||
|
|
@ -39,12 +39,16 @@ class LibraryController {
|
|||
}
|
||||
}
|
||||
|
||||
var library = new Library()
|
||||
const library = new Library()
|
||||
newLibraryPayload.displayOrder = this.db.libraries.length + 1
|
||||
library.setData(newLibraryPayload)
|
||||
await this.db.insertEntity('library', library)
|
||||
// TODO: Only emit to users that have access
|
||||
SocketAuthority.emitter('library_added', library.toJSON())
|
||||
|
||||
// Only emit to users with access to library
|
||||
const userFilter = (user) => {
|
||||
return user.checkCanAccessLibrary && user.checkCanAccessLibrary(library.id)
|
||||
}
|
||||
SocketAuthority.emitter('library_added', library.toJSON(), userFilter)
|
||||
|
||||
// Add library watcher
|
||||
this.watcher.addLibrary(library)
|
||||
|
|
@ -53,7 +57,7 @@ class LibraryController {
|
|||
}
|
||||
|
||||
findAll(req, res) {
|
||||
var librariesAccessible = req.user.librariesAccessible || []
|
||||
const librariesAccessible = req.user.librariesAccessible || []
|
||||
if (librariesAccessible && librariesAccessible.length) {
|
||||
return res.json(this.db.libraries.filter(lib => librariesAccessible.includes(lib.id)).map(lib => lib.toJSON()))
|
||||
}
|
||||
|
|
@ -77,12 +81,12 @@ class LibraryController {
|
|||
}
|
||||
|
||||
async update(req, res) {
|
||||
var library = req.library
|
||||
const library = req.library
|
||||
|
||||
// Validate new folder paths exist or can be created & resolve rel paths
|
||||
// returns 400 if a new folder fails to access
|
||||
if (req.body.folders) {
|
||||
var newFolderPaths = []
|
||||
const newFolderPaths = []
|
||||
req.body.folders = req.body.folders.map(f => {
|
||||
if (!f.id) {
|
||||
f.fullPath = Path.resolve(f.fullPath)
|
||||
|
|
@ -90,11 +94,11 @@ class LibraryController {
|
|||
}
|
||||
return f
|
||||
})
|
||||
for (var path of newFolderPaths) {
|
||||
var pathExists = await fs.pathExists(path)
|
||||
for (const path of newFolderPaths) {
|
||||
const pathExists = await fs.pathExists(path)
|
||||
if (!pathExists) {
|
||||
// Ensure dir will recursively create directories which might be preferred over mkdir
|
||||
var success = await fs.ensureDir(path).then(() => true).catch((error) => {
|
||||
const success = await fs.ensureDir(path).then(() => true).catch((error) => {
|
||||
Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error)
|
||||
return false
|
||||
})
|
||||
|
|
@ -107,7 +111,7 @@ class LibraryController {
|
|||
}
|
||||
}
|
||||
|
||||
var hasUpdates = library.update(req.body)
|
||||
const hasUpdates = library.update(req.body)
|
||||
// TODO: Should check if this is an update to folder paths or name only
|
||||
if (hasUpdates) {
|
||||
// Update watcher
|
||||
|
|
@ -117,7 +121,7 @@ class LibraryController {
|
|||
this.cronManager.updateLibraryScanCron(library)
|
||||
|
||||
// Remove libraryItems no longer in library
|
||||
var itemsToRemove = this.db.libraryItems.filter(li => li.libraryId === library.id && !library.checkFullPathInLibrary(li.path))
|
||||
const itemsToRemove = this.db.libraryItems.filter(li => li.libraryId === library.id && !library.checkFullPathInLibrary(li.path))
|
||||
if (itemsToRemove.length) {
|
||||
Logger.info(`[Scanner] Updating library, removing ${itemsToRemove.length} items`)
|
||||
for (let i = 0; i < itemsToRemove.length; i++) {
|
||||
|
|
@ -125,32 +129,37 @@ class LibraryController {
|
|||
}
|
||||
}
|
||||
await this.db.updateEntity('library', library)
|
||||
SocketAuthority.emitter('library_updated', library.toJSON())
|
||||
|
||||
// Only emit to users with access to library
|
||||
const userFilter = (user) => {
|
||||
return user.checkCanAccessLibrary && user.checkCanAccessLibrary(library.id)
|
||||
}
|
||||
SocketAuthority.emitter('library_updated', library.toJSON(), userFilter)
|
||||
}
|
||||
return res.json(library.toJSON())
|
||||
}
|
||||
|
||||
async delete(req, res) {
|
||||
var library = req.library
|
||||
const library = req.library
|
||||
|
||||
// Remove library watcher
|
||||
this.watcher.removeLibrary(library)
|
||||
|
||||
// Remove collections for library
|
||||
var collections = this.db.collections.filter(c => c.libraryId === library.id)
|
||||
const collections = this.db.collections.filter(c => c.libraryId === library.id)
|
||||
for (const collection of collections) {
|
||||
Logger.info(`[Server] deleting collection "${collection.name}" for library "${library.name}"`)
|
||||
await this.db.removeEntity('collection', collection.id)
|
||||
}
|
||||
|
||||
// Remove items in this library
|
||||
var libraryItems = this.db.libraryItems.filter(li => li.libraryId === library.id)
|
||||
const libraryItems = this.db.libraryItems.filter(li => li.libraryId === library.id)
|
||||
Logger.info(`[Server] deleting library "${library.name}" with ${libraryItems.length} items"`)
|
||||
for (let i = 0; i < libraryItems.length; i++) {
|
||||
await this.handleDeleteLibraryItem(libraryItems[i])
|
||||
}
|
||||
|
||||
var libraryJson = library.toJSON()
|
||||
const libraryJson = library.toJSON()
|
||||
await this.db.removeEntity('library', library.id)
|
||||
SocketAuthority.emitter('library_removed', libraryJson)
|
||||
return res.json(libraryJson)
|
||||
|
|
@ -172,10 +181,10 @@ class LibraryController {
|
|||
minified: req.query.minified === '1',
|
||||
collapseseries: req.query.collapseseries === '1'
|
||||
}
|
||||
var mediaIsBook = payload.mediaType === 'book'
|
||||
const mediaIsBook = payload.mediaType === 'book'
|
||||
|
||||
// Step 1 - Filter the retrieved library items
|
||||
var filterSeries = null
|
||||
let filterSeries = null
|
||||
if (payload.filterBy) {
|
||||
libraryItems = libraryHelpers.getFilteredLibraryItems(libraryItems, payload.filterBy, req.user, this.rssFeedManager.feedsArray)
|
||||
payload.total = libraryItems.length
|
||||
|
|
@ -218,7 +227,7 @@ class LibraryController {
|
|||
|
||||
if (payload.sortBy) {
|
||||
// old sort key TODO: should be mutated in dbMigration
|
||||
var sortKey = payload.sortBy
|
||||
let sortKey = payload.sortBy
|
||||
if (sortKey.startsWith('book.')) {
|
||||
sortKey = sortKey.replace('book.', 'media.metadata.')
|
||||
}
|
||||
|
|
@ -248,7 +257,7 @@ class LibraryController {
|
|||
}
|
||||
|
||||
// Sort series based on the sortBy attribute
|
||||
var direction = payload.sortDesc ? 'desc' : 'asc'
|
||||
const direction = payload.sortDesc ? 'desc' : 'asc'
|
||||
sortArray.push({
|
||||
[direction]: (li) => {
|
||||
if (mediaIsBook && sortBySequence) {
|
||||
|
|
@ -334,7 +343,7 @@ class LibraryController {
|
|||
}
|
||||
|
||||
async removeLibraryItemsWithIssues(req, res) {
|
||||
var libraryItemsWithIssues = req.libraryItems.filter(li => li.hasIssues)
|
||||
const libraryItemsWithIssues = req.libraryItems.filter(li => li.hasIssues)
|
||||
if (!libraryItemsWithIssues.length) {
|
||||
Logger.warn(`[LibraryController] No library items have issues`)
|
||||
return res.sendStatus(200)
|
||||
|
|
@ -351,8 +360,8 @@ class LibraryController {
|
|||
|
||||
// api/libraries/:id/series
|
||||
async getAllSeriesForLibrary(req, res) {
|
||||
var libraryItems = req.libraryItems
|
||||
var payload = {
|
||||
const libraryItems = req.libraryItems
|
||||
const payload = {
|
||||
results: [],
|
||||
total: 0,
|
||||
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
|
||||
|
|
@ -363,7 +372,7 @@ class LibraryController {
|
|||
minified: req.query.minified === '1'
|
||||
}
|
||||
|
||||
var series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, null, payload.filterBy, req.user, payload.minified)
|
||||
let series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, null, payload.filterBy, req.user, payload.minified)
|
||||
|
||||
const direction = payload.sortDesc ? 'desc' : 'asc'
|
||||
series = naturalSort(series).by([
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
const fs = require('../libs/fsExtra')
|
||||
const Logger = require('../Logger')
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
|
||||
|
|
@ -178,7 +179,15 @@ class LibraryItemController {
|
|||
|
||||
// GET api/items/:id/cover
|
||||
async getCover(req, res) {
|
||||
let { query: { width, height, format }, libraryItem } = req
|
||||
const { query: { width, height, format, raw }, libraryItem } = req
|
||||
|
||||
if (raw) { // any value
|
||||
if (!libraryItem.media.coverPath || !await fs.pathExists(libraryItem.media.coverPath)) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
return res.sendFile(libraryItem.media.coverPath)
|
||||
}
|
||||
|
||||
const options = {
|
||||
format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'),
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class CacheManager {
|
|||
|
||||
res.type(`image/${format}`)
|
||||
|
||||
var path = Path.join(this.CoverCachePath, `${libraryItem.id}_${width}${height ? `x${height}` : ''}`) + '.' + format
|
||||
const path = Path.join(this.CoverCachePath, `${libraryItem.id}_${width}${height ? `x${height}` : ''}`) + '.' + format
|
||||
|
||||
// Cache exists
|
||||
if (await fs.pathExists(path)) {
|
||||
|
|
@ -66,7 +66,7 @@ class CacheManager {
|
|||
return res.sendStatus(500)
|
||||
}
|
||||
|
||||
let writtenFile = await resizeImage(libraryItem.media.coverPath, path, width, height)
|
||||
const writtenFile = await resizeImage(libraryItem.media.coverPath, path, width, height)
|
||||
if (!writtenFile) return res.sendStatus(500)
|
||||
|
||||
// Set owner and permissions of cache image
|
||||
|
|
|
|||
|
|
@ -118,18 +118,25 @@ class PlaybackSessionManager {
|
|||
|
||||
async startSession(user, deviceInfo, libraryItem, episodeId, options) {
|
||||
// Close any sessions already open for user
|
||||
var userSessions = this.sessions.filter(playbackSession => playbackSession.userId === user.id)
|
||||
const userSessions = this.sessions.filter(playbackSession => playbackSession.userId === user.id)
|
||||
for (const session of userSessions) {
|
||||
Logger.info(`[PlaybackSessionManager] startSession: Closing open session "${session.displayTitle}" for user "${user.username}"`)
|
||||
await this.closeSession(user, session, null)
|
||||
}
|
||||
|
||||
var shouldDirectPlay = options.forceDirectPlay || (!options.forceTranscode && libraryItem.media.checkCanDirectPlay(options, episodeId))
|
||||
var mediaPlayer = options.mediaPlayer || 'unknown'
|
||||
const shouldDirectPlay = options.forceDirectPlay || (!options.forceTranscode && libraryItem.media.checkCanDirectPlay(options, episodeId))
|
||||
const mediaPlayer = options.mediaPlayer || 'unknown'
|
||||
|
||||
const userProgress = user.getMediaProgress(libraryItem.id, episodeId)
|
||||
var userStartTime = 0
|
||||
if (userProgress) userStartTime = Number.parseFloat(userProgress.currentTime) || 0
|
||||
let userStartTime = 0
|
||||
if (userProgress) {
|
||||
if (userProgress.isFinished) {
|
||||
Logger.info(`[PlaybackSessionManager] Starting session for user "${user.username}" and resetting progress for finished item "${libraryItem.media.metadata.title}"`)
|
||||
// Keep userStartTime as 0 so the client restarts the media
|
||||
} else {
|
||||
userStartTime = Number.parseFloat(userProgress.currentTime) || 0
|
||||
}
|
||||
}
|
||||
const newPlaybackSession = new PlaybackSession()
|
||||
newPlaybackSession.setData(libraryItem, user, mediaPlayer, deviceInfo, userStartTime, episodeId)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class MediaFileScanner {
|
|||
getTrackAndDiscNumberFromFilename(mediaMetadataFromScan, audioLibraryFile) {
|
||||
const { title, author, series, publishedYear } = mediaMetadataFromScan
|
||||
const { filename, path } = audioLibraryFile.metadata
|
||||
var partbasename = Path.basename(filename, Path.extname(filename))
|
||||
let partbasename = Path.basename(filename, Path.extname(filename))
|
||||
|
||||
// Remove title, author, series, and publishedYear from filename if there
|
||||
if (title) partbasename = partbasename.replace(title, '')
|
||||
|
|
@ -23,8 +23,8 @@ class MediaFileScanner {
|
|||
if (publishedYear) partbasename = partbasename.replace(publishedYear)
|
||||
|
||||
// Look for disc number
|
||||
var discNumber = null
|
||||
var discMatch = partbasename.match(/\b(disc|cd) ?(\d\d?)\b/i)
|
||||
let discNumber = null
|
||||
const discMatch = partbasename.match(/\b(disc|cd) ?(\d\d?)\b/i)
|
||||
if (discMatch && discMatch.length > 2 && discMatch[2]) {
|
||||
if (!isNaN(discMatch[2])) {
|
||||
discNumber = Number(discMatch[2])
|
||||
|
|
@ -35,14 +35,14 @@ class MediaFileScanner {
|
|||
}
|
||||
|
||||
// Look for disc number in folder path e.g. /Book Title/CD01/audiofile.mp3
|
||||
var pathdir = Path.dirname(path).split('/').pop()
|
||||
const pathdir = Path.dirname(path).split('/').pop()
|
||||
if (pathdir && /^cd\d{1,3}$/i.test(pathdir)) {
|
||||
var discFromFolder = Number(pathdir.replace(/cd/i, ''))
|
||||
const discFromFolder = Number(pathdir.replace(/cd/i, ''))
|
||||
if (!isNaN(discFromFolder) && discFromFolder !== null) discNumber = discFromFolder
|
||||
}
|
||||
|
||||
var numbersinpath = partbasename.match(/\d{1,4}/g)
|
||||
var trackNumber = numbersinpath && numbersinpath.length ? parseInt(numbersinpath[0]) : null
|
||||
const numbersinpath = partbasename.match(/\d{1,4}/g)
|
||||
const trackNumber = numbersinpath && numbersinpath.length ? parseInt(numbersinpath[0]) : null
|
||||
return {
|
||||
trackNumber,
|
||||
discNumber
|
||||
|
|
@ -51,7 +51,7 @@ class MediaFileScanner {
|
|||
|
||||
getAverageScanDurationMs(results) {
|
||||
if (!results.length) return 0
|
||||
var total = 0
|
||||
let total = 0
|
||||
for (let i = 0; i < results.length; i++) total += results[i].elapsed
|
||||
return Math.floor(total / results.length)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue