merge communication branch

This commit is contained in:
Burak Kaan Köse
2024-07-18 22:21:52 +02:00
183 changed files with 2283 additions and 700 deletions

View File

@@ -1,13 +1,4 @@
using System; using Windows.ApplicationModel.Background;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Windows.ApplicationModel.Background;
using Windows.Storage;
using Wino.Core;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Services;
using Wino.Core.UWP;
using Wino.Services;
namespace Wino.BackgroundTasks namespace Wino.BackgroundTasks
{ {
@@ -17,32 +8,34 @@ namespace Wino.BackgroundTasks
{ {
var def = taskInstance.GetDeferral(); var def = taskInstance.GetDeferral();
try //try
{ //{
var services = new ServiceCollection(); // var services = new ServiceCollection();
services.RegisterCoreServices(); // services.RegisterCoreServices();
services.RegisterCoreUWPServices(); // services.RegisterCoreUWPServices();
var providere = services.BuildServiceProvider(); // var providere = services.BuildServiceProvider();
var backgroundTaskService = providere.GetService<IBackgroundSynchronizer>(); // var backgroundTaskService = providere.GetService<IBackgroundSynchronizer>();
var dbService = providere.GetService<IDatabaseService>(); // var dbService = providere.GetService<IDatabaseService>();
var logInitializer = providere.GetService<ILogInitializer>(); // var logInitializer = providere.GetService<ILogInitializer>();
logInitializer.SetupLogger(ApplicationData.Current.LocalFolder.Path); // logInitializer.SetupLogger(ApplicationData.Current.LocalFolder.Path);
await dbService.InitializeAsync(); // await dbService.InitializeAsync();
await backgroundTaskService.RunBackgroundSynchronizationAsync(Core.Domain.Enums.BackgroundSynchronizationReason.SessionConnected); // await backgroundTaskService.RunBackgroundSynchronizationAsync(Core.Domain.Enums.BackgroundSynchronizationReason.SessionConnected);
} //}
catch (Exception ex) //catch (Exception ex)
{ //{
Log.Error(ex, "Background synchronization failed from background task."); // Log.Error(ex, "Background synchronization failed from background task.");
} //}
finally //finally
{ //{
def.Complete(); // def.Complete();
} //}
def.Complete();
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using SQLite; using SQLite;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.MailItem;
@@ -140,7 +141,7 @@ namespace Wino.Core.Domain.Entities
/// </summary> /// </summary>
[Ignore] [Ignore]
public MailAccount AssignedAccount { get; set; } public MailAccount AssignedAccount { get; set; }
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
public override string ToString() => $"{Subject} <-> {Id}"; public override string ToString() => $"{Subject} <-> {Id}";
} }
} }

View File

@@ -0,0 +1,11 @@
namespace Wino.Core.Domain.Enums
{
public enum WinoServerConnectionStatus
{
None,
Connecting,
Connected,
Disconnected,
Failed
}
}

View File

@@ -1,12 +0,0 @@
using System.Threading.Tasks;
namespace Wino.Core.Domain.Interfaces
{
public interface IAppInitializerService
{
string GetApplicationDataFolder();
string GetPublisherSharedFolder();
Task MigrateAsync();
}
}

View File

@@ -0,0 +1,21 @@
namespace Wino.Core.Domain.Interfaces
{
/// <summary>
/// Singleton object that holds the application data folder path and the publisher shared folder path.
/// Load the values before calling any service.
/// App data folder is used for storing files.
/// Pubhlisher cache folder is only used for database file so other apps can access it in the same package by same publisher.
/// </summary>
public interface IApplicationConfiguration
{
/// <summary>
/// Application data folder.
/// </summary>
string ApplicationDataFolderPath { get; set; }
/// <summary>
/// Publisher shared folder path.
/// </summary>
string PublisherSharedFolderPath { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
using System.Threading;
using System.Threading.Tasks;
using MailKit;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
namespace Wino.Core.Domain.Interfaces
{
public interface IBaseSynchronizer
{
/// <summary>
/// Account that is assigned for this synchronizer.
/// </summary>
MailAccount Account { get; }
/// <summary>
/// Synchronizer state.
/// </summary>
AccountSynchronizerState State { get; }
/// <summary>
/// Queues a single request to be executed in the next synchronization.
/// </summary>
/// <param name="request">Request to queue.</param>
void QueueRequest(IRequestBase request);
/// <summary>
/// TODO
/// </summary>
/// <returns>Whether active synchronization is stopped or not.</returns>
bool CancelActiveSynchronization();
/// <summary>
/// Performs a full synchronization with the server with given options.
/// This will also prepares batch requests for execution.
/// Requests are executed in the order they are queued and happens before the synchronization.
/// Result of the execution queue is processed during the synchronization.
/// </summary>
/// <param name="options">Options for synchronization.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Result summary of synchronization.</returns>
Task<SynchronizationResult> SynchronizeAsync(SynchronizationOptions options, CancellationToken cancellationToken = default);
/// <summary>
/// Downloads a single MIME message from the server and saves it to disk.
/// </summary>
/// <param name="mailItem">Mail item to download from server.</param>
/// <param name="transferProgress">Optional progress reporting for download operation.</param>
/// <param name="cancellationToken">Cancellation token.</param>
Task DownloadMissingMimeMessageAsync(IMailItem mailItem, ITransferProgress transferProgress, CancellationToken cancellationToken = default);
}
}

View File

@@ -11,6 +11,7 @@ namespace Wino.Core.Domain.Interfaces
Task<string> GetEditorBundlePathAsync(); Task<string> GetEditorBundlePathAsync();
Task LaunchFileAsync(string filePath); Task LaunchFileAsync(string filePath);
Task LaunchUriAsync(Uri uri); Task LaunchUriAsync(Uri uri);
bool IsAppRunning(); bool IsAppRunning();
string GetFullAppVersion(); string GetFullAppVersion();

View File

@@ -1,4 +1,4 @@
namespace Wino.Core.Domain.Models.Requests namespace Wino.Core.Domain.Interfaces
{ {
/// <summary> /// <summary>
/// Interface for all messages to report UI changes from synchronizers to UI. /// Interface for all messages to report UI changes from synchronizers to UI.
@@ -6,5 +6,6 @@
/// They are sent either from processor or view models to signal some other /// They are sent either from processor or view models to signal some other
/// parts of the application. /// parts of the application.
/// </summary> /// </summary>
public interface IUIMessage;
public interface IServerMessage;
} }

View File

@@ -0,0 +1,23 @@
using System;
using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Interfaces
{
public interface IWinoServerConnectionManager
{
Task<bool> ConnectAsync();
Task<bool> DisconnectAsync();
WinoServerConnectionStatus Status { get; }
event EventHandler<WinoServerConnectionStatus> StatusChanged;
void DisposeConnection();
void QueueRequest(IRequestBase request, Guid accountId);
}
public interface IWinoServerConnectionManager<TAppServiceConnection> : IWinoServerConnectionManager, IInitializeAsync
{
TAppServiceConnection Connection { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
using System;
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Interfaces
{
public interface IWinoSynchronizerFactory : IInitializeAsync
{
IBaseSynchronizer GetAccountSynchronizer(Guid accountId);
IBaseSynchronizer CreateNewSynchronizer(MailAccount account);
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// An interface that returns the UniqueId store for IMailItem.
/// For threads, it may be multiple items.
/// For single mails, it'll always be one item.
/// </summary>
public interface IMailHashContainer
{
IEnumerable<Guid> GetContainingIds();
}
}

View File

@@ -6,7 +6,7 @@ namespace Wino.Core.Domain.Models.MailItem
/// <summary> /// <summary>
/// Interface of simplest representation of a MailCopy. /// Interface of simplest representation of a MailCopy.
/// </summary> /// </summary>
public interface IMailItem public interface IMailItem : IMailHashContainer
{ {
Guid UniqueId { get; } Guid UniqueId { get; }
string Id { get; } string Id { get; }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
@@ -40,6 +41,8 @@ namespace Wino.Core.Domain.Models.MailItem
} }
} }
public IEnumerable<Guid> GetContainingIds() => ThreadItems?.Select(a => a.UniqueId) ?? default;
#region IMailItem #region IMailItem
public Guid UniqueId => LatestMailItem?.UniqueId ?? Guid.Empty; public Guid UniqueId => LatestMailItem?.UniqueId ?? Guid.Empty;

View File

@@ -1,8 +1,7 @@
using System; using System;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Requests namespace Wino.Core.Domain.Models.MailItem
{ {
/// <summary> /// <summary>
/// Defines a single rule for toggling user actions if needed. /// Defines a single rule for toggling user actions if needed.

View File

@@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace Wino.Core.Domain.Models.Reader
{
/// <summary>
/// Used to pass messages from the webview to the app.
/// </summary>
public class WebViewMessage
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
}
}

View File

@@ -1,9 +0,0 @@
namespace Wino.Core.Domain.Models.Requests
{
// Used to pass messages from the webview to the app.
public class WebViewMessage
{
public string type { get; set; }
public string value { get; set; }
}
}

View File

@@ -6,6 +6,10 @@
<LangVersion>12.0</LangVersion> <LangVersion>12.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Interfaces\IWinoSynchronizerFactory.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Translations\ca_ES\resources.json" /> <None Remove="Translations\ca_ES\resources.json" />
<None Remove="Translations\cs_CZ\resources.json" /> <None Remove="Translations\cs_CZ\resources.json" />
@@ -49,7 +53,9 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MimeKit" Version="4.4.0" /> <PackageReference Include="MimeKit" Version="4.7.1" />
<PackageReference Include="MailKit" Version="4.7.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" /> <PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
<PackageReference Include="System.Text.Json" Version="8.0.4" /> <PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup> </ItemGroup>
@@ -69,4 +75,7 @@
<LastGenOutput>Translator.Designer.cs</LastGenOutput> <LastGenOutput>Translator.Designer.cs</LastGenOutput>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Models\Communication\" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Windows.ApplicationModel.AppService;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP.Services; using Wino.Core.UWP.Services;
using Wino.Core.WinUI.Services; using Wino.Core.WinUI.Services;
@@ -10,18 +11,22 @@ namespace Wino.Core.UWP
{ {
public static void RegisterCoreUWPServices(this IServiceCollection services) public static void RegisterCoreUWPServices(this IServiceCollection services)
{ {
var serverConnectionManager = new WinoServerConnectionManager();
services.AddSingleton<IWinoServerConnectionManager>(serverConnectionManager);
services.AddSingleton<IWinoServerConnectionManager<AppServiceConnection>>(serverConnectionManager);
services.AddSingleton<IUnderlyingThemeService, UnderlyingThemeService>(); services.AddSingleton<IUnderlyingThemeService, UnderlyingThemeService>();
services.AddSingleton<INativeAppService, NativeAppService>(); services.AddSingleton<INativeAppService, NativeAppService>();
services.AddSingleton<IStoreManagementService, StoreManagementService>(); services.AddSingleton<IStoreManagementService, StoreManagementService>();
services.AddSingleton<IBackgroundTaskService, BackgroundTaskService>(); services.AddSingleton<IBackgroundTaskService, BackgroundTaskService>();
services.AddSingleton<IAppShellService, AppShellService>(); services.AddSingleton<IAppShellService, AppShellService>();
services.AddTransient<IAppInitializerService, AppInitializerService>();
services.AddTransient<IConfigurationService, ConfigurationService>(); services.AddTransient<IConfigurationService, ConfigurationService>();
services.AddTransient<IFileService, FileService>(); services.AddTransient<IFileService, FileService>();
services.AddTransient<IStoreRatingService, StoreRatingService>(); services.AddTransient<IStoreRatingService, StoreRatingService>();
services.AddTransient<IKeyPressService, KeyPressService>(); services.AddTransient<IKeyPressService, KeyPressService>();
services.AddTransient<IBackgroundSynchronizer, BackgroundSynchronizer>();
services.AddTransient<INotificationBuilder, NotificationBuilder>(); services.AddTransient<INotificationBuilder, NotificationBuilder>();
services.AddTransient<IClipboardService, ClipboardService>(); services.AddTransient<IClipboardService, ClipboardService>();
} }

View File

@@ -1,50 +0,0 @@
using System;
using System.Threading.Tasks;
using Windows.Storage;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services
{
public class AppInitializerService : IAppInitializerService
{
private readonly IBackgroundTaskService _backgroundTaskService;
public AppInitializerService(IBackgroundTaskService backgroundTaskService)
{
_backgroundTaskService = backgroundTaskService;
}
public string GetPublisherSharedFolder() => ApplicationData.Current.GetPublisherCacheFolder("WinoShared").Path;
public string GetApplicationDataFolder() => ApplicationData.Current.LocalFolder.Path;
public Task MigrateAsync()
{
UnregisterAllBackgroundTasks();
return Task.CompletedTask;
}
#region 1.6.8 -> 1.6.9
private void UnregisterAllBackgroundTasks()
{
_backgroundTaskService.UnregisterAllBackgroundTask();
}
#endregion
#region 1.7.0
/// <summary>
/// We decided to use publisher cache folder as a database going forward.
/// This migration will move the file from application local folder and delete it.
/// Going forward database will be initialized from publisher cache folder.
/// </summary>
private async Task MoveExistingDatabaseToSharedCacheFolderAsync()
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -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.");
}
}
}
}

View File

@@ -106,7 +106,15 @@ namespace Wino.Services
return _editorBundlePath; return _editorBundlePath;
} }
public bool IsAppRunning() => (Window.Current?.Content as Frame)?.Content != null; public bool IsAppRunning()
{
#if WINDOWS_UWP
return (Window.Current?.Content as Frame)?.Content != null;
#endif
return true;
}
public async Task LaunchFileAsync(string filePath) public async Task LaunchFileAsync(string filePath)
{ {

View File

@@ -7,7 +7,7 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Reader; using Wino.Core.Domain.Models.Reader;
using Wino.Core.Services; using Wino.Core.Services;
namespace Wino.Services namespace Wino.Core.UWP.Services
{ {
public class PreferencesService : ObservableObject, IPreferencesService public class PreferencesService : ObservableObject, IPreferencesService
{ {

View File

@@ -2,7 +2,6 @@
using System.ComponentModel; using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Microsoft.AppCenter.Crashes;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Messages.Shell; using Wino.Core.Messages.Shell;
@@ -116,17 +115,10 @@ namespace Wino.Services
private void UpdateAppCoreWindowTitle() private void UpdateAppCoreWindowTitle()
{ {
try var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
{
var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
if (appView != null) if (appView != null)
appView.Title = CoreWindowTitle; appView.Title = CoreWindowTitle;
}
catch (System.Exception ex)
{
Crashes.TrackError(ex);
}
} }
} }
} }

View File

@@ -182,6 +182,7 @@ namespace Wino.Services
await ApplyCustomThemeAsync(true); await ApplyCustomThemeAsync(true);
// Registering to color changes, thus we notice when user changes theme system wide // Registering to color changes, thus we notice when user changes theme system wide
uiSettings.ColorValuesChanged -= UISettingsColorChanged;
uiSettings.ColorValuesChanged += UISettingsColorChanged; uiSettings.ColorValuesChanged += UISettingsColorChanged;
} }

View File

@@ -0,0 +1,207 @@
using System;
using System.Diagnostics;
using System.Text.Json;
using System.Threading.Tasks;
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 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; }
set
{
if (_connection != null)
{
_connection.RequestReceived -= ServerMessageReceived;
_connection.ServiceClosed -= ServerDisconnected;
}
_connection = value;
if (value == null)
{
Status = WinoServerConnectionStatus.Disconnected;
}
else
{
value.RequestReceived += ServerMessageReceived;
value.ServiceClosed += ServerDisconnected;
Status = WinoServerConnectionStatus.Connected;
}
}
}
public async Task<bool> ConnectAsync()
{
if (Status == WinoServerConnectionStatus.Connected) return true;
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
try
{
Status = WinoServerConnectionStatus.Connecting;
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
// If the server connection is success, Status will be updated to Connected by BackgroundActivationHandlerEx.
}
catch (Exception)
{
Status = WinoServerConnectionStatus.Failed;
return false;
}
return true;
}
return false;
}
public async Task<bool> DisconnectAsync()
{
if (Connection == null || Status == WinoServerConnectionStatus.Disconnected) return true;
// TODO: Send disconnect message to the fulltrust process.
return true;
}
public async Task InitializeAsync()
{
var isConnectionSuccessfull = await ConnectAsync();
// TODO: Log connection status
}
private void ServerMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
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)
{
Debug.WriteLine($"C: UImessage ({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;
}
public void QueueRequest(IRequestBase request, Guid accountId)
{
// TODO: Queue this request to corresponding account's synchronizer request queue.
}
}
}

