Files
v2rayN/v2rayN/v2rayN.WebAPI/Services/V2rayRuntime.Scheduling.cs
T
2dust f64c29972d Split configuration management by storage type
Move file-backed settings to ConfigTextManager and database operations to ConfigDbManager. Update desktop, Web API, and test references, and add Web API development launch settings.
2026-11-14 16:47:48 +08:00

173 lines
6.7 KiB
C#

using ServiceLib.Common;
using ServiceLib.Models.Entities;
using ServiceLib.Services;
using v2rayN.WebAPI.Contracts;
namespace v2rayN.WebAPI.Services;
public sealed partial class V2rayRuntime
{
private CancellationTokenSource? _scheduledOperationsCancellation;
private Task? _scheduledOperationsTask;
private void StartScheduledOperations(CancellationToken hostToken)
{
_scheduledOperationsCancellation = CancellationTokenSource.CreateLinkedTokenSource(hostToken, _operations.ShutdownToken);
var cancellationToken = _scheduledOperationsCancellation.Token;
_scheduledOperationsTask = Task.Run(() => RunScheduledOperationsAsync(cancellationToken));
}
private async Task<bool> StopScheduledOperationsAsync(CancellationToken shutdownToken)
{
var cancellation = _scheduledOperationsCancellation;
var task = _scheduledOperationsTask;
if (cancellation is null || task is null)
{
return true;
}
cancellation.Cancel();
try
{
await task.WaitAsync(shutdownToken);
}
catch (OperationCanceledException) when (task.IsCompleted && cancellation.IsCancellationRequested)
{
// Expected during graceful service shutdown.
}
finally
{
if (task.IsCompleted)
{
cancellation.Dispose();
_scheduledOperationsCancellation = null;
_scheduledOperationsTask = null;
}
}
return true;
}
private async Task RunScheduledOperationsAsync(CancellationToken cancellationToken)
{
var elapsedMinutes = 1;
while (true)
{
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
await RunScheduledStepAsync("subscription", RunScheduledSubscriptionUpdatesAsync, cancellationToken);
if (elapsedMinutes % 20 == 0)
{
await RunScheduledStepAsync("save", async token =>
{
await using var operation = await _operations.EnterOperationAsync(token);
await _mutations.RunAsync(async () =>
{
await EnsureConfigSaveSucceededAsync(() => ConfigTextManager.Instance.SaveConfig());
await ProfileExManager.Instance.SaveTo();
}, operation.Token);
}, cancellationToken);
}
if (elapsedMinutes % 60 == 0)
{
await RunScheduledStepAsync("cleanup", async token =>
{
await using var operation = await _operations.EnterOperationAsync(token);
FileUtils.DeleteExpiredFiles(Utils.GetBinConfigPath(), DateTime.Now.AddHours(-1), "Test");
FileUtils.DeleteExpiredFiles(Utils.GetLogPath(), DateTime.Now.AddDays(-7));
FileUtils.DeleteExpiredFiles(Utils.GetTempPath(), DateTime.Now.AddDays(-7));
}, cancellationToken);
var elapsedHours = elapsedMinutes / 60;
if (Config.GuiItem.AutoUpdateInterval > 0 && elapsedHours % Config.GuiItem.AutoUpdateInterval == 0)
{
await RunScheduledStepAsync("geo-update", RunScheduledGeoUpdateAsync, cancellationToken);
}
}
if (elapsedMinutes % 1440 == 1)
{
await RunScheduledStepAsync("core-update-check", RunScheduledCoreUpdateCheckAsync, cancellationToken);
}
elapsedMinutes++;
}
}
private async Task RunScheduledStepAsync(string name, Func<CancellationToken, Task> step, CancellationToken cancellationToken)
{
try
{
await step(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception exception)
{
AddLog("task", $"{name}: {exception.Message}");
}
}
private async Task RunScheduledSubscriptionUpdatesAsync(CancellationToken cancellationToken)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
IReadOnlyList<SubItem> subscriptions;
await using (var operation = await _operations.EnterOperationAsync(cancellationToken))
{
subscriptions = (await AppManager.Instance.SubItems() ?? [])
.Where(item => item.AutoUpdateInterval > 0
&& now - item.UpdateTime >= item.AutoUpdateInterval * 60L)
.ToArray();
}
foreach (var subscription in subscriptions)
{
cancellationToken.ThrowIfCancellationRequested();
var update = StartSubscriptionUpdateTask(subscription.Id, useProxy: true, cancellationToken);
if (update is not null)
{
await update.WaitAsync(cancellationToken);
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
}
}
private async Task RunScheduledGeoUpdateAsync(CancellationToken cancellationToken)
{
await using var operation = await _operations.EnterExclusiveAsync(
cancellationToken,
allowReadOnlyObservations: true);
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(Config.CheckUpdateItem.UpdateViaProxy);
await new UpdateService(downloadService, Config, (success, message) =>
{
AddLog("update", message);
_events.Publish("geo-update-progress", new
{
success,
code = success ? "ok" : "geo_update_progress",
messageKey = success ? ApiMessageKeys.CommonCompleted : ApiMessageKeys.GeoUpdateProgress,
rawLog = message,
});
if (success)
{
_events.Publish("geo-update-completed", OperationView.Ok(ApiMessageKeys.CommonCompleted));
}
return Task.CompletedTask;
}).UpdateGeoFileAll(operation.Token);
}
private async Task RunScheduledCoreUpdateCheckAsync(CancellationToken cancellationToken)
{
await using var operation = await _operations.EnterOperationAsync(cancellationToken);
using var downloadService = await DownloadService.CreateDownloadServiceWithCheck(Config.CheckUpdateItem.UpdateViaProxy);
var updateService = new UpdateService(downloadService, Config, (_, _) => Task.CompletedTask);
var messages = await updateService.CheckHasUpdateOnlyAll(operation.Token);
foreach (var message in messages)
{
AddLog("update", message);
}
}
}