mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
dailynote refresh refactor
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
|
||||
namespace Snap.Hutao.Test.BaseClassLibrary;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Snap.Hutao.Test.BaseClassLibrary;
|
||||
|
||||
[TestClass]
|
||||
public class UnsafeAccessorTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void UnsafeAccessorCanGetInterfaceProperty()
|
||||
{
|
||||
TestClass test = new();
|
||||
int value = InternalGetInterfaceProperty(test);
|
||||
Assert.AreEqual(3, value);
|
||||
}
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_TestProperty")]
|
||||
private static extern int InternalGetInterfaceProperty(ITestInterface instance);
|
||||
|
||||
interface ITestInterface
|
||||
{
|
||||
internal int TestProperty { get; }
|
||||
}
|
||||
|
||||
internal sealed class TestClass : ITestInterface
|
||||
{
|
||||
public int TestProperty { get; } = 3;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Snap.Hutao.Test.PlatformExtensions;
|
||||
|
||||
@@ -11,6 +12,8 @@ public sealed class DependencyInjectionTest
|
||||
.AddSingleton<IService, ServiceA>()
|
||||
.AddSingleton<IService, ServiceB>()
|
||||
.AddScoped<IScopedService, ServiceA>()
|
||||
.AddKeyedTransient<IKeyedService, KeyedServiceA>("A")
|
||||
.AddKeyedTransient<IKeyedService, KeyedServiceB>("B")
|
||||
.AddTransient(typeof(IGenericService<>), typeof(GenericService<>))
|
||||
.AddLogging(builder => builder.AddConsole())
|
||||
.BuildServiceProvider();
|
||||
@@ -50,6 +53,15 @@ public sealed class DependencyInjectionTest
|
||||
Assert.IsNotNull(services.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(IScopedService)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void KeyedServicesCanBeResolvedAsEnumerable()
|
||||
{
|
||||
Assert.IsNotNull(services.GetRequiredKeyedService<IKeyedService>("A"));
|
||||
Assert.IsNotNull(services.GetRequiredKeyedService<IKeyedService>("B"));
|
||||
|
||||
Assert.AreEqual(0, services.GetServices<IKeyedService>().Count());
|
||||
}
|
||||
|
||||
private interface IService
|
||||
{
|
||||
Guid Id { get; }
|
||||
@@ -95,4 +107,14 @@ public sealed class DependencyInjectionTest
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private interface IKeyedService;
|
||||
|
||||
private sealed class KeyedServiceA : IKeyedService
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class KeyedServiceB : IKeyedService
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,6 @@ public sealed partial class App : Application
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public bool IsExiting { get; private set; }
|
||||
|
||||
public new void Exit()
|
||||
{
|
||||
XamlWindowLifetime.IsApplicationExiting = true;
|
||||
@@ -85,9 +83,9 @@ public sealed partial class App : Application
|
||||
activation.Activate(HutaoActivationArguments.FromAppActivationArguments(activatedEventArgs));
|
||||
activation.PostInitialization();
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// AppInstance.GetCurrent() calls failed
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Quartz;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
using Snap.Hutao.Service.Job;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Globalization;
|
||||
@@ -33,6 +38,9 @@ internal static class DependencyInjection
|
||||
})
|
||||
.AddMemoryCache()
|
||||
|
||||
// Quartz
|
||||
.AddQuartz()
|
||||
|
||||
// Hutao extensions
|
||||
.AddJsonOptions()
|
||||
.AddDatabase()
|
||||
|
||||
@@ -6,11 +6,13 @@ using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.LifeCycle.InterProcess;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Core.Shell;
|
||||
using Snap.Hutao.Core.Windowing.HotKey;
|
||||
using Snap.Hutao.Core.Windowing.NotifyIcon;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
using Snap.Hutao.Service.Discord;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
using Snap.Hutao.Service.Job;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
@@ -67,6 +69,9 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
|
||||
serviceProvider.GetRequiredService<App>().DispatcherShutdownMode = DispatcherShutdownMode.OnExplicitShutdown;
|
||||
_ = serviceProvider.GetRequiredService<NotifyIconController>();
|
||||
}
|
||||
|
||||
serviceProvider.GetRequiredService<IScheduleTaskInterop>().UnregisterAllTasks();
|
||||
serviceProvider.GetRequiredService<IQuartzService>().StartAsync(default).SafeForget();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -8,20 +8,9 @@ namespace Snap.Hutao.Core.Shell;
|
||||
/// </summary>
|
||||
internal interface IScheduleTaskInterop
|
||||
{
|
||||
bool IsDailyNoteRefreshEnabled();
|
||||
|
||||
/// <summary>
|
||||
/// 注册实时便笺刷新任务
|
||||
/// </summary>
|
||||
/// <param name="interval">间隔(秒)</param>
|
||||
/// <returns>是否注册或修改成功</returns>
|
||||
bool RegisterForDailyNoteRefresh(int interval);
|
||||
|
||||
/// <summary>
|
||||
/// 卸载全部注册的任务
|
||||
/// </summary>
|
||||
/// <returns>是否卸载成功</returns>
|
||||
bool UnregisterAllTasks();
|
||||
|
||||
bool UnregisterForDailyNoteRefresh();
|
||||
}
|
||||
@@ -16,60 +16,6 @@ namespace Snap.Hutao.Core.Shell;
|
||||
internal sealed class ScheduleTaskInterop : IScheduleTaskInterop
|
||||
{
|
||||
private const string DailyNoteRefreshTaskName = "SnapHutaoDailyNoteRefreshTask";
|
||||
private const string DailyNoteRefreshScriptName = "DailyNoteRefresh";
|
||||
|
||||
/// <summary>
|
||||
/// 注册实时便笺刷新任务
|
||||
/// </summary>
|
||||
/// <param name="interval">间隔(秒)</param>
|
||||
/// <returns>是否注册或修改成功</returns>
|
||||
public bool RegisterForDailyNoteRefresh(int interval)
|
||||
{
|
||||
try
|
||||
{
|
||||
TaskDefinition task = TaskService.Instance.NewTask();
|
||||
task.RegistrationInfo.Description = SH.CoreScheduleTaskHelperDailyNoteRefreshTaskDescription;
|
||||
task.Triggers.Add(new TimeTrigger() { Repetition = new(TimeSpan.FromSeconds(interval), TimeSpan.Zero), });
|
||||
|
||||
string scriptPath = EnsureWScriptCreated(DailyNoteRefreshScriptName, "hutao://DailyNote/Refresh");
|
||||
task.Actions.Add("wscript", $@"/b ""{scriptPath}""");
|
||||
|
||||
TaskService.Instance.RootFolder.RegisterTaskDefinition(DailyNoteRefreshTaskName, task);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (WScriptExists(DailyNoteRefreshScriptName, out string fullPath))
|
||||
{
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool UnregisterForDailyNoteRefresh()
|
||||
{
|
||||
try
|
||||
{
|
||||
TaskService.Instance.RootFolder.DeleteTask(DailyNoteRefreshTaskName, false);
|
||||
if (WScriptExists(DailyNoteRefreshScriptName, out string fullPath))
|
||||
{
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDailyNoteRefreshEnabled()
|
||||
{
|
||||
return TaskService.Instance.RootFolder.Tasks.Any(task => task.Name is DailyNoteRefreshTaskName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卸载全部注册的任务
|
||||
@@ -91,25 +37,4 @@ internal sealed class ScheduleTaskInterop : IScheduleTaskInterop
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string EnsureWScriptCreated(string name, string url, bool forceCreate = false)
|
||||
{
|
||||
if (WScriptExists(name, out string fullName) && !forceCreate)
|
||||
{
|
||||
return fullName;
|
||||
}
|
||||
|
||||
string script = $"""CreateObject("WScript.Shell").Run "cmd /c start {url}", 0, False""";
|
||||
File.WriteAllText(fullName, script);
|
||||
|
||||
return fullName;
|
||||
}
|
||||
|
||||
private static bool WScriptExists(string name, out string fullName)
|
||||
{
|
||||
string tempFolder = ApplicationData.Current.TemporaryFolder.Path;
|
||||
fullName = Path.Combine(tempFolder, "Script", $"{name}.vbs");
|
||||
Directory.CreateDirectory(Path.Combine(tempFolder, "Script"));
|
||||
return File.Exists(fullName);
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,8 @@ internal sealed partial class HotKeyOptions : ObservableObject, IDisposable
|
||||
|
||||
isDisposed = true;
|
||||
|
||||
UnregisterAll();
|
||||
MouseClickRepeatForeverKeyCombination.Unregister();
|
||||
|
||||
hotKeyMessageWindow.Dispose();
|
||||
cancellationTokenSource?.Dispose();
|
||||
|
||||
@@ -106,11 +107,6 @@ internal sealed partial class HotKeyOptions : ObservableObject, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterAll()
|
||||
{
|
||||
MouseClickRepeatForeverKeyCombination.Unregister();
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH002")]
|
||||
private void OnHotKeyPressed(HotKeyParameter parameter)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@ internal sealed partial class SettingEntry
|
||||
|
||||
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
|
||||
|
||||
public const string DailyNoteIsAutoRefreshEnabled = "DailyNote.IsAutoRefreshEnabled";
|
||||
public const string DailyNoteRefreshSeconds = "DailyNote.RefreshSeconds";
|
||||
public const string DailyNoteReminderNotify = "DailyNote.ReminderNotify";
|
||||
public const string DailyNoteSilentWhenPlayingGame = "DailyNote.SilentWhenPlayingGame";
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Shell;
|
||||
using Quartz;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Service.Job;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Service.DailyNote;
|
||||
@@ -26,10 +25,9 @@ internal sealed partial class DailyNoteOptions : DbStoreOptions
|
||||
new(SH.ViewModelDailyNoteRefreshTime60, OneMinute * 60),
|
||||
];
|
||||
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IScheduleTaskInterop scheduleTaskInterop;
|
||||
private readonly IQuartzService quartzService;
|
||||
|
||||
private bool? isAutoRefreshEnabled;
|
||||
private NameValue<int>? selectedRefreshTime;
|
||||
private bool? isReminderNotification;
|
||||
private bool? isSilentWhenPlayingGame;
|
||||
@@ -39,68 +37,41 @@ internal sealed partial class DailyNoteOptions : DbStoreOptions
|
||||
|
||||
public bool IsAutoRefreshEnabled
|
||||
{
|
||||
get => scheduleTaskInterop.IsDailyNoteRefreshEnabled();
|
||||
get => GetOption(ref isAutoRefreshEnabled, SettingEntry.DailyNoteIsAutoRefreshEnabled, true);
|
||||
set
|
||||
{
|
||||
if (runtimeOptions.IsElevated)
|
||||
if (SetOption(ref isAutoRefreshEnabled, SettingEntry.DailyNoteIsAutoRefreshEnabled, value))
|
||||
{
|
||||
// leave below untouched if we are running in elevated privilege
|
||||
return;
|
||||
}
|
||||
|
||||
if (value)
|
||||
{
|
||||
if (SelectedRefreshTime is not null)
|
||||
if (value)
|
||||
{
|
||||
if (!scheduleTaskInterop.RegisterForDailyNoteRefresh(SelectedRefreshTime.Value))
|
||||
if (SelectedRefreshTime is not null)
|
||||
{
|
||||
serviceProvider.GetRequiredService<IInfoBarService>().Warning(SH.ViewModelDailyNoteModifyTaskFail);
|
||||
quartzService.UpdateJobAsync(JobIdentity.DailyNoteGroupName, JobIdentity.DailyNoteRefreshTriggerName, builder =>
|
||||
{
|
||||
return builder.WithSimpleSchedule(sb => sb.WithIntervalInMinutes(SelectedRefreshTime.Value).RepeatForever());
|
||||
}).SafeForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!scheduleTaskInterop.UnregisterForDailyNoteRefresh())
|
||||
else
|
||||
{
|
||||
serviceProvider.GetRequiredService<IInfoBarService>().Warning(SH.ViewModelDailyNoteModifyTaskFail);
|
||||
quartzService.StopJobAsync(JobIdentity.DailyNoteGroupName, JobIdentity.DailyNoteRefreshTriggerName).SafeForget();
|
||||
}
|
||||
}
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public NameValue<int>? SelectedRefreshTime
|
||||
{
|
||||
get
|
||||
{
|
||||
if (runtimeOptions.IsElevated)
|
||||
{
|
||||
// leave untouched when we are running in elevated privilege
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetOption(ref selectedRefreshTime, SettingEntry.DailyNoteRefreshSeconds, time => RefreshTimes.Single(t => t.Value == int.Parse(time, CultureInfo.InvariantCulture)), RefreshTimes[1]);
|
||||
}
|
||||
|
||||
get => GetOption(ref selectedRefreshTime, SettingEntry.DailyNoteRefreshSeconds, time => RefreshTimes.Single(t => t.Value == int.Parse(time, CultureInfo.InvariantCulture)), RefreshTimes[1]);
|
||||
set
|
||||
{
|
||||
if (runtimeOptions.IsElevated)
|
||||
{
|
||||
// leave untouched when we are running in elevated privilege
|
||||
return;
|
||||
}
|
||||
|
||||
if (value is not null)
|
||||
{
|
||||
if (scheduleTaskInterop.RegisterForDailyNoteRefresh(value.Value))
|
||||
SetOption(ref selectedRefreshTime, SettingEntry.DailyNoteRefreshSeconds, value, value => $"{value.Value}");
|
||||
quartzService.UpdateJobAsync(JobIdentity.DailyNoteGroupName, JobIdentity.DailyNoteRefreshTriggerName, builder =>
|
||||
{
|
||||
SetOption(ref selectedRefreshTime, SettingEntry.DailyNoteRefreshSeconds, value, value => $"{value.Value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
serviceProvider.GetRequiredService<IInfoBarService>().Warning(SH.ViewModelDailyNoteModifyTaskFail);
|
||||
}
|
||||
return builder.WithSimpleSchedule(sb => sb.WithIntervalInSeconds(value.Value).RepeatForever());
|
||||
}).SafeForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
src/Snap.Hutao/Snap.Hutao/Service/Job/DailyNoteRefreshJob.cs
Normal file
23
src/Snap.Hutao/Snap.Hutao/Service/Job/DailyNoteRefreshJob.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Quartz;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
|
||||
namespace Snap.Hutao.Service.Job;
|
||||
|
||||
internal sealed partial class DailyNoteRefreshJob : IJob
|
||||
{
|
||||
private readonly IDailyNoteService dailyNoteService;
|
||||
|
||||
public DailyNoteRefreshJob(IDailyNoteService dailyNoteService)
|
||||
{
|
||||
this.dailyNoteService = dailyNoteService;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH003")]
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
await dailyNoteService.RefreshDailyNotesAsync(context.CancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Quartz;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
|
||||
namespace Snap.Hutao.Service.Job;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Transient, typeof(IJobScheduler))]
|
||||
internal sealed partial class DailyNoteRefreshJobScheduler : IJobScheduler
|
||||
{
|
||||
private readonly DailyNoteOptions dailyNoteOptions;
|
||||
|
||||
public async ValueTask ScheduleAsync(IScheduler scheduler)
|
||||
{
|
||||
if (!TryGetRefreshInterval(out int interval))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IJobDetail dailyNoteJob = JobBuilder.Create<DailyNoteRefreshJob>()
|
||||
.WithIdentity(JobIdentity.DailyNoteRefreshJobName, JobIdentity.DailyNoteGroupName)
|
||||
.Build();
|
||||
|
||||
ITrigger dailyNoteTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity(JobIdentity.DailyNoteRefreshTriggerName, JobIdentity.DailyNoteGroupName)
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(builder => builder.WithIntervalInMinutes(interval).RepeatForever())
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(dailyNoteJob, dailyNoteTrigger).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private bool TryGetRefreshInterval(out int interval)
|
||||
{
|
||||
if (dailyNoteOptions.IsAutoRefreshEnabled && dailyNoteOptions.SelectedRefreshTime is not null)
|
||||
{
|
||||
interval = dailyNoteOptions.SelectedRefreshTime.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
interval = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
11
src/Snap.Hutao/Snap.Hutao/Service/Job/IJobScheduler.cs
Normal file
11
src/Snap.Hutao/Snap.Hutao/Service/Job/IJobScheduler.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace Snap.Hutao.Service.Job;
|
||||
|
||||
internal interface IJobScheduler
|
||||
{
|
||||
ValueTask ScheduleAsync(IScheduler scheduler);
|
||||
}
|
||||
15
src/Snap.Hutao/Snap.Hutao/Service/Job/IQuartzService.cs
Normal file
15
src/Snap.Hutao/Snap.Hutao/Service/Job/IQuartzService.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace Snap.Hutao.Service.Job;
|
||||
|
||||
internal interface IQuartzService
|
||||
{
|
||||
ValueTask StartAsync(CancellationToken token = default);
|
||||
|
||||
ValueTask StopJobAsync(string group, string triggerName, CancellationToken token = default);
|
||||
|
||||
ValueTask UpdateJobAsync(string group, string triggerName, Func<TriggerBuilder, TriggerBuilder> configure, CancellationToken token = default);
|
||||
}
|
||||
14
src/Snap.Hutao/Snap.Hutao/Service/Job/JobIdentity.cs
Normal file
14
src/Snap.Hutao/Snap.Hutao/Service/Job/JobIdentity.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Quartz;
|
||||
using Snap.Hutao.Service.DailyNote;
|
||||
|
||||
namespace Snap.Hutao.Service.Job;
|
||||
|
||||
internal static class JobIdentity
|
||||
{
|
||||
public const string DailyNoteGroupName = "DailyNote";
|
||||
public const string DailyNoteRefreshJobName = "RefreshJob";
|
||||
public const string DailyNoteRefreshTriggerName = "RefreshTrigger";
|
||||
}
|
||||
84
src/Snap.Hutao/Snap.Hutao/Service/Job/QuartzService.cs
Normal file
84
src/Snap.Hutao/Snap.Hutao/Service/Job/QuartzService.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace Snap.Hutao.Service.Job;
|
||||
|
||||
[Injection(InjectAs.Singleton, typeof(IQuartzService))]
|
||||
[ConstructorGenerated]
|
||||
internal sealed partial class QuartzService : IQuartzService, IDisposable
|
||||
{
|
||||
private readonly TaskCompletionSource startupCompleted = new();
|
||||
|
||||
private readonly ISchedulerFactory schedulerFactory;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
private IScheduler? scheduler;
|
||||
|
||||
public async ValueTask StartAsync(CancellationToken token = default)
|
||||
{
|
||||
scheduler = await schedulerFactory.GetScheduler(token).ConfigureAwait(false);
|
||||
await scheduler.Start(token).ConfigureAwait(false);
|
||||
|
||||
foreach (IJobScheduler jobScheduler in serviceProvider.GetServices<IJobScheduler>())
|
||||
{
|
||||
await jobScheduler.ScheduleAsync(scheduler).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
startupCompleted.SetResult();
|
||||
}
|
||||
|
||||
public async ValueTask UpdateJobAsync(string group, string triggerName, Func<TriggerBuilder, TriggerBuilder> configure, CancellationToken token = default)
|
||||
{
|
||||
if (scheduler is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await startupCompleted.Task.ConfigureAwait(false);
|
||||
|
||||
TriggerKey key = new(triggerName, group);
|
||||
if (await scheduler.GetTrigger(key, token).ConfigureAwait(false) is { } old)
|
||||
{
|
||||
ITrigger newTrigger = configure(old.GetTriggerBuilder()).Build();
|
||||
await scheduler.RescheduleJob(key, newTrigger, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask StopJobAsync(string group, string triggerName, CancellationToken token = default)
|
||||
{
|
||||
if (scheduler is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await startupCompleted.Task.ConfigureAwait(false);
|
||||
|
||||
await scheduler.UnscheduleJob(new(triggerName, group), token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeAsync().GetAwaiter().GetResult();
|
||||
|
||||
async ValueTask DisposeAsync()
|
||||
{
|
||||
if (scheduler is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Wait until any ongoing startup logic has finished or the graceful shutdown period is over
|
||||
await startupCompleted.Task.ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await scheduler.Shutdown(false).ConfigureAwait(false);
|
||||
scheduler = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,8 +308,8 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -326,8 +326,9 @@
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240428000" />
|
||||
<PackageReference Include="QRCoder" Version="1.5.1" />
|
||||
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.9.0" />
|
||||
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
|
||||
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.16.0">
|
||||
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.16.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
Reference in New Issue
Block a user