Updated synchronization progress implementation.
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels.Data;
|
namespace Wino.Calendar.ViewModels.Data;
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
|||||||
AccountCalendars.CollectionChanged += CalendarListUpdated;
|
AccountCalendars.CollectionChanged += CalendarListUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CalendarListUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
private void CalendarListUpdated(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||||
{
|
{
|
||||||
@@ -59,13 +60,11 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
|||||||
|
|
||||||
private void CalendarPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
private void CalendarPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is AccountCalendarViewModel viewModel)
|
if (sender is AccountCalendarViewModel viewModel &&
|
||||||
|
e.PropertyName == nameof(AccountCalendarViewModel.IsChecked))
|
||||||
{
|
{
|
||||||
if (e.PropertyName == nameof(AccountCalendarViewModel.IsChecked))
|
ManageIsCheckedState();
|
||||||
{
|
UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true);
|
||||||
ManageIsCheckedState();
|
|
||||||
UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,19 +78,54 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
|||||||
public partial string AccountColorHex { get; set; } = string.Empty;
|
public partial string AccountColorHex { get; set; } = string.Empty;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(CanSynchronize), nameof(IsSynchronizationProgressVisible), nameof(IsProgressIndeterminate))]
|
||||||
public partial bool IsSynchronizationInProgress { get; set; }
|
public partial bool IsSynchronizationInProgress { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(SynchronizationProgress), nameof(SynchronizationProgressValue), nameof(IsProgressIndeterminate))]
|
||||||
|
public partial int TotalItemsToSync { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(SynchronizationProgress), nameof(SynchronizationProgressValue), nameof(IsProgressIndeterminate))]
|
||||||
|
public partial int RemainingItemsToSync { get; set; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string SynchronizationStatus { get; set; } = string.Empty;
|
public partial string SynchronizationStatus { get; set; } = string.Empty;
|
||||||
|
|
||||||
public bool CanSynchronize => !IsSynchronizationInProgress;
|
public bool CanSynchronize => !IsSynchronizationInProgress;
|
||||||
public bool IsSynchronizationProgressVisible => IsSynchronizationInProgress;
|
public bool IsSynchronizationProgressVisible => IsSynchronizationInProgress;
|
||||||
|
public bool IsProgressIndeterminate => IsSynchronizationInProgress && TotalItemsToSync <= 0;
|
||||||
|
|
||||||
private bool _isExternalPropChangeBlocked = false;
|
public double SynchronizationProgress
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (TotalItemsToSync <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return ((double)(TotalItemsToSync - RemainingItemsToSync) / TotalItemsToSync) * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double SynchronizationProgressValue => SynchronizationProgress;
|
||||||
|
|
||||||
|
private bool _isExternalPropChangeBlocked;
|
||||||
|
|
||||||
|
public void ApplySynchronizationProgress(AccountSynchronizationProgress progress)
|
||||||
|
{
|
||||||
|
if (progress == null || progress.AccountId != Account.Id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsSynchronizationInProgress = progress.IsInProgress;
|
||||||
|
TotalItemsToSync = progress.TotalUnits;
|
||||||
|
RemainingItemsToSync = progress.RemainingUnits;
|
||||||
|
SynchronizationStatus = progress.Status ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
private void ManageIsCheckedState()
|
private void ManageIsCheckedState()
|
||||||
{
|
{
|
||||||
if (_isExternalPropChangeBlocked) return;
|
if (_isExternalPropChangeBlocked)
|
||||||
|
return;
|
||||||
|
|
||||||
_isExternalPropChangeBlocked = true;
|
_isExternalPropChangeBlocked = true;
|
||||||
|
|
||||||
@@ -113,17 +147,13 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
|||||||
|
|
||||||
partial void OnIsCheckedStateChanged(bool? oldValue, bool? newValue)
|
partial void OnIsCheckedStateChanged(bool? oldValue, bool? newValue)
|
||||||
{
|
{
|
||||||
if (_isExternalPropChangeBlocked) return;
|
if (_isExternalPropChangeBlocked)
|
||||||
|
return;
|
||||||
// Update is triggered by user on the three-state checkbox.
|
|
||||||
// We should not report all changes one by one.
|
|
||||||
|
|
||||||
_isExternalPropChangeBlocked = true;
|
_isExternalPropChangeBlocked = true;
|
||||||
|
|
||||||
if (newValue == null)
|
if (newValue == null)
|
||||||
{
|
{
|
||||||
// Only primary calendars must be checked.
|
|
||||||
|
|
||||||
foreach (var calendar in AccountCalendars)
|
foreach (var calendar in AccountCalendars)
|
||||||
{
|
{
|
||||||
UpdateCalendarCheckedState(calendar, calendar.IsPrimary);
|
UpdateCalendarCheckedState(calendar, calendar.IsPrimary);
|
||||||
@@ -138,7 +168,6 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
_isExternalPropChangeBlocked = false;
|
_isExternalPropChangeBlocked = false;
|
||||||
|
|
||||||
CollectiveSelectionStateChanged?.Invoke(this, EventArgs.Empty);
|
CollectiveSelectionStateChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,22 +175,17 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
var currentValue = accountCalendarViewModel.IsChecked;
|
var currentValue = accountCalendarViewModel.IsChecked;
|
||||||
|
|
||||||
if (currentValue == newValue && !ignoreValueCheck) return;
|
if (currentValue == newValue && !ignoreValueCheck)
|
||||||
|
return;
|
||||||
|
|
||||||
accountCalendarViewModel.IsChecked = newValue;
|
accountCalendarViewModel.IsChecked = newValue;
|
||||||
|
|
||||||
// No need to report.
|
if (_isExternalPropChangeBlocked)
|
||||||
if (_isExternalPropChangeBlocked == true) return;
|
return;
|
||||||
|
|
||||||
CalendarSelectionStateChanged?.Invoke(this, accountCalendarViewModel);
|
CalendarSelectionStateChanged?.Invoke(this, accountCalendarViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnIsSynchronizationInProgressChanged(bool value)
|
|
||||||
{
|
|
||||||
OnPropertyChanged(nameof(CanSynchronize));
|
|
||||||
OnPropertyChanged(nameof(IsSynchronizationProgressVisible));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateAccount(MailAccount updatedAccount)
|
public void UpdateAccount(MailAccount updatedAccount)
|
||||||
{
|
{
|
||||||
if (updatedAccount == null || updatedAccount.Id != Account.Id)
|
if (updatedAccount == null || updatedAccount.Id != Account.Id)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
public enum SynchronizationProgressCategory
|
||||||
|
{
|
||||||
|
Mail,
|
||||||
|
Calendar
|
||||||
|
}
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
public interface IAccountMenuItem : IMenuItem
|
public interface IAccountMenuItem : IMenuItem
|
||||||
{
|
{
|
||||||
bool IsEnabled { get; set; }
|
bool IsEnabled { get; set; }
|
||||||
|
bool IsSynchronizationInProgress { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculated synchronization progress percentage (0-100). -1 for indeterminate.
|
/// Calculated synchronization progress percentage (0-100).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
double SynchronizationProgress { get; }
|
double SynchronizationProgress { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Progress value clamped for XAML progress controls.
|
||||||
|
/// </summary>
|
||||||
|
double SynchronizationProgressValue { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total items to sync. 0 for indeterminate progress.
|
/// Total items to sync. 0 for indeterminate progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -30,6 +37,7 @@ public interface IAccountMenuItem : IMenuItem
|
|||||||
|
|
||||||
int UnreadItemCount { get; set; }
|
int UnreadItemCount { get; set; }
|
||||||
IEnumerable<MailAccount> HoldingAccounts { get; }
|
IEnumerable<MailAccount> HoldingAccounts { get; }
|
||||||
|
void ApplySynchronizationProgress(AccountSynchronizationProgress progress);
|
||||||
void UpdateAccount(MailAccount account);
|
void UpdateAccount(MailAccount account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ public interface ISynchronizationManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsAccountSynchronizing(Guid accountId);
|
bool IsAccountSynchronizing(Guid accountId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the latest centralized synchronization progress snapshot for the given account and category.
|
||||||
|
/// </summary>
|
||||||
|
AccountSynchronizationProgress GetSynchronizationProgress(Guid accountId, SynchronizationProgressCategory category);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queues a mail action request to the corresponding account's synchronizer with optional synchronization triggering.
|
/// Queues a mail action request to the corresponding account's synchronizer with optional synchronization triggering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
@@ -6,6 +6,7 @@ using Wino.Core.Domain.Entities.Shared;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Folders;
|
using Wino.Core.Domain.Models.Folders;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.MenuItems;
|
namespace Wino.Core.Domain.MenuItems;
|
||||||
|
|
||||||
@@ -14,18 +15,22 @@ public partial class AccountMenuItem : MenuItemBase<MailAccount, MenuItemBase<IM
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private int unreadItemCount;
|
private int unreadItemCount;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsSynchronizationProgressVisible), nameof(IsProgressIndeterminate), nameof(SynchronizationProgress), nameof(SynchronizationProgressValue))]
|
||||||
|
public partial bool IsSynchronizationInProgress { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total items to sync. 0 means indeterminate progress.
|
/// Total items to sync. 0 means indeterminate progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedFor(nameof(IsSynchronizationProgressVisible), nameof(SynchronizationProgress), nameof(IsProgressIndeterminate))]
|
[NotifyPropertyChangedFor(nameof(SynchronizationProgress), nameof(SynchronizationProgressValue), nameof(IsProgressIndeterminate))]
|
||||||
public partial int TotalItemsToSync { get; set; }
|
public partial int TotalItemsToSync { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remaining items to sync.
|
/// Remaining items to sync.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedFor(nameof(SynchronizationProgress))]
|
[NotifyPropertyChangedFor(nameof(SynchronizationProgress), nameof(SynchronizationProgressValue), nameof(IsProgressIndeterminate))]
|
||||||
public partial int RemainingItemsToSync { get; set; }
|
public partial int RemainingItemsToSync { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,31 +44,22 @@ public partial class AccountMenuItem : MenuItemBase<MailAccount, MenuItemBase<IM
|
|||||||
|
|
||||||
public bool IsAttentionRequired => AttentionReason != AccountAttentionReason.None;
|
public bool IsAttentionRequired => AttentionReason != AccountAttentionReason.None;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates synchronization progress percentage (0-100).
|
|
||||||
/// Returns -1 for indeterminate progress when TotalItemsToSync is 0.
|
|
||||||
/// </summary>
|
|
||||||
public double SynchronizationProgress
|
public double SynchronizationProgress
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (TotalItemsToSync == 0 || RemainingItemsToSync == 0)
|
if (TotalItemsToSync <= 0)
|
||||||
return -1; // Indeterminate
|
return 0;
|
||||||
|
|
||||||
return ((double)(TotalItemsToSync - RemainingItemsToSync) / TotalItemsToSync) * 100;
|
return Math.Clamp(((double)(TotalItemsToSync - RemainingItemsToSync) / TotalItemsToSync) * 100, 0, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public double SynchronizationProgressValue => SynchronizationProgress;
|
||||||
/// Whether synchronization progress should be visible.
|
|
||||||
/// Visible when there's active synchronization (TotalItemsToSync > 0 or RemainingItemsToSync > 0).
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSynchronizationProgressVisible => TotalItemsToSync > 0 || RemainingItemsToSync > 0;
|
|
||||||
|
|
||||||
/// <summary>
|
public bool IsSynchronizationProgressVisible => IsSynchronizationInProgress;
|
||||||
/// Whether progress should be indeterminate (when total is 0 but there's still synchronization happening).
|
|
||||||
/// </summary>
|
public bool IsProgressIndeterminate => IsSynchronizationInProgress && TotalItemsToSync <= 0;
|
||||||
public bool IsProgressIndeterminate => TotalItemsToSync == 0 && RemainingItemsToSync == 0 && IsSynchronizationProgressVisible;
|
|
||||||
|
|
||||||
public Guid AccountId => Parameter.Id;
|
public Guid AccountId => Parameter.Id;
|
||||||
|
|
||||||
@@ -77,7 +73,6 @@ public partial class AccountMenuItem : MenuItemBase<MailAccount, MenuItemBase<IM
|
|||||||
if (SetProperty(ref attentionReason, value))
|
if (SetProperty(ref attentionReason, value))
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(IsAttentionRequired));
|
OnPropertyChanged(nameof(IsAttentionRequired));
|
||||||
|
|
||||||
UpdateFixAccountIssueMenuItem();
|
UpdateFixAccountIssueMenuItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,6 +103,17 @@ public partial class AccountMenuItem : MenuItemBase<MailAccount, MenuItemBase<IM
|
|||||||
UpdateAccount(account);
|
UpdateAccount(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ApplySynchronizationProgress(AccountSynchronizationProgress progress)
|
||||||
|
{
|
||||||
|
if (progress == null || progress.AccountId != AccountId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsSynchronizationInProgress = progress.IsInProgress;
|
||||||
|
TotalItemsToSync = progress.TotalUnits;
|
||||||
|
RemainingItemsToSync = progress.RemainingUnits;
|
||||||
|
SynchronizationStatus = progress.Status ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateAccount(MailAccount account)
|
public void UpdateAccount(MailAccount account)
|
||||||
{
|
{
|
||||||
Parameter = account;
|
Parameter = account;
|
||||||
@@ -118,7 +124,8 @@ public partial class AccountMenuItem : MenuItemBase<MailAccount, MenuItemBase<IM
|
|||||||
OnPropertyChanged(nameof(AccountColorHex));
|
OnPropertyChanged(nameof(AccountColorHex));
|
||||||
OnPropertyChanged(nameof(IsAttentionRequired));
|
OnPropertyChanged(nameof(IsAttentionRequired));
|
||||||
|
|
||||||
if (SubMenuItems == null) return;
|
if (SubMenuItems == null)
|
||||||
|
return;
|
||||||
|
|
||||||
foreach (var item in SubMenuItems)
|
foreach (var item in SubMenuItems)
|
||||||
{
|
{
|
||||||
@@ -133,12 +140,10 @@ public partial class AccountMenuItem : MenuItemBase<MailAccount, MenuItemBase<IM
|
|||||||
{
|
{
|
||||||
if (AttentionReason != AccountAttentionReason.None && !SubMenuItems.Any(a => a is FixAccountIssuesMenuItem))
|
if (AttentionReason != AccountAttentionReason.None && !SubMenuItems.Any(a => a is FixAccountIssuesMenuItem))
|
||||||
{
|
{
|
||||||
// Add fix issue item if not exists.
|
|
||||||
SubMenuItems.Insert(0, new FixAccountIssuesMenuItem(Parameter, this));
|
SubMenuItems.Insert(0, new FixAccountIssuesMenuItem(Parameter, this));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Remove existing if issue is resolved.
|
|
||||||
var fixAccountIssueItem = SubMenuItems.FirstOrDefault(a => a is FixAccountIssuesMenuItem);
|
var fixAccountIssueItem = SubMenuItems.FirstOrDefault(a => a is FixAccountIssuesMenuItem);
|
||||||
|
|
||||||
if (fixAccountIssueItem != null)
|
if (fixAccountIssueItem != null)
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.MenuItems;
|
namespace Wino.Core.Domain.MenuItems;
|
||||||
|
|
||||||
@@ -16,50 +17,37 @@ public partial class MergedAccountMenuItem : MenuItemBase<MergedInbox, IMenuItem
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private int unreadItemCount;
|
private int unreadItemCount;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Total items to sync across all merged accounts.
|
|
||||||
/// </summary>
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedFor(nameof(SynchronizationProgress), nameof(IsSynchronizationProgressVisible), nameof(IsProgressIndeterminate))]
|
[NotifyPropertyChangedFor(nameof(IsSynchronizationProgressVisible), nameof(IsProgressIndeterminate), nameof(SynchronizationProgress), nameof(SynchronizationProgressValue))]
|
||||||
|
public partial bool IsSynchronizationInProgress { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(SynchronizationProgress), nameof(SynchronizationProgressValue), nameof(IsProgressIndeterminate))]
|
||||||
public partial int TotalItemsToSync { get; set; }
|
public partial int TotalItemsToSync { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remaining items to sync across all merged accounts.
|
|
||||||
/// </summary>
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedFor(nameof(SynchronizationProgress), nameof(IsSynchronizationProgressVisible), nameof(IsProgressIndeterminate))]
|
[NotifyPropertyChangedFor(nameof(SynchronizationProgress), nameof(SynchronizationProgressValue), nameof(IsProgressIndeterminate))]
|
||||||
public partial int RemainingItemsToSync { get; set; }
|
public partial int RemainingItemsToSync { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Current synchronization status message.
|
|
||||||
/// </summary>
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string SynchronizationStatus { get; set; } = string.Empty;
|
public partial string SynchronizationStatus { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculated synchronization progress for merged accounts.
|
|
||||||
/// </summary>
|
|
||||||
public double SynchronizationProgress
|
public double SynchronizationProgress
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (TotalItemsToSync == 0 || RemainingItemsToSync == 0)
|
if (TotalItemsToSync <= 0)
|
||||||
return -1; // Indeterminate
|
return 0;
|
||||||
|
|
||||||
return ((double)(TotalItemsToSync - RemainingItemsToSync) / TotalItemsToSync) * 100;
|
return ((double)(TotalItemsToSync - RemainingItemsToSync) / TotalItemsToSync) * 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public double SynchronizationProgressValue => SynchronizationProgress;
|
||||||
/// Whether synchronization progress should be visible.
|
|
||||||
/// Visible when there's active synchronization (TotalItemsToSync > 0 or RemainingItemsToSync > 0).
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSynchronizationProgressVisible => TotalItemsToSync > 0 || RemainingItemsToSync > 0;
|
|
||||||
|
|
||||||
/// <summary>
|
public bool IsSynchronizationProgressVisible => IsSynchronizationInProgress;
|
||||||
/// Whether progress should be indeterminate.
|
|
||||||
/// </summary>
|
public bool IsProgressIndeterminate => IsSynchronizationInProgress && TotalItemsToSync <= 0;
|
||||||
public bool IsProgressIndeterminate => TotalItemsToSync == 0 && IsSynchronizationProgressVisible;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string mergedAccountName;
|
private string mergedAccountName;
|
||||||
@@ -78,22 +66,33 @@ public partial class MergedAccountMenuItem : MenuItemBase<MergedInbox, IMenuItem
|
|||||||
UnreadItemCount = SubMenuItems.OfType<IAccountMenuItem>().Sum(a => a.UnreadItemCount);
|
UnreadItemCount = SubMenuItems.OfType<IAccountMenuItem>().Sum(a => a.UnreadItemCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Aggregates synchronization progress from all child account menu items.
|
|
||||||
/// </summary>
|
|
||||||
public void RefreshSynchronizationProgress()
|
public void RefreshSynchronizationProgress()
|
||||||
{
|
{
|
||||||
var accountMenuItems = SubMenuItems.OfType<IAccountMenuItem>().ToList();
|
var activeAccountMenuItems = SubMenuItems
|
||||||
|
.OfType<IAccountMenuItem>()
|
||||||
|
.Where(a => a.IsSynchronizationInProgress)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
TotalItemsToSync = accountMenuItems.Sum(a => a.TotalItemsToSync);
|
IsSynchronizationInProgress = activeAccountMenuItems.Any();
|
||||||
RemainingItemsToSync = accountMenuItems.Sum(a => a.RemainingItemsToSync);
|
TotalItemsToSync = activeAccountMenuItems.Sum(a => a.TotalItemsToSync);
|
||||||
|
RemainingItemsToSync = activeAccountMenuItems.Sum(a => a.RemainingItemsToSync);
|
||||||
|
SynchronizationStatus = activeAccountMenuItems
|
||||||
|
.Select(a => a.SynchronizationStatus)
|
||||||
|
.FirstOrDefault(s => !string.IsNullOrWhiteSpace(s)) ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
// Use first non-empty status message
|
public void ApplySynchronizationProgress(AccountSynchronizationProgress progress)
|
||||||
SynchronizationStatus = accountMenuItems.FirstOrDefault(a => !string.IsNullOrEmpty(a.SynchronizationStatus))?.SynchronizationStatus ?? string.Empty;
|
{
|
||||||
|
if (progress == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsSynchronizationInProgress = progress.IsInProgress;
|
||||||
|
TotalItemsToSync = progress.TotalUnits;
|
||||||
|
RemainingItemsToSync = progress.RemainingUnits;
|
||||||
|
SynchronizationStatus = progress.Status ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateAccount(MailAccount account)
|
public void UpdateAccount(MailAccount account)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Models.Synchronization;
|
||||||
|
|
||||||
|
public record AccountSynchronizationProgress(
|
||||||
|
Guid AccountId,
|
||||||
|
SynchronizationProgressCategory Category,
|
||||||
|
bool IsInProgress,
|
||||||
|
bool IsIndeterminate,
|
||||||
|
double ProgressPercentage,
|
||||||
|
int TotalUnits,
|
||||||
|
int RemainingUnits,
|
||||||
|
string Status,
|
||||||
|
AccountSynchronizerState State)
|
||||||
|
{
|
||||||
|
public int CompletedUnits => Math.Max(0, TotalUnits - RemainingUnits);
|
||||||
|
|
||||||
|
public static AccountSynchronizationProgress Idle(Guid accountId, SynchronizationProgressCategory category)
|
||||||
|
=> new(
|
||||||
|
accountId,
|
||||||
|
category,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
string.Empty,
|
||||||
|
AccountSynchronizerState.Idle);
|
||||||
|
}
|
||||||
@@ -108,6 +108,11 @@
|
|||||||
"SyncAction_SynchronizingCalendarData": "Synchronizing calendar data",
|
"SyncAction_SynchronizingCalendarData": "Synchronizing calendar data",
|
||||||
"SyncAction_SynchronizingCalendarEvents": "Synchronizing calendar events",
|
"SyncAction_SynchronizingCalendarEvents": "Synchronizing calendar events",
|
||||||
"SyncAction_SynchronizingCalendarMetadata": "Synchronizing calendar metadata",
|
"SyncAction_SynchronizingCalendarMetadata": "Synchronizing calendar metadata",
|
||||||
|
"SynchronizationProgress_ApplyingChanges": "Applying changes",
|
||||||
|
"SynchronizationProgress_CalendarInProgress": "Calendar sync in progress",
|
||||||
|
"SynchronizationProgress_CalendarPercent": "Calendar sync {0}%",
|
||||||
|
"SynchronizationProgress_MailInProgress": "Mail sync in progress",
|
||||||
|
"SynchronizationProgress_MailPercent": "Mail sync {0}%",
|
||||||
"SyncAction_Unarchiving": "Unarchiving {0} mail(s)",
|
"SyncAction_Unarchiving": "Unarchiving {0} mail(s)",
|
||||||
"CalendarAllDayEventSummary": "all-day events",
|
"CalendarAllDayEventSummary": "all-day events",
|
||||||
"CalendarDisplayOptions_Color": "Color",
|
"CalendarDisplayOptions_Color": "Color",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Wino.Core.Services;
|
|||||||
/// Singleton manager that handles synchronizer instances and operations for all accounts.
|
/// Singleton manager that handles synchronizer instances and operations for all accounts.
|
||||||
/// Replaces the old WinoServerConnectionManager functionality.
|
/// Replaces the old WinoServerConnectionManager functionality.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SynchronizationManager : ISynchronizationManager
|
public class SynchronizationManager : ISynchronizationManager, IRecipient<AccountSynchronizerStateChanged>
|
||||||
{
|
{
|
||||||
private static readonly Lazy<SynchronizationManager> _instance = new(() => new SynchronizationManager());
|
private static readonly Lazy<SynchronizationManager> _instance = new(() => new SynchronizationManager());
|
||||||
public static SynchronizationManager Instance => _instance.Value;
|
public static SynchronizationManager Instance => _instance.Value;
|
||||||
@@ -31,6 +31,8 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
private readonly ConcurrentDictionary<Guid, IWinoSynchronizerBase> _synchronizerCache = new();
|
private readonly ConcurrentDictionary<Guid, IWinoSynchronizerBase> _synchronizerCache = new();
|
||||||
private readonly ConcurrentDictionary<Guid, CancellationTokenSource> _accountSynchronizationCancellationSources = new();
|
private readonly ConcurrentDictionary<Guid, CancellationTokenSource> _accountSynchronizationCancellationSources = new();
|
||||||
private readonly ConcurrentDictionary<Guid, SemaphoreSlim> _calendarSynchronizationLocks = new();
|
private readonly ConcurrentDictionary<Guid, SemaphoreSlim> _calendarSynchronizationLocks = new();
|
||||||
|
private readonly ConcurrentDictionary<Guid, AccountSynchronizationProgress> _mailSynchronizationProgress = new();
|
||||||
|
private readonly ConcurrentDictionary<Guid, AccountSynchronizationProgress> _calendarSynchronizationProgress = new();
|
||||||
private readonly SemaphoreSlim _initializationSemaphore = new(1, 1);
|
private readonly SemaphoreSlim _initializationSemaphore = new(1, 1);
|
||||||
private readonly ILogger _logger = Log.ForContext<SynchronizationManager>();
|
private readonly ILogger _logger = Log.ForContext<SynchronizationManager>();
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
private INotificationBuilder _notificationBuilder;
|
private INotificationBuilder _notificationBuilder;
|
||||||
|
|
||||||
private bool _isInitialized = false;
|
private bool _isInitialized = false;
|
||||||
|
private bool _isRegisteredForProgressMessages;
|
||||||
|
|
||||||
private SynchronizationManager() { }
|
private SynchronizationManager() { }
|
||||||
|
|
||||||
@@ -73,6 +76,11 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
|
|
||||||
// DO NOT create synchronizers here to avoid requiring window handles during initialization.
|
// DO NOT create synchronizers here to avoid requiring window handles during initialization.
|
||||||
// Synchronizers will be created lazily when first accessed via GetOrCreateSynchronizerAsync.
|
// Synchronizers will be created lazily when first accessed via GetOrCreateSynchronizerAsync.
|
||||||
|
if (!_isRegisteredForProgressMessages)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Register<AccountSynchronizerStateChanged>(this);
|
||||||
|
_isRegisteredForProgressMessages = true;
|
||||||
|
}
|
||||||
|
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
_logger.Information("SynchronizationManager dependencies initialized. Synchronizers will be created lazily.");
|
_logger.Information("SynchronizationManager dependencies initialized. Synchronizers will be created lazily.");
|
||||||
@@ -219,6 +227,21 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccountSynchronizationProgress GetSynchronizationProgress(Guid accountId, SynchronizationProgressCategory category)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
return category switch
|
||||||
|
{
|
||||||
|
SynchronizationProgressCategory.Calendar => _calendarSynchronizationProgress.TryGetValue(accountId, out var calendarProgress)
|
||||||
|
? calendarProgress
|
||||||
|
: AccountSynchronizationProgress.Idle(accountId, SynchronizationProgressCategory.Calendar),
|
||||||
|
_ => _mailSynchronizationProgress.TryGetValue(accountId, out var mailProgress)
|
||||||
|
? mailProgress
|
||||||
|
: AccountSynchronizationProgress.Idle(accountId, SynchronizationProgressCategory.Mail)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queues a request to the corresponding account's synchronizer with optional synchronization triggering.
|
/// Queues a request to the corresponding account's synchronizer with optional synchronization triggering.
|
||||||
/// Automatically determines whether to trigger mail or calendar synchronization based on the request type.
|
/// Automatically determines whether to trigger mail or calendar synchronization based on the request type.
|
||||||
@@ -651,6 +674,9 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
_logger.Information("Canceled ongoing synchronizations for account {AccountId}", accountId);
|
_logger.Information("Canceled ongoing synchronizations for account {AccountId}", accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PublishSynchronizationProgress(AccountSynchronizationProgress.Idle(accountId, SynchronizationProgressCategory.Mail));
|
||||||
|
PublishSynchronizationProgress(AccountSynchronizationProgress.Idle(accountId, SynchronizationProgressCategory.Calendar));
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -679,6 +705,9 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
_logger.Error(ex, "Failed to destroy synchronizer for account {AccountId}", accountId);
|
_logger.Error(ex, "Failed to destroy synchronizer for account {AccountId}", accountId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PublishSynchronizationProgress(AccountSynchronizationProgress.Idle(accountId, SynchronizationProgressCategory.Mail));
|
||||||
|
PublishSynchronizationProgress(AccountSynchronizationProgress.Idle(accountId, SynchronizationProgressCategory.Calendar));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -770,6 +799,33 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Receive(AccountSynchronizerStateChanged message)
|
||||||
|
{
|
||||||
|
var totalUnits = Math.Max(0, message.TotalItemsToSync);
|
||||||
|
var remainingUnits = totalUnits > 0
|
||||||
|
? Math.Clamp(message.RemainingItemsToSync, 0, totalUnits)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
var isInProgress = message.NewState != AccountSynchronizerState.Idle;
|
||||||
|
var isIndeterminate = isInProgress && totalUnits <= 0;
|
||||||
|
var progressPercentage = totalUnits > 0
|
||||||
|
? ((double)(totalUnits - remainingUnits) / totalUnits) * 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
var progress = new AccountSynchronizationProgress(
|
||||||
|
message.AccountId,
|
||||||
|
message.ProgressCategory,
|
||||||
|
isInProgress,
|
||||||
|
isIndeterminate,
|
||||||
|
progressPercentage,
|
||||||
|
totalUnits,
|
||||||
|
remainingUnits,
|
||||||
|
BuildSynchronizationStatus(message.ProgressCategory, message.NewState, totalUnits, progressPercentage, message.SynchronizationStatus),
|
||||||
|
message.NewState);
|
||||||
|
|
||||||
|
PublishSynchronizationProgress(progress);
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureInitialized()
|
private void EnsureInitialized()
|
||||||
{
|
{
|
||||||
if (!_isInitialized)
|
if (!_isInitialized)
|
||||||
@@ -804,17 +860,67 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
return account?.AttentionReason == AccountAttentionReason.InvalidCredentials;
|
return account?.AttentionReason == AccountAttentionReason.InvalidCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PublishSynchronizationProgress(AccountSynchronizationProgress progress)
|
||||||
|
{
|
||||||
|
var normalized = progress.IsInProgress
|
||||||
|
? progress
|
||||||
|
: AccountSynchronizationProgress.Idle(progress.AccountId, progress.Category);
|
||||||
|
|
||||||
|
var cache = normalized.Category == SynchronizationProgressCategory.Calendar
|
||||||
|
? _calendarSynchronizationProgress
|
||||||
|
: _mailSynchronizationProgress;
|
||||||
|
|
||||||
|
cache.AddOrUpdate(normalized.AccountId, normalized, (_, _) => normalized);
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new AccountSynchronizationProgressUpdatedMessage(normalized));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildSynchronizationStatus(
|
||||||
|
SynchronizationProgressCategory category,
|
||||||
|
AccountSynchronizerState state,
|
||||||
|
int totalUnits,
|
||||||
|
double progressPercentage,
|
||||||
|
string rawStatus)
|
||||||
|
{
|
||||||
|
if (state == AccountSynchronizerState.Idle)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
if (state == AccountSynchronizerState.ExecutingRequests)
|
||||||
|
return Translator.SynchronizationProgress_ApplyingChanges;
|
||||||
|
|
||||||
|
if (totalUnits > 0)
|
||||||
|
{
|
||||||
|
var roundedProgress = (int)Math.Round(progressPercentage, MidpointRounding.AwayFromZero);
|
||||||
|
|
||||||
|
return category == SynchronizationProgressCategory.Calendar
|
||||||
|
? string.Format(Translator.SynchronizationProgress_CalendarPercent, roundedProgress)
|
||||||
|
: string.Format(Translator.SynchronizationProgress_MailPercent, roundedProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category == SynchronizationProgressCategory.Calendar && !string.IsNullOrWhiteSpace(rawStatus))
|
||||||
|
return rawStatus;
|
||||||
|
|
||||||
|
return category == SynchronizationProgressCategory.Calendar
|
||||||
|
? Translator.SynchronizationProgress_CalendarInProgress
|
||||||
|
: Translator.SynchronizationProgress_MailInProgress;
|
||||||
|
}
|
||||||
|
|
||||||
private void PublishCalendarSynchronizationState(
|
private void PublishCalendarSynchronizationState(
|
||||||
Guid accountId,
|
Guid accountId,
|
||||||
CalendarSynchronizationType synchronizationType,
|
CalendarSynchronizationType synchronizationType,
|
||||||
bool isSynchronizationInProgress,
|
bool isSynchronizationInProgress,
|
||||||
string synchronizationStatus = "")
|
string synchronizationStatus = "")
|
||||||
{
|
{
|
||||||
WeakReferenceMessenger.Default.Send(new AccountCalendarSynchronizationStateChanged(
|
PublishSynchronizationProgress(new AccountSynchronizationProgress(
|
||||||
accountId,
|
accountId,
|
||||||
synchronizationType,
|
SynchronizationProgressCategory.Calendar,
|
||||||
isSynchronizationInProgress,
|
isSynchronizationInProgress,
|
||||||
synchronizationStatus));
|
isSynchronizationInProgress,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
synchronizationStatus,
|
||||||
|
isSynchronizationInProgress ? AccountSynchronizerState.Synchronizing : AccountSynchronizerState.Idle));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetCalendarSynchronizationStatus(CalendarSynchronizationType synchronizationType)
|
private static string GetCalendarSynchronizationStatus(CalendarSynchronizationType synchronizationType)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public abstract partial class BaseSynchronizer<TBaseRequest> : ObservableObject,
|
|||||||
private readonly ConcurrentDictionary<Guid, byte> _pendingCalendarOperationIds = new();
|
private readonly ConcurrentDictionary<Guid, byte> _pendingCalendarOperationIds = new();
|
||||||
private readonly ConcurrentQueue<SynchronizationIssue> _capturedSynchronizationIssues = new();
|
private readonly ConcurrentQueue<SynchronizationIssue> _capturedSynchronizationIssues = new();
|
||||||
protected readonly IMessenger Messenger;
|
protected readonly IMessenger Messenger;
|
||||||
|
protected SynchronizationProgressCategory CurrentSynchronizationProgressCategory { get; set; } = SynchronizationProgressCategory.Mail;
|
||||||
|
|
||||||
public MailAccount Account { get; }
|
public MailAccount Account { get; }
|
||||||
|
|
||||||
@@ -44,7 +45,8 @@ public abstract partial class BaseSynchronizer<TBaseRequest> : ObservableObject,
|
|||||||
value,
|
value,
|
||||||
TotalItemsToSync,
|
TotalItemsToSync,
|
||||||
RemainingItemsToSync,
|
RemainingItemsToSync,
|
||||||
SynchronizationStatus));
|
SynchronizationStatus,
|
||||||
|
CurrentSynchronizationProgressCategory));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,8 +77,8 @@ public abstract partial class BaseSynchronizer<TBaseRequest> : ObservableObject,
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (TotalItemsToSync == 0 || RemainingItemsToSync == 0)
|
if (TotalItemsToSync <= 0)
|
||||||
return -1; // Indeterminate
|
return 0;
|
||||||
|
|
||||||
return ((double)(TotalItemsToSync - RemainingItemsToSync) / TotalItemsToSync) * 100;
|
return ((double)(TotalItemsToSync - RemainingItemsToSync) / TotalItemsToSync) * 100;
|
||||||
}
|
}
|
||||||
@@ -118,7 +120,8 @@ public abstract partial class BaseSynchronizer<TBaseRequest> : ObservableObject,
|
|||||||
State,
|
State,
|
||||||
TotalItemsToSync,
|
TotalItemsToSync,
|
||||||
RemainingItemsToSync,
|
RemainingItemsToSync,
|
||||||
SynchronizationStatus));
|
SynchronizationStatus,
|
||||||
|
CurrentSynchronizationProgressCategory));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ using Microsoft.IdentityModel.Tokens;
|
|||||||
using MimeKit;
|
using MimeKit;
|
||||||
using MoreLinq;
|
using MoreLinq;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities.Calendar;
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
@@ -388,7 +389,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
} while (!string.IsNullOrEmpty(pageToken));
|
} while (!string.IsNullOrEmpty(pageToken));
|
||||||
|
|
||||||
_logger.Information("Folder {FolderName}: Downloaded {Count} messages", folder.FolderName, folderDownloaded);
|
_logger.Information("Folder {FolderName}: Downloaded {Count} messages", folder.FolderName, folderDownloaded);
|
||||||
UpdateSyncProgress(0, 0, $"Downloaded {totalMessagesDownloaded} messages");
|
UpdateSyncProgress(totalFolders, 0, Translator.SyncAction_SynchronizingAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Information("Initial sync completed. Downloaded {Count} unique messages for {Name}", downloadedMessageIds.Count, Account.Name);
|
_logger.Information("Initial sync completed. Downloaded {Count} unique messages for {Name}", downloadedMessageIds.Count, Account.Name);
|
||||||
@@ -521,8 +522,16 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
.Where(c => c.IsSynchronizationEnabled)
|
.Where(c => c.IsSynchronizationEnabled)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var calendar in localCalendars)
|
var totalCalendars = localCalendars.Count;
|
||||||
|
if (totalCalendars > 0)
|
||||||
{
|
{
|
||||||
|
UpdateSyncProgress(totalCalendars, totalCalendars, Translator.SyncAction_SynchronizingCalendarEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < totalCalendars; i++)
|
||||||
|
{
|
||||||
|
var calendar = localCalendars[i];
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = _calendarService.Events.List(calendar.RemoteCalendarId);
|
var request = _calendarService.Events.List(calendar.RemoteCalendarId);
|
||||||
@@ -602,6 +611,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _gmailChangeProcessor.UpdateAccountCalendarAsync(calendar).ConfigureAwait(false);
|
await _gmailChangeProcessor.UpdateAccountCalendarAsync(calendar).ConfigureAwait(false);
|
||||||
|
UpdateSyncProgress(totalCalendars, totalCalendars - (i + 1), Translator.SyncAction_SynchronizingCalendarEvents);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -624,6 +634,8 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
|
|
||||||
if (!errorContext.CanContinueSync)
|
if (!errorContext.CanContinueSync)
|
||||||
throw;
|
throw;
|
||||||
|
|
||||||
|
UpdateSyncProgress(totalCalendars, totalCalendars - (i + 1), Translator.SyncAction_SynchronizingCalendarEvents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using MailKit.Net.Imap;
|
|||||||
using MailKit.Search;
|
using MailKit.Search;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities.Calendar;
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
@@ -1192,8 +1193,16 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
var periodStartUtc = DateTimeOffset.UtcNow.AddYears(-1);
|
var periodStartUtc = DateTimeOffset.UtcNow.AddYears(-1);
|
||||||
var periodEndUtc = DateTimeOffset.UtcNow.AddYears(2);
|
var periodEndUtc = DateTimeOffset.UtcNow.AddYears(2);
|
||||||
|
|
||||||
foreach (var localCalendar in localCalendars)
|
var totalCalendars = localCalendars.Count;
|
||||||
|
if (totalCalendars > 0)
|
||||||
{
|
{
|
||||||
|
UpdateSyncProgress(totalCalendars, totalCalendars, Translator.SyncAction_SynchronizingCalendarEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < totalCalendars; i++)
|
||||||
|
{
|
||||||
|
var localCalendar = localCalendars[i];
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
if (!remoteCalendarsById.TryGetValue(localCalendar.RemoteCalendarId, out var remoteCalendar))
|
if (!remoteCalendarsById.TryGetValue(localCalendar.RemoteCalendarId, out var remoteCalendar))
|
||||||
@@ -1265,6 +1274,7 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
|
|
||||||
localCalendar.SynchronizationDeltaToken = remoteToken;
|
localCalendar.SynchronizationDeltaToken = remoteToken;
|
||||||
await _imapChangeProcessor.UpdateAccountCalendarAsync(localCalendar).ConfigureAwait(false);
|
await _imapChangeProcessor.UpdateAccountCalendarAsync(localCalendar).ConfigureAwait(false);
|
||||||
|
UpdateSyncProgress(totalCalendars, totalCalendars - (i + 1), Translator.SyncAction_SynchronizingCalendarEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CalendarSynchronizationResult.Empty;
|
return CalendarSynchronizationResult.Empty;
|
||||||
|
|||||||
@@ -2320,8 +2320,16 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
|
|
||||||
// TODO: Maybe we can batch each calendar?
|
// TODO: Maybe we can batch each calendar?
|
||||||
|
|
||||||
foreach (var calendar in localCalendars)
|
var totalCalendars = localCalendars.Count;
|
||||||
|
if (totalCalendars > 0)
|
||||||
{
|
{
|
||||||
|
UpdateSyncProgress(totalCalendars, totalCalendars, Translator.SyncAction_SynchronizingCalendarEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < totalCalendars; i++)
|
||||||
|
{
|
||||||
|
var calendar = localCalendars[i];
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bool isInitialSync = string.IsNullOrEmpty(calendar.SynchronizationDeltaToken);
|
bool isInitialSync = string.IsNullOrEmpty(calendar.SynchronizationDeltaToken);
|
||||||
@@ -2440,6 +2448,8 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
|
|
||||||
await _outlookChangeProcessor.UpdateCalendarDeltaSynchronizationToken(calendar.Id, deltaToken).ConfigureAwait(false);
|
await _outlookChangeProcessor.UpdateCalendarDeltaSynchronizationToken(calendar.Id, deltaToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateSyncProgress(totalCalendars, totalCalendars - (i + 1), Translator.SyncAction_SynchronizingCalendarEvents);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -2462,6 +2472,8 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
|
|
||||||
if (!errorContext.CanContinueSync)
|
if (!errorContext.CanContinueSync)
|
||||||
throw;
|
throw;
|
||||||
|
|
||||||
|
UpdateSyncProgress(totalCalendars, totalCalendars - (i + 1), Translator.SyncAction_SynchronizingCalendarEvents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
|||||||
|
|
||||||
if (shouldExecuteRequests && changeRequestQueue.Any())
|
if (shouldExecuteRequests && changeRequestQueue.Any())
|
||||||
{
|
{
|
||||||
|
CurrentSynchronizationProgressCategory = SynchronizationProgressCategory.Mail;
|
||||||
State = AccountSynchronizerState.ExecutingRequests;
|
State = AccountSynchronizerState.ExecutingRequests;
|
||||||
|
|
||||||
List<IRequestBundle<TBaseRequest>> nativeRequests = new();
|
List<IRequestBundle<TBaseRequest>> nativeRequests = new();
|
||||||
@@ -264,6 +265,7 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
|||||||
await synchronizationSemaphore.WaitAsync(activeSynchronizationCancellationToken);
|
await synchronizationSemaphore.WaitAsync(activeSynchronizationCancellationToken);
|
||||||
|
|
||||||
// Set indeterminate progress for initial state
|
// Set indeterminate progress for initial state
|
||||||
|
CurrentSynchronizationProgressCategory = SynchronizationProgressCategory.Mail;
|
||||||
UpdateSyncProgress(0, 0, "Synchronizing...");
|
UpdateSyncProgress(0, 0, "Synchronizing...");
|
||||||
|
|
||||||
State = AccountSynchronizerState.Synchronizing;
|
State = AccountSynchronizerState.Synchronizing;
|
||||||
@@ -388,6 +390,7 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
|||||||
if (shouldExecuteRequests)
|
if (shouldExecuteRequests)
|
||||||
{
|
{
|
||||||
calendarRequestsWereExecuting = true;
|
calendarRequestsWereExecuting = true;
|
||||||
|
CurrentSynchronizationProgressCategory = SynchronizationProgressCategory.Calendar;
|
||||||
State = AccountSynchronizerState.ExecutingRequests;
|
State = AccountSynchronizerState.ExecutingRequests;
|
||||||
|
|
||||||
List<IRequestBundle<TBaseRequest>> nativeRequests = new();
|
List<IRequestBundle<TBaseRequest>> nativeRequests = new();
|
||||||
@@ -482,6 +485,7 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
|||||||
await Task.Delay(maxExecutionDelay, cancellationToken);
|
await Task.Delay(maxExecutionDelay, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CurrentSynchronizationProgressCategory = SynchronizationProgressCategory.Calendar;
|
||||||
var synchronizationResult = await SynchronizeCalendarEventsInternalAsync(options, cancellationToken);
|
var synchronizationResult = await SynchronizeCalendarEventsInternalAsync(options, cancellationToken);
|
||||||
return FinalizeCalendarResult(synchronizationResult);
|
return FinalizeCalendarResult(synchronizationResult);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
IRecipient<MergedInboxRenamed>,
|
IRecipient<MergedInboxRenamed>,
|
||||||
IRecipient<LanguageChanged>,
|
IRecipient<LanguageChanged>,
|
||||||
IRecipient<AccountMenuItemsReordered>,
|
IRecipient<AccountMenuItemsReordered>,
|
||||||
IRecipient<AccountSynchronizerStateChanged>,
|
IRecipient<AccountSynchronizationProgressUpdatedMessage>,
|
||||||
IRecipient<NavigateAppPreferencesRequested>,
|
IRecipient<NavigateAppPreferencesRequested>,
|
||||||
IRecipient<AccountFolderConfigurationUpdated>,
|
IRecipient<AccountFolderConfigurationUpdated>,
|
||||||
IRecipient<AccountRemovedMessage>,
|
IRecipient<AccountRemovedMessage>,
|
||||||
@@ -157,6 +157,24 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ApplySynchronizationProgress(IAccountMenuItem accountMenuItem, SynchronizationProgressCategory category)
|
||||||
|
{
|
||||||
|
AccountSynchronizationProgress progress;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
progress = SynchronizationManager.Instance.GetSynchronizationProgress(
|
||||||
|
accountMenuItem.HoldingAccounts.First().Id,
|
||||||
|
category);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accountMenuItem.ApplySynchronizationProgress(progress);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadAccountsAsync()
|
private async Task LoadAccountsAsync()
|
||||||
{
|
{
|
||||||
// First clear all account menu items.
|
// First clear all account menu items.
|
||||||
@@ -185,9 +203,13 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
foreach (var mergedAccount in mergedAccounts)
|
foreach (var mergedAccount in mergedAccounts)
|
||||||
{
|
{
|
||||||
initializedAccountIds.Add(mergedAccount.Id);
|
initializedAccountIds.Add(mergedAccount.Id);
|
||||||
mergedAccountMenuItem.SubMenuItems.Add(new AccountMenuItem(mergedAccount, mergedAccountMenuItem));
|
var accountMenuItem = new AccountMenuItem(mergedAccount, mergedAccountMenuItem);
|
||||||
|
ApplySynchronizationProgress(accountMenuItem, SynchronizationProgressCategory.Mail);
|
||||||
|
mergedAccountMenuItem.SubMenuItems.Add(accountMenuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mergedAccountMenuItem.RefreshSynchronizationProgress();
|
||||||
|
|
||||||
await ExecuteUIThread(() =>
|
await ExecuteUIThread(() =>
|
||||||
{
|
{
|
||||||
MenuItems.Add(mergedAccountMenuItem);
|
MenuItems.Add(mergedAccountMenuItem);
|
||||||
@@ -196,9 +218,12 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var accountMenuItem = new AccountMenuItem(account, null);
|
||||||
|
ApplySynchronizationProgress(accountMenuItem, SynchronizationProgressCategory.Mail);
|
||||||
|
|
||||||
await ExecuteUIThread(() =>
|
await ExecuteUIThread(() =>
|
||||||
{
|
{
|
||||||
MenuItems.Add(new AccountMenuItem(account, null));
|
MenuItems.Add(accountMenuItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
initializedAccountIds.Add(account.Id);
|
initializedAccountIds.Add(account.Id);
|
||||||
@@ -1194,17 +1219,19 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
UpdateFolderCollection(mailItemFolder);
|
UpdateFolderCollection(mailItemFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void Receive(AccountSynchronizerStateChanged message)
|
public async void Receive(AccountSynchronizationProgressUpdatedMessage message)
|
||||||
{
|
{
|
||||||
var accountMenuItem = MenuItems.GetSpecificAccountMenuItem(message.AccountId);
|
var progress = message.Progress;
|
||||||
|
if (progress.Category != SynchronizationProgressCategory.Mail)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var accountMenuItem = MenuItems.GetSpecificAccountMenuItem(progress.AccountId);
|
||||||
|
|
||||||
if (accountMenuItem == null) return;
|
if (accountMenuItem == null) return;
|
||||||
|
|
||||||
await ExecuteUIThread(() =>
|
await ExecuteUIThread(() =>
|
||||||
{
|
{
|
||||||
accountMenuItem.TotalItemsToSync = message.TotalItemsToSync;
|
accountMenuItem.ApplySynchronizationProgress(progress);
|
||||||
accountMenuItem.RemainingItemsToSync = message.RemainingItemsToSync;
|
|
||||||
accountMenuItem.SynchronizationStatus = message.SynchronizationStatus;
|
|
||||||
|
|
||||||
// If this account is part of a merged inbox, update the merged inbox progress as well
|
// If this account is part of a merged inbox, update the merged inbox progress as well
|
||||||
if (accountMenuItem.ParentMenuItem is MergedAccountMenuItem mergedAccountMenuItem)
|
if (accountMenuItem.ParentMenuItem is MergedAccountMenuItem mergedAccountMenuItem)
|
||||||
@@ -1231,7 +1258,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
Messenger.Register<MergedInboxRenamed>(this);
|
Messenger.Register<MergedInboxRenamed>(this);
|
||||||
Messenger.Register<LanguageChanged>(this);
|
Messenger.Register<LanguageChanged>(this);
|
||||||
Messenger.Register<AccountMenuItemsReordered>(this);
|
Messenger.Register<AccountMenuItemsReordered>(this);
|
||||||
Messenger.Register<AccountSynchronizerStateChanged>(this);
|
Messenger.Register<AccountSynchronizationProgressUpdatedMessage>(this);
|
||||||
Messenger.Register<NavigateAppPreferencesRequested>(this);
|
Messenger.Register<NavigateAppPreferencesRequested>(this);
|
||||||
Messenger.Register<AccountFolderConfigurationUpdated>(this);
|
Messenger.Register<AccountFolderConfigurationUpdated>(this);
|
||||||
}
|
}
|
||||||
@@ -1248,7 +1275,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
Messenger.Unregister<MergedInboxRenamed>(this);
|
Messenger.Unregister<MergedInboxRenamed>(this);
|
||||||
Messenger.Unregister<LanguageChanged>(this);
|
Messenger.Unregister<LanguageChanged>(this);
|
||||||
Messenger.Unregister<AccountMenuItemsReordered>(this);
|
Messenger.Unregister<AccountMenuItemsReordered>(this);
|
||||||
Messenger.Unregister<AccountSynchronizerStateChanged>(this);
|
Messenger.Unregister<AccountSynchronizationProgressUpdatedMessage>(this);
|
||||||
Messenger.Unregister<NavigateAppPreferencesRequested>(this);
|
Messenger.Unregister<NavigateAppPreferencesRequested>(this);
|
||||||
Messenger.Unregister<AccountFolderConfigurationUpdated>(this);
|
Messenger.Unregister<AccountFolderConfigurationUpdated>(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ using CommunityToolkit.Mvvm.Messaging;
|
|||||||
using Wino.Calendar.ViewModels.Data;
|
using Wino.Calendar.ViewModels.Data;
|
||||||
using Wino.Calendar.ViewModels.Interfaces;
|
using Wino.Calendar.ViewModels.Interfaces;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Services;
|
||||||
using Wino.Messaging.Client.Calendar;
|
using Wino.Messaging.Client.Calendar;
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ public partial class AccountCalendarStateService : ObservableRecipient,
|
|||||||
IRecipient<CalendarListDeleted>,
|
IRecipient<CalendarListDeleted>,
|
||||||
IRecipient<AccountRemovedMessage>,
|
IRecipient<AccountRemovedMessage>,
|
||||||
IRecipient<AccountUpdatedMessage>,
|
IRecipient<AccountUpdatedMessage>,
|
||||||
IRecipient<AccountCalendarSynchronizationStateChanged>
|
IRecipient<AccountSynchronizationProgressUpdatedMessage>
|
||||||
{
|
{
|
||||||
private readonly object _calendarStateLock = new();
|
private readonly object _calendarStateLock = new();
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@ public partial class AccountCalendarStateService : ObservableRecipient,
|
|||||||
Messenger.Register<CalendarListDeleted>(this);
|
Messenger.Register<CalendarListDeleted>(this);
|
||||||
Messenger.Register<AccountRemovedMessage>(this);
|
Messenger.Register<AccountRemovedMessage>(this);
|
||||||
Messenger.Register<AccountUpdatedMessage>(this);
|
Messenger.Register<AccountUpdatedMessage>(this);
|
||||||
Messenger.Register<AccountCalendarSynchronizationStateChanged>(this);
|
Messenger.Register<AccountSynchronizationProgressUpdatedMessage>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SingleGroupCalendarCollectiveStateChanged(object? sender, EventArgs e)
|
private void SingleGroupCalendarCollectiveStateChanged(object? sender, EventArgs e)
|
||||||
@@ -105,6 +107,15 @@ public partial class AccountCalendarStateService : ObservableRecipient,
|
|||||||
{
|
{
|
||||||
groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged;
|
groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged;
|
||||||
groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged;
|
groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
groupedAccountCalendar.ApplySynchronizationProgress(SynchronizationManager.Instance.GetSynchronizationProgress(
|
||||||
|
groupedAccountCalendar.Account.Id,
|
||||||
|
SynchronizationProgressCategory.Calendar));
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
_internalGroupedAccountCalendars.Add(groupedAccountCalendar);
|
_internalGroupedAccountCalendars.Add(groupedAccountCalendar);
|
||||||
|
|
||||||
@@ -364,15 +375,18 @@ public partial class AccountCalendarStateService : ObservableRecipient,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void Receive(AccountCalendarSynchronizationStateChanged message)
|
public async void Receive(AccountSynchronizationProgressUpdatedMessage message)
|
||||||
{
|
{
|
||||||
|
if (message.Progress.Category != SynchronizationProgressCategory.Calendar)
|
||||||
|
return;
|
||||||
|
|
||||||
if (Dispatcher != null)
|
if (Dispatcher != null)
|
||||||
{
|
{
|
||||||
await Dispatcher.ExecuteOnUIThread(() => UpdateCalendarSynchronizationState(message));
|
await Dispatcher.ExecuteOnUIThread(() => UpdateCalendarSynchronizationState(message.Progress));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UpdateCalendarSynchronizationState(message);
|
UpdateCalendarSynchronizationState(message.Progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,19 +401,18 @@ public partial class AccountCalendarStateService : ObservableRecipient,
|
|||||||
groupedAccount?.UpdateAccount(updatedAccount);
|
groupedAccount?.UpdateAccount(updatedAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCalendarSynchronizationState(AccountCalendarSynchronizationStateChanged message)
|
private void UpdateCalendarSynchronizationState(Wino.Core.Domain.Models.Synchronization.AccountSynchronizationProgress progress)
|
||||||
{
|
{
|
||||||
GroupedAccountCalendarViewModel? groupedAccount;
|
GroupedAccountCalendarViewModel? groupedAccount;
|
||||||
lock (_calendarStateLock)
|
lock (_calendarStateLock)
|
||||||
{
|
{
|
||||||
groupedAccount = _internalGroupedAccountCalendars.FirstOrDefault(a => a.Account.Id == message.AccountId);
|
groupedAccount = _internalGroupedAccountCalendars.FirstOrDefault(a => a.Account.Id == progress.AccountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupedAccount == null)
|
if (groupedAccount == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
groupedAccount.IsSynchronizationInProgress = message.IsSynchronizationInProgress;
|
groupedAccount.ApplySynchronizationProgress(progress);
|
||||||
groupedAccount.SynchronizationStatus = message.SynchronizationStatus;
|
|
||||||
UpdateAggregateSynchronizationState();
|
UpdateAggregateSynchronizationState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,13 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<StackPanel VerticalAlignment="Center">
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="10" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Name="AccountNameTextblock"
|
x:Name="AccountNameTextblock"
|
||||||
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
|
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
|
||||||
@@ -75,22 +81,23 @@
|
|||||||
TextTrimming="CharacterEllipsis" />
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
|
Grid.Row="1"
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
MaxLines="1"
|
MaxLines="1"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
Text="{x:Bind Parameter.Address, Mode=OneWay}"
|
Text="{x:Bind Parameter.Address, Mode=OneWay}"
|
||||||
TextTrimming="CharacterEllipsis"
|
TextTrimming="CharacterEllipsis" />
|
||||||
Visibility="{x:Bind IsSynchronizationProgressVisible, Converter={StaticResource ReverseBooleanToVisibilityConverter}, Mode=OneWay}" />
|
|
||||||
|
|
||||||
<TextBlock
|
<ProgressBar
|
||||||
x:Name="SyncStatusText"
|
Grid.Row="2"
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
Height="3"
|
||||||
MaxLines="1"
|
Margin="0,4,0,0"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
IsIndeterminate="{x:Bind IsProgressIndeterminate, Mode=OneWay}"
|
||||||
Text="{x:Bind SynchronizationStatus, Mode=OneWay}"
|
ShowError="False"
|
||||||
TextTrimming="CharacterEllipsis"
|
ShowPaused="False"
|
||||||
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
|
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
||||||
</StackPanel>
|
Value="{x:Bind SynchronizationProgressValue, Mode=OneWay}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
@@ -117,9 +124,9 @@
|
|||||||
Margin="8,0"
|
Margin="8,0"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
x:Load="{x:Bind IsProgressIndeterminate, Mode=OneWay}"
|
||||||
IsActive="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
IsActive="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
||||||
IsIndeterminate="{x:Bind IsProgressIndeterminate, Mode=OneWay}"
|
IsIndeterminate="{x:Bind IsProgressIndeterminate, Mode=OneWay}" />
|
||||||
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</controls:AccountNavigationItem>
|
</controls:AccountNavigationItem>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
@@ -292,6 +299,15 @@
|
|||||||
TextTrimming="CharacterEllipsis"
|
TextTrimming="CharacterEllipsis"
|
||||||
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
|
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
Height="3"
|
||||||
|
Margin="0,4,0,0"
|
||||||
|
IsIndeterminate="{x:Bind IsProgressIndeterminate, Mode=OneWay}"
|
||||||
|
ShowError="False"
|
||||||
|
ShowPaused="False"
|
||||||
|
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
||||||
|
Value="{x:Bind SynchronizationProgressValue, Mode=OneWay}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
MaxLines="1"
|
MaxLines="1"
|
||||||
@@ -564,12 +580,20 @@
|
|||||||
<Run FontWeight="SemiBold" Text="{x:Bind Account.Name}" />
|
<Run FontWeight="SemiBold" Text="{x:Bind Account.Name}" />
|
||||||
<Run FontSize="12" Text=" (" /><Run FontSize="12" Text="{x:Bind Account.Address}" /><Run FontSize="12" Text=")" />
|
<Run FontSize="12" Text=" (" /><Run FontSize="12" Text="{x:Bind Account.Address}" /><Run FontSize="12" Text=")" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
<TextBlock
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
MaxLines="1"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{x:Bind SynchronizationStatus, Mode=OneWay}"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
Height="4"
|
Height="4"
|
||||||
IsIndeterminate="True"
|
IsIndeterminate="{x:Bind IsProgressIndeterminate, Mode=OneWay}"
|
||||||
ShowError="False"
|
ShowError="False"
|
||||||
ShowPaused="False"
|
ShowPaused="False"
|
||||||
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
|
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
||||||
|
Value="{x:Bind SynchronizationProgressValue, Mode=OneWay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</muxc:Expander.Header>
|
</muxc:Expander.Header>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
using Wino.Messaging.Server;
|
using Wino.Messaging.Server;
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ namespace Wino.Messaging;
|
|||||||
[JsonSerializable(typeof(AccountSynchronizationCompleted))]
|
[JsonSerializable(typeof(AccountSynchronizationCompleted))]
|
||||||
[JsonSerializable(typeof(RefreshUnreadCountsMessage))]
|
[JsonSerializable(typeof(RefreshUnreadCountsMessage))]
|
||||||
[JsonSerializable(typeof(AccountSynchronizerStateChanged))]
|
[JsonSerializable(typeof(AccountSynchronizerStateChanged))]
|
||||||
|
[JsonSerializable(typeof(AccountSynchronizationProgress))]
|
||||||
[JsonSerializable(typeof(AccountSynchronizationProgressUpdatedMessage))]
|
[JsonSerializable(typeof(AccountSynchronizationProgressUpdatedMessage))]
|
||||||
[JsonSerializable(typeof(AccountFolderConfigurationUpdated))]
|
[JsonSerializable(typeof(AccountFolderConfigurationUpdated))]
|
||||||
[JsonSerializable(typeof(CopyAuthURLRequested))]
|
[JsonSerializable(typeof(CopyAuthURLRequested))]
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
|
||||||
namespace Wino.Messaging.UI;
|
namespace Wino.Messaging.UI;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports back the account synchronization progress.
|
/// Reports back the account synchronization progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public record AccountSynchronizationProgressUpdatedMessage(Guid AccountId, double Progress) : UIMessageBase<AccountSynchronizationProgressUpdatedMessage>;
|
public record AccountSynchronizationProgressUpdatedMessage(AccountSynchronizationProgress Progress)
|
||||||
|
: UIMessageBase<AccountSynchronizationProgressUpdatedMessage>;
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ namespace Wino.Messaging.UI;
|
|||||||
/// <param name="TotalItemsToSync">Total items to sync (0 for indeterminate)</param>
|
/// <param name="TotalItemsToSync">Total items to sync (0 for indeterminate)</param>
|
||||||
/// <param name="RemainingItemsToSync">Remaining items to sync</param>
|
/// <param name="RemainingItemsToSync">Remaining items to sync</param>
|
||||||
/// <param name="SynchronizationStatus">Current synchronization status message</param>
|
/// <param name="SynchronizationStatus">Current synchronization status message</param>
|
||||||
|
/// <param name="ProgressCategory">Synchronization category that emitted the update</param>
|
||||||
public record AccountSynchronizerStateChanged(
|
public record AccountSynchronizerStateChanged(
|
||||||
Guid AccountId,
|
Guid AccountId,
|
||||||
AccountSynchronizerState NewState,
|
AccountSynchronizerState NewState,
|
||||||
int TotalItemsToSync = 0,
|
int TotalItemsToSync = 0,
|
||||||
int RemainingItemsToSync = 0,
|
int RemainingItemsToSync = 0,
|
||||||
string SynchronizationStatus = "") : UIMessageBase<AccountSynchronizerStateChanged>;
|
string SynchronizationStatus = "",
|
||||||
|
SynchronizationProgressCategory ProgressCategory = SynchronizationProgressCategory.Mail) : UIMessageBase<AccountSynchronizerStateChanged>;
|
||||||
|
|||||||
Reference in New Issue
Block a user