using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Quartz; using Quartz.Impl; using Quartz.Impl.Matchers; using Quartz.Spi; namespace Ombi.Helpers { public class OmbiQuartz { protected IScheduler _scheduler { get; set; } public static IScheduler Scheduler => Instance._scheduler; private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); // Singleton protected static OmbiQuartz _instance; /// /// Singleton /// public static OmbiQuartz Instance => _instance ?? (_instance = new OmbiQuartz()); protected OmbiQuartz() : this(true) { } /// /// Allows tests to skip the async scheduler initialisation and supply their own scheduler, /// since Init() runs async void and would otherwise race with (and overwrite) a test scheduler. /// protected OmbiQuartz(bool init) { if (init) { Init(); } } private async void Init() { _scheduler = await new StdSchedulerFactory().GetScheduler(); } public IScheduler UseJobFactory(IJobFactory jobFactory) { Scheduler.JobFactory = jobFactory; return Scheduler; } public static async Task IsJobRunning(string jobName) { var running = await Scheduler.GetCurrentlyExecutingJobs(); return running.Any(x => x.JobDetail.Key.Name.Equals(jobName, StringComparison.InvariantCultureIgnoreCase)); } public async Task AddJob(string name, string group, string cronExpression, Dictionary jobData = null) where T : IJob { var jobBuilder = JobBuilder.Create() .WithIdentity(new JobKey(name, group)); if (jobData != null) { foreach (var o in jobData) { jobBuilder.UsingJobData(o.Key, o.Value); } } if (!cronExpression.HasValue()) { jobBuilder.StoreDurably(true); } var job = jobBuilder.Build(); if (cronExpression.HasValue()) { ITrigger jobTrigger = TriggerBuilder.Create() .WithIdentity(name + "Trigger", group) .WithCronSchedule(cronExpression, x => x.WithMisfireHandlingInstructionFireAndProceed()) .ForJob(name, group) .StartNow() .Build(); await Scheduler.ScheduleJob(job, jobTrigger); } else { await Scheduler.AddJob(job, true); } } public static async Task TriggerJob(string jobName, string group) { await TriggerJobIfNotRunning(jobName, group); } /// /// Triggers the job unless it is already running. Returns whether the job was triggered, /// so callers that depend on the job running (e.g. a resync after wiping data) can react. /// public static async Task TriggerJobIfNotRunning(string jobName, string group, IDictionary data = null) { await _semaphore.WaitAsync(); try { if (await IsJobRunning(jobName)) { return false; } if (data != null) { await Scheduler.TriggerJob(new JobKey(jobName, group), new JobDataMap(data)); } else { await Scheduler.TriggerJob(new JobKey(jobName, group)); } return true; } finally { _semaphore.Release(); } } public static async Task TriggerJob(string jobName, string group, IDictionary data) { await Scheduler.TriggerJob(new JobKey(jobName, group), new JobDataMap(data)); } public static async Task Start() { await Scheduler.Start(); } } }