Update:Only load libraries from db when needed

This commit is contained in:
advplyr 2023-07-22 14:25:20 -05:00
parent 1c40af3eef
commit 1d974375a0
13 changed files with 189 additions and 60 deletions

View File

@ -104,10 +104,16 @@ class Auth {
}) })
} }
getUserLoginResponsePayload(user) { /**
* Payload returned to a user after successful login
* @param {oldUser} user
* @returns {object}
*/
async getUserLoginResponsePayload(user) {
const libraryIds = await Database.models.library.getAllLibraryIds()
return { return {
user: user.toJSONForBrowser(), user: user.toJSONForBrowser(),
userDefaultLibraryId: user.getDefaultLibraryId(Database.libraries), userDefaultLibraryId: user.getDefaultLibraryId(libraryIds),
serverSettings: Database.serverSettings.toJSONForBrowser(), serverSettings: Database.serverSettings.toJSONForBrowser(),
ereaderDevices: Database.emailSettings.getEReaderDevices(user), ereaderDevices: Database.emailSettings.getEReaderDevices(user),
Source: global.Source Source: global.Source
@ -136,7 +142,8 @@ class Auth {
return res.status(401).send('Invalid root password (hint: there is none)') return res.status(401).send('Invalid root password (hint: there is none)')
} else { } else {
Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`) Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`)
return res.json(this.getUserLoginResponsePayload(user)) const userLoginResponsePayload = await this.getUserLoginResponsePayload(user)
return res.json(userLoginResponsePayload)
} }
} }
@ -144,7 +151,8 @@ class Auth {
const compare = await bcrypt.compare(password, user.pash) const compare = await bcrypt.compare(password, user.pash)
if (compare) { if (compare) {
Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`) Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`)
res.json(this.getUserLoginResponsePayload(user)) const userLoginResponsePayload = await this.getUserLoginResponsePayload(user)
res.json(userLoginResponsePayload)
} else { } else {
Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`) Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`)
if (req.rateLimit.remaining <= 2) { if (req.rateLimit.remaining <= 2) {

View File

@ -17,7 +17,6 @@ class Database {
// TODO: below data should be loaded from the DB as needed // TODO: below data should be loaded from the DB as needed
this.libraryItems = [] this.libraryItems = []
this.users = [] this.users = []
this.libraries = []
this.settings = [] this.settings = []
this.collections = [] this.collections = []
this.playlists = [] this.playlists = []
@ -168,9 +167,6 @@ class Database {
this.users = await this.models.user.getOldUsers() this.users = await this.models.user.getOldUsers()
Logger.info(`[Database] Loaded ${this.users.length} users`) Logger.info(`[Database] Loaded ${this.users.length} users`)
this.libraries = await this.models.library.getAllOldLibraries()
Logger.info(`[Database] Loaded ${this.libraries.length} libraries`)
this.collections = await this.models.collection.getOldCollections() this.collections = await this.models.collection.getOldCollections()
Logger.info(`[Database] Loaded ${this.collections.length} collections`) Logger.info(`[Database] Loaded ${this.collections.length} collections`)
@ -190,6 +186,8 @@ class Database {
this.serverSettings.version = packageJson.version this.serverSettings.version = packageJson.version
await this.updateServerSettings() await this.updateServerSettings()
} }
this.models.library.getMaxDisplayOrder()
} }
async createRootUser(username, pash, token) { async createRootUser(username, pash, token) {
@ -254,7 +252,6 @@ class Database {
async createLibrary(oldLibrary) { async createLibrary(oldLibrary) {
if (!this.sequelize) return false if (!this.sequelize) return false
await this.models.library.createFromOld(oldLibrary) await this.models.library.createFromOld(oldLibrary)
this.libraries.push(oldLibrary)
} }
updateLibrary(oldLibrary) { updateLibrary(oldLibrary) {
@ -265,7 +262,6 @@ class Database {
async removeLibrary(libraryId) { async removeLibrary(libraryId) {
if (!this.sequelize) return false if (!this.sequelize) return false
await this.models.library.removeById(libraryId) await this.models.library.removeById(libraryId)
this.libraries = this.libraries.filter(lib => lib.id !== libraryId)
} }
async createCollection(oldCollection) { async createCollection(oldCollection) {

View File

@ -93,6 +93,10 @@ class Server {
this.auth.authMiddleware(req, res, next) this.auth.authMiddleware(req, res, next)
} }
/**
* Initialize database, backups, logs, rss feeds, cron jobs & watcher
* Cleanup stale/invalid data
*/
async init() { async init() {
Logger.info('[Server] Init v' + version) Logger.info('[Server] Init v' + version)
await this.playbackSessionManager.removeOrphanStreams() await this.playbackSessionManager.removeOrphanStreams()
@ -111,13 +115,15 @@ class Server {
await this.logManager.init() await this.logManager.init()
await this.apiRouter.checkRemoveEmptySeries(Database.series) // Remove empty series await this.apiRouter.checkRemoveEmptySeries(Database.series) // Remove empty series
await this.rssFeedManager.init() await this.rssFeedManager.init()
this.cronManager.init()
const libraries = await Database.models.library.getAllOldLibraries()
this.cronManager.init(libraries)
if (Database.serverSettings.scannerDisableWatcher) { if (Database.serverSettings.scannerDisableWatcher) {
Logger.info(`[Server] Watcher is disabled`) Logger.info(`[Server] Watcher is disabled`)
this.watcher.disabled = true this.watcher.disabled = true
} else { } else {
this.watcher.initWatcher(Database.libraries) this.watcher.initWatcher(libraries)
this.watcher.on('files', this.filesChanged.bind(this)) this.watcher.on('files', this.filesChanged.bind(this))
} }
} }

View File

@ -17,13 +17,12 @@ class FileSystemController {
}) })
// Do not include existing mapped library paths in response // Do not include existing mapped library paths in response
Database.libraries.forEach(lib => { const libraryFoldersPaths = await Database.models.libraryFolder.getAllLibraryFolderPaths()
lib.folders.forEach((folder) => { libraryFoldersPaths.forEach((path) => {
let dir = folder.fullPath let dir = path || ''
if (dir.includes(global.appRoot)) dir = dir.replace(global.appRoot, '') if (dir.includes(global.appRoot)) dir = dir.replace(global.appRoot, '')
excludedDirs.push(dir) excludedDirs.push(dir)
}) })
})
res.json({ res.json({
directories: await this.getDirectories(global.appRoot, '/', excludedDirs) directories: await this.getDirectories(global.appRoot, '/', excludedDirs)

View File

@ -44,7 +44,9 @@ class LibraryController {
const library = new Library() const library = new Library()
newLibraryPayload.displayOrder = Database.libraries.map(li => li.displayOrder).sort((a, b) => a - b).pop() + 1 let currentLargestDisplayOrder = await Database.models.library.getMaxDisplayOrder()
if (isNaN(currentLargestDisplayOrder)) currentLargestDisplayOrder = 0
newLibraryPayload.displayOrder = currentLargestDisplayOrder + 1
library.setData(newLibraryPayload) library.setData(newLibraryPayload)
await Database.createLibrary(library) await Database.createLibrary(library)
@ -60,17 +62,18 @@ class LibraryController {
res.json(library) res.json(library)
} }
findAll(req, res) { async findAll(req, res) {
const libraries = await Database.models.library.getAllOldLibraries()
const librariesAccessible = req.user.librariesAccessible || [] const librariesAccessible = req.user.librariesAccessible || []
if (librariesAccessible.length) { if (librariesAccessible.length) {
return res.json({ return res.json({
libraries: Database.libraries.filter(lib => librariesAccessible.includes(lib.id)).map(lib => lib.toJSON()) libraries: libraries.filter(lib => librariesAccessible.includes(lib.id)).map(lib => lib.toJSON())
}) })
} }
res.json({ res.json({
libraries: Database.libraries.map(lib => lib.toJSON()) libraries: libraries.map(lib => lib.toJSON())
// libraries: Database.libraries.map(lib => lib.toJSON())
}) })
} }
@ -151,6 +154,12 @@ class LibraryController {
return res.json(library.toJSON()) return res.json(library.toJSON())
} }
/**
* DELETE: /api/libraries/:id
* Delete a library
* @param {*} req
* @param {*} res
*/
async delete(req, res) { async delete(req, res) {
const library = req.library const library = req.library
@ -173,6 +182,10 @@ class LibraryController {
const libraryJson = library.toJSON() const libraryJson = library.toJSON()
await Database.removeLibrary(library.id) await Database.removeLibrary(library.id)
// Re-order libraries
await Database.models.library.resetDisplayOrder()
SocketAuthority.emitter('library_removed', libraryJson) SocketAuthority.emitter('library_removed', libraryJson)
return res.json(libraryJson) return res.json(libraryJson)
} }
@ -601,17 +614,23 @@ class LibraryController {
res.json(categories) res.json(categories)
} }
// PATCH: Change the order of libraries /**
* POST: /api/libraries/order
* Change the display order of libraries
* @param {*} req
* @param {*} res
*/
async reorder(req, res) { async reorder(req, res) {
if (!req.user.isAdminOrUp) { if (!req.user.isAdminOrUp) {
Logger.error('[LibraryController] ReorderLibraries invalid user', req.user) Logger.error('[LibraryController] ReorderLibraries invalid user', req.user)
return res.sendStatus(403) return res.sendStatus(403)
} }
const libraries = await Database.models.library.getAllOldLibraries()
var orderdata = req.body const orderdata = req.body
var hasUpdates = false let hasUpdates = false
for (let i = 0; i < orderdata.length; i++) { for (let i = 0; i < orderdata.length; i++) {
var library = Database.libraries.find(lib => lib.id === orderdata[i].id) const library = libraries.find(lib => lib.id === orderdata[i].id)
if (!library) { if (!library) {
Logger.error(`[LibraryController] Invalid library not found in reorder ${orderdata[i].id}`) Logger.error(`[LibraryController] Invalid library not found in reorder ${orderdata[i].id}`)
return res.sendStatus(500) return res.sendStatus(500)
@ -623,14 +642,14 @@ class LibraryController {
} }
if (hasUpdates) { if (hasUpdates) {
Database.libraries.sort((a, b) => a.displayOrder - b.displayOrder) libraries.sort((a, b) => a.displayOrder - b.displayOrder)
Logger.debug(`[LibraryController] Updated library display orders`) Logger.debug(`[LibraryController] Updated library display orders`)
} else { } else {
Logger.debug(`[LibraryController] Library orders were up to date`) Logger.debug(`[LibraryController] Library orders were up to date`)
} }
res.json({ res.json({
libraries: Database.libraries.map(lib => lib.toJSON()) libraries: libraries.map(lib => lib.toJSON())
}) })
} }
@ -902,13 +921,13 @@ class LibraryController {
res.send(opmlText) res.send(opmlText)
} }
middleware(req, res, next) { async middleware(req, res, next) {
if (!req.user.checkCanAccessLibrary(req.params.id)) { if (!req.user.checkCanAccessLibrary(req.params.id)) {
Logger.warn(`[LibraryController] Library ${req.params.id} not accessible to user ${req.user.username}`) Logger.warn(`[LibraryController] Library ${req.params.id} not accessible to user ${req.user.username}`)
return res.sendStatus(403) return res.sendStatus(403)
} }
const library = Database.libraries.find(lib => lib.id === req.params.id) const library = await Database.models.library.getOldById(req.params.id)
if (!library) { if (!library) {
return res.status(404).send('Library not found') return res.status(404).send('Library not found')
} }

View File

@ -24,18 +24,18 @@ class MiscController {
Logger.error('Invalid request, no files') Logger.error('Invalid request, no files')
return res.sendStatus(400) return res.sendStatus(400)
} }
var files = Object.values(req.files) const files = Object.values(req.files)
var title = req.body.title const title = req.body.title
var author = req.body.author const author = req.body.author
var series = req.body.series const series = req.body.series
var libraryId = req.body.library const libraryId = req.body.library
var folderId = req.body.folder const folderId = req.body.folder
var library = Database.libraries.find(lib => lib.id === libraryId) const library = await Database.models.library.getOldById(libraryId)
if (!library) { if (!library) {
return res.status(404).send(`Library not found with id ${libraryId}`) return res.status(404).send(`Library not found with id ${libraryId}`)
} }
var folder = library.folders.find(fold => fold.id === folderId) const folder = library.folders.find(fold => fold.id === folderId)
if (!folder) { if (!folder) {
return res.status(404).send(`Folder not found with id ${folderId} in library ${library.name}`) return res.status(404).send(`Folder not found with id ${folderId} in library ${library.name}`)
} }
@ -45,8 +45,8 @@ class MiscController {
} }
// For setting permissions recursively // For setting permissions recursively
var outputDirectory = '' let outputDirectory = ''
var firstDirPath = '' let firstDirPath = ''
if (library.isPodcast) { // Podcasts only in 1 folder if (library.isPodcast) { // Podcasts only in 1 folder
outputDirectory = Path.join(folder.fullPath, title) outputDirectory = Path.join(folder.fullPath, title)
@ -62,8 +62,7 @@ class MiscController {
} }
} }
var exists = await fs.pathExists(outputDirectory) if (await fs.pathExists(outputDirectory)) {
if (exists) {
Logger.error(`[Server] Upload directory "${outputDirectory}" already exists`) Logger.error(`[Server] Upload directory "${outputDirectory}" already exists`)
return res.status(500).send(`Directory "${outputDirectory}" already exists`) return res.status(500).send(`Directory "${outputDirectory}" already exists`)
} }
@ -132,12 +131,19 @@ class MiscController {
}) })
} }
authorize(req, res) { /**
* POST: /api/authorize
* Used to authorize an API token
*
* @param {*} req
* @param {*} res
*/
async authorize(req, res) {
if (!req.user) { if (!req.user) {
Logger.error('Invalid user in authorize') Logger.error('Invalid user in authorize')
return res.sendStatus(401) return res.sendStatus(401)
} }
const userResponse = this.auth.getUserLoginResponsePayload(req.user) const userResponse = await this.auth.getUserLoginResponsePayload(req.user)
res.json(userResponse) res.json(userResponse)
} }

View File

@ -19,7 +19,7 @@ class PodcastController {
} }
const payload = req.body const payload = req.body
const library = Database.libraries.find(lib => lib.id === payload.libraryId) const library = await Database.models.library.getOldById(payload.libraryId)
if (!library) { if (!library) {
Logger.error(`[PodcastController] Create: Library not found "${payload.libraryId}"`) Logger.error(`[PodcastController] Create: Library not found "${payload.libraryId}"`)
return res.status(404).send('Library not found') return res.status(404).send('Library not found')

View File

@ -13,13 +13,21 @@ class CronManager {
this.podcastCronExpressionsExecuting = [] this.podcastCronExpressionsExecuting = []
} }
init() { /**
this.initLibraryScanCrons() * Initialize library scan crons & podcast download crons
* @param {oldLibrary[]} libraries
*/
init(libraries) {
this.initLibraryScanCrons(libraries)
this.initPodcastCrons() this.initPodcastCrons()
} }
initLibraryScanCrons() { /**
for (const library of Database.libraries) { * Initialize library scan crons
* @param {oldLibrary[]} libraries
*/
initLibraryScanCrons(libraries) {
for (const library of libraries) {
if (library.settings.autoScanCronExpression) { if (library.settings.autoScanCronExpression) {
this.startCronForLibrary(library) this.startCronForLibrary(library)
} }

View File

@ -14,15 +14,15 @@ class NotificationManager {
return notificationData return notificationData
} }
onPodcastEpisodeDownloaded(libraryItem, episode) { async onPodcastEpisodeDownloaded(libraryItem, episode) {
if (!Database.notificationSettings.isUseable) return if (!Database.notificationSettings.isUseable) return
Logger.debug(`[NotificationManager] onPodcastEpisodeDownloaded: Episode "${episode.title}" for podcast ${libraryItem.media.metadata.title}`) Logger.debug(`[NotificationManager] onPodcastEpisodeDownloaded: Episode "${episode.title}" for podcast ${libraryItem.media.metadata.title}`)
const library = Database.libraries.find(lib => lib.id === libraryItem.libraryId) const library = await Database.models.library.getOldById(libraryItem.libraryId)
const eventData = { const eventData = {
libraryItemId: libraryItem.id, libraryItemId: libraryItem.id,
libraryId: libraryItem.libraryId, libraryId: libraryItem.libraryId,
libraryName: library ? library.name : 'Unknown', libraryName: library?.name || 'Unknown',
mediaTags: (libraryItem.media.tags || []).join(', '), mediaTags: (libraryItem.media.tags || []).join(', '),
podcastTitle: libraryItem.media.metadata.title, podcastTitle: libraryItem.media.metadata.title,
podcastAuthor: libraryItem.media.metadata.author || '', podcastAuthor: libraryItem.media.metadata.author || '',

View File

@ -4,6 +4,10 @@ const oldLibrary = require('../objects/Library')
module.exports = (sequelize) => { module.exports = (sequelize) => {
class Library extends Model { class Library extends Model {
/**
* Get all old libraries
* @returns {Promise<oldLibrary[]>}
*/
static async getAllOldLibraries() { static async getAllOldLibraries() {
const libraries = await this.findAll({ const libraries = await this.findAll({
include: sequelize.models.libraryFolder, include: sequelize.models.libraryFolder,
@ -12,6 +16,11 @@ module.exports = (sequelize) => {
return libraries.map(lib => this.getOldLibrary(lib)) return libraries.map(lib => this.getOldLibrary(lib))
} }
/**
* Convert expanded Library to oldLibrary
* @param {Library} libraryExpanded
* @returns {Promise<oldLibrary>}
*/
static getOldLibrary(libraryExpanded) { static getOldLibrary(libraryExpanded) {
const folders = libraryExpanded.libraryFolders.map(folder => { const folders = libraryExpanded.libraryFolders.map(folder => {
return { return {
@ -58,6 +67,11 @@ module.exports = (sequelize) => {
}) })
} }
/**
* Update library and library folders
* @param {object} oldLibrary
* @returns
*/
static async updateFromOld(oldLibrary) { static async updateFromOld(oldLibrary) {
const existingLibrary = await this.findByPk(oldLibrary.id, { const existingLibrary = await this.findByPk(oldLibrary.id, {
include: sequelize.models.libraryFolder include: sequelize.models.libraryFolder
@ -112,6 +126,11 @@ module.exports = (sequelize) => {
} }
} }
/**
* Destroy library by id
* @param {string} libraryId
* @returns
*/
static removeById(libraryId) { static removeById(libraryId) {
return this.destroy({ return this.destroy({
where: { where: {
@ -119,6 +138,59 @@ module.exports = (sequelize) => {
} }
}) })
} }
/**
* Get all library ids
* @returns {Promise<string[]>} array of library ids
*/
static async getAllLibraryIds() {
const libraries = await this.findAll({
attributes: ['id']
})
return libraries.map(l => l.id)
}
/**
* Find Library by primary key & return oldLibrary
* @param {string} libraryId
* @returns {Promise<oldLibrary|null>} Returns null if not found
*/
static async getOldById(libraryId) {
if (!libraryId) return null
const library = await this.findByPk(libraryId, {
include: sequelize.models.libraryFolder
})
if (!library) return null
return this.getOldLibrary(library)
}
/**
* Get the largest value in the displayOrder column
* Used for setting a new libraries display order
* @returns {Promise<number>}
*/
static getMaxDisplayOrder() {
return this.max('displayOrder') || 0
}
/**
* Updates displayOrder to be sequential
* Used after removing a library
*/
static async resetDisplayOrder() {
const libraries = await this.findAll({
order: [['displayOrder', 'ASC']]
})
for (let i = 0; i < libraries.length; i++) {
const library = libraries[i]
if (library.displayOrder !== i + 1) {
Logger.dev(`[Library] Updating display order of library from ${library.displayOrder} to ${i + 1}`)
await library.update({ displayOrder: i + 1 }).catch((error) => {
Logger.error(`[Library] Failed to update library display order to ${i + 1}`, error)
})
}
}
}
} }
Library.init({ Library.init({

View File

@ -1,7 +1,18 @@
const { DataTypes, Model } = require('sequelize') const { DataTypes, Model } = require('sequelize')
module.exports = (sequelize) => { module.exports = (sequelize) => {
class LibraryFolder extends Model { } class LibraryFolder extends Model {
/**
* Gets all library folder path strings
* @returns {Promise<string[]>} array of library folder paths
*/
static async getAllLibraryFolderPaths() {
const libraryFolders = await this.findAll({
attributes: ['path']
})
return libraryFolders.map(l => l.path)
}
}
LibraryFolder.init({ LibraryFolder.init({
id: { id: {

View File

@ -258,11 +258,15 @@ class User {
return hasUpdates return hasUpdates
} }
getDefaultLibraryId(libraries) { /**
* Get first available library id for user
*
* @param {string[]} libraryIds
* @returns {string|null}
*/
getDefaultLibraryId(libraryIds) {
// Libraries should already be in ascending display order, find first accessible // Libraries should already be in ascending display order, find first accessible
var firstAccessibleLibrary = libraries.find(lib => this.checkCanAccessLibrary(lib.id)) return libraryIds.find(lid => this.checkCanAccessLibrary(lid)) || null
if (!firstAccessibleLibrary) return null
return firstAccessibleLibrary.id
} }
// Returns most recent media progress w/ `media` object and optionally an `episode` object // Returns most recent media progress w/ `media` object and optionally an `episode` object

View File

@ -66,7 +66,7 @@ class Scanner {
} }
async scanLibraryItemByRequest(libraryItem) { async scanLibraryItemByRequest(libraryItem) {
const library = Database.libraries.find(lib => lib.id === libraryItem.libraryId) const library = await Database.models.library.getOldById(libraryItem.libraryId)
if (!library) { if (!library) {
Logger.error(`[Scanner] Scan libraryItem by id library not found "${libraryItem.libraryId}"`) Logger.error(`[Scanner] Scan libraryItem by id library not found "${libraryItem.libraryId}"`)
return ScanResult.NOTHING return ScanResult.NOTHING
@ -552,7 +552,7 @@ class Scanner {
for (const folderId in folderGroups) { for (const folderId in folderGroups) {
const libraryId = folderGroups[folderId].libraryId const libraryId = folderGroups[folderId].libraryId
const library = Database.libraries.find(lib => lib.id === libraryId) const library = await Database.models.library.getOldById(libraryId)
if (!library) { if (!library) {
Logger.error(`[Scanner] Library not found in files changed ${libraryId}`) Logger.error(`[Scanner] Library not found in files changed ${libraryId}`)
continue; continue;