View File

@@ -118,6 +118,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle> <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<LangVersion>12.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CoreUWPContainerSetup.cs" /> <Compile Include="CoreUWPContainerSetup.cs" />
@@ -127,9 +128,9 @@
<Compile Include="Models\Personalization\PreDefinedAppTheme.cs" /> <Compile Include="Models\Personalization\PreDefinedAppTheme.cs" />
<Compile Include="Models\Personalization\SystemAppTheme.cs" /> <Compile Include="Models\Personalization\SystemAppTheme.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\AppInitializerService.cs" /> <Compile Include="Services\PreferencesService.cs" />
<Compile Include="Services\AppShellService.cs" /> <Compile Include="Services\StatePersistenceService.cs" />
<Compile Include="Services\BackgroundSynchronizer.cs" /> <Compile Include="Services\WinoServerConnectionManager.cs" />
<Compile Include="Services\BackgroundTaskService.cs" /> <Compile Include="Services\BackgroundTaskService.cs" />
<Compile Include="Services\ClipboardService.cs" /> <Compile Include="Services\ClipboardService.cs" />
<Compile Include="Services\ConfigurationService.cs" /> <Compile Include="Services\ConfigurationService.cs" />
@@ -169,8 +170,16 @@
<Project>{e6b1632a-8901-41e8-9ddf-6793c7698b0b}</Project> <Project>{e6b1632a-8901-41e8-9ddf-6793c7698b0b}</Project>
<Name>Wino.Core</Name> <Name>Wino.Core</Name>
</ProjectReference> </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">
<Name>Windows Desktop Extensions for the UWP</Name>
</SDKReference>
</ItemGroup> </ItemGroup>
<ItemGroup />
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' "> <PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion> <VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup> </PropertyGroup>

View File

@@ -29,10 +29,17 @@ namespace Wino.Core.Authenticators
{ {
var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri(); var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri();
_publicClientApplication = PublicClientApplicationBuilder.Create(ClientId) var outlookAppBuilder = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(Authority) .WithAuthority(Authority);
.WithRedirectUri(authenticationRedirectUri)
.Build(); #if WINDOWS_UWP
outlookAppBuilder.WithRedirectUri(authenticationRedirectUri);
#else
outlookAppBuilder.WithDefaultRedirectUri();
#endif
_publicClientApplication = outlookAppBuilder.Build();
} }
#pragma warning disable S1133 // Deprecated code should be removed #pragma warning disable S1133 // Deprecated code should be removed

View File

@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Serilog.Core; using Serilog.Core;
using Wino.Core.Authenticators;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Integration.Processors; using Wino.Core.Integration.Processors;
using Wino.Core.Integration.Threading; using Wino.Core.Integration.Threading;
@@ -16,9 +17,9 @@ namespace Wino.Core
services.AddSingleton(loggerLevelSwitcher); services.AddSingleton(loggerLevelSwitcher);
services.AddSingleton<ILogInitializer, LogInitializer>(); services.AddSingleton<ILogInitializer, LogInitializer>();
services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>();
services.AddSingleton<ITranslationService, TranslationService>(); services.AddSingleton<ITranslationService, TranslationService>();
services.AddSingleton<IDatabaseService, DatabaseService>(); services.AddSingleton<IDatabaseService, DatabaseService>();
services.AddSingleton<IWinoSynchronizerFactory, WinoSynchronizerFactory>();
services.AddSingleton<IThreadingStrategyProvider, ThreadingStrategyProvider>(); services.AddSingleton<IThreadingStrategyProvider, ThreadingStrategyProvider>();
services.AddSingleton<IMimeFileService, MimeFileService>(); services.AddSingleton<IMimeFileService, MimeFileService>();
@@ -42,6 +43,10 @@ namespace Wino.Core
services.AddTransient<IFontService, FontService>(); services.AddTransient<IFontService, FontService>();
services.AddTransient<IUnsubscriptionService, UnsubscriptionService>(); services.AddTransient<IUnsubscriptionService, UnsubscriptionService>();
services.AddTransient<OutlookAuthenticator>();
services.AddTransient<GmailAuthenticator>();
services.AddTransient<CustomAuthenticator>();
services.AddTransient<OutlookThreadingStrategy>(); services.AddTransient<OutlookThreadingStrategy>();
services.AddTransient<GmailThreadingStrategy>(); services.AddTransient<GmailThreadingStrategy>();
services.AddTransient<ImapThreadStrategy>(); services.AddTransient<ImapThreadStrategy>();

View File

