mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-26 16:48:53 +01:00
Add:Schedule periodic library scans #655
This commit is contained in:
parent
0c20988e18
commit
2304f37cbe
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 = []
|
||||
|
@ -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`)
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
61
server/managers/CronManager.js
Normal file
61
server/managers/CronManager.js
Normal 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
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user