72 lines
3.8 KiB
C#
72 lines
3.8 KiB
C#
using GuestOps.Web;
|
|
using Microsoft.AspNetCore.DataProtection;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
var builder = Host.CreateApplicationBuilder(args);
|
|
builder.Logging.ClearProviders(); builder.Logging.AddConsole();
|
|
builder.Logging.AddFilter("System.Net.Http.HttpClient", LogLevel.Warning);
|
|
builder.Services.AddSingleton<IStore, MongoStore>();
|
|
var keyPath = builder.Configuration["Keys:Path"] ?? throw new InvalidOperationException("Keys:Path is required.");
|
|
builder.Services.AddDataProtection().SetApplicationName("GuestOps-Web").PersistKeysToFileSystem(new DirectoryInfo(keyPath));
|
|
builder.Services.AddHttpClient<GoogleMailbox>(c => c.Timeout = TimeSpan.FromSeconds(25)).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false });
|
|
builder.Services.AddTransient<ReplyDelivery>();
|
|
builder.Services.AddHostedService<DeliveryWorker>();
|
|
builder.Services.AddHostedService<MailboxWorker>();
|
|
await builder.Build().RunAsync();
|
|
|
|
sealed class MailboxWorker(IStore store, IServiceScopeFactory factory, ILogger<MailboxWorker> log) : BackgroundService
|
|
{
|
|
readonly string owner = Guid.NewGuid().ToString("N");
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
await store.Initialize();
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
foreach (var mailbox in await store.Mailboxes())
|
|
{
|
|
if (!await store.TryLease(mailbox.Id, owner)) continue;
|
|
// Per-page deadline is shorter than the lease. Import is idempotent.
|
|
using var deadline = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); deadline.CancelAfter(TimeSpan.FromMinutes(2));
|
|
try { using var scope = factory.CreateScope(); await scope.ServiceProvider.GetRequiredService<GoogleMailbox>().Sync(mailbox, deadline.Token); }
|
|
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; }
|
|
catch (Exception ex)
|
|
{
|
|
log.LogWarning("Mailbox synchronization paused for {Id} ({Type})", mailbox.Id, ex.GetType().Name);
|
|
mailbox.SyncError = "Synchronization failed. Retry later or reconnect your Google mailbox.";
|
|
await store.SaveSync(mailbox);
|
|
}
|
|
finally { await store.ReleaseLease(mailbox.Id, owner); }
|
|
}
|
|
}
|
|
catch (Exception ex) when (!stoppingToken.IsCancellationRequested) { log.LogWarning("Worker cycle paused ({Type})", ex.GetType().Name); }
|
|
await Task.Delay(TimeSpan.FromSeconds(60), stoppingToken);
|
|
}
|
|
}
|
|
}
|
|
|
|
sealed class DeliveryWorker(IStore store, IServiceScopeFactory factory, ILogger<DeliveryWorker> log) : BackgroundService
|
|
{
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
foreach (var message in await store.Deliveries())
|
|
{
|
|
using var scope = factory.CreateScope();
|
|
using var deadline = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); deadline.CancelAfter(TimeSpan.FromMinutes(2));
|
|
await scope.ServiceProvider.GetRequiredService<ReplyDelivery>().Process(message, deadline.Token);
|
|
}
|
|
}
|
|
catch (Exception ex) when (!stoppingToken.IsCancellationRequested) { log.LogWarning("Reply delivery cycle paused ({Type})", ex.GetType().Name); }
|
|
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
|
|
}
|
|
}
|
|
}
|