@@ -1,5 +1,5 @@
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Synchronizers; using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Messages.Synchronization namespace Wino.Core.Messages.Synchronization
{ {

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
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.Requests; using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests namespace Wino.Core.Requests
{ {

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
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.Requests; using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests namespace Wino.Core.Requests
{ {

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Requests; using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests namespace Wino.Core.Requests
{ {

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
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.Requests; using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests namespace Wino.Core.Requests
{ {

View File

@@ -5,6 +5,7 @@ using Wino.Core.Domain.Entities;
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.Requests; using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests namespace Wino.Core.Requests
{ {

View File

@@ -5,6 +5,7 @@ using Wino.Core.Domain.Entities;
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.Requests; using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests namespace Wino.Core.Requests
{ {

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
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.Requests; using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests namespace Wino.Core.Requests
{ {

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
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.Requests; using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests namespace Wino.Core.Requests
{ {

View File

@@ -2,6 +2,7 @@
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Requests; using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests namespace Wino.Core.Requests
{ {

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Requests; using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests namespace Wino.Core.Requests
{ {

View File

@@ -1,25 +0,0 @@
using System;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.Requests;
namespace Wino.Core.Requests
{
public record MailAddedMessage(MailCopy AddedMail) : IUIMessage;
public record MailRemovedMessage(MailCopy RemovedMail) : IUIMessage;
public record MailUpdatedMessage(MailCopy UpdatedMail) : IUIMessage;
public record MailDownloadedMessage(MailCopy DownloadedMail) : IUIMessage;
public record AccountCreatedMessage(MailAccount Account) : IUIMessage;
public record AccountRemovedMessage(MailAccount Account) : IUIMessage;
public record AccountUpdatedMessage(MailAccount Account) : IUIMessage;
public record DraftCreated(MailCopy DraftMail, MailAccount Account) : IUIMessage;
public record DraftFailed(MailCopy DraftMail, MailAccount Account) : IUIMessage;
public record DraftMapped(string LocalDraftCopyId, string RemoteDraftCopyId) : IUIMessage;
public record MergedInboxRenamed(Guid MergedInboxId, string NewName) : IUIMessage;
public record FolderRenamed(IMailItemFolder MailItemFolder) : IUIMessage;
public record FolderSynchronizationEnabled(IMailItemFolder MailItemFolder) : IUIMessage;
}

View File

@@ -12,7 +12,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Extensions; using Wino.Core.Extensions;
using Wino.Core.Messages.Accounts; using Wino.Core.Messages.Accounts;
using Wino.Core.Requests; using Wino.Messaging.Server;
namespace Wino.Core.Services namespace Wino.Core.Services
{ {

View File

@@ -0,0 +1,13 @@
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Services
{
public class ApplicationConfiguration : IApplicationConfiguration
{
public const string SharedFolderName = "WinoShared";
public string ApplicationDataFolderPath { get; set; }
public string PublisherSharedFolderPath { get; set; }
}
}

View File

@@ -1,6 +1,6 @@
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using SQLite; using SQLite;
using Wino.Core.Domain.Models.Requests; using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Services namespace Wino.Core.Services
{ {
@@ -16,7 +16,7 @@ namespace Wino.Core.Services
_databaseService = databaseService; _databaseService = databaseService;
} }
public void ReportUIChange<TMessage>(TMessage message) where TMessage : class, IUIMessage public void ReportUIChange<TMessage>(TMessage message) where TMessage : class, IServerMessage
=> Messenger.Send(message); => Messenger.Send(message);
} }
} }

View File

@@ -14,16 +14,16 @@ namespace Wino.Core.Services
public class DatabaseService : IDatabaseService public class DatabaseService : IDatabaseService
{ {
private string DatabaseName => "Wino172.db"; private const string DatabaseName = "Wino172.db";
private bool _isInitialized = false; private bool _isInitialized = false;
private readonly IAppInitializerService _appInitializerService; private readonly IApplicationConfiguration _folderConfiguration;
public SQLiteAsyncConnection Connection { get; private set; } public SQLiteAsyncConnection Connection { get; private set; }
public DatabaseService(IAppInitializerService appInitializerService) public DatabaseService(IApplicationConfiguration folderConfiguration)
{ {
_appInitializerService = appInitializerService; _folderConfiguration = folderConfiguration;
} }
public async Task InitializeAsync() public async Task InitializeAsync()
@@ -31,8 +31,8 @@ namespace Wino.Core.Services
if (_isInitialized) if (_isInitialized)
return; return;
var applicationData = _appInitializerService.GetPublisherSharedFolder(); var publisherCacheFolder = _folderConfiguration.PublisherSharedFolderPath;
var databaseFileName = Path.Combine(applicationData, DatabaseName); var databaseFileName = Path.Combine(publisherCacheFolder, DatabaseName);
Connection = new SQLiteAsyncConnection(databaseFileName) Connection = new SQLiteAsyncConnection(databaseFileName)
{ {
@@ -45,7 +45,6 @@ namespace Wino.Core.Services
}) })
}; };
await CreateTablesAsync(); await CreateTablesAsync();
_isInitialized = true; _isInitialized = true;

View File

@@ -16,7 +16,7 @@ using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Extensions; using Wino.Core.Extensions;
using Wino.Core.MenuItems; using Wino.Core.MenuItems;
using Wino.Core.Requests; using Wino.Messaging.Server;
namespace Wino.Core.Services namespace Wino.Core.Services
{ {

View File

@@ -11,11 +11,11 @@ namespace Wino.Core.Services
public const string ProtocolLogFileName = "ImapProtocolLog.log"; public const string ProtocolLogFileName = "ImapProtocolLog.log";
private readonly IPreferencesService _preferencesService; private readonly IPreferencesService _preferencesService;
private readonly IAppInitializerService _appInitializerService; private readonly IApplicationConfiguration _appInitializerService;
private Stream _protocolLogStream; private Stream _protocolLogStream;
public ImapTestService(IPreferencesService preferencesService, IAppInitializerService appInitializerService) public ImapTestService(IPreferencesService preferencesService, IApplicationConfiguration appInitializerService)
{ {
_preferencesService = preferencesService; _preferencesService = preferencesService;
_appInitializerService = appInitializerService; _appInitializerService = appInitializerService;
@@ -24,7 +24,7 @@ namespace Wino.Core.Services
private void EnsureProtocolLogFileExists() private void EnsureProtocolLogFileExists()
{ {
// Create new file for protocol logger. // Create new file for protocol logger.
var localAppFolderPath = _appInitializerService.GetApplicationDataFolder(); var localAppFolderPath = _appInitializerService.ApplicationDataFolderPath;
var logFile = Path.Combine(localAppFolderPath, ProtocolLogFileName); var logFile = Path.Combine(localAppFolderPath, ProtocolLogFileName);

View File

@@ -14,7 +14,7 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Comparers; using Wino.Core.Domain.Models.Comparers;
using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Extensions; using Wino.Core.Extensions;
using Wino.Core.Requests; using Wino.Messaging.Server;
namespace Wino.Core.Services namespace Wino.Core.Services
{ {

View File

@@ -19,18 +19,18 @@ namespace Wino.Core.Services
public class WinoRequestDelegator : IWinoRequestDelegator public class WinoRequestDelegator : IWinoRequestDelegator
{ {
private readonly IWinoRequestProcessor _winoRequestProcessor; private readonly IWinoRequestProcessor _winoRequestProcessor;
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory; private readonly IWinoServerConnectionManager _winoServerConnectionManager;
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly ILogger _logger = Log.ForContext<WinoRequestDelegator>(); private readonly ILogger _logger = Log.ForContext<WinoRequestDelegator>();
public WinoRequestDelegator(IWinoRequestProcessor winoRequestProcessor, public WinoRequestDelegator(IWinoRequestProcessor winoRequestProcessor,
IWinoSynchronizerFactory winoSynchronizerFactory, IWinoServerConnectionManager winoServerConnectionManager,
IFolderService folderService, IFolderService folderService,
IDialogService dialogService) IDialogService dialogService)
{ {
_winoRequestProcessor = winoRequestProcessor; _winoRequestProcessor = winoRequestProcessor;
_winoSynchronizerFactory = winoSynchronizerFactory; _winoServerConnectionManager = winoServerConnectionManager;
_folderService = folderService; _folderService = folderService;
_dialogService = dialogService; _dialogService = dialogService;
} }
@@ -132,19 +132,7 @@ namespace Wino.Core.Services
} }
private void QueueRequest(IRequestBase request, Guid accountId) private void QueueRequest(IRequestBase request, Guid accountId)
{ => _winoServerConnectionManager.QueueRequest(request, accountId);
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(accountId);
if (synchronizer == null)
{
_logger.Warning("Synchronizer not found for account {AccountId}.", accountId);
_logger.Warning("Skipping queueing request {Operation}.", request.Operation);
return;
}
synchronizer.QueueRequest(request);
}
private void QueueSynchronization(Guid accountId) private void QueueSynchronization(Guid accountId)
{ {

View File

@@ -9,7 +9,6 @@ using Wino.Core.Domain.Exceptions;
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.MailItem; using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Requests;
using Wino.Core.Requests; using Wino.Core.Requests;
namespace Wino.Core.Services namespace Wino.Core.Services

View File

@@ -21,50 +21,6 @@ using Wino.Core.Requests;
namespace Wino.Core.Synchronizers namespace Wino.Core.Synchronizers
{ {
public interface IBaseSynchronizer
{
/// <summary>
/// Account that is assigned for this synchronizer.
/// </summary>
MailAccount Account { get; }
/// <summary>
/// Synchronizer state.
/// </summary>
AccountSynchronizerState State { get; }
/// <summary>
/// Queues a single request to be executed in the next synchronization.
/// </summary>
/// <param name="request">Request to queue.</param>
void QueueRequest(IRequestBase request);
/// <summary>
/// TODO
/// </summary>
/// <returns>Whether active synchronization is stopped or not.</returns>
bool CancelActiveSynchronization();
/// <summary>
/// Performs a full synchronization with the server with given options.
/// This will also prepares batch requests for execution.
/// Requests are executed in the order they are queued and happens before the synchronization.
/// Result of the execution queue is processed during the synchronization.
/// </summary>
/// <param name="options">Options for synchronization.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Result summary of synchronization.</returns>
Task<SynchronizationResult> SynchronizeAsync(SynchronizationOptions options, CancellationToken cancellationToken = default);
/// <summary>
/// Downloads a single MIME message from the server and saves it to disk.
/// </summary>
/// <param name="mailItem">Mail item to download from server.</param>
/// <param name="transferProgress">Optional progress reporting for download operation.</param>
/// <param name="cancellationToken">Cancellation token.</param>
Task DownloadMissingMimeMessageAsync(IMailItem mailItem, ITransferProgress transferProgress, CancellationToken cancellationToken = default);
}
public abstract class BaseSynchronizer<TBaseRequest, TMessageType> : BaseMailIntegrator<TBaseRequest>, IBaseSynchronizer public abstract class BaseSynchronizer<TBaseRequest, TMessageType> : BaseMailIntegrator<TBaseRequest>, IBaseSynchronizer
{ {
private SemaphoreSlim synchronizationSemaphore = new(1); private SemaphoreSlim synchronizationSemaphore = new(1);

View File

@@ -7,6 +7,10 @@
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="WinoSynchronizerFactory.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Diagnostics" Version="8.2.2" /> <PackageReference Include="CommunityToolkit.Diagnostics" Version="8.2.2" />
@@ -18,11 +22,11 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MailKit" Version="4.6.0" /> <PackageReference Include="MailKit" Version="4.7.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Graph" Version="5.55.0" /> <PackageReference Include="Microsoft.Graph" Version="5.55.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.47.2" /> <PackageReference Include="Microsoft.Identity.Client" Version="4.47.2" />
<PackageReference Include="MimeKit" Version="4.6.0" /> <PackageReference Include="MimeKit" Version="4.7.1" />
<PackageReference Include="morelinq" Version="4.1.0" /> <PackageReference Include="morelinq" Version="4.1.0" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" /> <PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="Serilog" Version="3.1.1" /> <PackageReference Include="Serilog" Version="3.1.1" />
@@ -35,5 +39,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" /> <ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -10,13 +10,6 @@ using Wino.Core.Synchronizers;
namespace Wino.Core namespace Wino.Core
{ {
public interface IWinoSynchronizerFactory : IInitializeAsync
{
IBaseSynchronizer GetAccountSynchronizer(Guid accountId);
IBaseSynchronizer CreateNewSynchronizer(MailAccount account);
void DeleteSynchronizer(MailAccount account);
}
/// <summary> /// <summary>
/// Factory that keeps track of all integrator with associated mail accounts. /// Factory that keeps track of all integrator with associated mail accounts.
/// Synchronizer per-account makes sense because re-generating synchronizers are not ideal. /// Synchronizer per-account makes sense because re-generating synchronizers are not ideal.
@@ -82,7 +75,6 @@ namespace Wino.Core
return new OutlookSynchronizer(mailAccount, outlookAuthenticator, _outlookChangeProcessor); return new OutlookSynchronizer(mailAccount, outlookAuthenticator, _outlookChangeProcessor);
case Domain.Enums.MailProviderType.Gmail: case Domain.Enums.MailProviderType.Gmail:
var gmailAuthenticator = new GmailAuthenticator(_tokenService, _nativeAppService); var gmailAuthenticator = new GmailAuthenticator(_tokenService, _nativeAppService);
return new GmailSynchronizer(mailAccount, gmailAuthenticator, _gmailChangeProcessor); return new GmailSynchronizer(mailAccount, gmailAuthenticator, _gmailChangeProcessor);
case Domain.Enums.MailProviderType.Office365: case Domain.Enums.MailProviderType.Office365:
break; break;

View File

@@ -12,7 +12,7 @@ namespace Wino.Mail.ViewModels
{ {
private readonly IStoreRatingService _storeRatingService; private readonly IStoreRatingService _storeRatingService;
private readonly INativeAppService _nativeAppService; private readonly INativeAppService _nativeAppService;
private readonly IAppInitializerService _appInitializerService; private readonly IApplicationConfiguration _appInitializerService;
private readonly IFileService _fileService; private readonly IFileService _fileService;
private readonly ILogInitializer _logInitializer; private readonly ILogInitializer _logInitializer;
@@ -31,7 +31,7 @@ namespace Wino.Mail.ViewModels
IDialogService dialogService, IDialogService dialogService,
INativeAppService nativeAppService, INativeAppService nativeAppService,
IPreferencesService preferencesService, IPreferencesService preferencesService,
IAppInitializerService appInitializerService, IApplicationConfiguration appInitializerService,
IFileService fileService, IFileService fileService,
ILogInitializer logInitializer) : base(dialogService) ILogInitializer logInitializer) : base(dialogService)
{ {
@@ -77,7 +77,7 @@ namespace Wino.Mail.ViewModels
private async Task SaveLogInternalAsync(string sourceFileName) private async Task SaveLogInternalAsync(string sourceFileName)
{ {
var appDataFolder = _appInitializerService.GetApplicationDataFolder(); var appDataFolder = _appInitializerService.ApplicationDataFolderPath;
var logFile = Path.Combine(appDataFolder, sourceFileName); var logFile = Path.Combine(appDataFolder, sourceFileName);

View File

@@ -5,7 +5,6 @@ using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Wino.Core;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
@@ -13,13 +12,12 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Messages.Navigation; using Wino.Core.Messages.Navigation;
using Wino.Core.Requests; using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels namespace Wino.Mail.ViewModels
{ {
public partial class AccountDetailsPageViewModel : BaseViewModel public partial class AccountDetailsPageViewModel : BaseViewModel
{ {
private readonly IWinoSynchronizerFactory _synchronizerFactory;
private readonly IAccountService _accountService; private readonly IAccountService _accountService;
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
@@ -45,11 +43,9 @@ namespace Wino.Mail.ViewModels
public AccountDetailsPageViewModel(IDialogService dialogService, public AccountDetailsPageViewModel(IDialogService dialogService,
IWinoSynchronizerFactory synchronizerFactory,
IAccountService accountService, IAccountService accountService,
IFolderService folderService) : base(dialogService) IFolderService folderService) : base(dialogService)
{ {
_synchronizerFactory = synchronizerFactory;
_accountService = accountService; _accountService = accountService;
_folderService = folderService; _folderService = folderService;
} }
@@ -99,10 +95,7 @@ namespace Wino.Mail.ViewModels
await _accountService.DeleteAccountAsync(Account); await _accountService.DeleteAccountAsync(Account);
_synchronizerFactory.DeleteSynchronizer(Account); // TODO: Server: Cancel ongoing calls from server for this account.
// TODO: Clear existing requests.
// _synchronizationWorker.ClearRequests(Account.Id);
DialogService.InfoBarMessage(Translator.Info_AccountDeletedTitle, string.Format(Translator.Info_AccountDeletedMessage, Account.Name), InfoBarMessageType.Success); DialogService.InfoBarMessage(Translator.Info_AccountDeletedTitle, string.Format(Translator.Info_AccountDeletedMessage, Account.Name), InfoBarMessageType.Success);

View File

@@ -16,11 +16,10 @@ using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Store; using Wino.Core.Domain.Models.Store;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Messages.Authorization; using Wino.Core.Messages.Authorization;
using Wino.Core.Messages.Navigation; using Wino.Core.Messages.Navigation;
using Wino.Core.Requests;
using Wino.Mail.ViewModels.Data; using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels namespace Wino.Mail.ViewModels
{ {
@@ -35,7 +34,6 @@ namespace Wino.Mail.ViewModels
private readonly IStoreManagementService _storeManagementService; private readonly IStoreManagementService _storeManagementService;
private readonly IPreferencesService _preferencesService; private readonly IPreferencesService _preferencesService;
private readonly IAuthenticationProvider _authenticationProvider; private readonly IAuthenticationProvider _authenticationProvider;
private readonly IWinoSynchronizerFactory _synchronizerFactory;
public ObservableCollection<IAccountProviderDetailViewModel> Accounts { get; set; } = []; public ObservableCollection<IAccountProviderDetailViewModel> Accounts { get; set; } = [];
@@ -60,7 +58,6 @@ namespace Wino.Mail.ViewModels
public AccountManagementViewModel(IDialogService dialogService, public AccountManagementViewModel(IDialogService dialogService,
IWinoNavigationService navigationService, IWinoNavigationService navigationService,
IWinoSynchronizerFactory synchronizerFactory,
IAccountService accountService, IAccountService accountService,
IProviderService providerService, IProviderService providerService,
IFolderService folderService, IFolderService folderService,
@@ -69,7 +66,6 @@ namespace Wino.Mail.ViewModels
IAuthenticationProvider authenticationProvider) : base(dialogService) IAuthenticationProvider authenticationProvider) : base(dialogService)
{ {
_accountService = accountService; _accountService = accountService;
_synchronizerFactory = synchronizerFactory;
_dialogService = dialogService; _dialogService = dialogService;
_providerService = providerService; _providerService = providerService;
_folderService = folderService; _folderService = folderService;
@@ -205,29 +201,31 @@ namespace Wino.Mail.ViewModels
// Local account has been created. // Local account has been created.
// Create new synchronizer and start synchronization. // Create new synchronizer and start synchronization.
var synchronizer = _synchronizerFactory.CreateNewSynchronizer(createdAccount); // TODO: Server: Make sure that server synchronizes folders and sends back the result.
if (creationDialog is ICustomServerAccountCreationDialog customServerAccountCreationDialog) //var synchronizer = _synchronizerFactory.CreateNewSynchronizer(createdAccount);
customServerAccountCreationDialog.ShowPreparingFolders();
else
creationDialog.State = AccountCreationDialogState.PreparingFolders;
var options = new SynchronizationOptions() //if (creationDialog is ICustomServerAccountCreationDialog customServerAccountCreationDialog)
{ // customServerAccountCreationDialog.ShowPreparingFolders();
AccountId = createdAccount.Id, //else
Type = SynchronizationType.FoldersOnly // creationDialog.State = AccountCreationDialogState.PreparingFolders;
};
var synchronizationResult = await synchronizer.SynchronizeAsync(options); //var options = new SynchronizationOptions()
//{
// AccountId = createdAccount.Id,
// Type = SynchronizationType.FoldersOnly
//};
if (synchronizationResult.CompletedState != SynchronizationCompletedState.Success) //var synchronizationResult = await synchronizer.SynchronizeAsync(options);
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
// Check if Inbox folder is available for the account after synchronization. //if (synchronizationResult.CompletedState != SynchronizationCompletedState.Success)
var isInboxAvailable = await _folderService.IsInboxAvailableForAccountAsync(createdAccount.Id); // throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
if (!isInboxAvailable) //// Check if Inbox folder is available for the account after synchronization.
throw new Exception(Translator.Exception_InboxNotAvailable); //var isInboxAvailable = await _folderService.IsInboxAvailableForAccountAsync(createdAccount.Id);
//if (!isInboxAvailable)
// throw new Exception(Translator.Exception_InboxNotAvailable);
// Send changes to listeners. // Send changes to listeners.
ReportUIChange(new AccountCreatedMessage(createdAccount)); ReportUIChange(new AccountCreatedMessage(createdAccount));

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Microsoft.AppCenter.Crashes; using Microsoft.AppCenter.Crashes;
using MoreLinq; using MoreLinq;
@@ -25,8 +26,8 @@ using Wino.Core.Messages.Mails;
using Wino.Core.Messages.Navigation; using Wino.Core.Messages.Navigation;
using Wino.Core.Messages.Shell; using Wino.Core.Messages.Shell;
using Wino.Core.Messages.Synchronization; using Wino.Core.Messages.Synchronization;
using Wino.Core.Requests;
using Wino.Core.Services; using Wino.Core.Services;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels namespace Wino.Mail.ViewModels
{ {
@@ -62,6 +63,7 @@ namespace Wino.Mail.ViewModels
#endregion #endregion
public IStatePersistanceService StatePersistenceService { get; } public IStatePersistanceService StatePersistenceService { get; }
public IWinoServerConnectionManager ServerConnectionManager { get; }
public IPreferencesService PreferencesService { get; } public IPreferencesService PreferencesService { get; }
public IWinoNavigationService NavigationService { get; } public IWinoNavigationService NavigationService { get; }
@@ -73,7 +75,6 @@ namespace Wino.Mail.ViewModels
private readonly INotificationBuilder _notificationBuilder; private readonly INotificationBuilder _notificationBuilder;
private readonly IWinoRequestDelegator _winoRequestDelegator; private readonly IWinoRequestDelegator _winoRequestDelegator;
private readonly IWinoSynchronizerFactory _synchronizerFactory;
private readonly IBackgroundTaskService _backgroundTaskService; private readonly IBackgroundTaskService _backgroundTaskService;
private readonly IMimeFileService _mimeFileService; private readonly IMimeFileService _mimeFileService;
@@ -82,9 +83,11 @@ namespace Wino.Mail.ViewModels
private readonly SemaphoreSlim accountInitFolderUpdateSlim = new SemaphoreSlim(1); private readonly SemaphoreSlim accountInitFolderUpdateSlim = new SemaphoreSlim(1);
[ObservableProperty]
private string _activeConnectionStatus = WinoServerConnectionStatus.None.ToString();
public AppShellViewModel(IDialogService dialogService, public AppShellViewModel(IDialogService dialogService,
IWinoNavigationService navigationService, IWinoNavigationService navigationService,
IWinoSynchronizerFactory synchronizerFactory,
IBackgroundTaskService backgroundTaskService, IBackgroundTaskService backgroundTaskService,
IMimeFileService mimeFileService, IMimeFileService mimeFileService,
INativeAppService nativeAppService, INativeAppService nativeAppService,
@@ -97,13 +100,23 @@ namespace Wino.Mail.ViewModels
INotificationBuilder notificationBuilder, INotificationBuilder notificationBuilder,
IWinoRequestDelegator winoRequestDelegator, IWinoRequestDelegator winoRequestDelegator,
IFolderService folderService, IFolderService folderService,
IStatePersistanceService statePersistanceService) : base(dialogService) IStatePersistanceService statePersistanceService,
IWinoServerConnectionManager serverConnectionManager) : base(dialogService)
{ {
StatePersistenceService = statePersistanceService; StatePersistenceService = statePersistanceService;
ServerConnectionManager = serverConnectionManager;
ServerConnectionManager.StatusChanged += async (sender, status) =>
{
await ExecuteUIThread(() =>
{
ActiveConnectionStatus = status.ToString();
});
};
PreferencesService = preferencesService; PreferencesService = preferencesService;
NavigationService = navigationService; NavigationService = navigationService;
_synchronizerFactory = synchronizerFactory;
_backgroundTaskService = backgroundTaskService; _backgroundTaskService = backgroundTaskService;
_mimeFileService = mimeFileService; _mimeFileService = mimeFileService;
_nativeAppService = nativeAppService; _nativeAppService = nativeAppService;
@@ -117,6 +130,9 @@ namespace Wino.Mail.ViewModels
_winoRequestDelegator = winoRequestDelegator; _winoRequestDelegator = winoRequestDelegator;
} }
[RelayCommand]
private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync();
protected override void OnDispatcherAssigned() protected override void OnDispatcherAssigned()
{ {
base.OnDispatcherAssigned(); base.OnDispatcherAssigned();
@@ -776,65 +792,65 @@ namespace Wino.Mail.ViewModels
await _winoRequestDelegator.ExecuteAsync(draftPreperationRequest); await _winoRequestDelegator.ExecuteAsync(draftPreperationRequest);
} }
public async void Receive(NewSynchronizationRequested message) public async void Receive(NewSynchronizationRequested message)
{ {
// TODO: Queue new synchronization for an account.
// Don't send message for sync completion when we execute requests. // Don't send message for sync completion when we execute requests.
// People are usually interested in seeing the notification after they trigger the synchronization. // People are usually interested in seeing the notification after they trigger the synchronization.
bool shouldReportSynchronizationResult = message.Options.Type != SynchronizationType.ExecuteRequests; //bool shouldReportSynchronizationResult = message.Options.Type != SynchronizationType.ExecuteRequests;
var synchronizer = _synchronizerFactory.GetAccountSynchronizer(message.Options.AccountId); //var synchronizer = _synchronizerFactory.GetAccountSynchronizer(message.Options.AccountId);
if (synchronizer == null) return; //if (synchronizer == null) return;
var accountId = message.Options.AccountId; //var accountId = message.Options.AccountId;
message.Options.ProgressListener = this; //message.Options.ProgressListener = this;
bool isSynchronizationSucceeded = false; //bool isSynchronizationSucceeded = false;
try //try
{ //{
// TODO: Cancellation Token // // TODO: Cancellation Token
var synchronizationResult = await synchronizer.SynchronizeAsync(message.Options); // var synchronizationResult = await synchronizer.SynchronizeAsync(message.Options);
isSynchronizationSucceeded = synchronizationResult.CompletedState == SynchronizationCompletedState.Success; // isSynchronizationSucceeded = synchronizationResult.CompletedState == SynchronizationCompletedState.Success;
// Create notification for synchronization result. // // Create notification for synchronization result.
if (synchronizationResult.DownloadedMessages.Any()) // if (synchronizationResult.DownloadedMessages.Any())
{ // {
var accountInboxFolder = await _folderService.GetSpecialFolderByAccountIdAsync(message.Options.AccountId, SpecialFolderType.Inbox); // var accountInboxFolder = await _folderService.GetSpecialFolderByAccountIdAsync(message.Options.AccountId, SpecialFolderType.Inbox);
if (accountInboxFolder == null) return; // if (accountInboxFolder == null) return;
await _notificationBuilder.CreateNotificationsAsync(accountInboxFolder.Id, synchronizationResult.DownloadedMessages); // await _notificationBuilder.CreateNotificationsAsync(accountInboxFolder.Id, synchronizationResult.DownloadedMessages);
} // }
} //}
catch (AuthenticationAttentionException) //catch (AuthenticationAttentionException)
{ //{
await SetAccountAttentionAsync(accountId, AccountAttentionReason.InvalidCredentials); // await SetAccountAttentionAsync(accountId, AccountAttentionReason.InvalidCredentials);
} //}
catch (SystemFolderConfigurationMissingException) //catch (SystemFolderConfigurationMissingException)
{ //{
await SetAccountAttentionAsync(accountId, AccountAttentionReason.MissingSystemFolderConfiguration); // await SetAccountAttentionAsync(accountId, AccountAttentionReason.MissingSystemFolderConfiguration);
} //}
catch (OperationCanceledException) //catch (OperationCanceledException)
{ //{
DialogService.InfoBarMessage(Translator.Info_SyncCanceledMessage, Translator.Info_SyncCanceledMessage, InfoBarMessageType.Warning); // DialogService.InfoBarMessage(Translator.Info_SyncCanceledMessage, Translator.Info_SyncCanceledMessage, InfoBarMessageType.Warning);
} //}
catch (Exception ex) //catch (Exception ex)
{ //{
DialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, ex.Message, InfoBarMessageType.Error); // DialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, ex.Message, InfoBarMessageType.Error);
} //}
finally //finally
{ //{
if (shouldReportSynchronizationResult) // if (shouldReportSynchronizationResult)
Messenger.Send(new AccountSynchronizationCompleted(accountId, // Messenger.Send(new AccountSynchronizationCompleted(accountId,
isSynchronizationSucceeded ? SynchronizationCompletedState.Success : SynchronizationCompletedState.Failed, // isSynchronizationSucceeded ? SynchronizationCompletedState.Success : SynchronizationCompletedState.Failed,
message.Options.GroupedSynchronizationTrackingId)); // message.Options.GroupedSynchronizationTrackingId));
} //}
} }

View File

@@ -6,8 +6,7 @@ using Wino.Core.Domain.Entities;
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.Navigation; using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Requests; using Wino.Messaging.Server;
using Wino.Core.Requests;
namespace Wino.Mail.ViewModels namespace Wino.Mail.ViewModels
{ {
@@ -72,9 +71,7 @@ namespace Wino.Mail.ViewModels
protected virtual void OnFolderRenamed(IMailItemFolder mailItemFolder) { } protected virtual void OnFolderRenamed(IMailItemFolder mailItemFolder) { }
protected virtual void OnFolderSynchronizationEnabled(IMailItemFolder mailItemFolder) { } protected virtual void OnFolderSynchronizationEnabled(IMailItemFolder mailItemFolder) { }
public void ReportUIChange<TMessage>(TMessage message) where TMessage : class, IUIMessage public void ReportUIChange<TMessage>(TMessage message) where TMessage : class, IServerMessage => Messenger.Send(message);
=> Messenger.Send(message);
void IRecipient<AccountCreatedMessage>.Receive(AccountCreatedMessage message) => OnAccountCreated(message.Account); void IRecipient<AccountCreatedMessage>.Receive(AccountCreatedMessage message) => OnAccountCreated(message.Account);
void IRecipient<AccountRemovedMessage>.Receive(AccountRemovedMessage message) => OnAccountRemoved(message.Account); void IRecipient<AccountRemovedMessage>.Receive(AccountRemovedMessage message) => OnAccountRemoved(message.Account);
void IRecipient<AccountUpdatedMessage>.Receive(AccountUpdatedMessage message) => OnAccountUpdated(message.Account); void IRecipient<AccountUpdatedMessage>.Receive(AccountUpdatedMessage message) => OnAccountUpdated(message.Account);

View File

@@ -18,7 +18,9 @@ namespace Wino.Mail.ViewModels.Collections
// If the item provider here for update or removal doesn't exist here // If the item provider here for update or removal doesn't exist here
// we can ignore the operation. // we can ignore the operation.
public HashSet<Guid> MailCopyIdHashSet = new HashSet<Guid>(); public HashSet<Guid> MailCopyIdHashSet = [];
public event EventHandler<IMailItem> MailItemRemoved;
private ListItemComparer listComparer = new ListItemComparer(); private ListItemComparer listComparer = new ListItemComparer();
@@ -61,45 +63,51 @@ namespace Wino.Mail.ViewModels.Collections
return mailItem.FromName; return mailItem.FromName;
} }
private async Task InsertItemInternalAsync(object groupKey, IMailItem mailItem) private void UpdateUniqueIdHashes(IMailHashContainer itemContainer, bool isAdd)
=> await ExecuteUIThread(() =>
{
if (mailItem is MailCopy mailCopy)
{
MailCopyIdHashSet.Add(mailCopy.UniqueId);
_mailItemSource.InsertItem(groupKey, listComparer, new MailItemViewModel(mailCopy), listComparer.GetItemComparer());
}
else if (mailItem is ThreadMailItem threadMailItem)
{
foreach (var item in threadMailItem.ThreadItems)
{
MailCopyIdHashSet.Add(item.UniqueId);
}
_mailItemSource.InsertItem(groupKey, listComparer, new ThreadMailItemViewModel(threadMailItem), listComparer.GetItemComparer());
}
else if (mailItem is MailItemViewModel)
{
MailCopyIdHashSet.Add(mailItem.UniqueId);
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer.GetItemComparer());
}
});
private async Task RemoveItemInternalAsync(ObservableGroup<object, IMailItem> group, IMailItem mailItem)
{ {
MailCopyIdHashSet.Remove(mailItem.UniqueId); foreach (var item in itemContainer.GetContainingIds())
await ExecuteUIThread(() =>
{ {
group.Remove(mailItem); if (isAdd)
if (group.Count == 0)
{ {
_mailItemSource.RemoveGroup(group.Key); MailCopyIdHashSet.Add(item);
} }
}); else
{
MailCopyIdHashSet.Remove(item);
}
}
}
private void InsertItemInternal(object groupKey, IMailItem mailItem)
{
UpdateUniqueIdHashes(mailItem, true);
if (mailItem is MailCopy mailCopy)
{
_mailItemSource.InsertItem(groupKey, listComparer, new MailItemViewModel(mailCopy), listComparer.GetItemComparer());
}
else if (mailItem is ThreadMailItem threadMailItem)
{
_mailItemSource.InsertItem(groupKey, listComparer, new ThreadMailItemViewModel(threadMailItem), listComparer.GetItemComparer());
}
else
{
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer.GetItemComparer());
}
}
private void RemoveItemInternal(ObservableGroup<object, IMailItem> group, IMailItem mailItem)
{
UpdateUniqueIdHashes(mailItem, false);
MailItemRemoved?.Invoke(this, mailItem);
group.Remove(mailItem);
if (group.Count == 0)
{
_mailItemSource.RemoveGroup(group.Key);
}
} }
public async Task AddAsync(MailCopy addedItem) public async Task AddAsync(MailCopy addedItem)
@@ -109,6 +117,9 @@ namespace Wino.Mail.ViewModels.Collections
var groupCount = _mailItemSource.Count; var groupCount = _mailItemSource.Count;
var addedAccountProviderType = addedItem.AssignedAccount.ProviderType;
var threadingStrategy = ThreadingStrategyProvider?.GetStrategy(addedAccountProviderType);
for (int i = 0; i < groupCount; i++) for (int i = 0; i < groupCount; i++)
{ {
if (shouldExit) break; if (shouldExit) break;
@@ -119,10 +130,6 @@ namespace Wino.Mail.ViewModels.Collections
{ {
var item = group[k]; var item = group[k];
var addedAccountProviderType = addedItem.AssignedAccount.ProviderType;
var threadingStrategy = ThreadingStrategyProvider?.GetStrategy(addedAccountProviderType);
if (threadingStrategy?.ShouldThreadWithItem(addedItem, item) ?? false) if (threadingStrategy?.ShouldThreadWithItem(addedItem, item) ?? false)
{ {
shouldExit = true; shouldExit = true;
@@ -140,23 +147,32 @@ namespace Wino.Mail.ViewModels.Collections
var existingGroupKey = GetGroupingKey(threadMailItemViewModel); var existingGroupKey = GetGroupingKey(threadMailItemViewModel);
threadMailItemViewModel.AddMailItemViewModel(addedItem); await ExecuteUIThread(() => { threadMailItemViewModel.AddMailItemViewModel(addedItem); });
var newGroupKey = GetGroupingKey(threadMailItemViewModel); var newGroupKey = GetGroupingKey(threadMailItemViewModel);
if (!existingGroupKey.Equals(newGroupKey)) if (!existingGroupKey.Equals(newGroupKey))
{ {
await RemoveItemInternalAsync(group, threadMailItemViewModel); var mailThreadItems = threadMailItemViewModel.GetThreadMailItem();
await InsertItemInternalAsync(newGroupKey, threadMailItemViewModel);
await ExecuteUIThread(() =>
{
// Group must be changed for this thread.
// Remove the thread first.
RemoveItemInternal(group, threadMailItemViewModel);
// Insert new view model because the previous one might've been deleted with the group.
InsertItemInternal(newGroupKey, new ThreadMailItemViewModel(mailThreadItems));
});
} }
else
await ExecuteUIThread(() => { threadMailItemViewModel.NotifyPropertyChanges(); });
if (!MailCopyIdHashSet.Contains(addedItem.UniqueId))
{ {
MailCopyIdHashSet.Add(addedItem.UniqueId); await ExecuteUIThread(() => { threadMailItemViewModel.NotifyPropertyChanges(); });
} }
UpdateUniqueIdHashes(addedItem, true);
break; break;
} }
else else
@@ -177,10 +193,10 @@ namespace Wino.Mail.ViewModels.Collections
if (item is MailItemViewModel itemViewModel) if (item is MailItemViewModel itemViewModel)
{ {
itemViewModel.Update(addedItem); await ExecuteUIThread(() => { itemViewModel.Update(addedItem); });
MailCopyIdHashSet.Remove(itemViewModel.UniqueId); UpdateUniqueIdHashes(itemViewModel, false);
MailCopyIdHashSet.Add(addedItem.UniqueId); UpdateUniqueIdHashes(addedItem, true);
} }
} }
else else
@@ -189,15 +205,18 @@ namespace Wino.Mail.ViewModels.Collections
var threadMailItem = new ThreadMailItem(); var threadMailItem = new ThreadMailItem();
threadMailItem.AddThreadItem(item); await ExecuteUIThread(() =>
threadMailItem.AddThreadItem(addedItem); {
threadMailItem.AddThreadItem(item);
threadMailItem.AddThreadItem(addedItem);
if (threadMailItem.ThreadItems.Count == 1) return; if (threadMailItem.ThreadItems.Count == 1) return;
var newGroupKey = GetGroupingKey(threadMailItem); var newGroupKey = GetGroupingKey(threadMailItem);
await RemoveItemInternalAsync(group, item); RemoveItemInternal(group, item);
await InsertItemInternalAsync(newGroupKey, threadMailItem); InsertItemInternal(newGroupKey, threadMailItem);
});
} }
break; break;
@@ -208,6 +227,9 @@ namespace Wino.Mail.ViewModels.Collections
// Update properties. // Update properties.
if (item.Id == addedItem.Id && item is MailItemViewModel itemViewModel) if (item.Id == addedItem.Id && item is MailItemViewModel itemViewModel)
{ {
UpdateUniqueIdHashes(itemViewModel, false);
UpdateUniqueIdHashes(addedItem, true);
await ExecuteUIThread(() => { itemViewModel.Update(addedItem); }); await ExecuteUIThread(() => { itemViewModel.Update(addedItem); });
shouldExit = true; shouldExit = true;
@@ -224,7 +246,7 @@ namespace Wino.Mail.ViewModels.Collections
var groupKey = GetGroupingKey(addedItem); var groupKey = GetGroupingKey(addedItem);
await InsertItemInternalAsync(groupKey, addedItem); await ExecuteUIThread(() => { InsertItemInternal(groupKey, addedItem); });
} }
} }
@@ -268,12 +290,9 @@ namespace Wino.Mail.ViewModels.Collections
} }
else else
{ {
foreach (var item in group) foreach (var item in group)
{ {
existingGroup.Add(item); existingGroup.Add(item);
// _mailItemSource.InsertItem(existingGroup, item);
} }
} }
} }
@@ -325,11 +344,14 @@ namespace Wino.Mail.ViewModels.Collections
if (itemContainer == null) return; if (itemContainer == null) return;
// mailCopyIdHashSet.Remove(itemContainer.ItemViewModel.UniqueId); if (itemContainer.ItemViewModel != null)
{
UpdateUniqueIdHashes(itemContainer.ItemViewModel, false);
}
itemContainer.ItemViewModel?.Update(updatedMailCopy); itemContainer.ItemViewModel?.Update(updatedMailCopy);
// mailCopyIdHashSet.Add(updatedMailCopy.UniqueId); UpdateUniqueIdHashes(updatedMailCopy, true);
// Call thread notifications if possible. // Call thread notifications if possible.
itemContainer.ThreadViewModel?.NotifyPropertyChanges(); itemContainer.ThreadViewModel?.NotifyPropertyChanges();
@@ -426,6 +448,8 @@ namespace Wino.Mail.ViewModels.Collections
* -> Remove the thread. * -> Remove the thread.
*/ */
var oldGroupKey = GetGroupingKey(threadMailItemViewModel);
await ExecuteUIThread(() => { threadMailItemViewModel.RemoveCopyItem(removalItem); }); await ExecuteUIThread(() => { threadMailItemViewModel.RemoveCopyItem(removalItem); });
if (threadMailItemViewModel.ThreadItems.Count == 1) if (threadMailItemViewModel.ThreadItems.Count == 1)
@@ -435,25 +459,37 @@ namespace Wino.Mail.ViewModels.Collections
var singleViewModel = threadMailItemViewModel.GetSingleItemViewModel(); var singleViewModel = threadMailItemViewModel.GetSingleItemViewModel();
var groupKey = GetGroupingKey(singleViewModel); var groupKey = GetGroupingKey(singleViewModel);
await RemoveItemInternalAsync(group, threadMailItemViewModel); await ExecuteUIThread(() =>
{
RemoveItemInternal(group, threadMailItemViewModel);
InsertItemInternal(groupKey, singleViewModel);
});
// If thread->single conversion is being done, we should ignore it for non-draft items. // If thread->single conversion is being done, we should ignore it for non-draft items.
// eg. Deleting a reply message from draft folder. Single non-draft item should not be re-added. // eg. Deleting a reply message from draft folder. Single non-draft item should not be re-added.
if (!PruneSingleNonDraftItems || singleViewModel.IsDraft) if (PruneSingleNonDraftItems && !singleViewModel.IsDraft)
{ {
await InsertItemInternalAsync(groupKey, singleViewModel); // This item should not be here anymore.
// It's basically a reply mail in Draft folder.
var newGroup = _mailItemSource.FirstGroupByKeyOrDefault(groupKey);
if (newGroup != null)
{
await ExecuteUIThread(() => { RemoveItemInternal(newGroup, singleViewModel); });
}
} }
} }
else if (threadMailItemViewModel.ThreadItems.Count == 0) else if (threadMailItemViewModel.ThreadItems.Count == 0)
{ {
await RemoveItemInternalAsync(group, threadMailItemViewModel); await ExecuteUIThread(() => { RemoveItemInternal(group, threadMailItemViewModel); });
} }
else else
{ {
// Item inside the thread is removed. // Item inside the thread is removed.
await ExecuteUIThread(() => { threadMailItemViewModel.ThreadItems.Remove(removalItem); });
threadMailItemViewModel.ThreadItems.Remove(removalItem); UpdateUniqueIdHashes(removalItem, false);
} }
shouldExit = true; shouldExit = true;
@@ -461,7 +497,8 @@ namespace Wino.Mail.ViewModels.Collections
} }
else if (item.UniqueId == removeItem.UniqueId) else if (item.UniqueId == removeItem.UniqueId)
{ {
await RemoveItemInternalAsync(group, item); await ExecuteUIThread(() => { RemoveItemInternal(group, item); });
shouldExit = true; shouldExit = true;
break; break;

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.MailItem;
@@ -94,5 +95,7 @@ namespace Wino.Mail.ViewModels.Data
OnPropertyChanged(nameof(Subject)); OnPropertyChanged(nameof(Subject));
OnPropertyChanged(nameof(PreviewText)); OnPropertyChanged(nameof(PreviewText));
} }
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
} }
} }

View File

@@ -12,18 +12,14 @@ namespace Wino.Mail.ViewModels.Data
/// <summary> /// <summary>
/// Thread mail item (multiple IMailItem) view model representation. /// Thread mail item (multiple IMailItem) view model representation.
/// </summary> /// </summary>
public class ThreadMailItemViewModel : ObservableObject, IMailItemThread, IComparable<string>, IComparable<DateTime> public partial class ThreadMailItemViewModel : ObservableObject, IMailItemThread, IComparable<string>, IComparable<DateTime>
{ {
public ObservableCollection<IMailItem> ThreadItems => ((IMailItemThread)_threadMailItem).ThreadItems; public ObservableCollection<IMailItem> ThreadItems => ((IMailItemThread)_threadMailItem).ThreadItems;
private readonly ThreadMailItem _threadMailItem; private readonly ThreadMailItem _threadMailItem;
[ObservableProperty]
private bool isThreadExpanded; private bool isThreadExpanded;
public bool IsThreadExpanded
{
get => isThreadExpanded;
set => SetProperty(ref isThreadExpanded, value);
}
public ThreadMailItemViewModel(ThreadMailItem threadMailItem) public ThreadMailItemViewModel(ThreadMailItem threadMailItem)
{ {
@@ -36,6 +32,8 @@ namespace Wino.Mail.ViewModels.Data
} }
} }
public ThreadMailItem GetThreadMailItem() => _threadMailItem;
public IEnumerable<MailCopy> GetMailCopies() public IEnumerable<MailCopy> GetMailCopies()
=> ThreadItems.OfType<MailItemViewModel>().Select(a => a.MailCopy); => ThreadItems.OfType<MailItemViewModel>().Select(a => a.MailCopy);
@@ -123,5 +121,7 @@ namespace Wino.Mail.ViewModels.Data
// Get single mail item view model out of the only item in thread items. // Get single mail item view model out of the only item in thread items.
public MailItemViewModel GetSingleItemViewModel() => ThreadItems.First() as MailItemViewModel; public MailItemViewModel GetSingleItemViewModel() => ThreadItems.First() as MailItemViewModel;
public IEnumerable<Guid> GetContainingIds() => ((IMailItemThread)_threadMailItem).GetContainingIds();
} }
} }

View File

@@ -72,7 +72,6 @@ namespace Wino.Mail.ViewModels
private readonly IMailService _mailService; private readonly IMailService _mailService;
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
private readonly IThreadingStrategyProvider _threadingStrategyProvider; private readonly IThreadingStrategyProvider _threadingStrategyProvider;
private readonly IContextMenuItemService _contextMenuItemService; private readonly IContextMenuItemService _contextMenuItemService;
private readonly IWinoRequestDelegator _winoRequestDelegator; private readonly IWinoRequestDelegator _winoRequestDelegator;
@@ -143,7 +142,6 @@ namespace Wino.Mail.ViewModels
IMailService mailService, IMailService mailService,
IStatePersistanceService statePersistanceService, IStatePersistanceService statePersistanceService,
IFolderService folderService, IFolderService folderService,
IWinoSynchronizerFactory winoSynchronizerFactory,
IThreadingStrategyProvider threadingStrategyProvider, IThreadingStrategyProvider threadingStrategyProvider,
IContextMenuItemService contextMenuItemService, IContextMenuItemService contextMenuItemService,
IWinoRequestDelegator winoRequestDelegator, IWinoRequestDelegator winoRequestDelegator,
@@ -156,7 +154,6 @@ namespace Wino.Mail.ViewModels
_mailService = mailService; _mailService = mailService;
_folderService = folderService; _folderService = folderService;
_winoSynchronizerFactory = winoSynchronizerFactory;
_threadingStrategyProvider = threadingStrategyProvider; _threadingStrategyProvider = threadingStrategyProvider;
_contextMenuItemService = contextMenuItemService; _contextMenuItemService = contextMenuItemService;
_winoRequestDelegator = winoRequestDelegator; _winoRequestDelegator = winoRequestDelegator;
@@ -172,6 +169,24 @@ namespace Wino.Mail.ViewModels
{ {
await ExecuteUIThread(() => { SelectedItemCollectionUpdated(a.EventArgs); }); await ExecuteUIThread(() => { SelectedItemCollectionUpdated(a.EventArgs); });
}); });
MailCollection.MailItemRemoved += (c, removedItem) =>
{
if (removedItem is ThreadMailItemViewModel removedThreadViewModelItem)
{
foreach (var viewModel in removedThreadViewModelItem.ThreadItems.Cast<MailItemViewModel>())
{
if (SelectedItems.Contains(viewModel))
{
SelectedItems.Remove(viewModel);
}
}
}
else if (removedItem is MailItemViewModel removedMailItemViewModel && SelectedItems.Contains(removedMailItemViewModel))
{
SelectedItems.Remove(removedMailItemViewModel);
}
};
} }
#region Properties #region Properties
@@ -310,25 +325,6 @@ namespace Wino.Mail.ViewModels
MailCollection.CoreDispatcher = Dispatcher; MailCollection.CoreDispatcher = Dispatcher;
} }
//protected override async void OnFolderUpdated(MailItemFolder updatedFolder, MailAccount account)
//{
// base.OnFolderUpdated(updatedFolder, account);
// // Don't need to update if the folder update does not belong to the current folder menu item.
// if (ActiveFolder == null || updatedFolder == null || !ActiveFolder.HandlingFolders.Any(a => a.Id == updatedFolder.Id)) return;
// await ExecuteUIThread(() =>
// {
// ActiveFolder.UpdateFolder(updatedFolder);
// OnPropertyChanged(nameof(CanSynchronize));
// OnPropertyChanged(nameof(IsFolderSynchronizationEnabled));
// });
// // Force synchronization after enabling the folder.
// SyncFolder();
//}
private async void UpdateBarMessage(InfoBarMessageType severity, string title, string message) private async void UpdateBarMessage(InfoBarMessageType severity, string title, string message)
{ {
await ExecuteUIThread(() => await ExecuteUIThread(() =>
@@ -603,6 +599,8 @@ namespace Wino.Mail.ViewModels
{ {
base.OnMailAdded(addedMail); base.OnMailAdded(addedMail);
if (addedMail.AssignedAccount == null || addedMail.AssignedFolder == null) return;
try try
{ {
await listManipulationSemepahore.WaitAsync(); await listManipulationSemepahore.WaitAsync();
@@ -618,12 +616,9 @@ namespace Wino.Mail.ViewModels
if (!shouldPreventIgnoringFilter && ShouldPreventItemAdd(addedMail)) return; if (!shouldPreventIgnoringFilter && ShouldPreventItemAdd(addedMail)) return;
await ExecuteUIThread(async () => await MailCollection.AddAsync(addedMail);
{
await MailCollection.AddAsync(addedMail);
NotifyItemFoundState(); await ExecuteUIThread(() => { NotifyItemFoundState(); });
});
} }
catch { } catch { }
finally finally
@@ -693,6 +688,7 @@ namespace Wino.Mail.ViewModels
gmailUnreadFolderMarkedAsReadUniqueIds.Remove(removedMail.UniqueId); gmailUnreadFolderMarkedAsReadUniqueIds.Remove(removedMail.UniqueId);
} }
} }
protected override async void OnDraftCreated(MailCopy draftMail, MailAccount account) protected override async void OnDraftCreated(MailCopy draftMail, MailAccount account)
{ {
base.OnDraftCreated(draftMail, account); base.OnDraftCreated(draftMail, account);
@@ -704,10 +700,10 @@ namespace Wino.Mail.ViewModels
await listManipulationSemepahore.WaitAsync(); await listManipulationSemepahore.WaitAsync();
// Create the item. Draft folder navigation is already done at this point. // Create the item. Draft folder navigation is already done at this point.
await ExecuteUIThread(async () => await MailCollection.AddAsync(draftMail);
{
await MailCollection.AddAsync(draftMail);
await ExecuteUIThread(() =>
{
// New draft is created by user. Select the item. // New draft is created by user. Select the item.
Messenger.Send(new MailItemNavigationRequested(draftMail.UniqueId, ScrollToItem: true)); Messenger.Send(new MailItemNavigationRequested(draftMail.UniqueId, ScrollToItem: true));
@@ -940,8 +936,6 @@ namespace Wino.Mail.ViewModels
if (navigatingMailItem != null) if (navigatingMailItem != null)
WeakReferenceMessenger.Default.Send(new SelectMailItemContainerEvent(navigatingMailItem, message.ScrollToItem)); WeakReferenceMessenger.Default.Send(new SelectMailItemContainerEvent(navigatingMailItem, message.ScrollToItem));
else
Debugger.Break();
} }
#endregion #endregion
@@ -980,17 +974,19 @@ namespace Wino.Mail.ViewModels
foreach (var accountId in accountIds) foreach (var accountId in accountIds)
{ {
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(accountId); // TODO: Server: Check whether account is already synchronizing from the server.
if (synchronizer == null) continue; //var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(accountId);
bool isAccountSynchronizing = synchronizer.State != AccountSynchronizerState.Idle; //if (synchronizer == null) continue;
if (isAccountSynchronizing) //bool isAccountSynchronizing = synchronizer.State != AccountSynchronizerState.Idle;
{
isAnyAccountSynchronizing = true; //if (isAccountSynchronizing)
break; //{
} // isAnyAccountSynchronizing = true;
// break;
//}
} }
} }

View File

@@ -37,7 +37,6 @@ namespace Wino.Mail.ViewModels
private readonly IMimeFileService _mimeFileService; private readonly IMimeFileService _mimeFileService;
private readonly Core.Domain.Interfaces.IMailService _mailService; private readonly Core.Domain.Interfaces.IMailService _mailService;
private readonly IFileService _fileService; private readonly IFileService _fileService;
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
private readonly IWinoRequestDelegator _requestDelegator; private readonly IWinoRequestDelegator _requestDelegator;
private readonly IClipboardService _clipboardService; private readonly IClipboardService _clipboardService;
private readonly IUnsubscriptionService _unsubscriptionService; private readonly IUnsubscriptionService _unsubscriptionService;
@@ -124,7 +123,6 @@ namespace Wino.Mail.ViewModels
IMimeFileService mimeFileService, IMimeFileService mimeFileService,
Core.Domain.Interfaces.IMailService mailService, Core.Domain.Interfaces.IMailService mailService,
IFileService fileService, IFileService fileService,
IWinoSynchronizerFactory winoSynchronizerFactory,
IWinoRequestDelegator requestDelegator, IWinoRequestDelegator requestDelegator,
IStatePersistanceService statePersistanceService, IStatePersistanceService statePersistanceService,
IClipboardService clipboardService, IClipboardService clipboardService,
@@ -141,7 +139,6 @@ namespace Wino.Mail.ViewModels
_mimeFileService = mimeFileService; _mimeFileService = mimeFileService;
_mailService = mailService; _mailService = mailService;
_fileService = fileService; _fileService = fileService;
_winoSynchronizerFactory = winoSynchronizerFactory;
_requestDelegator = requestDelegator; _requestDelegator = requestDelegator;
} }
@@ -344,30 +341,31 @@ namespace Wino.Mail.ViewModels
} }
} }
private async Task HandleSingleItemDownloadAsync(MailItemViewModel mailItemViewModel) private async Task HandleSingleItemDownloadAsync(MailItemViewModel mailItemViewModel)
{ {
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(mailItemViewModel.AssignedAccount.Id); // TODO: Server: Download single mime and report back the item here.
try //var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(mailItemViewModel.AssignedAccount.Id);
{
// To show the progress on the UI.
CurrentDownloadPercentage = 1;
await synchronizer.DownloadMissingMimeMessageAsync(mailItemViewModel.MailCopy, this, renderCancellationTokenSource.Token); //try
} //{
catch (OperationCanceledException) // // To show the progress on the UI.
{ // CurrentDownloadPercentage = 1;
Log.Information("MIME download is canceled.");
} // await synchronizer.DownloadMissingMimeMessageAsync(mailItemViewModel.MailCopy, this, renderCancellationTokenSource.Token);
catch (Exception ex) //}
{ //catch (OperationCanceledException)
DialogService.InfoBarMessage(Translator.GeneralTitle_Error, ex.Message, InfoBarMessageType.Error); //{
} // Log.Information("MIME download is canceled.");
finally //}
{ //catch (Exception ex)
ResetProgress(); //{
} // DialogService.InfoBarMessage(Translator.GeneralTitle_Error, ex.Message, InfoBarMessageType.Error);
//}
//finally
//{
// ResetProgress();
//}
} }
private async Task RenderAsync(MailItemViewModel mailItemViewModel, CancellationToken cancellationToken = default) private async Task RenderAsync(MailItemViewModel mailItemViewModel, CancellationToken cancellationToken = default)

View File

@@ -9,8 +9,8 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Messages.Navigation; using Wino.Core.Messages.Navigation;
using Wino.Core.Requests;
using Wino.Mail.ViewModels.Data; using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels namespace Wino.Mail.ViewModels
{ {

View File

@@ -17,6 +17,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" /> <ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
<ProjectReference Include="..\Wino.Core\Wino.Core.csproj" /> <ProjectReference Include="..\Wino.Core\Wino.Core.csproj" />
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,18 +1,14 @@
using System.Diagnostics; using System.Threading.Tasks;
using System.Threading.Tasks;
using Serilog; using Serilog;
using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.Background; using Windows.ApplicationModel.Background;
using Windows.UI.Notifications; using Windows.UI.Notifications;
using Wino.Core;
using Wino.Core.Domain; using Wino.Core.Domain;
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.MailItem; using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.UWP.Services; using Wino.Core.UWP.Services;
using Wino.Services;
#if NET8_0 #if NET8_0
using CommunityToolkit.WinUI.Notifications; using CommunityToolkit.WinUI.Notifications;
@@ -27,26 +23,23 @@ namespace Wino.Activation
private const string BackgroundExecutionLogTag = "[BackgroundExecution] "; private const string BackgroundExecutionLogTag = "[BackgroundExecution] ";
private readonly IWinoRequestDelegator _winoRequestDelegator; private readonly IWinoRequestDelegator _winoRequestDelegator;
private readonly IBackgroundSynchronizer _backgroundSynchronizer;
private readonly INativeAppService _nativeAppService; private readonly INativeAppService _nativeAppService;
private readonly IWinoRequestProcessor _winoRequestProcessor; private readonly IWinoRequestProcessor _winoRequestProcessor;
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory; private readonly IWinoServerConnectionManager _winoServerConnectionManager;
private readonly IMailService _mailService; private readonly IMailService _mailService;
private ToastArguments _toastArguments; private ToastArguments _toastArguments;
BackgroundTaskDeferral _deferral; BackgroundTaskDeferral _deferral;
public BackgroundActivationHandler(IWinoRequestDelegator winoRequestDelegator, public BackgroundActivationHandler(IWinoRequestDelegator winoRequestDelegator,
IBackgroundSynchronizer backgroundSynchronizer,
INativeAppService nativeAppService, INativeAppService nativeAppService,
IWinoRequestProcessor winoRequestProcessor, IWinoRequestProcessor winoRequestProcessor,
IWinoSynchronizerFactory winoSynchronizerFactory, IWinoServerConnectionManager winoServerConnectionManager,
IMailService mailService) IMailService mailService)
{ {
_winoRequestDelegator = winoRequestDelegator; _winoRequestDelegator = winoRequestDelegator;
_backgroundSynchronizer = backgroundSynchronizer;
_nativeAppService = nativeAppService; _nativeAppService = nativeAppService;
_winoRequestProcessor = winoRequestProcessor; _winoRequestProcessor = winoRequestProcessor;
_winoSynchronizerFactory = winoSynchronizerFactory; _winoServerConnectionManager = winoServerConnectionManager;
_mailService = mailService; _mailService = mailService;
} }
@@ -90,38 +83,28 @@ namespace Wino.Activation
{ {
// We need to synchronize changes without reflection the UI changes. // We need to synchronize changes without reflection the UI changes.
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(mailItem.AssignedAccount.Id); // var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(mailItem.AssignedAccount.Id);
var prepRequest = new MailOperationPreperationRequest(action, mailItem); var prepRequest = new MailOperationPreperationRequest(action, mailItem);
var requests = await _winoRequestProcessor.PrepareRequestsAsync(prepRequest); var requests = await _winoRequestProcessor.PrepareRequestsAsync(prepRequest);
foreach (var request in requests) foreach (var request in requests)
{ {
synchronizer.QueueRequest(request); _winoServerConnectionManager.QueueRequest(request, mailItem.AssignedAccount.Id);
// synchronizer.QueueRequest(request);
} }
var options = new SynchronizationOptions() //var options = new SynchronizationOptions()
{ //{
Type = SynchronizationType.ExecuteRequests, // Type = SynchronizationType.ExecuteRequests,
AccountId = mailItem.AssignedAccount.Id // AccountId = mailItem.AssignedAccount.Id
}; //};
await synchronizer.SynchronizeAsync(options); //await synchronizer.SynchronizeAsync(options);
} }
} }
} }
else if (taskName == BackgroundTaskService.BackgroundSynchronizationTimerTaskNameEx)
{
var watch = new Stopwatch();
watch.Start();
// Run timer based background synchronization.
await _backgroundSynchronizer.RunBackgroundSynchronizationAsync(BackgroundSynchronizationReason.Timer);
watch.Stop();
Log.Information($"{BackgroundExecutionLogTag}Background synchronization is completed in {watch.Elapsed.TotalSeconds} seconds.");
}
instance.Canceled -= OnBackgroundExecutionCanceled; instance.Canceled -= OnBackgroundExecutionCanceled;

View File

@@ -8,6 +8,14 @@ using Microsoft.Extensions.DependencyInjection;
using Serilog; using Serilog;
using Windows.ApplicationModel; using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.ApplicationModel.Core;
using Windows.Foundation.Metadata;
using Windows.Storage;
using Windows.System.Profile;
using Windows.UI;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Wino.Activation; using Wino.Activation;
@@ -22,6 +30,25 @@ namespace Wino
{ {
private BackgroundTaskDeferral backgroundTaskDeferral;
private readonly IWinoServerConnectionManager<AppServiceConnection> _appServiceConnectionManager;
private readonly ILogInitializer _logInitializer;
private readonly IThemeService _themeService;
private readonly IDatabaseService _databaseService;
private readonly IApplicationConfiguration _appInitializerService;
private readonly ITranslationService _translationService;
private readonly IApplicationConfiguration _applicationFolderConfiguration;
// Order matters.
private List<IInitializeAsync> initializeServices => new List<IInitializeAsync>()
{
_databaseService,
_appServiceConnectionManager,
_translationService,
_themeService,
};
public App() public App()
{ {
InitializeComponent(); InitializeComponent();
@@ -30,6 +57,9 @@ namespace Wino
EnteredBackground += OnEnteredBackground; EnteredBackground += OnEnteredBackground;
LeavingBackground += OnLeavingBackground; LeavingBackground += OnLeavingBackground;
Resuming += OnResuming;
Suspending += OnSuspending;
Services = ConfigureServices(); Services = ConfigureServices();
_logInitializer = Services.GetService<ILogInitializer>(); _logInitializer = Services.GetService<ILogInitializer>();
@@ -39,19 +69,135 @@ namespace Wino
ConfigurePrelaunch(); ConfigurePrelaunch();
ConfigureXbox(); ConfigureXbox();
_applicationFolderConfiguration = Services.GetService<IApplicationConfiguration>();
// Make sure the paths are setup on app start.
_applicationFolderConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path;
_applicationFolderConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path;
_appServiceConnectionManager = Services.GetService<IWinoServerConnectionManager<AppServiceConnection>>();
_themeService = Services.GetService<IThemeService>(); _themeService = Services.GetService<IThemeService>();
_databaseService = Services.GetService<IDatabaseService>(); _databaseService = Services.GetService<IDatabaseService>();
_appInitializerService = Services.GetService<IAppInitializerService>(); _appInitializerService = Services.GetService<IApplicationConfiguration>();
_synchronizerFactory = Services.GetService<IWinoSynchronizerFactory>();
_translationService = Services.GetService<ITranslationService>(); _translationService = Services.GetService<ITranslationService>();
_appShellService = Services.GetService<IAppShellService>(); _appShellService = Services.GetService<IAppShellService>();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
} }
private async void OnResuming(object sender, object e)
{
// App Service connection was lost on suspension.
// We must restore it.
// Server might be running already, but re-launching it will trigger a new connection attempt.
await _appServiceConnectionManager.ConnectAsync();
}
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
deferral.Complete();
}
private void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}"); private void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}");
private void OnLeavingBackground(object sender, LeavingBackgroundEventArgs e) => LogActivation($"Wino went foreground."); private void OnLeavingBackground(object sender, LeavingBackgroundEventArgs e) => LogActivation($"Wino went foreground.");
private void OnEnteredBackground(object sender, EnteredBackgroundEventArgs e) => LogActivation($"Wino went background."); private void OnEnteredBackground(object sender, EnteredBackgroundEventArgs e) => LogActivation($"Wino went background.");
private IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.RegisterCoreServices();
services.RegisterCoreUWPServices();
RegisterUWPServices(services);
RegisterViewModels(services);
RegisterActivationHandlers(services);
return services.BuildServiceProvider();
}
#region Dependency Injection
private void RegisterActivationHandlers(IServiceCollection services)
{
services.AddTransient<ProtocolActivationHandler>();
// services.AddTransient<BackgroundActivationHandler>();
services.AddTransient<ToastNotificationActivationHandler>();
services.AddTransient<FileActivationHandler>();
}
private void RegisterUWPServices(IServiceCollection services)
{
services.AddSingleton<IApplicationResourceManager<ResourceDictionary>, ApplicationResourceManager>();
services.AddSingleton<IThemeService, ThemeService>();
services.AddSingleton<IPreferencesService, PreferencesService>();
services.AddSingleton<IStatePersistanceService, StatePersistenceService>();
services.AddSingleton<ILaunchProtocolService, LaunchProtocolService>();
services.AddSingleton<IWinoNavigationService, WinoNavigationService>();
services.AddSingleton<IDialogService, DialogService>();
}
private void RegisterViewModels(IServiceCollection services)
{
services.AddSingleton(typeof(AppShellViewModel));
services.AddTransient(typeof(SettingsDialogViewModel));
services.AddTransient(typeof(PersonalizationPageViewModel));
services.AddTransient(typeof(SettingOptionsPageViewModel));
services.AddTransient(typeof(MailListPageViewModel));
services.AddTransient(typeof(MailRenderingPageViewModel));
services.AddTransient(typeof(AccountManagementViewModel));
services.AddTransient(typeof(WelcomePageViewModel));
services.AddTransient(typeof(AboutPageViewModel));
services.AddTransient(typeof(ComposePageViewModel));
services.AddTransient(typeof(IdlePageViewModel));
services.AddTransient(typeof(SettingsPageViewModel));
services.AddTransient(typeof(NewAccountManagementPageViewModel));
services.AddTransient(typeof(AccountDetailsPageViewModel));
services.AddTransient(typeof(SignatureManagementPageViewModel));
services.AddTransient(typeof(MessageListPageViewModel));
services.AddTransient(typeof(ReadingPanePageViewModel));
services.AddTransient(typeof(MergedAccountDetailsPageViewModel));
services.AddTransient(typeof(LanguageTimePageViewModel));
}
#endregion
#region Misc Configuration
private void ConfigureLogger() => _logInitializer.SetupLogger(ApplicationData.Current.LocalFolder.Path);
private void ConfigureAppCenter() => AppCenter.Start(AppCenterKey, typeof(Analytics), typeof(Crashes));
private void ConfigurePrelaunch()
{
if (ApiInformation.IsMethodPresent("Windows.ApplicationModel.Core.CoreApplication", "EnablePrelaunch"))
CoreApplication.EnablePrelaunch(true);
}
private void ConfigureXbox()
{
// Xbox users should use Reveal focus.
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 6))
{
FocusVisualKind = AnalyticsInfo.VersionInfo.DeviceFamily == "Xbox" ? FocusVisualKind.Reveal : FocusVisualKind.HighVisibility;
}
}
private void ConfigureTitleBar()
{
var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
var applicationViewTitleBar = ApplicationView.GetForCurrentView().TitleBar;
// Extend shell content into core window to meet design requirements.
coreTitleBar.ExtendViewIntoTitleBar = true;
// Change system buttons and background colors to meet design requirements.
applicationViewTitleBar.ButtonBackgroundColor = Colors.Transparent;
applicationViewTitleBar.BackgroundColor = Colors.Transparent;
applicationViewTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
applicationViewTitleBar.ButtonForegroundColor = Colors.White;
}
protected override void OnWindowCreated(WindowCreatedEventArgs args) protected override void OnWindowCreated(WindowCreatedEventArgs args)
{ {
@@ -96,6 +242,20 @@ namespace Wino
{ {
base.OnBackgroundActivated(args); base.OnBackgroundActivated(args);
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails appServiceTriggerDetails)
{
// Only accept connections from callers in the same package
if (appServiceTriggerDetails.CallerPackageFamilyName == Package.Current.Id.FamilyName)
{
// Connection established from the fulltrust process
backgroundTaskDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnBackgroundTaskCanceled;
_appServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection;
}
}
LogActivation($"OnBackgroundActivated -> {args.GetType().Name}, TaskInstanceIdName -> {args.TaskInstance?.Task?.Name ?? "NA"}"); LogActivation($"OnBackgroundActivated -> {args.GetType().Name}, TaskInstanceIdName -> {args.TaskInstance?.Task?.Name ?? "NA"}");
await ActivateWinoAsync(args); await ActivateWinoAsync(args);
@@ -121,7 +281,10 @@ namespace Wino
private async Task ActivateWinoAsync(object args) private async Task ActivateWinoAsync(object args)
{ {
await PreInitializationAsync(); foreach (var service in initializeServices)
{
await service.InitializeAsync();
}
if (IsInteractiveLaunchArgs(args)) if (IsInteractiveLaunchArgs(args))
{ {
@@ -163,5 +326,25 @@ namespace Wino
} }
} }
} }
private IEnumerable<ActivationHandler> GetActivationHandlers()
{
yield return Services.GetService<ProtocolActivationHandler>();
// yield return Services.GetService<BackgroundActivationHandler>(); // Old UWP background task handler.
yield return Services.GetService<ToastNotificationActivationHandler>();
yield return Services.GetService<FileActivationHandler>();
}
public async void OnBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
Log.Information($"Background task {sender.Task.Name} was canceled. Reason: {reason}");
await _appServiceConnectionManager.DisconnectAsync();
backgroundTaskDeferral?.Complete();
backgroundTaskDeferral = null;
_appServiceConnectionManager.Connection = null;
}
} }
} }

View File

@@ -514,6 +514,14 @@
IsOpen="False" IsOpen="False"
PreferredPlacement="Bottom" PreferredPlacement="Bottom"
Target="{x:Bind ShellInfoBar}" /> Target="{x:Bind ShellInfoBar}" />
<Grid
Grid.Column="1"
Padding="14"
HorizontalAlignment="Right"
VerticalAlignment="Bottom">
<Button Content="{x:Bind ViewModel.ActiveConnectionStatus, Mode=OneWay}" Command="{x:Bind ViewModel.ReconnectServerCommand}" />
</Grid>
</Grid> </Grid>
</muxc:NavigationView> </muxc:NavigationView>

View File

@@ -8,7 +8,7 @@ using Windows.UI.ViewManagement.Core;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests; using Wino.Core.Domain.Models.Reader;
using Wino.Views.Settings; using Wino.Views.Settings;
#if NET8_0 #if NET8_0
@@ -320,41 +320,41 @@ namespace Wino.Dialogs
{ {
var change = JsonSerializer.Deserialize<WebViewMessage>(args.WebMessageAsJson); var change = JsonSerializer.Deserialize<WebViewMessage>(args.WebMessageAsJson);
if (change.type == "bold") if (change.Type == "bold")
{ {
BoldButton.IsChecked = change.value == "true"; BoldButton.IsChecked = change.Value == "true";
} }
else if (change.type == "italic") else if (change.Type == "italic")
{ {
ItalicButton.IsChecked = change.value == "true"; ItalicButton.IsChecked = change.Value == "true";
} }
else if (change.type == "underline") else if (change.Type == "underline")
{ {
UnderlineButton.IsChecked = change.value == "true"; UnderlineButton.IsChecked = change.Value == "true";
} }
else if (change.type == "strikethrough") else if (change.Type == "strikethrough")
{ {
StrokeButton.IsChecked = change.value == "true"; StrokeButton.IsChecked = change.Value == "true";
} }
else if (change.type == "ol") else if (change.Type == "ol")
{ {
OrderedListButton.IsChecked = change.value == "true"; OrderedListButton.IsChecked = change.Value == "true";
} }
else if (change.type == "ul") else if (change.Type == "ul")
{ {
BulletListButton.IsChecked = change.value == "true"; BulletListButton.IsChecked = change.Value == "true";
} }
else if (change.type == "indent") else if (change.Type == "indent")
{ {
IncreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true; IncreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
} }
else if (change.type == "outdent") else if (change.Type == "outdent")
{ {
DecreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true; DecreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
} }
else if (change.type == "alignment") else if (change.Type == "alignment")
{ {
var parsedValue = change.value switch var parsedValue = change.Value switch
{ {
"jodit-icon_left" => 0, "jodit-icon_left" => 0,
"jodit-icon_center" => 1, "jodit-icon_center" => 1,

View File

@@ -92,13 +92,6 @@
Enabled="false" Enabled="false"
DisplayName="Wino Startup Service" /> DisplayName="Wino Startup Service" />
</uap5:Extension> </uap5:Extension>
<!-- Registration of full trust backend application. -->
<!--<uap:Extension Category="windows.appService">
<uap:AppService Name="Wino.AppService" />
</uap:Extension>-->
<!--<desktop:Extension Category="windows.fullTrustProcess" Executable="Wino.AppService.exe" />-->
</Extensions> </Extensions>
</Application> </Application>
</Applications> </Applications>

View File

@@ -16,7 +16,6 @@ using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Messages.Shell; using Wino.Core.Messages.Shell;
using Wino.Core.Messages.Synchronization; using Wino.Core.Messages.Synchronization;
using Wino.Core.Requests;
using Wino.Core.UWP.Extensions; using Wino.Core.UWP.Extensions;
using Wino.Dialogs; using Wino.Dialogs;
using Wino.Helpers; using Wino.Helpers;

View File

@@ -20,7 +20,7 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
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.Requests; using Wino.Core.Domain.Models.Reader;
using Wino.Core.Messages.Mails; using Wino.Core.Messages.Mails;
using Wino.Core.Messages.Shell; using Wino.Core.Messages.Shell;
using Wino.Extensions; using Wino.Extensions;
@@ -486,41 +486,41 @@ namespace Wino.Views
{ {
var change = JsonSerializer.Deserialize<WebViewMessage>(args.WebMessageAsJson); var change = JsonSerializer.Deserialize<WebViewMessage>(args.WebMessageAsJson);
if (change.type == "bold") if (change.Type == "bold")
{ {
BoldButton.IsChecked = change.value == "true"; BoldButton.IsChecked = change.Value == "true";
} }
else if (change.type == "italic") else if (change.Type == "italic")
{ {
ItalicButton.IsChecked = change.value == "true"; ItalicButton.IsChecked = change.Value == "true";
} }
else if (change.type == "underline") else if (change.Type == "underline")
{ {
UnderlineButton.IsChecked = change.value == "true"; UnderlineButton.IsChecked = change.Value == "true";
} }
else if (change.type == "strikethrough") else if (change.Type == "strikethrough")
{ {
StrokeButton.IsChecked = change.value == "true"; StrokeButton.IsChecked = change.Value == "true";
} }
else if (change.type == "ol") else if (change.Type == "ol")
{ {
OrderedListButton.IsChecked = change.value == "true"; OrderedListButton.IsChecked = change.Value == "true";
} }
else if (change.type == "ul") else if (change.Type == "ul")
{ {
BulletListButton.IsChecked = change.value == "true"; BulletListButton.IsChecked = change.Value == "true";
} }
else if (change.type == "indent") else if (change.Type == "indent")
{ {
IncreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true; IncreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
} }
else if (change.type == "outdent") else if (change.Type == "outdent")
{ {
DecreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true; DecreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
} }
else if (change.type == "alignment") else if (change.Type == "alignment")
{ {
var parsedValue = change.value switch var parsedValue = change.Value switch
{ {
"jodit-icon_left" => 0, "jodit-icon_left" => 0,
"jodit-icon_center" => 1, "jodit-icon_center" => 1,

View File

@@ -6,8 +6,8 @@ using MoreLinq;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Messages.Navigation; using Wino.Core.Messages.Navigation;
using Wino.Core.Requests;
using Wino.Mail.ViewModels.Data; using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Server;
using Wino.Views.Abstract; using Wino.Views.Abstract;
using Wino.Views.Account; using Wino.Views.Account;
using Wino.Views.Settings; using Wino.Views.Settings;

View File

@@ -336,8 +336,6 @@
<Compile Include="Services\ApplicationResourceManager.cs" /> <Compile Include="Services\ApplicationResourceManager.cs" />
<Compile Include="Services\DialogService.cs" /> <Compile Include="Services\DialogService.cs" />
<Compile Include="Services\LaunchProtocolService.cs" /> <Compile Include="Services\LaunchProtocolService.cs" />
<Compile Include="Services\PreferencesService.cs" />
<Compile Include="Services\StatePersistenceService.cs" />
<Compile Include="Services\ToastActivationService.cs" /> <Compile Include="Services\ToastActivationService.cs" />
<Compile Include="Services\WinoNavigationService.cs" /> <Compile Include="Services\WinoNavigationService.cs" />
<Compile Include="Styles\CommandBarItems.xaml.cs"> <Compile Include="Styles\CommandBarItems.xaml.cs">
@@ -770,7 +768,6 @@
<Content Include="Assets\Square44x44Logo.scale-200.png" /> <Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" /> <Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" /> <Content Include="Assets\Wide310x150Logo.scale-200.png" />
<None Include="Wino.Mail_TemporaryKey.pfx" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Assets\Thumbnails\uber.com.png" /> <Content Include="Assets\Thumbnails\uber.com.png" />
@@ -802,6 +799,10 @@
<Project>{d62f1c03-da57-4709-a640-0283296a8e66}</Project> <Project>{d62f1c03-da57-4709-a640-0283296a8e66}</Project>
<Name>Wino.Mail.ViewModels</Name> <Name>Wino.Mail.ViewModels</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj">
<Project>{0c307d7e-256f-448c-8265-5622a812fbcc}</Project>
<Name>Wino.Messaging</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<SDKReference Include="WindowsDesktop, Version=10.0.22621.0"> <SDKReference Include="WindowsDesktop, Version=10.0.22621.0">

View File

@@ -0,0 +1,14 @@
using System;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Messaging.Client.Accounts
{
/// <summary>
/// When menu item for the account is requested to be extended.
/// Additional properties are also supported to navigate to correct IMailItem.
/// </summary>
/// <param name="AutoSelectAccount">Account to extend menu item for.</param>
/// <param name="FolderId">Folder to select after expansion.</param>
/// <param name="NavigateMailItem">Mail item to select if possible in the expanded folder.</param>
public record AccountMenuItemExtended(Guid FolderId, IMailItem NavigateMailItem);
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
namespace Wino.Messaging.Client.Accounts
{
/// <summary>
/// Emitted when account menu items are reordered.
/// </summary>
/// <param name="newOrderDictionary">New order info.</param>
public record AccountMenuItemsReordered(Dictionary<Guid, int> newOrderDictionary);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Accounts
{
/// <summary>
/// When a full menu refresh for accounts menu is requested.
/// </summary>
public record AccountsMenuRefreshRequested(bool AutomaticallyNavigateFirstItem = true);
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Wino.Messaging.Client.Authorization
{
/// <summary>
/// When Google authentication makes a callback to the app via protocol activation to the app.
/// </summary>
/// <param name="AuthorizationResponseUri">Callback Uri that Google returned.</param>
public record ProtocolAuthorizationCallbackReceived(Uri AuthorizationResponseUri);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When rendered html is requested to cancel.
/// </summary>
public record CancelRenderingContentRequested;
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When reset all mail selections requested.
/// </summary>
public record ClearMailSelectionsRequested;
}

View File

@@ -0,0 +1,10 @@
using Wino.Core.Domain.Models.Reader;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When a new composing requested.
/// </summary>
/// <param name="RenderModel"></param>
public record CreateNewComposeMailRequested(MailRenderModel RenderModel);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When rendering frame should be disposed.
/// </summary>
public class DisposeRenderingFrameRequested { }
}

View File

@@ -0,0 +1,8 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When existing a new html is requested to be rendered due to mail selection or signature.
/// </summary>
/// <param name="HtmlBody">HTML to render in WebView2.</param>
public record HtmlRenderingRequested(string HtmlBody);
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When IMAP setup dialog requestes back breadcrumb navigation.
/// Not providing PageType will go back to previous page by doing back navigation.
/// </summary>
/// <param name="PageType">Type to go back.</param>
/// <param name="Parameter">Back parameters.</param>
public record ImapSetupBackNavigationRequested(Type PageType = null, object Parameter = null);
}

View File

@@ -0,0 +1,10 @@
using Wino.Core.Domain.Entities;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When user asked to dismiss IMAP setup dialog.
/// </summary>
/// <param name="CompletedServerInformation"> Validated server information that is ready to be saved to database. </param>
public record ImapSetupDismissRequested(CustomServerInformation CompletedServerInformation = null);
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When IMAP setup dialog breadcrumb navigation requested.
/// </summary>
/// <param name="PageType">Page type to navigate.</param>
/// <param name="Parameter">Navigation parameters.</param>
public record ImapSetupNavigationRequested(Type PageType, object Parameter);
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When a IMailItem needs to be navigated (or selected)
/// </summary>
/// <param name="UniqueMailId">UniqueId of the mail to navigate.</param>
/// <param name="ScrollToItem">Whether navigated item should be scrolled to or not..</param>
public record MailItemNavigationRequested(Guid UniqueMailId, bool ScrollToItem = false);
}

View File

@@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// Selects the given FolderMenuItem in the shell folders list.
/// </summary>
public class NavigateMailFolderEvent : NavigateMailFolderEventArgs
{
public NavigateMailFolderEvent(IBaseFolderMenuItem baseFolderMenuItem, TaskCompletionSource<bool> folderInitLoadAwaitTask = null)
: base(baseFolderMenuItem, folderInitLoadAwaitTask)
{
}
}
}

View File

@@ -0,0 +1,6 @@
using System;
namespace Wino.Messaging.Client.Mails
{
public record RefreshUnreadCountsMessage(Guid AccountId);
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When mail save as PDF requested.
/// </summary>
public record SaveAsPDFRequested(string FileSavePath);
}

View File

@@ -0,0 +1,8 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When selected mail count is changed.
/// </summary>
/// <param name="SelectedItemCount">New selected mail count.</param>
public record SelectedMailItemsChanged(int SelectedItemCount);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Navigation
{
/// <summary>
/// When back navigation is requested for breadcrumb pages.
/// </summary>
public record BackBreadcrumNavigationRequested { }
}

View File

@@ -0,0 +1,12 @@
using Wino.Core.Domain.Enums;
namespace Wino.Messaging.Client.Navigation
{
/// <summary>
/// When Breadcrumb control navigation requested.
/// </summary>
/// <param name="PageTitle">Title to display for the page.</param>
/// <param name="PageType">Enum equilavent of the page to navigate.</param>
/// <param name="Parameter">Additional parameters to the page.</param>
public record BreadcrumbNavigationRequested(string PageTitle, WinoPage PageType, object Parameter = null);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Navigation
{
/// <summary>
/// Navigates to settings page.
/// </summary>
public record NavigateSettingsRequested;
}

View File

@@ -0,0 +1,8 @@
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// When the application theme changed.
/// </summary>
/// <param name="IsUnderlyingThemeDark"></param>
public record ApplicationThemeChanged(bool IsUnderlyingThemeDark);
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using Wino.Core.Domain.Entities;
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// When
/// - There is no selection of any folder for any account
/// - Multiple accounts exists
/// - User clicked 'Create New Mail'
///
/// flyout must be presented to pick correct account.
/// This message will be picked up by UWP Shell.
/// </summary>
public record CreateNewMailWithMultipleAccountsRequested(IEnumerable<MailAccount> AllAccounts);
}

View File

@@ -0,0 +1,17 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// For displaying right sliding notification message in shell.
/// </summary>
/// <param name="Severity">Severity of notification.</param>
/// <param name="Title">Title of the message.</param>
/// <param name="Message">Message content.</param>
public record InfoBarMessageRequested(InfoBarMessageType Severity,
string Title,
string Message,
string ActionButtonTitle = "",
Action Action = null);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// When application language is updated.
/// </summary>
public record LanguageChanged;
}

View File

@@ -0,0 +1,4 @@
namespace Wino.Messaging.Client.Shell
{
public class MailtoProtocolMessageRequested { }
}

View File

@@ -0,0 +1,10 @@
using Wino.Core.Domain.Enums;
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// When navigation pane mode is changed.
/// </summary>
/// <param name="NewMode">New navigation mode.</param>
public record NavigationPaneModeChanged(MenuPaneMode NewMode);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// When reading mail state or reader pane narrowed state is changed.
/// </summary>
public record ShellStateUpdated;
}

Some files were not shown because too many files have changed in this diff Show More