diff --git a/Shoko.Server/API/v3/Controllers/QueueController.cs b/Shoko.Server/API/v3/Controllers/QueueController.cs index acb240ee5..49c9208ba 100644 --- a/Shoko.Server/API/v3/Controllers/QueueController.cs +++ b/Shoko.Server/API/v3/Controllers/QueueController.cs @@ -93,9 +93,9 @@ public async Task Pause() /// Void. [Authorize("admin")] [HttpPost("Clear")] - public ActionResult Clear() + public async Task Clear() { - // TODO clear queue + await _queueHandler.Clear(); return Ok(); } diff --git a/Shoko.Server/Scheduling/QuartzStartup.cs b/Shoko.Server/Scheduling/QuartzStartup.cs index 9a717d25a..bb828d36e 100644 --- a/Shoko.Server/Scheduling/QuartzStartup.cs +++ b/Shoko.Server/Scheduling/QuartzStartup.cs @@ -3,12 +3,14 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Microsoft.Data.Sqlite; using Microsoft.Extensions.DependencyInjection; using MySqlConnector; using Quartz; using Quartz.AspNetCore; using QuartzJobFactory; +using QuartzJobFactory.Attributes; using Shoko.Server.Scheduling.Acquisition.Filters; using Shoko.Server.Scheduling.Delegates; using Shoko.Server.Scheduling.Jobs; @@ -21,9 +23,36 @@ namespace Shoko.Server.Scheduling; public static class QuartzStartup { + public static async Task ScheduleRecurringJobs(bool replace) + { + // this needs to run immediately upon scheduling, so it replaces always. Others will run on other schedules + await ScheduleRecurringJob( + triggerConfig: t => t.WithSimpleSchedule(tr => tr.WithIntervalInMinutes(5).RepeatForever()).StartNow(), replace: true); + + // TODO the other schedule-based jobs that are on timers + } + + private static async Task ScheduleRecurringJob(Action jobConfig = null, Func triggerConfig = null, bool replace = false) where T : class, IJob + { + var scheduler = await Utils.ServiceContainer.GetRequiredService().GetScheduler(); + jobConfig ??= _ => {}; + triggerConfig ??= t => t; + var groupName = typeof(T).GetCustomAttribute()?.GroupName; + var jobKey = JobKeyBuilder.Create().WithGroup(groupName).UsingJobData(jobConfig).Build(); + if (!await scheduler.CheckExists(jobKey)) + { + await scheduler.ScheduleJob(JobBuilder.Create().UsingJobData(jobConfig).WithGeneratedIdentity().Build(), + triggerConfig(TriggerBuilder.Create()).Build()); + } else if (replace) + { + await scheduler.RescheduleJob(new TriggerKey(jobKey.Name, jobKey.Group), triggerConfig(TriggerBuilder.Create()).Build()); + } + } + internal static void AddQuartz(this IServiceCollection services) { // this lets us inject the shoko JobFactory explicitly, instead of only IJobFactory + ShokoEventHandler.Instance.Starting += (_, _) => ScheduleRecurringJobs(false).GetAwaiter().GetResult(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -39,11 +68,6 @@ internal static void AddQuartz(this IServiceCollection services) q.MaxBatchSize = (int) Math.Round(Environment.ProcessorCount * 1.25D, MidpointRounding.AwayFromZero); q.BatchTriggerAcquisitionFireAheadTimeWindow = TimeSpan.FromSeconds(30); q.UseJobFactory(); - - // Register the connectivity monitor job with a trigger that executes every 5 minutes - q.ScheduleJob( - trigger => trigger.WithIdentity("UptimeMonitor", "System").WithSimpleSchedule(tr => tr.WithIntervalInMinutes(5).RepeatForever()).StartNow(), - j => j.WithGeneratedIdentity()); }); services.AddQuartzServer(options => diff --git a/Shoko.Server/Scheduling/QueueHandler.cs b/Shoko.Server/Scheduling/QueueHandler.cs index dfd594f25..627cd1524 100644 --- a/Shoko.Server/Scheduling/QueueHandler.cs +++ b/Shoko.Server/Scheduling/QueueHandler.cs @@ -57,6 +57,14 @@ public async Task Resume() await scheduler.Start(); } + public async Task Clear() + { + var scheduler = await _schedulerFactory.GetScheduler(); + if (scheduler.IsShutdown || !scheduler.IsStarted) return; + await scheduler.Clear(); + await QuartzStartup.ScheduleRecurringJobs(false); + } + public bool Paused { get diff --git a/Shoko.Server/Scheduling/ThreadPooledJobStore.cs b/Shoko.Server/Scheduling/ThreadPooledJobStore.cs index bfff85829..2fb13ed07 100644 --- a/Shoko.Server/Scheduling/ThreadPooledJobStore.cs +++ b/Shoko.Server/Scheduling/ThreadPooledJobStore.cs @@ -37,16 +37,16 @@ public ThreadPooledJobStore(ILogger logger, IEnumerable SettingsSaved; public event EventHandler AVDumpEvent; - public event EventHandler Start; + public event EventHandler Starting; + public event EventHandler Started; public event EventHandler Shutdown; @@ -224,9 +225,14 @@ public void OnAVDumpGenericException(AVDumpHelper.AVDumpSession session, Excepti }); } - public void OnStart() + public void OnStarting() { - Start?.Invoke(null, EventArgs.Empty); + Starting?.Invoke(null, EventArgs.Empty); + } + + public void OnStarted() + { + Started?.Invoke(null, EventArgs.Empty); } public bool OnShutdown() diff --git a/Shoko.Server/Server/ShokoServer.cs b/Shoko.Server/Server/ShokoServer.cs index bb53f143a..379a1ba2e 100644 --- a/Shoko.Server/Server/ShokoServer.cs +++ b/Shoko.Server/Server/ShokoServer.cs @@ -121,6 +121,8 @@ public bool StartUpServer() // run rotator once and set 24h delay Utils.ServiceContainer.GetRequiredService().Start(); + ShokoEventHandler.Instance.OnStarting(); + // for log readability, this will simply init the singleton Utils.ServiceContainer.GetService(); return true; @@ -165,6 +167,7 @@ private bool CheckBlockedFiles() #region Database settings and initial start up + public event EventHandler ServerStarting; public event EventHandler LoginFormNeeded; public event EventHandler DatabaseSetup; public event EventHandler DBSetupCompleted; @@ -203,6 +206,7 @@ private void WorkerSetupDB_ReportProgress() } DBSetupCompleted?.Invoke(this, EventArgs.Empty); + ShokoEventHandler.Instance.OnStarted(); } private void ShowDatabaseSetup()