UIMessage communication. Single instancing for server and re-connection mechanism on suspension.
This commit is contained in:
@@ -25,7 +25,6 @@ namespace Wino.Core.UWP
|
||||
services.AddTransient<IFileService, FileService>();
|
||||
services.AddTransient<IStoreRatingService, StoreRatingService>();
|
||||
services.AddTransient<IKeyPressService, KeyPressService>();
|
||||
services.AddTransient<IBackgroundSynchronizer, BackgroundSynchronizer>();
|
||||
services.AddTransient<INotificationBuilder, NotificationBuilder>();
|
||||
services.AddTransient<IClipboardService, ClipboardService>();
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using Windows.Storage;
|
||||
using Wino.Core;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Synchronizers;
|
||||
|
||||
namespace Wino.Services
|
||||
{
|
||||
public interface IBackgroundSynchronizer
|
||||
{
|
||||
Task RunBackgroundSynchronizationAsync(BackgroundSynchronizationReason reason);
|
||||
void CreateLock();
|
||||
void ReleaseLock();
|
||||
bool IsBackgroundSynchronizationLocked();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for handling background synchronization on timer and session connected events.
|
||||
/// </summary>
|
||||
public class BackgroundSynchronizer : IBackgroundSynchronizer
|
||||
{
|
||||
private const string BackgroundSynchronizationLock = nameof(BackgroundSynchronizationLock);
|
||||
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
|
||||
|
||||
public BackgroundSynchronizer(IAccountService accountService,
|
||||
IFolderService folderService,
|
||||
IWinoSynchronizerFactory winoSynchronizerFactory)
|
||||
{
|
||||
_accountService = accountService;
|
||||
_folderService = folderService;
|
||||
_winoSynchronizerFactory = winoSynchronizerFactory;
|
||||
}
|
||||
|
||||
public void CreateLock() => ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] = true;
|
||||
public void ReleaseLock() => ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] = false;
|
||||
|
||||
public bool IsBackgroundSynchronizationLocked()
|
||||
=> ApplicationData.Current.LocalSettings.Values.ContainsKey(BackgroundSynchronizationLock)
|
||||
&& ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] is bool boolValue && boolValue;
|
||||
|
||||
public async Task RunBackgroundSynchronizationAsync(BackgroundSynchronizationReason reason)
|
||||
{
|
||||
Log.Information($"{reason} background synchronization is kicked in.");
|
||||
|
||||
// This should never crash.
|
||||
// We might be in-process or out-of-process.
|
||||
|
||||
//if (IsBackgroundSynchronizationLocked())
|
||||
//{
|
||||
// Log.Warning("Background synchronization is locked. Hence another background synchronization is canceled.");
|
||||
// return;
|
||||
//}
|
||||
|
||||
try
|
||||
{
|
||||
CreateLock();
|
||||
|
||||
var accounts = await _accountService.GetAccountsAsync();
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
// We can't sync broken account.
|
||||
if (account.AttentionReason != AccountAttentionReason.None)
|
||||
continue;
|
||||
|
||||
// TODO
|
||||
// We can't synchronize without system folder setup is done.
|
||||
//var isSystemFolderSetupDone = await _folderService.CheckSystemFolderSetupDoneAsync(account.Id);
|
||||
|
||||
//// No need to throw here. It's a background process.
|
||||
//if (!isSystemFolderSetupDone)
|
||||
// continue;
|
||||
|
||||
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(account.Id);
|
||||
|
||||
if (synchronizer.State != AccountSynchronizerState.Idle)
|
||||
{
|
||||
Log.Information("Skipping background synchronization for {Name} since current state is {State}", synchronizer.Account.Name, synchronizer.State);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await HandleSynchronizationAsync(synchronizer, reason);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[BackgroundSynchronization] Failed with message {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleSynchronizationAsync(IBaseSynchronizer synchronizer, BackgroundSynchronizationReason reason)
|
||||
{
|
||||
if (synchronizer.State != AccountSynchronizerState.Idle) return;
|
||||
|
||||
var account = synchronizer.Account;
|
||||
|
||||
try
|
||||
{
|
||||
// SessionConnected will do Full synchronization for logon, Timer task will do Inbox only.
|
||||
|
||||
var syncType = reason == BackgroundSynchronizationReason.SessionConnected ? SynchronizationType.Full : SynchronizationType.Inbox;
|
||||
|
||||
var options = new SynchronizationOptions()
|
||||
{
|
||||
AccountId = account.Id,
|
||||
Type = syncType,
|
||||
};
|
||||
|
||||
await synchronizer.SynchronizeAsync(options);
|
||||
}
|
||||
catch (AuthenticationAttentionException authenticationAttentionException)
|
||||
{
|
||||
Log.Error(authenticationAttentionException, $"[BackgroundSync] Invalid credentials for account {account.Address}");
|
||||
|
||||
account.AttentionReason = AccountAttentionReason.InvalidCredentials;
|
||||
await _accountService.UpdateAccountAsync(account);
|
||||
}
|
||||
catch (SystemFolderConfigurationMissingException configMissingException)
|
||||
{
|
||||
Log.Error(configMissingException, $"[BackgroundSync] Missing system folder configuration for account {account.Address}");
|
||||
|
||||
account.AttentionReason = AccountAttentionReason.MissingSystemFolderConfiguration;
|
||||
await _accountService.UpdateAccountAsync(account);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "[BackgroundSync] Synchronization failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,36 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.AppService;
|
||||
using Windows.Foundation.Metadata;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Messaging;
|
||||
using Wino.Messaging.Enums;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Core.UWP.Services
|
||||
{
|
||||
public partial class WinoServerConnectionManager : ObservableObject, IWinoServerConnectionManager<AppServiceConnection>
|
||||
public class WinoServerConnectionManager : IWinoServerConnectionManager<AppServiceConnection>
|
||||
{
|
||||
private WinoServerConnectionStatus status;
|
||||
|
||||
public WinoServerConnectionStatus Status
|
||||
{
|
||||
get { return status; }
|
||||
private set
|
||||
{
|
||||
status = value;
|
||||
StatusChanged?.Invoke(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
private AppServiceConnection _connection;
|
||||
|
||||
public event EventHandler<WinoServerConnectionStatus> StatusChanged;
|
||||
|
||||
public AppServiceConnection Connection
|
||||
{
|
||||
get { return _connection; }
|
||||
@@ -40,9 +58,6 @@ namespace Wino.Core.UWP.Services
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private WinoServerConnectionStatus _status;
|
||||
|
||||
public async Task<bool> ConnectAsync()
|
||||
{
|
||||
if (Status == WinoServerConnectionStatus.Connected) return true;
|
||||
@@ -87,12 +102,98 @@ namespace Wino.Core.UWP.Services
|
||||
|
||||
private void ServerMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
|
||||
{
|
||||
// TODO: Handle server messsages.
|
||||
if (args.Request.Message.TryGetValue(MessageConstants.MessageTypeKey, out object messageTypeObject) && messageTypeObject is int messageTypeInt)
|
||||
{
|
||||
var messageType = (MessageType)messageTypeInt;
|
||||
|
||||
if (args.Request.Message.TryGetValue(MessageConstants.MessageDataKey, out object messageDataObject) && messageDataObject is string messageJson)
|
||||
{
|
||||
switch (messageType)
|
||||
{
|
||||
case MessageType.UIMessage:
|
||||
if (args.Request.Message.TryGetValue(MessageConstants.MessageDataTypeKey, out object dataTypeObject) && dataTypeObject is string dataTypeName)
|
||||
{
|
||||
HandleUIMessage(messageJson, dataTypeName);
|
||||
}
|
||||
else
|
||||
throw new ArgumentException("Message data type is missing.");
|
||||
|
||||
break;
|
||||
case MessageType.ServerAction:
|
||||
HandleServerAction(messageJson);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleServerAction(string messageJson)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks IServerMessage objects and delegate it to Messenger for UI to process.
|
||||
/// </summary>
|
||||
/// <param name="messageJson">Message data in json format.</param>
|
||||
private void HandleUIMessage(string messageJson, string typeName)
|
||||
{
|
||||
switch (typeName)
|
||||
{
|
||||
case nameof(MailAddedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailAddedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(MailDownloadedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailDownloadedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(MailRemovedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailRemovedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(MailUpdatedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailUpdatedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(AccountCreatedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountCreatedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(AccountRemovedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountRemovedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(AccountUpdatedMessage):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountUpdatedMessage>(messageJson));
|
||||
break;
|
||||
case nameof(DraftCreated):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftCreated>(messageJson));
|
||||
break;
|
||||
case nameof(DraftFailed):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftFailed>(messageJson));
|
||||
break;
|
||||
case nameof(DraftMapped):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftMapped>(messageJson));
|
||||
break;
|
||||
case nameof(FolderRenamed):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<FolderRenamed>(messageJson));
|
||||
break;
|
||||
case nameof(FolderSynchronizationEnabled):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<FolderSynchronizationEnabled>(messageJson));
|
||||
break;
|
||||
case nameof(MergedInboxRenamed):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MergedInboxRenamed>(messageJson));
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Invalid data type name passed to client.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ServerDisconnected(AppServiceConnection sender, AppServiceClosedEventArgs args)
|
||||
{
|
||||
// TODO: Handle server disconnection.
|
||||
}
|
||||
|
||||
public void DisposeConnection()
|
||||
{
|
||||
if (Connection == null) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,6 @@
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\AppInitializerService.cs" />
|
||||
<Compile Include="Services\WinoServerConnectionManager.cs" />
|
||||
<Compile Include="Services\BackgroundSynchronizer.cs" />
|
||||
<Compile Include="Services\BackgroundTaskService.cs" />
|
||||
<Compile Include="Services\ClipboardService.cs" />
|
||||
<Compile Include="Services\ConfigurationService.cs" />
|
||||
@@ -170,6 +169,10 @@
|
||||
<Project>{e6b1632a-8901-41e8-9ddf-6793c7698b0b}</Project>
|
||||
<Name>Wino.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj">
|
||||
<Project>{0c307d7e-256f-448c-8265-5622a812fbcc}</Project>
|
||||
<Name>Wino.Messaging</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<SDKReference Include="WindowsDesktop, Version=10.0.22621.0">
|
||||
|
||||
Reference in New Issue
Block a user