Merge branch 'master' into series_cleanup_2

This commit is contained in:
advplyr 2024-09-14 10:11:16 -05:00
commit bedba39af9
10 changed files with 140 additions and 60 deletions

View file

@ -176,9 +176,9 @@ class Database {
}
try {
const migrationManager = new MigrationManager(this.sequelize, global.ConfigPath)
const migrationManager = new MigrationManager(this.sequelize, this.isNew, global.ConfigPath)
await migrationManager.init(packageJson.version)
if (!this.isNew) await migrationManager.runMigrations()
await migrationManager.runMigrations()
} catch (error) {
Logger.error(`[Database] Failed to run migrations`, error)
throw new Error('Database migration failed')

View file

@ -11,11 +11,13 @@ class MigrationManager {
/**
* @param {import('../Database').sequelize} sequelize
* @param {boolean} isDatabaseNew
* @param {string} [configPath]
*/
constructor(sequelize, configPath = global.configPath) {
constructor(sequelize, isDatabaseNew, configPath = global.configPath) {
if (!sequelize || !(sequelize instanceof Sequelize)) throw new Error('Sequelize instance is required for MigrationManager.')
this.sequelize = sequelize
this.isDatabaseNew = isDatabaseNew
if (!configPath) throw new Error('Config path is required for MigrationManager.')
this.configPath = configPath
this.migrationsSourceDir = path.join(__dirname, '..', 'migrations')
@ -42,6 +44,7 @@ class MigrationManager {
await this.fetchVersionsFromDatabase()
if (!this.maxVersion || !this.databaseVersion) throw new Error('Failed to fetch versions from the database.')
Logger.debug(`[MigrationManager] Database version: ${this.databaseVersion}, Max version: ${this.maxVersion}, Server version: ${this.serverVersion}`)
if (semver.gt(this.serverVersion, this.maxVersion)) {
try {
@ -63,6 +66,11 @@ class MigrationManager {
async runMigrations() {
if (!this.initialized) throw new Error('MigrationManager is not initialized. Call init() first.')
if (this.isDatabaseNew) {
Logger.info('[MigrationManager] Database is new. Skipping migrations.')
return
}
const versionCompare = semver.compare(this.serverVersion, this.databaseVersion)
if (versionCompare == 0) {
Logger.info('[MigrationManager] Database is already up to date.')
@ -180,7 +188,15 @@ class MigrationManager {
async checkOrCreateMigrationsMetaTable() {
const queryInterface = this.sequelize.getQueryInterface()
if (!(await queryInterface.tableExists(MigrationManager.MIGRATIONS_META_TABLE))) {
let migrationsMetaTableExists = await queryInterface.tableExists(MigrationManager.MIGRATIONS_META_TABLE)
if (this.isDatabaseNew && migrationsMetaTableExists) {
// This can happen if database was initialized with force: true
await queryInterface.dropTable(MigrationManager.MIGRATIONS_META_TABLE)
migrationsMetaTableExists = false
}
if (!migrationsMetaTableExists) {
await queryInterface.createTable(MigrationManager.MIGRATIONS_META_TABLE, {
key: {
type: DataTypes.STRING,
@ -192,9 +208,10 @@ class MigrationManager {
}
})
await this.sequelize.query("INSERT INTO :migrationsMeta (key, value) VALUES ('version', :version), ('maxVersion', '0.0.0')", {
replacements: { version: this.serverVersion, migrationsMeta: MigrationManager.MIGRATIONS_META_TABLE },
replacements: { version: this.isDatabaseNew ? this.serverVersion : '0.0.0', migrationsMeta: MigrationManager.MIGRATIONS_META_TABLE },
type: Sequelize.QueryTypes.INSERT
})
Logger.debug(`[MigrationManager] Created migrationsMeta table: "${MigrationManager.MIGRATIONS_META_TABLE}"`)
}
}
@ -219,6 +236,7 @@ class MigrationManager {
await fs.copy(sourceFile, targetFile) // Asynchronously copy the files
})
)
Logger.debug(`[MigrationManager] Copied migrations to the config directory: "${this.migrationsDir}"`)
}
/**

View file

@ -1,5 +1,6 @@
const { DataTypes, Model, where, fn, col } = require('sequelize')
const parseNameString = require('../utils/parsers/parseNameString')
const { asciiOnlyToLowerCase } = require('../utils/index')
class Author extends Model {
constructor(values, options) {
@ -55,7 +56,7 @@ class Author extends Model {
static async getByNameAndLibrary(authorName, libraryId) {
return this.findOne({
where: [
where(fn('lower', col('name')), authorName.toLowerCase()),
where(fn('lower', col('name')), asciiOnlyToLowerCase(authorName)),
{
libraryId
}

View file

@ -1,6 +1,7 @@
const { DataTypes, Model, where, fn, col } = require('sequelize')
const { getTitlePrefixAtEnd } = require('../utils/index')
const { asciiOnlyToLowerCase } = require('../utils/index')
class Series extends Model {
constructor(values, options) {
@ -41,7 +42,7 @@ class Series extends Model {
static async getByNameAndLibrary(seriesName, libraryId) {
return this.findOne({
where: [
where(fn('lower', col('name')), seriesName.toLowerCase()),
where(fn('lower', col('name')), asciiOnlyToLowerCase(seriesName)),
{
libraryId
}

View file

@ -107,7 +107,7 @@ class User extends Model {
upload: type === 'root' || type === 'admin',
accessAllLibraries: true,
accessAllTags: true,
accessExplicitContent: true,
accessExplicitContent: type === 'root' || type === 'admin',
selectedTagsNotAccessible: false,
librariesAccessible: [],
itemTagsSelected: []

View file

@ -75,13 +75,14 @@ class LibraryScan {
return date.format(new Date(), 'YYYY-MM-DD') + '_' + this.id + '.txt'
}
get scanResultsString() {
if (this.error) return this.error
const strs = []
if (this.resultsAdded) strs.push(`${this.resultsAdded} added`)
if (this.resultsUpdated) strs.push(`${this.resultsUpdated} updated`)
if (this.resultsMissing) strs.push(`${this.resultsMissing} missing`)
if (!strs.length) return `Everything was up to date (${elapsedPretty(this.elapsed / 1000)})`
return strs.join(', ') + ` (${elapsedPretty(this.elapsed / 1000)})`
const changesDetected = strs.length > 0 ? strs.join(', ') : 'No changes detected'
const timeElapsed = `(${elapsedPretty(this.elapsed / 1000)})`
const error = this.error ? `${this.error}. ` : ''
return `${error}${changesDetected} ${timeElapsed}`
}
toJSON() {

View file

@ -79,43 +79,39 @@ class LibraryScanner {
Logger.info(`[LibraryScanner] Starting${forceRescan ? ' (forced)' : ''} library scan ${libraryScan.id} for ${libraryScan.libraryName}`)
const canceled = await this.scanLibrary(libraryScan, forceRescan)
try {
const canceled = await this.scanLibrary(libraryScan, forceRescan)
libraryScan.setComplete()
if (canceled) {
Logger.info(`[LibraryScanner] Library scan canceled for "${libraryScan.libraryName}"`)
delete this.cancelLibraryScan[libraryScan.libraryId]
Logger.info(`[LibraryScanner] Library scan "${libraryScan.id}" ${canceled ? 'canceled after' : 'completed in'} ${libraryScan.elapsedTimestamp} | ${libraryScan.resultStats}`)
if (!canceled) {
library.lastScan = Date.now()
library.lastScanVersion = packageJson.version
if (library.isBook) {
const newExtraData = library.extraData || {}
newExtraData.lastScanMetadataPrecedence = library.settings.metadataPrecedence
library.extraData = newExtraData
library.changed('extraData', true)
}
await library.save()
}
task.setFinished(`${canceled ? 'Canceled' : 'Completed'}. ${libraryScan.scanResultsString}`)
} catch (err) {
libraryScan.setComplete(err)
Logger.error(`[LibraryScanner] Library scan ${libraryScan.id} failed after ${libraryScan.elapsedTimestamp} | ${libraryScan.resultStats}.`, err)
task.setFailed(`Failed. ${libraryScan.scanResultsString}`)
}
libraryScan.setComplete()
Logger.info(`[LibraryScanner] Library scan ${libraryScan.id} completed in ${libraryScan.elapsedTimestamp} | ${libraryScan.resultStats}`)
if (this.cancelLibraryScan[libraryScan.libraryId]) delete this.cancelLibraryScan[libraryScan.libraryId]
this.librariesScanning = this.librariesScanning.filter((ls) => ls.id !== library.id)
if (canceled && !libraryScan.totalResults) {
task.setFinished('Scan canceled')
TaskManager.taskFinished(task)
const emitData = libraryScan.getScanEmitData
emitData.results = null
return
}
library.lastScan = Date.now()
library.lastScanVersion = packageJson.version
if (library.isBook) {
const newExtraData = library.extraData || {}
newExtraData.lastScanMetadataPrecedence = library.settings.metadataPrecedence
library.extraData = newExtraData
library.changed('extraData', true)
}
await library.save()
task.setFinished(libraryScan.scanResultsString)
TaskManager.taskFinished(task)
if (libraryScan.totalResults) {
libraryScan.saveLog()
}
libraryScan.saveLog()
}
/**
@ -140,7 +136,7 @@ class LibraryScanner {
libraryItemDataFound = libraryItemDataFound.concat(itemDataFoundInFolder)
}
if (this.cancelLibraryScan[libraryScan.libraryId]) return true
if (this.shouldCancelScan(libraryScan)) return true
const existingLibraryItems = await Database.libraryItemModel.findAll({
where: {
@ -148,7 +144,7 @@ class LibraryScanner {
}
})
if (this.cancelLibraryScan[libraryScan.libraryId]) return true
if (this.shouldCancelScan(libraryScan)) return true
const libraryItemIdsMissing = []
let oldLibraryItemsUpdated = []
@ -216,7 +212,7 @@ class LibraryScanner {
oldLibraryItemsUpdated = []
}
if (this.cancelLibraryScan[libraryScan.libraryId]) return true
if (this.shouldCancelScan(libraryScan)) return true
}
// Emit item updates to client
if (oldLibraryItemsUpdated.length) {
@ -247,7 +243,7 @@ class LibraryScanner {
)
}
if (this.cancelLibraryScan[libraryScan.libraryId]) return true
if (this.shouldCancelScan(libraryScan)) return true
// Add new library items
if (libraryItemDataFound.length) {
@ -271,7 +267,7 @@ class LibraryScanner {
newOldLibraryItems = []
}
if (this.cancelLibraryScan[libraryScan.libraryId]) return true
if (this.shouldCancelScan(libraryScan)) return true
}
// Emit new items to client
if (newOldLibraryItems.length) {
@ -282,6 +278,17 @@ class LibraryScanner {
)
}
}
libraryScan.addLog(LogLevel.INFO, `Scan completed. ${libraryScan.resultStats}`)
return false
}
shouldCancelScan(libraryScan) {
if (this.cancelLibraryScan[libraryScan.libraryId]) {
libraryScan.addLog(LogLevel.INFO, `Scan canceled. ${libraryScan.resultStats}`)
return true
}
return false
}
/**