Merge branch 'master' into server/respond-with-objects

This commit is contained in:
advplyr 2022-12-12 17:20:14 -06:00
commit 5c31687a0f
36 changed files with 322 additions and 247 deletions

View file

@ -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)
})
})

View file

@ -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)
}
}

View file

@ -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([

View file

@ -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'),

View file

@ -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

View file

@ -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)

View file

@ -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)
}