Auto sync trigger and cancellation support.

This commit is contained in:
Burak Kaan Köse
2026-02-11 14:50:59 +01:00
parent 96d2efb3f0
commit 96dcdc8e03
4 changed files with 205 additions and 8 deletions
+140
View File
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
@@ -32,7 +34,13 @@ public partial class App : WinoApplication,
IRecipient<NewMailSynchronizationRequested>,
IRecipient<NewCalendarSynchronizationRequested>
{
private const int InboxSyncsPerFullSync = 20;
private ISynchronizationManager? _synchronizationManager;
private IPreferencesService? _preferencesService;
private IAccountService? _accountService;
private CancellationTokenSource? _autoSynchronizationLoopCts;
private readonly SemaphoreSlim _autoSynchronizationSemaphore = new(1, 1);
private readonly Dictionary<Guid, int> _inboxSyncCounters = [];
public App()
{
@@ -136,6 +144,12 @@ public partial class App : WinoApplication,
await InitializeServicesAsync();
_synchronizationManager = Services.GetRequiredService<ISynchronizationManager>();
_preferencesService = Services.GetRequiredService<IPreferencesService>();
_accountService = Services.GetRequiredService<IAccountService>();
_preferencesService.PreferenceChanged -= PreferencesServiceChanged;
_preferencesService.PreferenceChanged += PreferencesServiceChanged;
RestartAutoSynchronizationLoop();
// Check if launched from toast notification.
if (IsNotificationActivation(out AppNotificationActivatedEventArgs toastArgs))
@@ -463,6 +477,132 @@ public partial class App : WinoApplication,
};
}
private void PreferencesServiceChanged(object? sender, string propertyName)
{
if (propertyName != nameof(IPreferencesService.EmailSyncIntervalMinutes))
return;
RestartAutoSynchronizationLoop();
}
private void RestartAutoSynchronizationLoop()
{
if (_preferencesService == null)
return;
StopAutoSynchronizationLoop();
int intervalMinutes = Math.Max(1, _preferencesService.EmailSyncIntervalMinutes);
_autoSynchronizationLoopCts = new CancellationTokenSource();
_ = RunAutoSynchronizationLoopAsync(TimeSpan.FromMinutes(intervalMinutes), _autoSynchronizationLoopCts.Token);
LogActivation($"Automatic sync loop started. Interval: {intervalMinutes} minute(s).");
}
private void StopAutoSynchronizationLoop()
{
if (_autoSynchronizationLoopCts == null)
return;
_autoSynchronizationLoopCts.Cancel();
_autoSynchronizationLoopCts.Dispose();
_autoSynchronizationLoopCts = null;
}
private async Task RunAutoSynchronizationLoopAsync(TimeSpan interval, CancellationToken cancellationToken)
{
try
{
await ExecuteAutoSynchronizationAsync(cancellationToken).ConfigureAwait(false);
using var timer = new PeriodicTimer(interval);
while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false))
{
await ExecuteAutoSynchronizationAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// no-op
}
catch (Exception ex)
{
LogActivation($"Automatic sync loop failed: {ex.Message}");
}
}
private async Task ExecuteAutoSynchronizationAsync(CancellationToken cancellationToken)
{
if (_synchronizationManager == null || _accountService == null)
return;
bool lockTaken = false;
try
{
lockTaken = await _autoSynchronizationSemaphore.WaitAsync(0, cancellationToken).ConfigureAwait(false);
if (!lockTaken)
return;
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
var currentAccountIds = accounts.Select(a => a.Id).ToHashSet();
_inboxSyncCounters.Keys.Where(a => !currentAccountIds.Contains(a)).ToList().ForEach(a => _inboxSyncCounters.Remove(a));
foreach (var account in accounts)
{
cancellationToken.ThrowIfCancellationRequested();
if (_synchronizationManager.IsAccountSynchronizing(account.Id))
continue;
var inboxSyncOptions = new MailSynchronizationOptions()
{
AccountId = account.Id,
Type = MailSynchronizationType.InboxOnly
};
var inboxSyncResult = await _synchronizationManager.SynchronizeMailAsync(inboxSyncOptions, cancellationToken).ConfigureAwait(false);
if (inboxSyncResult.CompletedState is SynchronizationCompletedState.Success or SynchronizationCompletedState.PartiallyCompleted)
{
_inboxSyncCounters.TryAdd(account.Id, 0);
_inboxSyncCounters[account.Id]++;
if (_inboxSyncCounters[account.Id] >= InboxSyncsPerFullSync)
{
var fullSyncOptions = new MailSynchronizationOptions()
{
AccountId = account.Id,
Type = MailSynchronizationType.FullFolders
};
await _synchronizationManager.SynchronizeMailAsync(fullSyncOptions, cancellationToken).ConfigureAwait(false);
_inboxSyncCounters[account.Id] = 0;
}
}
if (!account.IsCalendarAccessGranted)
continue;
var calendarOptions = new CalendarSynchronizationOptions()
{
AccountId = account.Id,
Type = CalendarSynchronizationType.CalendarMetadata
};
await _synchronizationManager.SynchronizeCalendarAsync(calendarOptions, cancellationToken).ConfigureAwait(false);
}
}
finally
{
if (lockTaken)
{
_autoSynchronizationSemaphore.Release();
}
}
}
/// <summary>
/// Handles activation redirected from another instance (single-instancing).
/// This is called when a second instance tries to launch and redirects to this existing instance.