Add:Schedule periodic library scans #655

This commit is contained in:
advplyr 2022-08-17 18:44:21 -05:00
parent 0c20988e18
commit 2304f37cbe
10 changed files with 112 additions and 20 deletions

View File

@ -12,7 +12,7 @@
</div>
<div class="px-4 w-full text-sm pt-6 pb-20 rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
<component v-if="libraryCopy && show" :is="tabName" :is-new="!library" :library="libraryCopy" :processing.sync="processing" @update="updateLibrary" @close="show = false" />
<component v-if="libraryCopy && show" ref="tabComponent" :is="tabName" :is-new="!library" :library="libraryCopy" :processing.sync="processing" @update="updateLibrary" @close="show = false" />
<div class="absolute bottom-0 left-0 w-full px-4 py-4 border-t border-white border-opacity-10">
<div class="flex justify-end">
@ -144,6 +144,13 @@ export default {
submit() {
if (!this.validate()) return
// If custom expression input is focused then unfocus it instead of submitting
if (this.$refs.tabComponent && this.$refs.tabComponent.checkBlurExpressionInput) {
if (this.$refs.tabComponent.checkBlurExpressionInput()) {
return
}
}
if (this.library) {
this.submitUpdateLibrary()
} else {

View File

@ -4,7 +4,7 @@
<p class="text-lg">Schedule Automatic Library Scans</p>
<ui-checkbox v-model="enableAutoScan" @input="toggleEnableAutoScan" label="Enable" checkbox-bg="bg" label-class="pl-2 text-base" />
</div>
<widgets-cron-expression-builder v-if="enableAutoScan" v-model="cronExpression" @input="updatedCron" />
<widgets-cron-expression-builder ref="cronExpressionBuilder" v-if="enableAutoScan" v-model="cronExpression" @input="updatedCron" />
</div>
</template>
@ -25,6 +25,11 @@ export default {
},
computed: {},
methods: {
checkBlurExpressionInput() {
// returns true if advanced cron input is focused
if (!this.$refs.cronExpressionBuilder) return false
return this.$refs.cronExpressionBuilder.checkBlurExpressionInput()
},
toggleEnableAutoScan(v) {
if (!v) this.updatedCron(null)
else if (!this.cronExpression) {

View File

@ -36,7 +36,8 @@ export default {
data() {
return {
showPassword: false,
isHovering: false
isHovering: false,
isFocused: false
}
},
computed: {
@ -66,9 +67,11 @@ export default {
this.inputValue = ''
},
focused() {
this.isFocused = true
this.$emit('focus')
},
blurred() {
this.isFocused = false
this.$emit('blur')
},
change(e) {

View File

@ -24,7 +24,7 @@
</template>
<template v-else>
<p class="px-1 text-sm font-semibold">Cron Expression</p>
<ui-text-input v-model="customCronExpression" @blur="cronExpressionBlur" label="Cron Expression" :padding-y="2" text-center class="w-full text-4xl -tracking-widest mb-4 font-mono" />
<ui-text-input ref="customExpressionInput" v-model="customCronExpression" @blur="cronExpressionBlur" label="Cron Expression" :padding-y="2" text-center class="w-full text-4xl -tracking-widest mb-4 font-mono" />
<div class="flex items-center justify-center">
<widgets-loading-spinner v-if="isValidating" class="mr-2" />
@ -127,6 +127,14 @@ export default {
}
},
methods: {
checkBlurExpressionInput() {
if (!this.showAdvancedView || !this.$refs.customExpressionInput) return false
if (this.$refs.customExpressionInput.isFocused) {
this.$refs.customExpressionInput.blur()
return true
}
return false
},
updateCron() {
if (!this.minuteIsValid || !this.hourIsValid || !this.selectedWeekdays.length) {
this.cronExpression = null
@ -168,11 +176,12 @@ export default {
this.customCronError = 'Invalid cron expression'
this.isValid = false
return
} else if (this.customCronExpression.split(' ')[0] === '*') {
this.customCronError = 'Cannot use * in minutes position'
this.isValid = false
return
}
// if (this.customCronExpression.split(' ')[0] === '*') {
// this.customCronError = 'Cannot use * in minutes position'
// this.isValid = false
// return
// }
if (this.customCronExpression !== this.cronExpression) {
this.selectedWeekdays = []

View File

@ -32,6 +32,7 @@ const PlaybackSessionManager = require('./managers/PlaybackSessionManager')
const PodcastManager = require('./managers/PodcastManager')
const AudioMetadataMangaer = require('./managers/AudioMetadataManager')
const RssFeedManager = require('./managers/RssFeedManager')
const CronManager = require('./managers/CronManager')
class Server {
constructor(SOURCE, PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH) {
@ -74,9 +75,10 @@ class Server {
this.rssFeedManager = new RssFeedManager(this.db, this.emitter.bind(this))
this.scanner = new Scanner(this.db, this.coverManager, this.emitter.bind(this))
this.cronManager = new CronManager(this.db, this.scanner)
// Routers
this.apiRouter = new ApiRouter(this.db, this.auth, this.scanner, this.playbackSessionManager, this.abMergeManager, this.coverManager, this.backupManager, this.watcher, this.cacheManager, this.podcastManager, this.audioMetadataManager, this.rssFeedManager, this.emitter.bind(this), this.clientEmitter.bind(this))
this.apiRouter = new ApiRouter(this.db, this.auth, this.scanner, this.playbackSessionManager, this.abMergeManager, this.coverManager, this.backupManager, this.watcher, this.cacheManager, this.podcastManager, this.audioMetadataManager, this.rssFeedManager, this.cronManager, this.emitter.bind(this), this.clientEmitter.bind(this))
this.hlsRouter = new HlsRouter(this.db, this.auth, this.playbackSessionManager, this.emitter.bind(this))
this.staticRouter = new StaticRouter(this.db)
@ -151,6 +153,7 @@ class Server {
await this.logManager.init()
await this.rssFeedManager.init()
this.podcastManager.init()
this.cronManager.init()
if (this.db.serverSettings.scannerDisableWatcher) {
Logger.info(`[Server] Watcher is disabled`)

View File

@ -108,6 +108,9 @@ class LibraryController {
// Update watcher
this.watcher.updateLibrary(library)
// Update auto scan cron
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))
if (itemsToRemove.length) {

View File

@ -3,14 +3,14 @@
const EventEmitter = require('events');
const TimeMatcher = require('./time-matcher');
class Scheduler extends EventEmitter{
constructor(pattern, timezone, autorecover){
class Scheduler extends EventEmitter {
constructor(pattern, timezone, autorecover) {
super();
this.timeMatcher = new TimeMatcher(pattern, timezone);
this.autorecover = autorecover;
}
start(){
start() {
// clear timeout if exists
this.stop();
@ -22,11 +22,11 @@ class Scheduler extends EventEmitter{
const elapsedTime = process.hrtime(lastCheck);
const elapsedMs = (elapsedTime[0] * 1e9 + elapsedTime[1]) / 1e6;
const missedExecutions = Math.floor(elapsedMs / 1000);
for(let i = missedExecutions; i >= 0; i--){
for (let i = missedExecutions; i >= 0; i--) {
const date = new Date(new Date().getTime() - i * 1000);
let date_tmp = this.timeMatcher.apply(date);
if(lastExecution.getTime() < date_tmp.getTime() && (i === 0 || this.autorecover) && this.timeMatcher.match(date)){
if (lastExecution.getTime() < date_tmp.getTime() && (i === 0 || this.autorecover) && this.timeMatcher.match(date)) {
this.emit('scheduled-time-matched', date_tmp);
date_tmp.setMilliseconds(0);
lastExecution = date_tmp;
@ -38,8 +38,8 @@ class Scheduler extends EventEmitter{
matchTime();
}
stop(){
if(this.timeout){
stop() {
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = null;

View File

@ -56,14 +56,14 @@ class BackupManager {
updateCronSchedule() {
if (this.scheduleTask && !this.serverSettings.backupSchedule) {
Logger.info(`[BackupManager] Disabling backup schedule`)
if (this.scheduleTask.destroy) this.scheduleTask.destroy()
if (this.scheduleTask.stop) this.scheduleTask.stop()
this.scheduleTask = null
} else if (!this.scheduleTask && this.serverSettings.backupSchedule) {
Logger.info(`[BackupManager] Starting backup schedule ${this.serverSettings.backupSchedule}`)
this.scheduleCron()
} else if (this.serverSettings.backupSchedule) {
Logger.info(`[BackupManager] Restarting backup schedule ${this.serverSettings.backupSchedule}`)
if (this.scheduleTask.destroy) this.scheduleTask.destroy()
if (this.scheduleTask.stop) this.scheduleTask.stop()
this.scheduleCron()
}
}

View File

@ -0,0 +1,61 @@
const cron = require('../libs/nodeCron')
const Logger = require('../Logger')
class CronManager {
constructor(db, scanner) {
this.db = db
this.scanner = scanner
this.libraryScanCrons = []
}
init() {
this.initLibraryScanCrons()
}
initLibraryScanCrons() {
for (const library of this.db.libraries) {
if (library.settings.autoScanCronExpression) {
this.startCronForLibrary(library)
}
}
}
startCronForLibrary(library) {
Logger.debug(`[CronManager] Init library scan cron for ${library.name} on schedule ${library.settings.autoScanCronExpression}`)
const libScanCron = cron.schedule(library.settings.autoScanCronExpression, () => {
Logger.debug(`[CronManager] Library scan cron executing for ${library.name}`)
this.scanner.scan(library)
})
this.libraryScanCrons.push({
libraryId: library.id,
expression: library.settings.autoScanCronExpression,
task: libScanCron
})
}
removeCronForLibrary(library) {
Logger.debug(`[CronManager] Removing library scan cron for ${library.name}`)
this.libraryScanCrons = this.libraryScanCrons.filter(lsc => lsc.libraryId !== library.id)
}
updateLibraryScanCron(library) {
const expression = library.settings.autoScanCronExpression
const existingCron = this.libraryScanCrons.find(lsc => lsc.libraryId === library.id)
if (!expression && existingCron) {
if (existingCron.task.stop) existingCron.task.stop()
this.removeCronForLibrary(library)
} else if (!existingCron && expression) {
this.startCronForLibrary(library)
} else if (existingCron && existingCron.expression !== expression) {
if (existingCron.task.stop) existingCron.task.stop()
this.removeCronForLibrary(library)
this.startCronForLibrary(library)
}
}
}
module.exports = CronManager

View File

@ -25,7 +25,7 @@ const Series = require('../objects/entities/Series')
const FileSystemController = require('../controllers/FileSystemController')
class ApiRouter {
constructor(db, auth, scanner, playbackSessionManager, abMergeManager, coverManager, backupManager, watcher, cacheManager, podcastManager, audioMetadataManager, rssFeedManager, emitter, clientEmitter) {
constructor(db, auth, scanner, playbackSessionManager, abMergeManager, coverManager, backupManager, watcher, cacheManager, podcastManager, audioMetadataManager, rssFeedManager, cronManager, emitter, clientEmitter) {
this.db = db
this.auth = auth
this.scanner = scanner
@ -38,6 +38,7 @@ class ApiRouter {
this.podcastManager = podcastManager
this.audioMetadataManager = audioMetadataManager
this.rssFeedManager = rssFeedManager
this.cronManager = cronManager
this.emitter = emitter
this.clientEmitter = clientEmitter