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 {
user: user.toJSONForBrowser(),
userDefaultLibraryId: user.getDefaultLibraryId(Database.libraries),
userDefaultLibraryId: user.getDefaultLibraryId(libraryIds),
serverSettings: Database.serverSettings.toJSONForBrowser(),
ereaderDevices: Database.emailSettings.getEReaderDevices(user),
Source: global.Source
@ -136,7 +142,8 @@ class Auth {
return res.status(401).send('Invalid root password (hint: there is none)')
} else {
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)
if (compare) {
Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`)
res.json(this.getUserLoginResponsePayload(user))
const userLoginResponsePayload = await this.getUserLoginResponsePayload(user)
res.json(userLoginResponsePayload)
} else {
Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`)
if (req.rateLimit.remaining <= 2) {

View File

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

View File

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

View File

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

View File

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

View File

@ -24,18 +24,18 @@ class MiscController {
Logger.error('Invalid request, no files')
return res.sendStatus(400)
}
var files = Object.values(req.files)
var title = req.body.title
var author = req.body.author
var series = req.body.series
var libraryId = req.body.library
var folderId = req.body.folder
const files = Object.values(req.files)
const title = req.body.title
const author = req.body.author
const series = req.body.series
const libraryId = req.body.library
const folderId = req.body.folder
var library = Database.libraries.find(lib => lib.id === libraryId)
const library = await Database.models.library.getOldById(libraryId)
if (!library) {
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) {
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
var outputDirectory = ''
var firstDirPath = ''
let outputDirectory = ''
let firstDirPath = ''
if (library.isPodcast) { // Podcasts only in 1 folder
outputDirectory = Path.join(folder.fullPath, title)
@ -62,8 +62,7 @@ class MiscController {
}
}
var exists = await fs.pathExists(outputDirectory)
if (exists) {
if (await fs.pathExists(outputDirectory)) {
Logger.error(`[Server] Upload 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) {
Logger.error('Invalid user in authorize')
return res.sendStatus(401)
}
const userResponse = this.auth.getUserLoginResponsePayload(req.user)
const userResponse = await this.auth.getUserLoginResponsePayload(req.user)
res.json(userResponse)
}

View File

@ -19,7 +19,7 @@ class PodcastController {
}
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) {
Logger.error(`[PodcastController] Create: Library not found "${payload.libraryId}"`)
return res.status(404).send('Library not found')

View File

@ -13,13 +13,21 @@ class CronManager {
this.podcastCronExpressionsExecuting = []
}
init() {
this.initLibraryScanCrons()
/**
* Initialize library scan crons & podcast download crons
* @param {oldLibrary[]} libraries
*/
init(libraries) {
this.initLibraryScanCrons(libraries)
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) {
this.startCronForLibrary(library)
}

View File

@ -14,15 +14,15 @@ class NotificationManager {
return notificationData
}
onPodcastEpisodeDownloaded(libraryItem, episode) {
async onPodcastEpisodeDownloaded(libraryItem, episode) {
if (!Database.notificationSettings.isUseable) return
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 = {
libraryItemId: libraryItem.id,
libraryId: libraryItem.libraryId,
libraryName: library ? library.name : 'Unknown',
libraryName: library?.name || 'Unknown',
mediaTags: (libraryItem.media.tags || []).join(', '),
podcastTitle: libraryItem.media.metadata.title,
podcastAuthor: libraryItem.media.metadata.author || '',

View File

@ -4,6 +4,10 @@ const oldLibrary = require('../objects/Library')
module.exports = (sequelize) => {
class Library extends Model {
/**
* Get all old libraries
* @returns {Promise<oldLibrary[]>}
*/
static async getAllOldLibraries() {
const libraries = await this.findAll({
include: sequelize.models.libraryFolder,
@ -12,6 +16,11 @@ module.exports = (sequelize) => {
return libraries.map(lib => this.getOldLibrary(lib))
}
/**
* Convert expanded Library to oldLibrary
* @param {Library} libraryExpanded
* @returns {Promise<oldLibrary>}
*/
static getOldLibrary(libraryExpanded) {
const folders = libraryExpanded.libraryFolders.map(folder => {
return {
@ -58,6 +67,11 @@ module.exports = (sequelize) => {
})
}
/**
* Update library and library folders
* @param {object} oldLibrary
* @returns
*/
static async updateFromOld(oldLibrary) {
const existingLibrary = await this.findByPk(oldLibrary.id, {
include: sequelize.models.libraryFolder
@ -112,6 +126,11 @@ module.exports = (sequelize) => {
}
}
/**
* Destroy library by id
* @param {string} libraryId
* @returns
*/
static removeById(libraryId) {
return this.destroy({
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({

View File

@ -1,7 +1,18 @@
const { DataTypes, Model } = require('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({
id: {

View File

@ -258,11 +258,15 @@ class User {
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
var firstAccessibleLibrary = libraries.find(lib => this.checkCanAccessLibrary(lib.id))
if (!firstAccessibleLibrary) return null
return firstAccessibleLibrary.id
return libraryIds.find(lid => this.checkCanAccessLibrary(lid)) || null
}
// Returns most recent media progress w/ `media` object and optionally an `episode` object

View File

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