Delegating changes to UI and triggering new background synchronization.

This commit is contained in:
Burak Kaan Köse
2024-07-18 20:03:58 +02:00
parent 7ef045a0ad
commit 38f44a8b39
25 changed files with 331 additions and 104 deletions

View File

@@ -1,9 +1,15 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using H.NotifyIcon;
using Microsoft.Extensions.DependencyInjection;
using Windows.Storage;
using Wino.Core;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Services;
using Wino.Core.UWP.Services;
using Wino.Services;
namespace Wino.Server
{
@@ -16,6 +22,7 @@ namespace Wino.Server
/// </summary>
public partial class App : Application
{
private const string NotifyIconResourceKey = "NotifyIcon";
private const string WinoServerAppName = "Wino.Server";
private const string WinoServerActiatedName = "Wino.Server.Activated";
@@ -32,18 +39,42 @@ namespace Wino.Server
var services = new ServiceCollection();
services.AddTransient<ServerContext>();
services.AddTransient<TrayIconViewModel>();
services.AddTransient<ServerViewModel>();
services.RegisterCoreServices();
// Below services belongs to UWP.Core package and some APIs are not available for WPF.
// We register them here to avoid compilation errors.
services.AddSingleton<IConfigurationService, ConfigurationService>();
services.AddSingleton<INativeAppService, NativeAppService>();
services.AddSingleton<IPreferencesService, PreferencesService>();
return services.BuildServiceProvider();
}
private async Task<ServerViewModel> InitializeNewServerAsync()
{
// TODO: Error handling.
var databaseService = Services.GetService<IDatabaseService>();
var applicationFolderConfiguration = Services.GetService<IApplicationConfiguration>();
applicationFolderConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path;
applicationFolderConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path;
await databaseService.InitializeAsync();
var serverViewModel = Services.GetRequiredService<ServerViewModel>();
await serverViewModel.InitializeAsync();
return serverViewModel;
}
protected override async void OnStartup(StartupEventArgs e)
{
bool isCreatedNew;
_mutex = new Mutex(true, WinoServerAppName, out isCreatedNew);
_mutex = new Mutex(true, WinoServerAppName, out bool isCreatedNew);
_eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, WinoServerActiatedName);
if (isCreatedNew)
@@ -57,7 +88,7 @@ namespace Wino.Server
Current.Dispatcher.BeginInvoke(async () =>
{
if (notifyIcon.DataContext is TrayIconViewModel trayIconViewModel)
if (notifyIcon.DataContext is ServerViewModel trayIconViewModel)
{
await trayIconViewModel.ReconnectAsync();
}
@@ -73,18 +104,16 @@ namespace Wino.Server
base.OnStartup(e);
var serverViewModel = await InitializeNewServerAsync();
// Create taskbar icon for the new server.
notifyIcon = (TaskbarIcon)FindResource("NotifyIcon");
var viewModel = Services.GetRequiredService<TrayIconViewModel>();
await viewModel.Context.InitializeAsync();
notifyIcon.DataContext = viewModel;
notifyIcon = (TaskbarIcon)FindResource(NotifyIconResourceKey);
notifyIcon.DataContext = serverViewModel;
notifyIcon.ForceCreate(enablesEfficiencyMode: true);
}
else
{
// Notify other instance so it could bring itself to foreground.
// Notify other instance so it could reconnect to UWP app if needed.
_eventWaitHandle.Set();
// Terminate this instance.

View File

@@ -1,44 +1,84 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Windows.ApplicationModel;
using Windows.ApplicationModel.AppService;
using Windows.Foundation.Collections;
using Wino.Core.Authenticators;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Integration.Processors;
using Wino.Core.Services;
using Wino.Core.Synchronizers;
using Wino.Messaging;
using Wino.Messaging.Enums;
using Wino.Messaging.Server;
namespace Wino.Server
{
public class ServerContext : IInitializeAsync
public class ServerContext :
IRecipient<AccountCreatedMessage>,
IRecipient<AccountUpdatedMessage>,
IRecipient<AccountRemovedMessage>,
IRecipient<DraftCreated>,
IRecipient<DraftFailed>,
IRecipient<DraftMapped>,
IRecipient<FolderRenamed>,
IRecipient<FolderSynchronizationEnabled>,
IRecipient<MailAddedMessage>,
IRecipient<MailDownloadedMessage>,
IRecipient<MailRemovedMessage>,
IRecipient<MailUpdatedMessage>,
IRecipient<MergedInboxRenamed>
{
private static object connectionLock = new object();
private AppServiceConnection connection = null;
private readonly IDatabaseService _databaseService;
public ServerContext(IDatabaseService databaseService)
private readonly IDatabaseService _databaseService;
private readonly IApplicationConfiguration _applicationFolderConfiguration;
public ServerContext(IDatabaseService databaseService, IApplicationConfiguration applicationFolderConfiguration)
{
_databaseService = databaseService;
_applicationFolderConfiguration = applicationFolderConfiguration;
WeakReferenceMessenger.Default.RegisterAll(this);
}
private string GetAppPackagFamilyName()
{
// If running as a standalone app, Package will throw exception.
// Return hardcoded value for debugging purposes.
// Connection will not be available in this case.
#region Message Handlers
try
{
return Package.Current.Id.FamilyName;
}
catch (Exception)
{
return "Debug.Wino.Server.FamilyName";
}
}
public async void Receive(MailAddedMessage message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(AccountCreatedMessage message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(AccountUpdatedMessage message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(AccountRemovedMessage message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(DraftCreated message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(DraftFailed message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(DraftMapped message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(FolderRenamed message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(FolderSynchronizationEnabled message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(MailDownloadedMessage message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(MailRemovedMessage message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(MailUpdatedMessage message) => await SendMessageAsync(MessageType.UIMessage, message);
public async void Receive(MergedInboxRenamed message) => await SendMessageAsync(MessageType.UIMessage, message);
#endregion
/// <summary>
/// Open connection to UWP app service
/// </summary>
@@ -65,6 +105,51 @@ namespace Wino.Server
}
}
public async Task TestOutlookSynchronizer()
{
var accountService = App.Current.Services.GetService<IAccountService>();
var accs = await accountService.GetAccountsAsync();
var acc = accs.ElementAt(0);
var authenticator = App.Current.Services.GetService<OutlookAuthenticator>();
var processor = App.Current.Services.GetService<IOutlookChangeProcessor>();
var sync = new OutlookSynchronizer(acc, authenticator, processor);
var options = new SynchronizationOptions()
{
AccountId = acc.Id,
Type = Core.Domain.Enums.SynchronizationType.Full
};
var result = await sync.SynchronizeAsync(options);
}
/// <summary>
/// Disposes current connection to UWP app service.
/// </summary>
private void DisposeConnection()
{
lock (connectionLock)
{
if (connection == null) return;
connection.RequestReceived -= OnWinRTMessageReceived;
connection.ServiceClosed -= OnConnectionClosed;
connection.Dispose();
connection = null;
}
}
/// <summary>
/// Sends a serialized object to UWP application if connection exists with given type.
/// </summary>
/// <param name="messageType">Type of the message.</param>
/// <param name="message">IServerMessage object that will be serialized.</param>
/// <returns></returns>
/// <exception cref="ArgumentException">When the message is not IServerMessage.</exception>
private async Task SendMessageAsync(MessageType messageType, object message)
{
if (connection == null) return;
@@ -81,6 +166,7 @@ namespace Wino.Server
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
};
Debug.WriteLine($"S: {messageType} ({message.GetType().Name})");
await connection.SendMessageAsync(set);
}
@@ -98,26 +184,38 @@ namespace Wino.Server
private void OnWinRTMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
// TODO: Handle incoming messages from UWP/WINUI Application.
}
private void DisposeConnection()
#region Init
private string GetAppPackagFamilyName()
{
lock (connectionLock)
// If running as a standalone app, Package will throw exception.
// Return hardcoded value for debugging purposes.
// Connection will not be available in this case.
try
{
if (connection == null) return;
connection.RequestReceived -= OnWinRTMessageReceived;
connection.ServiceClosed -= OnConnectionClosed;
connection.Dispose();
connection = null;
return Package.Current.Id.FamilyName;
}
catch (Exception)
{
return "Debug.Wino.Server.FamilyName";
}
}
public async Task InitializeAsync()
{
await InitializeAppServiceConnectionAsync();
await _databaseService.InitializeAsync();
}
#endregion
}
}

View File

@@ -2,22 +2,23 @@
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Wino.Core.Domain.Interfaces;
namespace Wino.Server
{
public partial class TrayIconViewModel : ObservableObject
public partial class ServerViewModel : ObservableObject, IInitializeAsync
{
public ServerContext Context { get; }
public TrayIconViewModel(ServerContext serverContext)
public ServerViewModel(ServerContext serverContext)
{
Context = serverContext;
}
[RelayCommand]
public void LaunchWino()
public async Task LaunchWinoAsync()
{
await Context.TestOutlookSynchronizer();
// ServerContext.SendTestMessageAsync();
}
@@ -33,5 +34,7 @@ namespace Wino.Server
}
public async Task ReconnectAsync() => await Context.InitializeAppServiceConnectionAsync();
public Task InitializeAsync() => Context.InitializeAppServiceConnectionAsync();
}
}

View File

@@ -16,6 +16,12 @@
<ItemGroup>
<None Remove="Images\Wino_Icon.ico" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Wino.Core.UWP\Services\ConfigurationService.cs" Link="Services\ConfigurationService.cs" />
<Compile Include="..\Wino.Core.UWP\Services\NativeAppService.cs" Link="Services\NativeAppService.cs" />
<Compile Include="..\Wino.Core.UWP\Services\NotificationBuilder.cs" Link="Services\NotificationBuilder.cs" />
<Compile Include="..\Wino.Core.UWP\Services\PreferencesService.cs" Link="Services\PreferencesService.cs" />
</ItemGroup>
<ItemGroup>
<Resource Include="Images\Wino_Icon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
@@ -30,4 +36,7 @@
<ProjectReference Include="..\Wino.Core\Wino.Core.csproj" />
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services\" />
</ItemGroup>
</Project>