From b1cda26bb9aa8467f36e8f73d596f429ab406a16 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sat, 23 Aug 2025 13:32:42 +0200 Subject: [PATCH] Added scheduler call type using Quartz --- src/Indexer/Indexer.csproj | 2 + src/Indexer/Models/ActionJob.cs | 11 ++++ src/Indexer/Models/Script.cs | 2 + src/Indexer/Models/Worker.cs | 95 ++++++++++++++++++++++++++++++--- src/Indexer/Program.cs | 3 ++ 5 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 src/Indexer/Models/ActionJob.cs diff --git a/src/Indexer/Indexer.csproj b/src/Indexer/Indexer.csproj index 61f1a7a..5978d32 100644 --- a/src/Indexer/Indexer.csproj +++ b/src/Indexer/Indexer.csproj @@ -9,6 +9,8 @@ + + diff --git a/src/Indexer/Models/ActionJob.cs b/src/Indexer/Models/ActionJob.cs new file mode 100644 index 0000000..68342a1 --- /dev/null +++ b/src/Indexer/Models/ActionJob.cs @@ -0,0 +1,11 @@ +using Indexer.Models; +using Quartz; +public class ActionJob : IJob +{ + public Task Execute(IJobExecutionContext context) + { + var action = (Action)context.MergedJobDataMap["action"]; + action?.Invoke(); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Indexer/Models/Script.cs b/src/Indexer/Models/Script.cs index bc167f3..d3b58bc 100644 --- a/src/Indexer/Models/Script.cs +++ b/src/Indexer/Models/Script.cs @@ -120,6 +120,8 @@ public class IntervalCallbackInfos : ICallbackInfos } +public class ScheduleCallbackInfos : ICallbackInfos {} + public class ManualTriggerCallbackInfos : ICallbackInfos {} public struct ScriptUpdateInfo diff --git a/src/Indexer/Models/Worker.cs b/src/Indexer/Models/Worker.cs index 4b79a8b..9374562 100644 --- a/src/Indexer/Models/Worker.cs +++ b/src/Indexer/Models/Worker.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; using Indexer.Exceptions; +using Quartz; +using Quartz.Impl; namespace Indexer.Models; @@ -69,7 +71,7 @@ public class WorkerCollection timer.AutoReset = true; timer.Enabled = true; DateTime now = DateTime.Now; - IntervalCall call = new(timer, worker.Scriptable, _logger, callConfig) + IntervalCall intervallCall = new(timer, worker.Scriptable, _logger, callConfig) { LastExecution = now, LastSuccessfulExecution = now @@ -79,29 +81,32 @@ public class WorkerCollection try { DateTime beforeExecution = DateTime.Now; - call.IsExecuting = true; + intervallCall.IsExecuting = true; try { worker.Scriptable.Update(new IntervalCallbackInfos() { sender = sender, e = e }); } finally { - call.IsExecuting = false; - call.LastExecution = beforeExecution; + intervallCall.IsExecuting = false; + intervallCall.LastExecution = beforeExecution; worker.LastExecution = beforeExecution; } DateTime afterExecution = DateTime.Now; - UpdateCallAndWorkerTimestamps(call, worker, beforeExecution, afterExecution); + UpdateCallAndWorkerTimestamps(intervallCall, worker, beforeExecution, afterExecution); } catch (Exception ex) { _logger.LogError("Exception occurred in a Call of Worker \"{name}\": \"{ex}\"", worker.Name, ex.Message); } }; - worker.Calls.Add(call); + worker.Calls.Add(intervallCall); break; case "schedule": // TODO implement scheduled tasks using Quartz - throw new NotImplementedException("schedule not implemented yet"); + ScheduleCall scheduleCall = new(worker, callConfig, _logger); + worker.Calls.Add(scheduleCall); + break; + //throw new NotImplementedException("schedule not implemented yet"); case "fileupdate": if (callConfig.Path is null) { @@ -221,6 +226,7 @@ public class CallConfig public required string Type { get; set; } public long? Interval { get; set; } // For Type: Interval public string? Path { get; set; } // For Type: FileSystemWatcher + public string? Schedule { get; set; } // For Type: Schedule } public interface ICall @@ -294,23 +300,96 @@ public class ScheduleCall : ICall { public bool IsEnabled { get; set; } public bool IsExecuting { get; set; } + public Worker Worker { get; } + public JobKey JobKey { get; } + public JobDataMap JobDataMap { get; } public CallConfig CallConfig { get; set; } + private ILogger _logger { get; } public DateTime? LastExecution { get; set; } public DateTime? LastSuccessfulExecution { get; set; } + private StdSchedulerFactory SchedulerFactory { get; } + private IScheduler Scheduler { get; } - public ScheduleCall(CallConfig callConfig) + public ScheduleCall(Worker worker, CallConfig callConfig, ILogger logger) { + Worker = worker; CallConfig = callConfig; + _logger = logger; IsEnabled = true; IsExecuting = false; + JobKey = new(worker.Name); + SchedulerFactory = new(); + Scheduler = SchedulerFactory.GetScheduler(CancellationToken.None).Result; + JobDataMap = []; + JobDataMap["action"] = () => + { + try + { + DateTime beforeExecution = DateTime.Now; + IsExecuting = true; + try + { + worker.Scriptable.Update(new ScheduleCallbackInfos()); + } + finally + { + IsExecuting = false; + LastExecution = beforeExecution; + worker.LastExecution = beforeExecution; + } + DateTime afterExecution = DateTime.Now; + WorkerCollection.UpdateCallAndWorkerTimestamps(this, worker, beforeExecution, afterExecution); + } + catch (Exception ex) + { + _logger.LogError("Exception occurred in a Call of Worker \"{name}\": \"{ex}\"", worker.Name, ex.Message); + } + }; + CreateJob().Wait(); + Start(); } public void Start() { + if (!IsExecuting) + { + Scheduler.Start(CancellationToken.None).Wait(); + IsExecuting = true; + } } public void Stop() { + Scheduler.PauseAll(); + } + + + private async Task CreateJob() + { + if (CallConfig.Schedule is null) + { + throw new IndexerConfigurationException($"Interval not set for a Call in Worker \"{Worker.Name}\""); + } + try + { + + await Scheduler.ScheduleJob( + JobBuilder.Create() + .WithIdentity(JobKey) + .Build(), + TriggerBuilder.Create() + .ForJob(JobKey) + .WithIdentity(Worker.Name + "-trigger") + .UsingJobData(JobDataMap) + .WithCronSchedule(CallConfig.Schedule) + .Build(), + CancellationToken.None); + } + catch (FormatException) + { + _logger.LogCritical("Malformed Quartz Cron expression! Check your configuration and consult the documentation."); + throw; + } } public HealthCheckResult HealthCheck() diff --git a/src/Indexer/Program.cs b/src/Indexer/Program.cs index a91eee0..0090eb9 100644 --- a/src/Indexer/Program.cs +++ b/src/Indexer/Program.cs @@ -5,6 +5,7 @@ using ElmahCore; using ElmahCore.Mvc; using ElmahCore.Mvc.Logger; using Serilog; +using Quartz; var builder = WebApplication.CreateBuilder(args); @@ -30,6 +31,8 @@ builder.Services.AddElmah(Options => Options.LogPath = builder.Configuration.GetValue("EmbeddingsearchIndexer:Elmah:LogFolder") ?? "~/logs"; }); +builder.Services.AddQuartz(); + var app = builder.Build(); List? allowedIps = builder.Configuration.GetSection("EmbeddingsearchIndexer:Elmah:AllowedHosts") .Get>();