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.Entities.Shared;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Models.Accounts;
|
using Wino.Core.Domain.Models.Accounts;
|
||||||
@@ -23,6 +24,18 @@ public interface IBaseSynchronizer
|
|||||||
/// <param name="request">Request to queue.</param>
|
/// <param name="request">Request to queue.</param>
|
||||||
void QueueRequest(IRequestBase request);
|
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>
|
/// <summary>
|
||||||
/// Synchronizes profile information with the server.
|
/// Synchronizes profile information with the server.
|
||||||
/// Sender name and Profile picture are updated.
|
/// Sender name and Profile picture are updated.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -20,6 +21,8 @@ public abstract partial class BaseSynchronizer<TBaseRequest> : ObservableObject,
|
|||||||
protected CancellationToken activeSynchronizationCancellationToken;
|
protected CancellationToken activeSynchronizationCancellationToken;
|
||||||
|
|
||||||
protected List<IRequestBase> changeRequestQueue = [];
|
protected List<IRequestBase> changeRequestQueue = [];
|
||||||
|
private readonly ConcurrentDictionary<Guid, byte> _pendingMailOperationIds = new();
|
||||||
|
private readonly ConcurrentDictionary<Guid, byte> _pendingCalendarOperationIds = new();
|
||||||
protected readonly IMessenger Messenger;
|
protected readonly IMessenger Messenger;
|
||||||
|
|
||||||
public MailAccount Account { get; }
|
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.
|
/// Queues a single request to be executed in the next synchronization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">Request to execute.</param>
|
/// <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>
|
/// <summary>
|
||||||
/// Runs existing queued requests in the queue.
|
/// 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");
|
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));
|
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");
|
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));
|
Messenger.Send(new SynchronizationActionsCompleted(Account.Id));
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
// Update is triggered when we leave the page.
|
// Update is triggered when we leave the page.
|
||||||
private bool isUpdatingMimeBlocked = false;
|
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(DiscardCommand))]
|
||||||
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
|
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
|
||||||
@@ -52,24 +52,29 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
[NotifyPropertyChangedFor(nameof(IsLocalDraft))]
|
[NotifyPropertyChangedFor(nameof(IsLocalDraft))]
|
||||||
[NotifyCanExecuteChangedFor(nameof(DiscardCommand))]
|
[NotifyCanExecuteChangedFor(nameof(DiscardCommand))]
|
||||||
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
|
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
|
||||||
private MailItemViewModel currentMailDraftItem;
|
public partial MailItemViewModel CurrentMailDraftItem { get; set; }
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool isImportanceSelected;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private MessageImportance selectedMessageImportance;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool isCCBCCVisible;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string subject;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyCanExecuteChangedFor(nameof(DiscardCommand))]
|
[NotifyCanExecuteChangedFor(nameof(DiscardCommand))]
|
||||||
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
|
[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]
|
[ObservableProperty]
|
||||||
public partial List<MailAccountAlias> AvailableAliases { get; set; }
|
public partial List<MailAccountAlias> AvailableAliases { get; set; }
|
||||||
@@ -312,6 +317,8 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
CurrentMailDraftItem.MailCopy.AssignedAccount.Preferences,
|
CurrentMailDraftItem.MailCopy.AssignedAccount.Preferences,
|
||||||
base64EncodedMessage);
|
base64EncodedMessage);
|
||||||
|
|
||||||
|
IsDraftBusy = true;
|
||||||
|
|
||||||
await _worker.ExecuteAsync(draftSendPreparationRequest);
|
await _worker.ExecuteAsync(draftSendPreparationRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,6 +437,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
{
|
{
|
||||||
CurrentMailDraftItem = mailItem;
|
CurrentMailDraftItem = mailItem;
|
||||||
|
|
||||||
|
await UpdatePendingOperationStateAsync();
|
||||||
await TryPrepareComposeAsync(true);
|
await TryPrepareComposeAsync(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,6 +454,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
// Set the new draft item and prepare it.
|
// Set the new draft item and prepare it.
|
||||||
CurrentMailDraftItem = message.MailItemViewModel;
|
CurrentMailDraftItem = message.MailItemViewModel;
|
||||||
|
await UpdatePendingOperationStateAsync();
|
||||||
await TryPrepareComposeAsync(true);
|
await TryPrepareComposeAsync(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,6 +509,23 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
return true;
|
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)
|
private async Task TryPrepareComposeAsync(bool downloadIfNeeded)
|
||||||
{
|
{
|
||||||
if (CurrentMailDraftItem == null) return;
|
if (CurrentMailDraftItem == null) return;
|
||||||
@@ -674,11 +700,13 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
if (updatedMail.UniqueId == CurrentMailDraftItem.MailCopy.UniqueId)
|
if (updatedMail.UniqueId == CurrentMailDraftItem.MailCopy.UniqueId)
|
||||||
{
|
{
|
||||||
await ExecuteUIThread(() =>
|
await ExecuteUIThread(async () =>
|
||||||
{
|
{
|
||||||
CurrentMailDraftItem.UpdateFrom(updatedMail);
|
CurrentMailDraftItem.UpdateFrom(updatedMail);
|
||||||
DiscardCommand.NotifyCanExecuteChanged();
|
DiscardCommand.NotifyCanExecuteChanged();
|
||||||
SendCommand.NotifyCanExecuteChanged();
|
SendCommand.NotifyCanExecuteChanged();
|
||||||
|
|
||||||
|
await UpdatePendingOperationStateAsync();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,6 +190,14 @@
|
|||||||
CommandAlignment="Right"
|
CommandAlignment="Right"
|
||||||
IsDynamicOverflowEnabled="True"
|
IsDynamicOverflowEnabled="True"
|
||||||
OverflowButtonAlignment="Left">
|
OverflowButtonAlignment="Left">
|
||||||
|
<AppBarButton
|
||||||
|
MinWidth="40"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Visibility="{x:Bind ViewModel.IsDraftBusy, Mode=OneWay}">
|
||||||
|
<ProgressRing IsActive="True" />
|
||||||
|
</AppBarButton>
|
||||||
|
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
Width="Auto"
|
||||||
MinWidth="40"
|
MinWidth="40"
|
||||||
|
|||||||
Reference in New Issue
Block a user