Implement mail and calendar item synchronizer state (#815)
* Track pending sync operations per mail/calendar item * Updated progressbar for in progress drafts
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
@@ -23,6 +24,18 @@ public interface IBaseSynchronizer
|
||||
/// <param name="request">Request to queue.</param>
|
||||
void QueueRequest(IRequestBase request);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether there is an in-progress (queued or currently executing) operation for the given mail unique id.
|
||||
/// </summary>
|
||||
/// <param name="mailUniqueId">Mail unique id to check.</param>
|
||||
bool HasPendingOperation(Guid mailUniqueId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether there is an in-progress (queued or currently executing) operation for the given calendar item id.
|
||||
/// </summary>
|
||||
/// <param name="calendarItemId">Calendar item id to check.</param>
|
||||
bool HasPendingCalendarOperation(Guid calendarItemId);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes profile information with the server.
|
||||
/// Sender name and Profile picture are updated.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
@@ -20,6 +21,8 @@ public abstract partial class BaseSynchronizer<TBaseRequest> : ObservableObject,
|
||||
protected CancellationToken activeSynchronizationCancellationToken;
|
||||
|
||||
protected List<IRequestBase> changeRequestQueue = [];
|
||||
private readonly ConcurrentDictionary<Guid, byte> _pendingMailOperationIds = new();
|
||||
private readonly ConcurrentDictionary<Guid, byte> _pendingCalendarOperationIds = new();
|
||||
protected readonly IMessenger Messenger;
|
||||
|
||||
public MailAccount Account { get; }
|
||||
@@ -119,7 +122,47 @@ public abstract partial class BaseSynchronizer<TBaseRequest> : ObservableObject,
|
||||
/// Queues a single request to be executed in the next synchronization.
|
||||
/// </summary>
|
||||
/// <param name="request">Request to execute.</param>
|
||||
public void QueueRequest(IRequestBase request) => changeRequestQueue.Add(request);
|
||||
public void QueueRequest(IRequestBase request)
|
||||
{
|
||||
changeRequestQueue.Add(request);
|
||||
TrackQueuedRequest(request);
|
||||
}
|
||||
|
||||
public bool HasPendingOperation(Guid mailUniqueId) => _pendingMailOperationIds.ContainsKey(mailUniqueId);
|
||||
|
||||
public bool HasPendingCalendarOperation(Guid calendarItemId) => _pendingCalendarOperationIds.ContainsKey(calendarItemId);
|
||||
|
||||
protected void TrackQueuedRequest(IRequestBase request)
|
||||
{
|
||||
if (request is IMailActionRequest mailActionRequest)
|
||||
{
|
||||
_pendingMailOperationIds.TryAdd(mailActionRequest.Item.UniqueId, 0);
|
||||
}
|
||||
|
||||
if (request is ICalendarActionRequest calendarActionRequest)
|
||||
{
|
||||
_pendingCalendarOperationIds.TryAdd(calendarActionRequest.Item.Id, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UntrackProcessedRequest(IRequestBase request)
|
||||
{
|
||||
if (request is IMailActionRequest mailActionRequest)
|
||||
{
|
||||
_pendingMailOperationIds.TryRemove(mailActionRequest.Item.UniqueId, out _);
|
||||
}
|
||||
|
||||
if (request is ICalendarActionRequest calendarActionRequest)
|
||||
{
|
||||
_pendingCalendarOperationIds.TryRemove(calendarActionRequest.Item.Id, out _);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UntrackProcessedRequests(IEnumerable<IRequestBase> requests)
|
||||
{
|
||||
foreach (var request in requests)
|
||||
UntrackProcessedRequest(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs existing queued requests in the queue.
|
||||
|
||||
@@ -219,7 +219,14 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
||||
|
||||
Console.WriteLine($"Prepared {nativeRequests.Count()} native requests");
|
||||
|
||||
await ExecuteNativeRequestsAsync(nativeRequests, activeSynchronizationCancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await ExecuteNativeRequestsAsync(nativeRequests, activeSynchronizationCancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
UntrackProcessedRequests(requestCopies);
|
||||
}
|
||||
|
||||
Messenger.Send(new SynchronizationActionsCompleted(Account.Id));
|
||||
|
||||
@@ -419,7 +426,14 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
||||
|
||||
Console.WriteLine($"Prepared {nativeRequests.Count()} native calendar requests");
|
||||
|
||||
await ExecuteNativeRequestsAsync(nativeRequests, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await ExecuteNativeRequestsAsync(nativeRequests, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
UntrackProcessedRequests(requestCopies);
|
||||
}
|
||||
|
||||
Messenger.Send(new SynchronizationActionsCompleted(Account.Id));
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
||||
// Update is triggered when we leave the page.
|
||||
private bool isUpdatingMimeBlocked = false;
|
||||
|
||||
private bool canSendMail => ComposingAccount != null && !IsLocalDraft && CurrentMimeMessage != null;
|
||||
private bool canSendMail => ComposingAccount != null && !IsLocalDraft && CurrentMimeMessage != null && !IsDraftBusy;
|
||||
|
||||
[NotifyCanExecuteChangedFor(nameof(DiscardCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
|
||||
@@ -52,24 +52,29 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
||||
[NotifyPropertyChangedFor(nameof(IsLocalDraft))]
|
||||
[NotifyCanExecuteChangedFor(nameof(DiscardCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
|
||||
private MailItemViewModel currentMailDraftItem;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isImportanceSelected;
|
||||
|
||||
[ObservableProperty]
|
||||
private MessageImportance selectedMessageImportance;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isCCBCCVisible;
|
||||
|
||||
[ObservableProperty]
|
||||
private string subject;
|
||||
public partial MailItemViewModel CurrentMailDraftItem { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(DiscardCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
|
||||
private MailAccount composingAccount;
|
||||
public partial bool IsDraftBusy { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsImportanceSelected { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial MessageImportance SelectedMessageImportance { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsCCBCCVisible { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Subject { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(DiscardCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
|
||||
public partial MailAccount ComposingAccount { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial List<MailAccountAlias> AvailableAliases { get; set; }
|
||||
@@ -312,6 +317,8 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
||||
CurrentMailDraftItem.MailCopy.AssignedAccount.Preferences,
|
||||
base64EncodedMessage);
|
||||
|
||||
IsDraftBusy = true;
|
||||
|
||||
await _worker.ExecuteAsync(draftSendPreparationRequest);
|
||||
}
|
||||
|
||||
@@ -430,6 +437,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
||||
{
|
||||
CurrentMailDraftItem = mailItem;
|
||||
|
||||
await UpdatePendingOperationStateAsync();
|
||||
await TryPrepareComposeAsync(true);
|
||||
}
|
||||
}
|
||||
@@ -446,6 +454,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
||||
|
||||
// Set the new draft item and prepare it.
|
||||
CurrentMailDraftItem = message.MailItemViewModel;
|
||||
await UpdatePendingOperationStateAsync();
|
||||
await TryPrepareComposeAsync(true);
|
||||
}
|
||||
|
||||
@@ -500,6 +509,23 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task UpdatePendingOperationStateAsync()
|
||||
{
|
||||
IsDraftBusy = false;
|
||||
|
||||
if (CurrentMailDraftItem?.MailCopy == null || !CurrentMailDraftItem.MailCopy.IsDraft)
|
||||
return;
|
||||
|
||||
var accountId = CurrentMailDraftItem.MailCopy.AssignedAccount?.Id ?? Guid.Empty;
|
||||
|
||||
if (accountId == Guid.Empty)
|
||||
return;
|
||||
|
||||
var synchronizer = await SynchronizationManager.Instance.GetSynchronizerAsync(accountId).ConfigureAwait(false);
|
||||
|
||||
IsDraftBusy = synchronizer?.HasPendingOperation(CurrentMailDraftItem.MailCopy.UniqueId) ?? false;
|
||||
}
|
||||
|
||||
private async Task TryPrepareComposeAsync(bool downloadIfNeeded)
|
||||
{
|
||||
if (CurrentMailDraftItem == null) return;
|
||||
@@ -674,11 +700,13 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
||||
|
||||
if (updatedMail.UniqueId == CurrentMailDraftItem.MailCopy.UniqueId)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
await ExecuteUIThread(async () =>
|
||||
{
|
||||
CurrentMailDraftItem.UpdateFrom(updatedMail);
|
||||
DiscardCommand.NotifyCanExecuteChanged();
|
||||
SendCommand.NotifyCanExecuteChanged();
|
||||
|
||||
await UpdatePendingOperationStateAsync();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,14 @@
|
||||
CommandAlignment="Right"
|
||||
IsDynamicOverflowEnabled="True"
|
||||
OverflowButtonAlignment="Left">
|
||||
<AppBarButton
|
||||
MinWidth="40"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Visibility="{x:Bind ViewModel.IsDraftBusy, Mode=OneWay}">
|
||||
<ProgressRing IsActive="True" />
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton
|
||||
Width="Auto"
|
||||
MinWidth="40"
|
||||
|
||||
Reference in New Issue
Block a user