Full trust Wino Server implementation. (#295)
* Separation of messages. Introducing Wino.Messages library. * Wino.Server and Wino.Packaging projects. Enabling full trust for UWP and app service connection manager basics. * Remove debug code. * Enable generating assembly info to deal with unsupported os platform warnings. * Fix server-client connection. * UIMessage communication. Single instancing for server and re-connection mechanism on suspension. * Removed IWinoSynchronizerFactory from UWP project. * Removal of background task service from core. * Delegating changes to UI and triggering new background synchronization. * Fix build error. * Moved core lib messages to Messaging project. * Better client-server communication. Handling of requests in the server. New synchronizer factory in the server. * WAM broker and MSAL token caching for OutlookAuthenticator. Handling account creation for Outlook. * WinoServerResponse basics. * Delegating protocol activation for Gmail authenticator. * Adding margin to searchbox to match action bar width. * Move libraries into lib folder. * Storing base64 encoded mime on draft creation instead of MimeMessage object. Fixes serialization/deserialization issue with S.T.Json * Scrollbar adjustments * WınoExpander for thread expander layout ıssue. * Handling synchronizer state changes. * Double init on background activation. * FIxing packaging issues and new Wino Mail launcher protocol for activation from full thrust process. * Remove debug deserialization. * Remove debug code. * Making sure the server connection is established when the app is launched. * Thrust -> Trust string replacement... * Rename package to Wino Mail * Enable translated values in the server. * Fixed an issue where toast activation can't find the clicked mail after the folder is initialized. * Revert debug code. * Change server background sync to every 3 minute and Inbox only synchronization. * Revert google auth changes. * App preferences page. * Changing tray icon visibility on preference change. * Start the server with invisible tray icon if set to invisible. * Reconnect button on the title bar. * Handling of toast actions. * Enable x86 build for server during packaging. * Get rid of old background tasks and v180 migration. * Terminate client when Exit clicked in server. * Introducing SynchronizationSource to prevent notifying UI after server tick synchronization. * Remove confirmAppClose restricted capability and unused debug code in manifest. * Closing the reconnect info popup when reconnect is clicked. * Custom RetryHandler for OutlookSynchronizer and separating client/server logs. * Running server on Windows startup. * Fix startup exe. * Fix for expander list view item paddings. * Force full sync on app launch instead of Inbox. * Fix draft creation. * Fix an issue with custom folder sync logic. * Reporting back account sync progress from server. * Fix sending drafts and missing notifications for imap. * Changing imap folder sync requirements. * Retain file count is set to 3. * Disabled swipe gestures temporarily due to native crash with SwipeControl * Save all attachments implementation. * Localization for save all attachments button. * Fix logging dates for logs. * Fixing ARM64 build. * Add ARM64 build config to packaging project. * Comment out OutOfProcPDB for ARM64. * Hnadling GONE response for Outlook folder synchronization.
This commit is contained in:
@@ -1,13 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Windows.ApplicationModel;
|
||||||
using Serilog;
|
|
||||||
using Windows.ApplicationModel.Background;
|
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 +10,10 @@ namespace Wino.BackgroundTasks
|
|||||||
{
|
{
|
||||||
var def = taskInstance.GetDeferral();
|
var def = taskInstance.GetDeferral();
|
||||||
|
|
||||||
try
|
// Run server on session connected by launching the Full Thrust process.
|
||||||
{
|
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
|
||||||
var services = new ServiceCollection();
|
|
||||||
|
|
||||||
services.RegisterCoreServices();
|
def.Complete();
|
||||||
services.RegisterCoreUWPServices();
|
|
||||||
|
|
||||||
var providere = services.BuildServiceProvider();
|
|
||||||
|
|
||||||
var backgroundTaskService = providere.GetService<IBackgroundSynchronizer>();
|
|
||||||
var dbService = providere.GetService<IDatabaseService>();
|
|
||||||
var logInitializer = providere.GetService<ILogInitializer>();
|
|
||||||
|
|
||||||
logInitializer.SetupLogger(ApplicationData.Current.LocalFolder.Path);
|
|
||||||
|
|
||||||
await dbService.InitializeAsync();
|
|
||||||
await backgroundTaskService.RunBackgroundSynchronizationAsync(Core.Domain.Enums.BackgroundSynchronizationReason.SessionConnected);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Background synchronization failed from background task.");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
def.Complete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,25 +18,6 @@
|
|||||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||||
<AllowCrossPlatformRetargeting>false</AllowCrossPlatformRetargeting>
|
<AllowCrossPlatformRetargeting>false</AllowCrossPlatformRetargeting>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
|||||||
@@ -8,8 +8,10 @@
|
|||||||
public const string WinoLocalDraftHeader = "X-Wino-Draft-Id";
|
public const string WinoLocalDraftHeader = "X-Wino-Draft-Id";
|
||||||
public const string LocalDraftStartPrefix = "localDraft_";
|
public const string LocalDraftStartPrefix = "localDraft_";
|
||||||
|
|
||||||
public const string ToastMailItemIdKey = nameof(ToastMailItemIdKey);
|
public const string ToastMailUniqueIdKey = nameof(ToastMailUniqueIdKey);
|
||||||
public const string ToastMailItemRemoteFolderIdKey = nameof(ToastMailItemRemoteFolderIdKey);
|
|
||||||
public const string ToastActionKey = nameof(ToastActionKey);
|
public const string ToastActionKey = nameof(ToastActionKey);
|
||||||
|
|
||||||
|
public const string ClientLogFile = "Client_.log";
|
||||||
|
public const string ServerLogFile = "Server_.log";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ namespace Wino.Core.Domain.Entities
|
|||||||
|
|
||||||
public Guid AccountId { get; set; }
|
public Guid AccountId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unique object storage for authenticators if needed.
|
||||||
|
/// </summary>
|
||||||
|
public string UniqueId { get; set; }
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
|
|
||||||
public void RefreshTokens(TokenInformationBase tokenInformationBase)
|
public void RefreshTokens(TokenInformationBase tokenInformationBase)
|
||||||
|
|||||||
12
Wino.Core.Domain/Enums/ServerBackgroundMode.cs
Normal file
12
Wino.Core.Domain/Enums/ServerBackgroundMode.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Wino.Core.Domain.Enums
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// What should happen to server app when the client is terminated.
|
||||||
|
/// </summary>
|
||||||
|
public enum ServerBackgroundMode
|
||||||
|
{
|
||||||
|
MinimizedTray, // Still runs, tray icon is visible.
|
||||||
|
Invisible, // Still runs, tray icon is invisible.
|
||||||
|
Terminate // Server is terminated as Wino terminates.
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Wino.Core.Domain/Enums/StartupBehaviorResult.cs
Normal file
11
Wino.Core.Domain/Enums/StartupBehaviorResult.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Wino.Core.Domain.Enums
|
||||||
|
{
|
||||||
|
public enum StartupBehaviorResult
|
||||||
|
{
|
||||||
|
Enabled,
|
||||||
|
Disabled,
|
||||||
|
DisabledByUser,
|
||||||
|
DisabledByPolicy,
|
||||||
|
Fatal
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Wino.Core.Domain/Enums/SynchronizationSource.cs
Normal file
12
Wino.Core.Domain/Enums/SynchronizationSource.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Wino.Core.Domain.Enums
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration for the source of synchronization.
|
||||||
|
/// Right now it can either be from the client or the server.
|
||||||
|
/// </summary>
|
||||||
|
public enum SynchronizationSource
|
||||||
|
{
|
||||||
|
Client,
|
||||||
|
Server
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
MailListPage,
|
MailListPage,
|
||||||
ReadComposePanePage,
|
ReadComposePanePage,
|
||||||
LanguageTimePage,
|
LanguageTimePage,
|
||||||
|
AppPreferencesPage,
|
||||||
SettingOptionsPage,
|
SettingOptionsPage,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
Wino.Core.Domain/Enums/WinoServerConnectionStatus.cs
Normal file
11
Wino.Core.Domain/Enums/WinoServerConnectionStatus.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Wino.Core.Domain.Enums
|
||||||
|
{
|
||||||
|
public enum WinoServerConnectionStatus
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Connecting,
|
||||||
|
Connected,
|
||||||
|
Disconnected,
|
||||||
|
Failed
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Exceptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An exception thrown when the background task execution policies are denied for some reason.
|
|
||||||
/// </summary>
|
|
||||||
public class BackgroundTaskExecutionRequestDeniedException : Exception { }
|
|
||||||
}
|
|
||||||
12
Wino.Core.Domain/Exceptions/WinoServerException.cs
Normal file
12
Wino.Core.Domain/Exceptions/WinoServerException.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Exceptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All server crash types. Wino Server ideally should not throw anything else than this Exception type.
|
||||||
|
/// </summary>
|
||||||
|
public class WinoServerException : Exception
|
||||||
|
{
|
||||||
|
public WinoServerException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Wino.Core.Domain/Extensions/MimeExtensions.cs
Normal file
20
Wino.Core.Domain/Extensions/MimeExtensions.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Extensions
|
||||||
|
{
|
||||||
|
public static class MimeExtensions
|
||||||
|
{
|
||||||
|
public static string GetBase64MimeMessage(this MimeKit.MimeMessage message)
|
||||||
|
{
|
||||||
|
using System.IO.MemoryStream memoryStream = new();
|
||||||
|
message.WriteTo(MimeKit.FormatOptions.Default, memoryStream);
|
||||||
|
byte[] buffer = memoryStream.GetBuffer();
|
||||||
|
int count = (int)memoryStream.Length;
|
||||||
|
|
||||||
|
return Convert.ToBase64String(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MimeKit.MimeMessage GetMimeMessageFromBase64(this string base64)
|
||||||
|
=> MimeKit.MimeMessage.Load(new System.IO.MemoryStream(Convert.FromBase64String(base64)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces
|
|
||||||
{
|
|
||||||
public interface IAppInitializerService
|
|
||||||
{
|
|
||||||
string GetApplicationDataFolder();
|
|
||||||
string GetPublisherSharedFolder();
|
|
||||||
|
|
||||||
Task MigrateAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
Wino.Core.Domain/Interfaces/IApplicationConfiguration.cs
Normal file
21
Wino.Core.Domain/Interfaces/IApplicationConfiguration.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
@@ -23,28 +22,12 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initial creation of token. Requires user interaction.
|
/// Initial creation of token. Requires user interaction.
|
||||||
/// This will save token into database, but still returns for account creation
|
/// This will cache the token but still returns for account creation
|
||||||
/// since account address is required.
|
/// since account address is required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="expectedAccountAddress">Token cache might ask for regeneration of token for specific
|
|
||||||
/// account address. If one is provided and re-generation native token doesn't belong to this address
|
|
||||||
/// token saving to database won't happen.</param>
|
|
||||||
/// <returns>Freshly created TokenInformation..</returns>
|
/// <returns>Freshly created TokenInformation..</returns>
|
||||||
Task<TokenInformation> GenerateTokenAsync(MailAccount account, bool saveToken);
|
Task<TokenInformation> GenerateTokenAsync(MailAccount account, bool saveToken);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Required for external authorization on launched browser to continue.
|
|
||||||
/// Used for Gmail.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="authorizationResponseUri">Response's redirect uri.</param>
|
|
||||||
void ContinueAuthorization(Uri authorizationResponseUri);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For external browser required authentications.
|
|
||||||
/// Canceling Gmail authentication dialog etc.
|
|
||||||
/// </summary>
|
|
||||||
void CancelAuthorization();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ClientId in case of needed for authorization/authentication.
|
/// ClientId in case of needed for authorization/authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
6
Wino.Core.Domain/Interfaces/IAuthenticatorTypes.cs
Normal file
6
Wino.Core.Domain/Interfaces/IAuthenticatorTypes.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Wino.Core.Domain.Interfaces
|
||||||
|
{
|
||||||
|
public interface IOutlookAuthenticator : IAuthenticator { }
|
||||||
|
public interface IGmailAuthenticator : IAuthenticator { }
|
||||||
|
public interface IImapAuthenticator : IAuthenticator { }
|
||||||
|
}
|
||||||
54
Wino.Core.Domain/Interfaces/IBaseSynchronizer.cs
Normal file
54
Wino.Core.Domain/Interfaces/IBaseSynchronizer.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Wino.Core.Domain/Interfaces/IClientMessage.cs
Normal file
8
Wino.Core.Domain/Interfaces/IClientMessage.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Wino.Core.Domain.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All messages that Client sends to Server and awaits a response in return.
|
||||||
|
/// For example; triggering a new synchronization request.
|
||||||
|
/// </summary>
|
||||||
|
public interface IClientMessage;
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
public interface ILogInitializer
|
public interface ILogInitializer
|
||||||
{
|
{
|
||||||
void SetupLogger(string logFolderPath);
|
void SetupLogger(string fullLogFilePath);
|
||||||
|
|
||||||
void RefreshLoggingLevel();
|
void RefreshLoggingLevel();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
{
|
{
|
||||||
Task<MailCopy> GetSingleMailItemAsync(string mailCopyId, string remoteFolderId);
|
Task<MailCopy> GetSingleMailItemAsync(string mailCopyId, string remoteFolderId);
|
||||||
Task<MailCopy> GetSingleMailItemAsync(Guid uniqueMailId);
|
Task<MailCopy> GetSingleMailItemAsync(Guid uniqueMailId);
|
||||||
Task<MailCopy> CreateDraftAsync(MailAccount composerAccount, MimeMessage generatedReplyMime, MimeMessage replyingMimeMessage = null, IMailItem replyingMailItem = null);
|
Task<MailCopy> CreateDraftAsync(MailAccount composerAccount, string generatedReplyMimeMessageBase64, MimeMessage replyingMimeMessage = null, IMailItem replyingMailItem = null);
|
||||||
Task<List<IMailItem>> FetchMailsAsync(MailListInitializationOptions options);
|
Task<List<IMailItem>> FetchMailsAsync(MailListInitializationOptions options);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -51,7 +51,16 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// <param name="newThreadId"></param>
|
/// <param name="newThreadId"></param>
|
||||||
Task MapLocalDraftAsync(string newMailCopyId, string newDraftId, string newThreadId);
|
Task MapLocalDraftAsync(string newMailCopyId, string newDraftId, string newThreadId);
|
||||||
|
|
||||||
Task<MimeMessage> CreateDraftMimeMessageAsync(Guid accountId, DraftCreationOptions options);
|
/// <summary>
|
||||||
|
/// Creates a draft message with the given options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountId">Account to create draft for.</param>
|
||||||
|
/// <param name="options">Draft creation options.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Base64 encoded string of MimeMessage object.
|
||||||
|
/// This is mainly for serialization purposes.
|
||||||
|
/// </returns>
|
||||||
|
Task<string> CreateDraftMimeBase64Async(Guid accountId, DraftCreationOptions options);
|
||||||
Task UpdateMailAsync(MailCopy mailCopy);
|
Task UpdateMailAsync(MailCopy mailCopy);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -92,5 +101,14 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="folderId">Folder id to get unread mails for.</param>
|
/// <param name="folderId">Folder id to get unread mails for.</param>
|
||||||
Task<List<MailCopy>> GetUnreadMailsByFolderIdAsync(Guid folderId);
|
Task<List<MailCopy>> GetUnreadMailsByFolderIdAsync(Guid folderId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the mail exists in the folder.
|
||||||
|
/// When deciding Create or Update existing mail, we need to check if the mail exists in the folder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageId">Message id</param>
|
||||||
|
/// <param name="folderId">Folder's local id.</param>
|
||||||
|
/// <returns>Whether mail exists in the folder or not.</returns>
|
||||||
|
Task<bool> IsMailExistsAsync(string mailCopyId, Guid folderId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,20 @@ 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);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Launches the default browser with the specified uri and waits for protocol activation to finish.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authenticator"></param>
|
||||||
|
/// <returns>Response callback from the browser.</returns>
|
||||||
|
Task<Uri> GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finalizes GetAuthorizationResponseUriAsync for current IAuthenticator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authorizationResponseUri"></param>
|
||||||
|
void ContinueAuthorization(Uri authorizationResponseUri);
|
||||||
|
|
||||||
bool IsAppRunning();
|
bool IsAppRunning();
|
||||||
|
|
||||||
string GetFullAppVersion();
|
string GetFullAppVersion();
|
||||||
@@ -21,5 +35,11 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// Some cryptographic shit is needed for requesting Google authentication in UWP.
|
/// Some cryptographic shit is needed for requesting Google authentication in UWP.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GoogleAuthorizationRequest GetGoogleAuthorizationRequest();
|
GoogleAuthorizationRequest GetGoogleAuthorizationRequest();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the function that returns a pointer for main window hwnd for UWP.
|
||||||
|
/// This is used to display WAM broker dialog on running UWP app called by a windowless server code.
|
||||||
|
/// </summary>
|
||||||
|
Func<IntPtr> GetCoreWindowHwnd { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,5 +150,10 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// Setting: Whether the next item should be automatically selected once the current item is moved or removed.
|
/// Setting: Whether the next item should be automatically selected once the current item is moved or removed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool AutoSelectNextItem { get; set; }
|
bool AutoSelectNextItem { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setting: Gets or sets what should happen to server app when the client is terminated.
|
||||||
|
/// </summary>
|
||||||
|
ServerBackgroundMode ServerTerminationBehavior { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
TRequest NativeRequest { get; }
|
TRequest NativeRequest { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IRequestBase
|
public interface IRequestBase : IClientMessage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Synchronizer option to perform.
|
/// Synchronizer option to perform.
|
||||||
|
|||||||
20
Wino.Core.Domain/Interfaces/IStartupBehaviorService.cs
Normal file
20
Wino.Core.Domain/Interfaces/IStartupBehaviorService.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Interfaces
|
||||||
|
{
|
||||||
|
public interface IStartupBehaviorService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether Wino Server is set to launch on startup or not.
|
||||||
|
/// </summary>
|
||||||
|
Task<StartupBehaviorResult> GetCurrentStartupBehaviorAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables/disables the current startup behavior for Wino Server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isEnabled">Whether to launch enabled or disabled.</param>
|
||||||
|
/// <returns>True if operation success, false if not.</returns>
|
||||||
|
Task<StartupBehaviorResult> ToggleStartupBehavior(bool isEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An interface for reporting progress of the synchronization.
|
|
||||||
/// Gmail does not support reporting folder progress.
|
|
||||||
/// For others, account progress is calculated based on the number of folders.
|
|
||||||
/// </summary>
|
|
||||||
public interface ISynchronizationProgress
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Reports account synchronization progress.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="accountId">Account id for the report.</param>
|
|
||||||
/// <param name="progress">Value. This is always between 0 - 100</param>
|
|
||||||
void AccountProgressUpdated(Guid accountId, int progress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
Wino.Core.Domain/Interfaces/ISynchronizerFactory.cs
Normal file
11
Wino.Core.Domain/Interfaces/ISynchronizerFactory.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Interfaces
|
||||||
|
{
|
||||||
|
public interface ISynchronizerFactory
|
||||||
|
{
|
||||||
|
Task<IBaseSynchronizer> GetAccountSynchronizerAsync(Guid accountId);
|
||||||
|
Task InitializeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 IUIMessage;
|
||||||
}
|
}
|
||||||
60
Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs
Normal file
60
Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Models.Server;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Interfaces
|
||||||
|
{
|
||||||
|
public interface IWinoServerConnectionManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When the connection status changes, this event will be triggered.
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler<WinoServerConnectionStatus> StatusChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the connection status.
|
||||||
|
/// </summary>
|
||||||
|
WinoServerConnectionStatus Status { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Launches Full Trust process (Wino Server) and awaits connection completion.
|
||||||
|
/// If connection is not established in 5 seconds, it will return false.
|
||||||
|
/// If the server process is already running, it'll connect to existing one.
|
||||||
|
/// If the server process is not running, it'll be launched and connection establishment is awaited.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether connection is established or not.</returns>
|
||||||
|
Task<bool> ConnectAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnects from existing connection and disposes the connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether disconnection is succesfull or not.</returns>
|
||||||
|
Task<bool> DisconnectAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues a new user request to be processed by Wino Server.
|
||||||
|
/// Healthy connection must present before calling this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">Request to queue for synchronizer in the server.</param>
|
||||||
|
/// <param name="accountId">Account id to queueu request for.</param>
|
||||||
|
Task QueueRequestAsync(IRequestBase request, Guid accountId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns response from server for the given request.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">Response type.</typeparam>
|
||||||
|
/// <typeparam name="TRequestType">Request type.</typeparam>
|
||||||
|
/// <param name="clientMessage">Request type.</param>
|
||||||
|
/// <returns>Response received from the server for the given TResponse type.</returns>
|
||||||
|
Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType clientMessage) where TRequestType : IClientMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IWinoServerConnectionManager<TAppServiceConnection> : IWinoServerConnectionManager, IInitializeAsync
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Existing connection handle to the server of TAppServiceConnection type.
|
||||||
|
/// </summary>
|
||||||
|
TAppServiceConnection Connection { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Wino.Core.Domain/Interfaces/IWinoSynchronizerFactory.cs
Normal file
11
Wino.Core.Domain/Interfaces/IWinoSynchronizerFactory.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
@@ -8,6 +9,7 @@ namespace Wino.Core.Domain.Models.MailItem
|
|||||||
{
|
{
|
||||||
public class DraftCreationOptions
|
public class DraftCreationOptions
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
public MimeMessage ReferenceMimeMessage { get; set; }
|
public MimeMessage ReferenceMimeMessage { get; set; }
|
||||||
public MailCopy ReferenceMailCopy { get; set; }
|
public MailCopy ReferenceMailCopy { get; set; }
|
||||||
public DraftCreationReason Reason { get; set; }
|
public DraftCreationReason Reason { get; set; }
|
||||||
|
|||||||
@@ -1,23 +1,49 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Text.Json.Serialization;
|
||||||
using System.Text;
|
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
|
using Wino.Core.Domain.Extensions;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.MailItem
|
namespace Wino.Core.Domain.Models.MailItem
|
||||||
{
|
{
|
||||||
public class DraftPreperationRequest : DraftCreationOptions
|
public class DraftPreperationRequest : DraftCreationOptions
|
||||||
{
|
{
|
||||||
public DraftPreperationRequest(MailAccount account, MailCopy createdLocalDraftCopy, MimeMessage createdLocalDraftMimeMessage)
|
public DraftPreperationRequest(MailAccount account, MailCopy createdLocalDraftCopy, string base64EncodedMimeMessage)
|
||||||
{
|
{
|
||||||
Account = account ?? throw new ArgumentNullException(nameof(account));
|
Account = account ?? throw new ArgumentNullException(nameof(account));
|
||||||
|
|
||||||
CreatedLocalDraftCopy = createdLocalDraftCopy ?? throw new ArgumentNullException(nameof(createdLocalDraftCopy));
|
CreatedLocalDraftCopy = createdLocalDraftCopy ?? throw new ArgumentNullException(nameof(createdLocalDraftCopy));
|
||||||
CreatedLocalDraftMimeMessage = createdLocalDraftMimeMessage ?? throw new ArgumentNullException(nameof(createdLocalDraftMimeMessage));
|
|
||||||
|
// MimeMessage is not serializable with System.Text.Json. Convert to base64 string.
|
||||||
|
// This is additional work when deserialization needed, but not much to do atm.
|
||||||
|
|
||||||
|
Base64LocalDraftMimeMessage = base64EncodedMimeMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
private DraftPreperationRequest() { }
|
||||||
|
|
||||||
public MailCopy CreatedLocalDraftCopy { get; set; }
|
public MailCopy CreatedLocalDraftCopy { get; set; }
|
||||||
public MimeMessage CreatedLocalDraftMimeMessage { get; set; }
|
|
||||||
|
public string Base64LocalDraftMimeMessage { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private MimeMessage createdLocalDraftMimeMessage;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public MimeMessage CreatedLocalDraftMimeMessage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (createdLocalDraftMimeMessage == null)
|
||||||
|
{
|
||||||
|
createdLocalDraftMimeMessage = Base64LocalDraftMimeMessage.GetMimeMessageFromBase64();
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdLocalDraftMimeMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public MailAccount Account { get; }
|
public MailAccount Account { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,49 @@
|
|||||||
using MimeKit;
|
using System.Text.Json.Serialization;
|
||||||
|
using MimeKit;
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
|
using Wino.Core.Domain.Extensions;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.MailItem
|
namespace Wino.Core.Domain.Models.MailItem
|
||||||
{
|
{
|
||||||
public record SendDraftPreparationRequest(MailCopy MailItem, MimeMessage Mime, MailItemFolder DraftFolder, MailItemFolder SentFolder, MailAccountPreferences AccountPreferences);
|
public class SendDraftPreparationRequest
|
||||||
|
{
|
||||||
|
public MailCopy MailItem { get; set; }
|
||||||
|
public string Base64MimeMessage { get; set; }
|
||||||
|
public MailItemFolder SentFolder { get; set; }
|
||||||
|
public MailItemFolder DraftFolder { get; set; }
|
||||||
|
public MailAccountPreferences AccountPreferences { get; set; }
|
||||||
|
|
||||||
|
public SendDraftPreparationRequest(MailCopy mailItem,
|
||||||
|
MailItemFolder sentFolder,
|
||||||
|
MailItemFolder draftFolder,
|
||||||
|
MailAccountPreferences accountPreferences,
|
||||||
|
string base64MimeMessage)
|
||||||
|
{
|
||||||
|
MailItem = mailItem;
|
||||||
|
SentFolder = sentFolder;
|
||||||
|
DraftFolder = draftFolder;
|
||||||
|
AccountPreferences = accountPreferences;
|
||||||
|
Base64MimeMessage = base64MimeMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
private SendDraftPreparationRequest() { }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private MimeMessage mime;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public MimeMessage Mime
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (mime == null)
|
||||||
|
{
|
||||||
|
mime = Base64MimeMessage.GetMimeMessageFromBase64();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
16
Wino.Core.Domain/Models/Reader/WebViewMessage.cs
Normal file
16
Wino.Core.Domain/Models/Reader/WebViewMessage.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Wino.Core.Domain/Models/Requests/ServerRequestPackage.cs
Normal file
18
Wino.Core.Domain/Models/Requests/ServerRequestPackage.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Models.Requests
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates request to queue and account for synchronizer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="AccountId"><inheritdoc/></param>
|
||||||
|
/// <param name="Request"></param>
|
||||||
|
/// <param name="Request">Prepared request for the server.</param>
|
||||||
|
/// <param name="AccountId">Whihc account to execute this request for.</param>
|
||||||
|
public record ServerRequestPackage(Guid AccountId, IRequestBase Request) : IClientMessage
|
||||||
|
{
|
||||||
|
public override string ToString() => $"Server Package: {Request.GetType().Name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
42
Wino.Core.Domain/Models/Server/WinoServerResponse.cs
Normal file
42
Wino.Core.Domain/Models/Server/WinoServerResponse.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using Wino.Core.Domain.Exceptions;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Models.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates responses from the Wino server.
|
||||||
|
/// Exceptions are stored separately in the Message and StackTrace properties due to serialization issues.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the expected response.</typeparam>
|
||||||
|
public class WinoServerResponse<T>
|
||||||
|
{
|
||||||
|
public bool IsSuccess { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public T Data { get; set; }
|
||||||
|
|
||||||
|
// protected WinoServerResponse() { }
|
||||||
|
|
||||||
|
public static WinoServerResponse<T> CreateSuccessResponse(T data)
|
||||||
|
{
|
||||||
|
return new WinoServerResponse<T>
|
||||||
|
{
|
||||||
|
IsSuccess = true,
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WinoServerResponse<T> CreateErrorResponse(string message)
|
||||||
|
{
|
||||||
|
return new WinoServerResponse<T>
|
||||||
|
{
|
||||||
|
IsSuccess = false,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ThrowIfFailed()
|
||||||
|
{
|
||||||
|
if (!IsSuccess)
|
||||||
|
throw new WinoServerException(Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.Synchronization
|
namespace Wino.Core.Domain.Models.Synchronization
|
||||||
{
|
{
|
||||||
@@ -27,11 +26,6 @@ namespace Wino.Core.Domain.Models.Synchronization
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Guid> SynchronizationFolderIds { get; set; }
|
public List<Guid> SynchronizationFolderIds { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A listener to be notified about the progress of the synchronization.
|
|
||||||
/// </summary>
|
|
||||||
public ISynchronizationProgress ProgressListener { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When doing a linked inbox synchronization, we must ignore reporting completion to the caller for each folder.
|
/// When doing a linked inbox synchronization, we must ignore reporting completion to the caller for each folder.
|
||||||
/// This Id will help tracking that. Id is unique, but this one can be the same for all sync requests
|
/// This Id will help tracking that. Id is unique, but this one can be the same for all sync requests
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
|
|
||||||
@@ -6,8 +7,14 @@ namespace Wino.Core.Domain.Models.Synchronization
|
|||||||
{
|
{
|
||||||
public class SynchronizationResult
|
public class SynchronizationResult
|
||||||
{
|
{
|
||||||
protected SynchronizationResult() { }
|
public SynchronizationResult() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the new downloaded messages from synchronization.
|
||||||
|
/// Server will create notifications for these messages.
|
||||||
|
/// It's ignored in serialization. Client should not react to this.
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
public IEnumerable<IMailItem> DownloadedMessages { get; set; } = new List<IMailItem>();
|
public IEnumerable<IMailItem> DownloadedMessages { get; set; } = new List<IMailItem>();
|
||||||
public SynchronizationCompletedState CompletedState { get; set; }
|
public SynchronizationCompletedState CompletedState { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,7 @@
|
|||||||
"ElementTheme_Default": "Use system setting",
|
"ElementTheme_Default": "Use system setting",
|
||||||
"ElementTheme_Light": "Light mode",
|
"ElementTheme_Light": "Light mode",
|
||||||
"Emoji": "Emoji",
|
"Emoji": "Emoji",
|
||||||
|
"Exception_WinoServerException": "Wino server failed.",
|
||||||
"Exception_ImapAutoDiscoveryFailed": "Couldn't find mailbox settings.",
|
"Exception_ImapAutoDiscoveryFailed": "Couldn't find mailbox settings.",
|
||||||
"Exception_ImapClientPoolFailed": "IMAP Client Pool failed.",
|
"Exception_ImapClientPoolFailed": "IMAP Client Pool failed.",
|
||||||
"Exception_AuthenticationCanceled": "Authentication canceled",
|
"Exception_AuthenticationCanceled": "Authentication canceled",
|
||||||
@@ -335,6 +336,7 @@
|
|||||||
"ProtocolLogAvailable_Message": "Protocol logs are available for diagnostics.",
|
"ProtocolLogAvailable_Message": "Protocol logs are available for diagnostics.",
|
||||||
"Results": "Results",
|
"Results": "Results",
|
||||||
"Right": "Right",
|
"Right": "Right",
|
||||||
|
"Reader_SaveAllAttachmentButtonText": "Save all attachments",
|
||||||
"SynchronizationFolderReport_Success": "up to date",
|
"SynchronizationFolderReport_Success": "up to date",
|
||||||
"SynchronizationFolderReport_Failed": "synchronization is failed",
|
"SynchronizationFolderReport_Failed": "synchronization is failed",
|
||||||
"SearchBarPlaceholder": "Search",
|
"SearchBarPlaceholder": "Search",
|
||||||
@@ -418,6 +420,19 @@
|
|||||||
"SettingsFolderMenuStyle_Description": "Change whether account folders should be nested inside an account menu item or not. Toggle this off if you like the old menu system in Windows Mail",
|
"SettingsFolderMenuStyle_Description": "Change whether account folders should be nested inside an account menu item or not. Toggle this off if you like the old menu system in Windows Mail",
|
||||||
"SettingsManageAccountSettings_Description": "Notifications, signatures, synchronization and other settings per account.",
|
"SettingsManageAccountSettings_Description": "Notifications, signatures, synchronization and other settings per account.",
|
||||||
"SettingsManageAccountSettings_Title": "Manage Account Settings",
|
"SettingsManageAccountSettings_Title": "Manage Account Settings",
|
||||||
|
"SettingsAppPreferences_Title": "App Preferences",
|
||||||
|
"SettingsAppPreferences_Description": "General settings / preferences for Wino Mail.",
|
||||||
|
"SettingsAppPreferences_CloseBehavior_Title": "Application close behavior",
|
||||||
|
"SettingsAppPreferences_CloseBehavior_Description": "What should happen when you close the app?",
|
||||||
|
"SettingsAppPreferences_StartupBehavior_Title": "Start minimized on Windows startup",
|
||||||
|
"SettingsAppPreferences_StartupBehavior_Description": "Allow Wino Mail to launch minimized when Windows starts. Always allow it to receive notifications.",
|
||||||
|
"SettingsAppPreferences_StartupBehavior_Enabled": "Wino Mail successfully set to be launched in the background on Windows startup.",
|
||||||
|
"SettingsAppPreferences_StartupBehavior_Disabled": "Wino Mail will not be launched on Windows startup. This will cause you to miss notifications when you restart your computer.",
|
||||||
|
"SettingsAppPreferences_StartupBehavior_DisabledByPolicy": "Your administrator or group policies disabled running applications on startup. Thus, Wino Mail can't be set to launch on Windows startup.",
|
||||||
|
"SettingsAppPreferences_StartupBehavior_DisabledByUser": "Please go to Task Manager -> Startup tab to allow Wino Mail to launch on Windows startup.",
|
||||||
|
"SettingsAppPreferences_StartupBehavior_FatalError": "Fatal error occurred while changing the startup mode for Wino Mail.",
|
||||||
|
"SettingsAppPreferences_StartupBehavior_Enable": "Enable",
|
||||||
|
"SettingsAppPreferences_StartupBehavior_Disable": "Disable",
|
||||||
"SettingsReorderAccounts_Title": "Reorder Accounts",
|
"SettingsReorderAccounts_Title": "Reorder Accounts",
|
||||||
"SettingsReorderAccounts_Description": "Change the order of accounts in the account list.",
|
"SettingsReorderAccounts_Description": "Change the order of accounts in the account list.",
|
||||||
"SettingsManageLink_Description": "Move items to add new link or remove existing link.",
|
"SettingsManageLink_Description": "Move items to add new link or remove existing link.",
|
||||||
@@ -523,6 +538,16 @@
|
|||||||
"SettingsSignature_AddCustomSignature_Button": "Add signature",
|
"SettingsSignature_AddCustomSignature_Button": "Add signature",
|
||||||
"SettingsSignature_EditSignature_Title": "Edit signature",
|
"SettingsSignature_EditSignature_Title": "Edit signature",
|
||||||
"SettingsSignature_DeleteSignature_Title": "Delete signature",
|
"SettingsSignature_DeleteSignature_Title": "Delete signature",
|
||||||
"SettingsSignature_NoneSignatureName": "None"
|
"SettingsSignature_NoneSignatureName": "None",
|
||||||
|
"SettingsAppPreferences_ServerBackgroundingMode_MinimizeTray_Title": "Minimize to system tray",
|
||||||
|
"SettingsAppPreferences_ServerBackgroundingMode_MinimizeTray_Description": "Wino Mail will keep running on the system tray. Available to launch by clicking on an icon. You will be notified as new mails arrive.",
|
||||||
|
"SettingsAppPreferences_ServerBackgroundingMode_Invisible_Title": "Run in the background",
|
||||||
|
"SettingsAppPreferences_ServerBackgroundingMode_Invisible_Description": "Wino Mail will keep running in the background. You will be notified as new mails arrive.",
|
||||||
|
"SettingsAppPreferences_ServerBackgroundingMode_Terminate_Title": "Terminate",
|
||||||
|
"SettingsAppPreferences_ServerBackgroundingMode_Terminate_Description": "Wino Mail will not keep running anywhere. You will not be notified as new mails arrive. Launch Wino Mail again to continue mail synchronization.",
|
||||||
|
"TitleBarServerDisconnectedButton_Title": "no connection",
|
||||||
|
"TitleBarServerDisconnectedButton_Description": "Wino is disconnected from the network. Click reconnect to restore connection.",
|
||||||
|
"TitleBarServerReconnectButton_Title": "reconnect",
|
||||||
|
"TitleBarServerReconnectingButton_Title": "connecting"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
125
Wino.Core.Domain/Translator.Designer.cs
generated
125
Wino.Core.Domain/Translator.Designer.cs
generated
@@ -548,6 +548,11 @@ namespace Wino.Core.Domain
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Emoji => Resources.GetTranslatedString(@"Emoji");
|
public static string Emoji => Resources.GetTranslatedString(@"Emoji");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wino server failed.
|
||||||
|
/// </summary>
|
||||||
|
public static string Exception_WinoServerException => Resources.GetTranslatedString(@"Exception_WinoServerException");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Couldn't find mailbox settings.
|
/// Couldn't find mailbox settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1698,6 +1703,11 @@ namespace Wino.Core.Domain
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Right => Resources.GetTranslatedString(@"Right");
|
public static string Right => Resources.GetTranslatedString(@"Right");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save all attachments
|
||||||
|
/// </summary>
|
||||||
|
public static string Reader_SaveAllAttachmentButtonText => Resources.GetTranslatedString(@"Reader_SaveAllAttachmentButtonText");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// up to date
|
/// up to date
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2113,6 +2123,71 @@ namespace Wino.Core.Domain
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static string SettingsManageAccountSettings_Title => Resources.GetTranslatedString(@"SettingsManageAccountSettings_Title");
|
public static string SettingsManageAccountSettings_Title => Resources.GetTranslatedString(@"SettingsManageAccountSettings_Title");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// App Preferences
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_Title => Resources.GetTranslatedString(@"SettingsAppPreferences_Title");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// General settings / preferences for Wino Mail.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_Description => Resources.GetTranslatedString(@"SettingsAppPreferences_Description");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Application close behavior
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_CloseBehavior_Title => Resources.GetTranslatedString(@"SettingsAppPreferences_CloseBehavior_Title");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What should happen when you close the app?
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_CloseBehavior_Description => Resources.GetTranslatedString(@"SettingsAppPreferences_CloseBehavior_Description");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start minimized on Windows startup
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_StartupBehavior_Title => Resources.GetTranslatedString(@"SettingsAppPreferences_StartupBehavior_Title");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allow Wino Mail to launch minimized when Windows starts. Always allow it to receive notifications.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_StartupBehavior_Description => Resources.GetTranslatedString(@"SettingsAppPreferences_StartupBehavior_Description");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wino Mail successfully set to be launched in the background on Windows startup.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_StartupBehavior_Enabled => Resources.GetTranslatedString(@"SettingsAppPreferences_StartupBehavior_Enabled");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wino Mail will not be launched on Windows startup. This will cause you to miss notifications when you restart your computer.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_StartupBehavior_Disabled => Resources.GetTranslatedString(@"SettingsAppPreferences_StartupBehavior_Disabled");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Your administrator or group policies disabled running applications on startup. Thus, Wino Mail can't be set to launch on Windows startup.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_StartupBehavior_DisabledByPolicy => Resources.GetTranslatedString(@"SettingsAppPreferences_StartupBehavior_DisabledByPolicy");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Please go to Task Manager -> Startup tab to allow Wino Mail to launch on Windows startup.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_StartupBehavior_DisabledByUser => Resources.GetTranslatedString(@"SettingsAppPreferences_StartupBehavior_DisabledByUser");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fatal error occurred while changing the startup mode for Wino Mail.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_StartupBehavior_FatalError => Resources.GetTranslatedString(@"SettingsAppPreferences_StartupBehavior_FatalError");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_StartupBehavior_Enable => Resources.GetTranslatedString(@"SettingsAppPreferences_StartupBehavior_Enable");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disable
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_StartupBehavior_Disable => Resources.GetTranslatedString(@"SettingsAppPreferences_StartupBehavior_Disable");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reorder Accounts
|
/// Reorder Accounts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2642,5 +2717,55 @@ namespace Wino.Core.Domain
|
|||||||
/// None
|
/// None
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string SettingsSignature_NoneSignatureName => Resources.GetTranslatedString(@"SettingsSignature_NoneSignatureName");
|
public static string SettingsSignature_NoneSignatureName => Resources.GetTranslatedString(@"SettingsSignature_NoneSignatureName");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimize to system tray
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_ServerBackgroundingMode_MinimizeTray_Title => Resources.GetTranslatedString(@"SettingsAppPreferences_ServerBackgroundingMode_MinimizeTray_Title");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wino Mail will keep running on the system tray. Available to launch by clicking on an icon. You will be notified as new mails arrive.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_ServerBackgroundingMode_MinimizeTray_Description => Resources.GetTranslatedString(@"SettingsAppPreferences_ServerBackgroundingMode_MinimizeTray_Description");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run in the background
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_ServerBackgroundingMode_Invisible_Title => Resources.GetTranslatedString(@"SettingsAppPreferences_ServerBackgroundingMode_Invisible_Title");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wino Mail will keep running in the background. You will be notified as new mails arrive.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_ServerBackgroundingMode_Invisible_Description => Resources.GetTranslatedString(@"SettingsAppPreferences_ServerBackgroundingMode_Invisible_Description");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Terminate
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_ServerBackgroundingMode_Terminate_Title => Resources.GetTranslatedString(@"SettingsAppPreferences_ServerBackgroundingMode_Terminate_Title");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wino Mail will not keep running anywhere. You will not be notified as new mails arrive. Launch Wino Mail again to continue mail synchronization.
|
||||||
|
/// </summary>
|
||||||
|
public static string SettingsAppPreferences_ServerBackgroundingMode_Terminate_Description => Resources.GetTranslatedString(@"SettingsAppPreferences_ServerBackgroundingMode_Terminate_Description");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// no connection
|
||||||
|
/// </summary>
|
||||||
|
public static string TitleBarServerDisconnectedButton_Title => Resources.GetTranslatedString(@"TitleBarServerDisconnectedButton_Title");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wino is disconnected from the network. Click reconnect to restore connection.
|
||||||
|
/// </summary>
|
||||||
|
public static string TitleBarServerDisconnectedButton_Description => Resources.GetTranslatedString(@"TitleBarServerDisconnectedButton_Description");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// reconnect
|
||||||
|
/// </summary>
|
||||||
|
public static string TitleBarServerReconnectButton_Title => Resources.GetTranslatedString(@"TitleBarServerReconnectButton_Title");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// connecting
|
||||||
|
/// </summary>
|
||||||
|
public static string TitleBarServerReconnectingButton_Title => Resources.GetTranslatedString(@"TitleBarServerReconnectingButton_Title");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,19 @@
|
|||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
<LangVersion>12.0</LangVersion>
|
<LangVersion>12.0</LangVersion>
|
||||||
|
<Platforms>AnyCPU;x64;x86</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Models\Communication\**" />
|
||||||
|
<EmbeddedResource Remove="Models\Communication\**" />
|
||||||
|
<None Remove="Models\Communication\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<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,9 +60,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="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="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" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||||
|
|||||||
@@ -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.Services;
|
using Wino.Services;
|
||||||
@@ -9,19 +10,24 @@ 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.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>();
|
||||||
|
services.AddTransient<IStartupBehaviorService, StartupBehaviorService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
Wino.Core.UWP/Extensions/StartupTaskStateExtensions.cs
Normal file
25
Wino.Core.UWP/Extensions/StartupTaskStateExtensions.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using Windows.ApplicationModel;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Core.UWP.Extensions
|
||||||
|
{
|
||||||
|
public static class StartupTaskStateExtensions
|
||||||
|
{
|
||||||
|
public static StartupBehaviorResult AsStartupBehaviorResult(this StartupTaskState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case StartupTaskState.Disabled:
|
||||||
|
case StartupTaskState.DisabledByPolicy:
|
||||||
|
return StartupBehaviorResult.Disabled;
|
||||||
|
case StartupTaskState.DisabledByUser:
|
||||||
|
return StartupBehaviorResult.DisabledByUser;
|
||||||
|
case StartupTaskState.Enabled:
|
||||||
|
case StartupTaskState.EnabledByPolicy:
|
||||||
|
return StartupBehaviorResult.Enabled;
|
||||||
|
default:
|
||||||
|
return StartupBehaviorResult.Fatal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Serilog;
|
|
||||||
using Windows.Storage;
|
|
||||||
using Wino.Core;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
using Wino.Core.Domain.Exceptions;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
using Wino.Core.Domain.Models.Synchronization;
|
|
||||||
using Wino.Core.Synchronizers;
|
|
||||||
|
|
||||||
namespace Wino.Services
|
|
||||||
{
|
|
||||||
public interface IBackgroundSynchronizer
|
|
||||||
{
|
|
||||||
Task RunBackgroundSynchronizationAsync(BackgroundSynchronizationReason reason);
|
|
||||||
void CreateLock();
|
|
||||||
void ReleaseLock();
|
|
||||||
bool IsBackgroundSynchronizationLocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Service responsible for handling background synchronization on timer and session connected events.
|
|
||||||
/// </summary>
|
|
||||||
public class BackgroundSynchronizer : IBackgroundSynchronizer
|
|
||||||
{
|
|
||||||
private const string BackgroundSynchronizationLock = nameof(BackgroundSynchronizationLock);
|
|
||||||
|
|
||||||
private readonly IAccountService _accountService;
|
|
||||||
private readonly IFolderService _folderService;
|
|
||||||
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
|
|
||||||
|
|
||||||
public BackgroundSynchronizer(IAccountService accountService,
|
|
||||||
IFolderService folderService,
|
|
||||||
IWinoSynchronizerFactory winoSynchronizerFactory)
|
|
||||||
{
|
|
||||||
_accountService = accountService;
|
|
||||||
_folderService = folderService;
|
|
||||||
_winoSynchronizerFactory = winoSynchronizerFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CreateLock() => ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] = true;
|
|
||||||
public void ReleaseLock() => ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] = false;
|
|
||||||
|
|
||||||
public bool IsBackgroundSynchronizationLocked()
|
|
||||||
=> ApplicationData.Current.LocalSettings.Values.ContainsKey(BackgroundSynchronizationLock)
|
|
||||||
&& ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] is bool boolValue && boolValue;
|
|
||||||
|
|
||||||
public async Task RunBackgroundSynchronizationAsync(BackgroundSynchronizationReason reason)
|
|
||||||
{
|
|
||||||
Log.Information($"{reason} background synchronization is kicked in.");
|
|
||||||
|
|
||||||
// This should never crash.
|
|
||||||
// We might be in-process or out-of-process.
|
|
||||||
|
|
||||||
//if (IsBackgroundSynchronizationLocked())
|
|
||||||
//{
|
|
||||||
// Log.Warning("Background synchronization is locked. Hence another background synchronization is canceled.");
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
CreateLock();
|
|
||||||
|
|
||||||
var accounts = await _accountService.GetAccountsAsync();
|
|
||||||
|
|
||||||
foreach (var account in accounts)
|
|
||||||
{
|
|
||||||
// We can't sync broken account.
|
|
||||||
if (account.AttentionReason != AccountAttentionReason.None)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// We can't synchronize without system folder setup is done.
|
|
||||||
//var isSystemFolderSetupDone = await _folderService.CheckSystemFolderSetupDoneAsync(account.Id);
|
|
||||||
|
|
||||||
//// No need to throw here. It's a background process.
|
|
||||||
//if (!isSystemFolderSetupDone)
|
|
||||||
// continue;
|
|
||||||
|
|
||||||
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(account.Id);
|
|
||||||
|
|
||||||
if (synchronizer.State != AccountSynchronizerState.Idle)
|
|
||||||
{
|
|
||||||
Log.Information("Skipping background synchronization for {Name} since current state is {State}", synchronizer.Account.Name, synchronizer.State);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await HandleSynchronizationAsync(synchronizer, reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error($"[BackgroundSynchronization] Failed with message {ex.Message}");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ReleaseLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleSynchronizationAsync(IBaseSynchronizer synchronizer, BackgroundSynchronizationReason reason)
|
|
||||||
{
|
|
||||||
if (synchronizer.State != AccountSynchronizerState.Idle) return;
|
|
||||||
|
|
||||||
var account = synchronizer.Account;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// SessionConnected will do Full synchronization for logon, Timer task will do Inbox only.
|
|
||||||
|
|
||||||
var syncType = reason == BackgroundSynchronizationReason.SessionConnected ? SynchronizationType.Full : SynchronizationType.Inbox;
|
|
||||||
|
|
||||||
var options = new SynchronizationOptions()
|
|
||||||
{
|
|
||||||
AccountId = account.Id,
|
|
||||||
Type = syncType,
|
|
||||||
};
|
|
||||||
|
|
||||||
await synchronizer.SynchronizeAsync(options);
|
|
||||||
}
|
|
||||||
catch (AuthenticationAttentionException authenticationAttentionException)
|
|
||||||
{
|
|
||||||
Log.Error(authenticationAttentionException, $"[BackgroundSync] Invalid credentials for account {account.Address}");
|
|
||||||
|
|
||||||
account.AttentionReason = AccountAttentionReason.InvalidCredentials;
|
|
||||||
await _accountService.UpdateAccountAsync(account);
|
|
||||||
}
|
|
||||||
catch (SystemFolderConfigurationMissingException configMissingException)
|
|
||||||
{
|
|
||||||
Log.Error(configMissingException, $"[BackgroundSync] Missing system folder configuration for account {account.Address}");
|
|
||||||
|
|
||||||
account.AttentionReason = AccountAttentionReason.MissingSystemFolderConfiguration;
|
|
||||||
await _accountService.UpdateAccountAsync(account);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "[BackgroundSync] Synchronization failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +1,60 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Windows.ApplicationModel.Background;
|
using Windows.ApplicationModel.Background;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Exceptions;
|
|
||||||
|
|
||||||
namespace Wino.Core.UWP.Services
|
namespace Wino.Core.UWP.Services
|
||||||
{
|
{
|
||||||
public class BackgroundTaskService : IBackgroundTaskService
|
public class BackgroundTaskService : IBackgroundTaskService
|
||||||
{
|
{
|
||||||
private const string IsBackgroundExecutionDeniedMessageKey = nameof(IsBackgroundExecutionDeniedMessageKey);
|
private const string Is180BackgroundTasksRegisteredKey = nameof(Is180BackgroundTasksRegisteredKey);
|
||||||
|
|
||||||
public const string BackgroundSynchronizationTimerTaskNameEx = nameof(BackgroundSynchronizationTimerTaskNameEx);
|
|
||||||
public const string ToastActivationTaskEx = nameof(ToastActivationTaskEx);
|
public const string ToastActivationTaskEx = nameof(ToastActivationTaskEx);
|
||||||
|
|
||||||
private const string SessionConnectedTaskEntryPoint = "Wino.BackgroundTasks.SessionConnectedTask";
|
private const string SessionConnectedTaskEntryPoint = "Wino.BackgroundTasks.SessionConnectedTask";
|
||||||
private const string SessionConnectedTaskName = "SessionConnectedTask";
|
private const string SessionConnectedTaskName = "SessionConnectedTask";
|
||||||
|
|
||||||
private readonly IConfigurationService _configurationService;
|
private readonly IConfigurationService _configurationService;
|
||||||
private readonly List<string> registeredBackgroundTaskNames = new List<string>();
|
|
||||||
|
|
||||||
public BackgroundTaskService(IConfigurationService configurationService)
|
public BackgroundTaskService(IConfigurationService configurationService)
|
||||||
{
|
{
|
||||||
_configurationService = configurationService;
|
_configurationService = configurationService;
|
||||||
|
|
||||||
LoadRegisteredTasks();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calling WinRT all the time for registered tasks might be slow. Cache them on ctor.
|
|
||||||
private void LoadRegisteredTasks()
|
|
||||||
{
|
|
||||||
foreach (var task in BackgroundTaskRegistration.AllTasks)
|
|
||||||
{
|
|
||||||
registeredBackgroundTaskNames.Add(task.Value.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Information($"Found {registeredBackgroundTaskNames.Count} registered background tasks. [{string.Join(',', registeredBackgroundTaskNames)}]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task HandleBackgroundTaskRegistrations()
|
public async Task HandleBackgroundTaskRegistrations()
|
||||||
{
|
{
|
||||||
|
bool is180BackgroundTaskRegistered = _configurationService.Get<bool>(Is180BackgroundTasksRegisteredKey);
|
||||||
|
|
||||||
|
// Don't re-register tasks.
|
||||||
|
if (is180BackgroundTaskRegistered) return;
|
||||||
|
|
||||||
var response = await BackgroundExecutionManager.RequestAccessAsync();
|
var response = await BackgroundExecutionManager.RequestAccessAsync();
|
||||||
|
|
||||||
if (response == BackgroundAccessStatus.DeniedBySystemPolicy ||
|
if (response != BackgroundAccessStatus.DeniedBySystemPolicy ||
|
||||||
response == BackgroundAccessStatus.DeniedByUser)
|
response != BackgroundAccessStatus.DeniedByUser)
|
||||||
{
|
{
|
||||||
// Only notify users about disabled background execution once.
|
// Unregister all tasks and register new ones.
|
||||||
|
|
||||||
bool isNotifiedBefore = _configurationService.Get(IsBackgroundExecutionDeniedMessageKey, false);
|
UnregisterAllBackgroundTask();
|
||||||
|
|
||||||
if (!isNotifiedBefore)
|
|
||||||
{
|
|
||||||
_configurationService.Set(IsBackgroundExecutionDeniedMessageKey, true);
|
|
||||||
|
|
||||||
throw new BackgroundTaskExecutionRequestDeniedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RegisterSessionConnectedTask();
|
RegisterSessionConnectedTask();
|
||||||
RegisterTimerSynchronizationTask();
|
|
||||||
RegisterToastNotificationHandlerBackgroundTask();
|
_configurationService.Set(Is180BackgroundTasksRegisteredKey, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsBackgroundTaskRegistered(string taskName)
|
|
||||||
=> registeredBackgroundTaskNames.Contains(taskName);
|
|
||||||
|
|
||||||
public void UnregisterAllBackgroundTask()
|
public void UnregisterAllBackgroundTask()
|
||||||
{
|
{
|
||||||
foreach (var task in BackgroundTaskRegistration.AllTasks)
|
foreach (var task in BackgroundTaskRegistration.AllTasks)
|
||||||
{
|
{
|
||||||
task.Value.Unregister(true);
|
task.Value.Unregister(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void LogBackgroundTaskRegistration(string taskName)
|
Log.Information("Unregistered all background tasks.");
|
||||||
{
|
|
||||||
Log.Information($"Registered new background task -> {taskName}");
|
|
||||||
|
|
||||||
registeredBackgroundTaskNames.Add($"{taskName}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BackgroundTaskRegistration RegisterSessionConnectedTask()
|
private BackgroundTaskRegistration RegisterSessionConnectedTask()
|
||||||
{
|
{
|
||||||
if (IsBackgroundTaskRegistered(SessionConnectedTaskName)) return null;
|
|
||||||
|
|
||||||
var builder = new BackgroundTaskBuilder
|
var builder = new BackgroundTaskBuilder
|
||||||
{
|
{
|
||||||
Name = SessionConnectedTaskName,
|
Name = SessionConnectedTaskName,
|
||||||
@@ -95,41 +63,6 @@ namespace Wino.Core.UWP.Services
|
|||||||
|
|
||||||
builder.SetTrigger(new SystemTrigger(SystemTriggerType.SessionConnected, false));
|
builder.SetTrigger(new SystemTrigger(SystemTriggerType.SessionConnected, false));
|
||||||
|
|
||||||
LogBackgroundTaskRegistration(SessionConnectedTaskName);
|
|
||||||
|
|
||||||
return builder.Register();
|
|
||||||
}
|
|
||||||
|
|
||||||
private BackgroundTaskRegistration RegisterToastNotificationHandlerBackgroundTask()
|
|
||||||
{
|
|
||||||
if (IsBackgroundTaskRegistered(ToastActivationTaskEx)) return null;
|
|
||||||
|
|
||||||
var builder = new BackgroundTaskBuilder
|
|
||||||
{
|
|
||||||
Name = ToastActivationTaskEx
|
|
||||||
};
|
|
||||||
|
|
||||||
builder.SetTrigger(new ToastNotificationActionTrigger());
|
|
||||||
|
|
||||||
LogBackgroundTaskRegistration(ToastActivationTaskEx);
|
|
||||||
|
|
||||||
return builder.Register();
|
|
||||||
}
|
|
||||||
|
|
||||||
private BackgroundTaskRegistration RegisterTimerSynchronizationTask()
|
|
||||||
{
|
|
||||||
if (IsBackgroundTaskRegistered(BackgroundSynchronizationTimerTaskNameEx)) return null;
|
|
||||||
|
|
||||||
var builder = new BackgroundTaskBuilder
|
|
||||||
{
|
|
||||||
Name = BackgroundSynchronizationTimerTaskNameEx
|
|
||||||
};
|
|
||||||
|
|
||||||
builder.SetTrigger(new TimeTrigger(15, false));
|
|
||||||
builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
|
|
||||||
|
|
||||||
LogBackgroundTaskRegistration(BackgroundSynchronizationTimerTaskNameEx);
|
|
||||||
|
|
||||||
return builder.Register();
|
return builder.Register();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,17 @@ using Windows.Storage;
|
|||||||
using Windows.Storage.Streams;
|
using Windows.Storage.Streams;
|
||||||
using Windows.System;
|
using Windows.System;
|
||||||
using Windows.UI.Shell;
|
using Windows.UI.Shell;
|
||||||
using Windows.UI.Xaml;
|
|
||||||
using Windows.UI.Xaml.Controls;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Authorization;
|
using Wino.Core.Domain.Models.Authorization;
|
||||||
|
using Wino.Core.Domain.Exceptions;
|
||||||
|
using Wino.Core.Domain;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if WINDOWS_UWP
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Wino.Services
|
namespace Wino.Services
|
||||||
{
|
{
|
||||||
@@ -20,8 +27,18 @@ namespace Wino.Services
|
|||||||
{
|
{
|
||||||
private string _mimeMessagesFolder;
|
private string _mimeMessagesFolder;
|
||||||
private string _editorBundlePath;
|
private string _editorBundlePath;
|
||||||
|
private TaskCompletionSource<Uri> authorizationCompletedTaskSource;
|
||||||
|
|
||||||
public string GetWebAuthenticationBrokerUri() => WebAuthenticationBroker.GetCurrentApplicationCallbackUri().AbsoluteUri;
|
public Func<IntPtr> GetCoreWindowHwnd { get; set; }
|
||||||
|
|
||||||
|
public string GetWebAuthenticationBrokerUri()
|
||||||
|
{
|
||||||
|
#if WINDOWS_UWP
|
||||||
|
return WebAuthenticationBroker.GetCurrentApplicationCallbackUri().AbsoluteUri;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string> GetMimeMessageStoragePath()
|
public async Task<string> GetMimeMessageStoragePath()
|
||||||
{
|
{
|
||||||
@@ -91,7 +108,16 @@ namespace Wino.Services
|
|||||||
return _editorBundlePath;
|
return _editorBundlePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsAppRunning() => (Window.Current?.Content as Frame)?.Content != null;
|
[Obsolete("This should be removed. There should be no functionality.")]
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
@@ -100,7 +126,7 @@ namespace Wino.Services
|
|||||||
await Launcher.LaunchFileAsync(file);
|
await Launcher.LaunchFileAsync(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task LaunchUriAsync(Uri uri) => Xamarin.Essentials.Launcher.OpenAsync(uri);
|
public Task LaunchUriAsync(Uri uri) => Launcher.LaunchUriAsync(uri).AsTask();
|
||||||
|
|
||||||
public string GetFullAppVersion()
|
public string GetFullAppVersion()
|
||||||
{
|
{
|
||||||
@@ -127,5 +153,28 @@ namespace Wino.Services
|
|||||||
|
|
||||||
await taskbarManager.RequestPinCurrentAppAsync();
|
await taskbarManager.RequestPinCurrentAppAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Uri> GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri)
|
||||||
|
{
|
||||||
|
if (authorizationCompletedTaskSource != null)
|
||||||
|
{
|
||||||
|
authorizationCompletedTaskSource.TrySetException(new AuthenticationException(Translator.Exception_AuthenticationCanceled));
|
||||||
|
authorizationCompletedTaskSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizationCompletedTaskSource = new TaskCompletionSource<Uri>();
|
||||||
|
|
||||||
|
await LaunchUriAsync(new Uri(authorizationUri));
|
||||||
|
|
||||||
|
return await authorizationCompletedTaskSource.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ContinueAuthorization(Uri authorizationResponseUri)
|
||||||
|
{
|
||||||
|
if (authorizationCompletedTaskSource != null)
|
||||||
|
{
|
||||||
|
authorizationCompletedTaskSource.TrySetResult(authorizationResponseUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Toolkit.Uwp.Notifications;
|
using CommunityToolkit.WinUI.Notifications;
|
||||||
using Windows.Data.Xml.Dom;
|
using Windows.Data.Xml.Dom;
|
||||||
using Windows.UI.Notifications;
|
using Windows.UI.Notifications;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
@@ -70,8 +70,8 @@ namespace Wino.Core.UWP.Services
|
|||||||
|
|
||||||
foreach (var mailItem in validItems)
|
foreach (var mailItem in validItems)
|
||||||
{
|
{
|
||||||
if (mailItem.IsRead)
|
//if (mailItem.IsRead)
|
||||||
continue;
|
// continue;
|
||||||
|
|
||||||
var builder = new ToastContentBuilder();
|
var builder = new ToastContentBuilder();
|
||||||
builder.SetToastScenario(ToastScenario.Default);
|
builder.SetToastScenario(ToastScenario.Default);
|
||||||
@@ -104,11 +104,11 @@ namespace Wino.Core.UWP.Services
|
|||||||
builder.AddText(mailItem.Subject);
|
builder.AddText(mailItem.Subject);
|
||||||
builder.AddText(mailItem.PreviewText);
|
builder.AddText(mailItem.PreviewText);
|
||||||
|
|
||||||
builder.AddArgument(Constants.ToastMailItemIdKey, mailItem.UniqueId.ToString());
|
builder.AddArgument(Constants.ToastMailUniqueIdKey, mailItem.UniqueId.ToString());
|
||||||
builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate);
|
builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate);
|
||||||
|
|
||||||
builder.AddButton(GetMarkedAsRead(mailItem.Id, mailItem.AssignedFolder.RemoteFolderId));
|
builder.AddButton(GetMarkedAsRead(mailItem.UniqueId));
|
||||||
builder.AddButton(GetDeleteButton(mailItem.Id, mailItem.AssignedFolder.RemoteFolderId));
|
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
|
||||||
builder.AddButton(GetDismissButton());
|
builder.AddButton(GetDismissButton());
|
||||||
|
|
||||||
builder.Show();
|
builder.Show();
|
||||||
@@ -123,21 +123,19 @@ namespace Wino.Core.UWP.Services
|
|||||||
.SetDismissActivation()
|
.SetDismissActivation()
|
||||||
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/dismiss.png"));
|
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/dismiss.png"));
|
||||||
|
|
||||||
private ToastButton GetDeleteButton(string mailCopyId, string remoteFolderId)
|
private ToastButton GetDeleteButton(Guid mailUniqueId)
|
||||||
=> new ToastButton()
|
=> new ToastButton()
|
||||||
.SetContent(Translator.MailOperation_Delete)
|
.SetContent(Translator.MailOperation_Delete)
|
||||||
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/delete.png"))
|
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/delete.png"))
|
||||||
.AddArgument(Constants.ToastMailItemIdKey, mailCopyId)
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
.AddArgument(Constants.ToastMailItemRemoteFolderIdKey, remoteFolderId)
|
|
||||||
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete)
|
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete)
|
||||||
.SetBackgroundActivation();
|
.SetBackgroundActivation();
|
||||||
|
|
||||||
private ToastButton GetMarkedAsRead(string mailCopyId, string remoteFolderId)
|
private ToastButton GetMarkedAsRead(Guid mailUniqueId)
|
||||||
=> new ToastButton()
|
=> new ToastButton()
|
||||||
.SetContent(Translator.MailOperation_MarkAsRead)
|
.SetContent(Translator.MailOperation_MarkAsRead)
|
||||||
.SetImageUri(new System.Uri("ms-appx:///Assets/NotificationIcons/markread.png"))
|
.SetImageUri(new System.Uri("ms-appx:///Assets/NotificationIcons/markread.png"))
|
||||||
.AddArgument(Constants.ToastMailItemIdKey, mailCopyId)
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
.AddArgument(Constants.ToastMailItemRemoteFolderIdKey, remoteFolderId)
|
|
||||||
.AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead)
|
.AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead)
|
||||||
.SetBackgroundActivation();
|
.SetBackgroundActivation();
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
@@ -201,5 +201,11 @@ namespace Wino.Services
|
|||||||
get => _configurationService.Get(nameof(AutoSelectNextItem), true);
|
get => _configurationService.Get(nameof(AutoSelectNextItem), true);
|
||||||
set => SaveProperty(propertyName: nameof(AutoSelectNextItem), value);
|
set => SaveProperty(propertyName: nameof(AutoSelectNextItem), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServerBackgroundMode ServerTerminationBehavior
|
||||||
|
{
|
||||||
|
get => _configurationService.Get(nameof(ServerTerminationBehavior), ServerBackgroundMode.MinimizedTray);
|
||||||
|
set => SaveProperty(propertyName: nameof(ServerTerminationBehavior), value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
37
Wino.Core.UWP/Services/StartupBehaviorService.cs
Normal file
37
Wino.Core.UWP/Services/StartupBehaviorService.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Windows.ApplicationModel;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.UWP.Extensions;
|
||||||
|
|
||||||
|
namespace Wino.Core.UWP.Services
|
||||||
|
{
|
||||||
|
public class StartupBehaviorService : IStartupBehaviorService
|
||||||
|
{
|
||||||
|
private const string WinoServerTaskId = "WinoServer";
|
||||||
|
|
||||||
|
public async Task<StartupBehaviorResult> ToggleStartupBehavior(bool isEnabled)
|
||||||
|
{
|
||||||
|
var task = await StartupTask.GetAsync(WinoServerTaskId);
|
||||||
|
|
||||||
|
if (isEnabled)
|
||||||
|
{
|
||||||
|
await task.RequestEnableAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
task.Disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await GetCurrentStartupBehaviorAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StartupBehaviorResult> GetCurrentStartupBehaviorAsync()
|
||||||
|
{
|
||||||
|
var task = await StartupTask.GetAsync(WinoServerTaskId);
|
||||||
|
|
||||||
|
return task.State.AsStartupBehaviorResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,8 @@
|
|||||||
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.Messaging.Client.Shell;
|
||||||
|
|
||||||
namespace Wino.Services
|
namespace Wino.Services
|
||||||
{
|
{
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,10 +20,10 @@ using Wino.Core.Domain.Enums;
|
|||||||
using Wino.Core.Domain.Exceptions;
|
using Wino.Core.Domain.Exceptions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Personalization;
|
using Wino.Core.Domain.Models.Personalization;
|
||||||
using Wino.Core.Messages.Shell;
|
|
||||||
using Wino.Core.UWP.Extensions;
|
using Wino.Core.UWP.Extensions;
|
||||||
using Wino.Core.UWP.Models.Personalization;
|
using Wino.Core.UWP.Models.Personalization;
|
||||||
using Wino.Core.UWP.Services;
|
using Wino.Core.UWP.Services;
|
||||||
|
using Wino.Messaging.Client.Shell;
|
||||||
|
|
||||||
namespace Wino.Services
|
namespace Wino.Services
|
||||||
{
|
{
|
||||||
@@ -167,6 +167,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Windows.UI.ViewManagement;
|
using Windows.UI.ViewManagement;
|
||||||
using Windows.UI.Xaml;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Core.UWP.Services
|
namespace Wino.Core.UWP.Services
|
||||||
@@ -21,12 +21,12 @@ namespace Wino.Core.UWP.Services
|
|||||||
|
|
||||||
public bool IsUnderlyingThemeDark()
|
public bool IsUnderlyingThemeDark()
|
||||||
{
|
{
|
||||||
var currentTheme = _configurationService.Get(SelectedAppThemeKey, ElementTheme.Default);
|
var currentTheme = _configurationService.Get(SelectedAppThemeKey, ApplicationElementTheme.Default);
|
||||||
|
|
||||||
if (currentTheme == ElementTheme.Default)
|
if (currentTheme == ApplicationElementTheme.Default)
|
||||||
return uiSettings.GetColorValue(UIColorType.Background).ToString() == "#FF000000";
|
return uiSettings.GetColorValue(UIColorType.Background).ToString() == "#FF000000";
|
||||||
else
|
else
|
||||||
return currentTheme == ElementTheme.Dark;
|
return currentTheme == ApplicationElementTheme.Dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
316
Wino.Core.UWP/Services/WinoServerConnectionManager.cs
Normal file
316
Wino.Core.UWP/Services/WinoServerConnectionManager.cs
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Nito.AsyncEx;
|
||||||
|
using Serilog;
|
||||||
|
using Windows.ApplicationModel;
|
||||||
|
using Windows.ApplicationModel.AppService;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
using Windows.Foundation.Metadata;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Requests;
|
||||||
|
using Wino.Core.Domain.Models.Server;
|
||||||
|
using Wino.Core.Integration.Json;
|
||||||
|
using Wino.Messaging;
|
||||||
|
using Wino.Messaging.Client.Connection;
|
||||||
|
using Wino.Messaging.Enums;
|
||||||
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
|
namespace Wino.Core.UWP.Services
|
||||||
|
{
|
||||||
|
public class WinoServerConnectionManager :
|
||||||
|
IWinoServerConnectionManager<AppServiceConnection>,
|
||||||
|
IRecipient<WinoServerConnectionEstrablished>
|
||||||
|
{
|
||||||
|
private const int ServerConnectionTimeoutMs = 5000;
|
||||||
|
|
||||||
|
public event EventHandler<WinoServerConnectionStatus> StatusChanged;
|
||||||
|
private TaskCompletionSource<bool> _connectionTaskCompletionSource;
|
||||||
|
|
||||||
|
private ILogger Logger => Logger.ForContext<WinoServerConnectionManager>();
|
||||||
|
|
||||||
|
private WinoServerConnectionStatus status;
|
||||||
|
|
||||||
|
public WinoServerConnectionStatus Status
|
||||||
|
{
|
||||||
|
get { return status; }
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
status = value;
|
||||||
|
StatusChanged?.Invoke(this, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppServiceConnection _connection;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
|
||||||
|
{
|
||||||
|
TypeInfoResolver = new ServerRequestTypeInfoResolver()
|
||||||
|
};
|
||||||
|
|
||||||
|
public WinoServerConnectionManager()
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ConnectAsync()
|
||||||
|
{
|
||||||
|
if (Status == WinoServerConnectionStatus.Connected) return true;
|
||||||
|
|
||||||
|
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_connectionTaskCompletionSource ??= new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
var connectionCancellationToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(ServerConnectionTimeoutMs));
|
||||||
|
|
||||||
|
Status = WinoServerConnectionStatus.Connecting;
|
||||||
|
|
||||||
|
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
|
||||||
|
|
||||||
|
// Connection establishment handler is in App.xaml.cs OnBackgroundActivated.
|
||||||
|
// Once the connection is established, the handler will set the Connection property
|
||||||
|
// and WinoServerConnectionEstrablished will be fired by the messenger.
|
||||||
|
|
||||||
|
await _connectionTaskCompletionSource.Task.WaitAsync(connectionCancellationToken.Token);
|
||||||
|
}
|
||||||
|
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 not string dataTypeName)
|
||||||
|
throw new ArgumentException("Message data type is missing.");
|
||||||
|
|
||||||
|
HandleUIMessage(messageJson, dataTypeName);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unpacks IServerMessage objects and delegate it to Messenger for UI to process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageJson">Message data in json format.</param>
|
||||||
|
private void HandleUIMessage(string messageJson, string typeName)
|
||||||
|
{
|
||||||
|
switch (typeName)
|
||||||
|
{
|
||||||
|
case nameof(MailAddedMessage):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailAddedMessage>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(MailDownloadedMessage):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailDownloadedMessage>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(MailRemovedMessage):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailRemovedMessage>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(MailUpdatedMessage):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailUpdatedMessage>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(AccountCreatedMessage):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountCreatedMessage>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(AccountRemovedMessage):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountRemovedMessage>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(AccountUpdatedMessage):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountUpdatedMessage>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(DraftCreated):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftCreated>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(DraftFailed):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftFailed>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(DraftMapped):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftMapped>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(FolderRenamed):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<FolderRenamed>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(FolderSynchronizationEnabled):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<FolderSynchronizationEnabled>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(MergedInboxRenamed):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MergedInboxRenamed>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(AccountSynchronizationCompleted):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountSynchronizationCompleted>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(RefreshUnreadCountsMessage):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<RefreshUnreadCountsMessage>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(AccountSynchronizerStateChanged):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountSynchronizerStateChanged>(messageJson));
|
||||||
|
break;
|
||||||
|
case nameof(AccountSynchronizationProgressUpdatedMessage):
|
||||||
|
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountSynchronizationProgressUpdatedMessage>(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 async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||||
|
{
|
||||||
|
var queuePackage = new ServerRequestPackage(accountId, request);
|
||||||
|
|
||||||
|
var queueResponse = await GetResponseInternalAsync<bool, ServerRequestPackage>(queuePackage, new Dictionary<string, object>()
|
||||||
|
{
|
||||||
|
{ MessageConstants.MessageDataRequestAccountIdKey, accountId }
|
||||||
|
});
|
||||||
|
|
||||||
|
queueResponse.ThrowIfFailed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType message) where TRequestType : IClientMessage
|
||||||
|
=> GetResponseInternalAsync<TResponse, TRequestType>(message);
|
||||||
|
|
||||||
|
private async Task<WinoServerResponse<TResponse>> GetResponseInternalAsync<TResponse, TRequestType>(TRequestType message, Dictionary<string, object> parameters = null)
|
||||||
|
{
|
||||||
|
if (Connection == null)
|
||||||
|
return WinoServerResponse<TResponse>.CreateErrorResponse("Server connection is not established.");
|
||||||
|
|
||||||
|
string serializedMessage = string.Empty;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serializedMessage = JsonSerializer.Serialize(message, _jsonSerializerOptions);
|
||||||
|
}
|
||||||
|
catch (Exception serializationException)
|
||||||
|
{
|
||||||
|
Logger.Error(serializationException, $"Failed to serialize client message for sending.");
|
||||||
|
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to serialize message.\n{serializationException.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppServiceResponse response = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var valueSet = new ValueSet
|
||||||
|
{
|
||||||
|
{ MessageConstants.MessageTypeKey, (int)MessageType.ServerMessage },
|
||||||
|
{ MessageConstants.MessageDataKey, serializedMessage },
|
||||||
|
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add additional parameters into ValueSet
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
foreach (var item in parameters)
|
||||||
|
{
|
||||||
|
valueSet.Add(item.Key, item.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await Connection.SendMessageAsync(valueSet);
|
||||||
|
}
|
||||||
|
catch (Exception serverSendException)
|
||||||
|
{
|
||||||
|
Logger.Error(serverSendException, $"Failed to send message to server.");
|
||||||
|
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to send message to server.\n{serverSendException.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// It should be always Success.
|
||||||
|
if (response.Status != AppServiceResponseStatus.Success)
|
||||||
|
return WinoServerResponse<TResponse>.CreateErrorResponse($"Wino Server responded with '{response.Status}' status to message delivery.");
|
||||||
|
|
||||||
|
// All responses must contain a message data.
|
||||||
|
if (!(response.Message.TryGetValue(MessageConstants.MessageDataKey, out object messageDataObject) && messageDataObject is string messageJson))
|
||||||
|
return WinoServerResponse<TResponse>.CreateErrorResponse("Server response did not contain message data.");
|
||||||
|
|
||||||
|
// Try deserialize the message data.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<WinoServerResponse<TResponse>>(messageJson);
|
||||||
|
}
|
||||||
|
catch (Exception jsonDeserializationError)
|
||||||
|
{
|
||||||
|
Logger.Error(jsonDeserializationError, $"Failed to deserialize server response message data.");
|
||||||
|
return WinoServerResponse<TResponse>.CreateErrorResponse($"Failed to deserialize Wino server response message data.\n{jsonDeserializationError.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(WinoServerConnectionEstrablished message)
|
||||||
|
{
|
||||||
|
if (_connectionTaskCompletionSource != null)
|
||||||
|
{
|
||||||
|
_connectionTaskCompletionSource.TrySetResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,25 +17,6 @@
|
|||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
@@ -118,17 +99,21 @@
|
|||||||
</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" />
|
||||||
<Compile Include="Dispatcher.cs" />
|
<Compile Include="Dispatcher.cs" />
|
||||||
<Compile Include="Extensions\ElementThemeExtensions.cs" />
|
<Compile Include="Extensions\ElementThemeExtensions.cs" />
|
||||||
|
<Compile Include="Extensions\StartupTaskStateExtensions.cs" />
|
||||||
<Compile Include="Models\Personalization\CustomAppTheme.cs" />
|
<Compile Include="Models\Personalization\CustomAppTheme.cs" />
|
||||||
<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\BackgroundSynchronizer.cs" />
|
<Compile Include="Services\StartupBehaviorService.cs" />
|
||||||
|
<Compile Include="Services\StatePersistenceService.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" />
|
||||||
@@ -143,9 +128,9 @@
|
|||||||
<EmbeddedResource Include="Properties\Wino.Core.UWP.rd.xml" />
|
<EmbeddedResource Include="Properties\Wino.Core.UWP.rd.xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!--<PackageReference Include="CommunityToolkit.Uwp.Helpers">
|
<PackageReference Include="CommunityToolkit.WinUI.Notifications">
|
||||||
<Version>8.0.230907</Version>
|
<Version>7.1.2</Version>
|
||||||
</PackageReference>-->
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.AppCenter.Analytics">
|
<PackageReference Include="Microsoft.AppCenter.Analytics">
|
||||||
<Version>5.0.4</Version>
|
<Version>5.0.4</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -155,9 +140,6 @@
|
|||||||
<PackageReference Include="Microsoft.Toolkit.Uwp">
|
<PackageReference Include="Microsoft.Toolkit.Uwp">
|
||||||
<Version>7.1.3</Version>
|
<Version>7.1.3</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications">
|
|
||||||
<Version>7.1.3</Version>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj">
|
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj">
|
||||||
@@ -168,8 +150,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)' < '14.0' ">
|
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wino.Core.Domain.Entities;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
using Wino.Core.Services;
|
|
||||||
|
|
||||||
namespace Wino.Core.Authenticators
|
|
||||||
{
|
|
||||||
public class CustomAuthenticator : BaseAuthenticator, IAuthenticator
|
|
||||||
{
|
|
||||||
public CustomAuthenticator(ITokenService tokenService) : base(tokenService) { }
|
|
||||||
|
|
||||||
public override MailProviderType ProviderType => MailProviderType.IMAP4;
|
|
||||||
|
|
||||||
public string ClientId => throw new NotImplementedException(); // Not needed.
|
|
||||||
|
|
||||||
public event EventHandler<string> InteractiveAuthenticationRequired;
|
|
||||||
|
|
||||||
public void CancelAuthorization() { }
|
|
||||||
|
|
||||||
public void ContinueAuthorization(Uri authorizationResponseUri) { }
|
|
||||||
|
|
||||||
public Task<TokenInformation> GenerateTokenAsync(MailAccount account, bool saveToken)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<TokenInformation> GetTokenAsync(MailAccount account)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Nito.AsyncEx;
|
|
||||||
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,11 +11,10 @@ using Wino.Core.Domain.Interfaces;
|
|||||||
using Wino.Core.Domain.Models.Authentication;
|
using Wino.Core.Domain.Models.Authentication;
|
||||||
using Wino.Core.Domain.Models.Authorization;
|
using Wino.Core.Domain.Models.Authorization;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
using Xamarin.Essentials;
|
|
||||||
|
|
||||||
namespace Wino.Core.Authenticators
|
namespace Wino.Core.Authenticators
|
||||||
{
|
{
|
||||||
public class GmailAuthenticator : BaseAuthenticator, IAuthenticator
|
public class GmailAuthenticator : BaseAuthenticator, IGmailAuthenticator
|
||||||
{
|
{
|
||||||
public string ClientId { get; } = "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
|
public string ClientId { get; } = "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
|
||||||
|
|
||||||
@@ -27,9 +24,6 @@ namespace Wino.Core.Authenticators
|
|||||||
|
|
||||||
public override MailProviderType ProviderType => MailProviderType.Gmail;
|
public override MailProviderType ProviderType => MailProviderType.Gmail;
|
||||||
|
|
||||||
private TaskCompletionSource<Uri> _authorizationCompletionSource = null;
|
|
||||||
private CancellationTokenSource _authorizationCancellationTokenSource = null;
|
|
||||||
|
|
||||||
private readonly INativeAppService _nativeAppService;
|
private readonly INativeAppService _nativeAppService;
|
||||||
|
|
||||||
public event EventHandler<string> InteractiveAuthenticationRequired;
|
public event EventHandler<string> InteractiveAuthenticationRequired;
|
||||||
@@ -99,8 +93,6 @@ namespace Wino.Core.Authenticators
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ContinueAuthorization(Uri authorizationResponseUri) => _authorizationCompletionSource?.TrySetResult(authorizationResponseUri);
|
|
||||||
|
|
||||||
public async Task<TokenInformation> GetTokenAsync(MailAccount account)
|
public async Task<TokenInformation> GetTokenAsync(MailAccount account)
|
||||||
{
|
{
|
||||||
var cachedToken = await TokenService.GetTokenInformationAsync(account.Id)
|
var cachedToken = await TokenService.GetTokenInformationAsync(account.Id)
|
||||||
@@ -127,29 +119,19 @@ namespace Wino.Core.Authenticators
|
|||||||
{
|
{
|
||||||
var authRequest = _nativeAppService.GetGoogleAuthorizationRequest();
|
var authRequest = _nativeAppService.GetGoogleAuthorizationRequest();
|
||||||
|
|
||||||
_authorizationCompletionSource = new TaskCompletionSource<Uri>();
|
|
||||||
_authorizationCancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
var authorizationUri = authRequest.BuildRequest(ClientId);
|
var authorizationUri = authRequest.BuildRequest(ClientId);
|
||||||
|
|
||||||
await Browser.OpenAsync(authorizationUri, BrowserLaunchMode.SystemPreferred);
|
|
||||||
|
|
||||||
Uri responseRedirectUri = null;
|
Uri responseRedirectUri = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
responseRedirectUri = await _authorizationCompletionSource.Task.WaitAsync(_authorizationCancellationTokenSource.Token);
|
//await _authorizationCompletionSource.Task.WaitAsync(_authorizationCancellationTokenSource.Token);
|
||||||
|
responseRedirectUri = await _nativeAppService.GetAuthorizationResponseUriAsync(this, authorizationUri);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
throw new AuthenticationException(Translator.Exception_AuthenticationCanceled);
|
throw new AuthenticationException(Translator.Exception_AuthenticationCanceled);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
_authorizationCancellationTokenSource.Dispose();
|
|
||||||
_authorizationCancellationTokenSource = null;
|
|
||||||
_authorizationCompletionSource = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
authRequest.ValidateAuthorizationCode(responseRedirectUri);
|
authRequest.ValidateAuthorizationCode(responseRedirectUri);
|
||||||
|
|
||||||
@@ -213,7 +195,5 @@ namespace Wino.Core.Authenticators
|
|||||||
RefreshToken = activeRefreshToken
|
RefreshToken = activeRefreshToken
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CancelAuthorization() => _authorizationCancellationTokenSource?.Cancel();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Wino.Core.Authenticators
|
|||||||
{
|
{
|
||||||
public class Office365Authenticator : OutlookAuthenticator
|
public class Office365Authenticator : OutlookAuthenticator
|
||||||
{
|
{
|
||||||
public Office365Authenticator(ITokenService tokenService, INativeAppService nativeAppService) : base(tokenService, nativeAppService) { }
|
public Office365Authenticator(ITokenService tokenService, INativeAppService nativeAppService, IApplicationConfiguration applicationConfiguration) : base(tokenService, nativeAppService, applicationConfiguration) { }
|
||||||
|
|
||||||
public override MailProviderType ProviderType => MailProviderType.Office365;
|
public override MailProviderType ProviderType => MailProviderType.Office365;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Identity.Client;
|
using Microsoft.Identity.Client;
|
||||||
|
using Microsoft.Identity.Client.Broker;
|
||||||
|
using Microsoft.Identity.Client.Extensions.Msal;
|
||||||
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;
|
||||||
@@ -12,76 +14,82 @@ using Wino.Core.Services;
|
|||||||
|
|
||||||
namespace Wino.Core.Authenticators
|
namespace Wino.Core.Authenticators
|
||||||
{
|
{
|
||||||
public class OutlookAuthenticator : BaseAuthenticator, IAuthenticator
|
/// <summary>
|
||||||
|
/// Authenticator for Outlook provider.
|
||||||
|
/// Token cache is managed by MSAL, not by Wino.
|
||||||
|
/// </summary>
|
||||||
|
public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||||
{
|
{
|
||||||
|
private const string TokenCacheFileName = "OutlookCache.bin";
|
||||||
|
private bool isTokenCacheAttached = false;
|
||||||
|
|
||||||
// Outlook
|
// Outlook
|
||||||
private const string Authority = "https://login.microsoftonline.com/common";
|
private const string Authority = "https://login.microsoftonline.com/common";
|
||||||
|
|
||||||
public string ClientId { get; } = "b19c2035-d740-49ff-b297-de6ec561b208";
|
public string ClientId { get; } = "b19c2035-d740-49ff-b297-de6ec561b208";
|
||||||
|
|
||||||
private readonly string[] MailScope = new string[] { "email", "mail.readwrite", "offline_access", "mail.send" };
|
private readonly string[] MailScope = ["email", "mail.readwrite", "offline_access", "mail.send"];
|
||||||
|
|
||||||
public override MailProviderType ProviderType => MailProviderType.Outlook;
|
public override MailProviderType ProviderType => MailProviderType.Outlook;
|
||||||
|
|
||||||
private readonly IPublicClientApplication _publicClientApplication;
|
private readonly IPublicClientApplication _publicClientApplication;
|
||||||
|
private readonly IApplicationConfiguration _applicationConfiguration;
|
||||||
|
|
||||||
public OutlookAuthenticator(ITokenService tokenService, INativeAppService nativeAppService) : base(tokenService)
|
public OutlookAuthenticator(ITokenService tokenService,
|
||||||
|
INativeAppService nativeAppService,
|
||||||
|
IApplicationConfiguration applicationConfiguration) : base(tokenService)
|
||||||
{
|
{
|
||||||
|
_applicationConfiguration = applicationConfiguration;
|
||||||
|
|
||||||
var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri();
|
var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri();
|
||||||
|
|
||||||
_publicClientApplication = PublicClientApplicationBuilder.Create(ClientId)
|
var options = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
|
||||||
.WithAuthority(Authority)
|
{
|
||||||
.WithRedirectUri(authenticationRedirectUri)
|
Title = "Wino Mail",
|
||||||
.Build();
|
ListOperatingSystemAccounts = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var outlookAppBuilder = PublicClientApplicationBuilder.Create(ClientId)
|
||||||
|
.WithParentActivityOrWindow(nativeAppService.GetCoreWindowHwnd)
|
||||||
|
.WithBroker(options)
|
||||||
|
.WithDefaultRedirectUri()
|
||||||
|
.WithAuthority(Authority);
|
||||||
|
|
||||||
|
_publicClientApplication = outlookAppBuilder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable S1133 // Deprecated code should be removed
|
|
||||||
[Obsolete("Not used for OutlookAuthenticator.")]
|
|
||||||
#pragma warning restore S1133 // Deprecated code should be removed
|
|
||||||
public void ContinueAuthorization(Uri authorizationResponseUri) { }
|
|
||||||
|
|
||||||
#pragma warning disable S1133 // Deprecated code should be removed
|
|
||||||
[Obsolete("Not used for OutlookAuthenticator.")]
|
|
||||||
#pragma warning restore S1133 // Deprecated code should be removed
|
|
||||||
public void CancelAuthorization() { }
|
|
||||||
|
|
||||||
public async Task<TokenInformation> GetTokenAsync(MailAccount account)
|
public async Task<TokenInformation> GetTokenAsync(MailAccount account)
|
||||||
{
|
{
|
||||||
var cachedToken = await TokenService.GetTokenInformationAsync(account.Id)
|
if (!isTokenCacheAttached)
|
||||||
?? throw new AuthenticationAttentionException(account);
|
|
||||||
|
|
||||||
// We have token but it's expired.
|
|
||||||
// Silently refresh the token and save new token.
|
|
||||||
|
|
||||||
if (cachedToken.IsExpired)
|
|
||||||
{
|
{
|
||||||
var cachedOutlookAccount = (await _publicClientApplication.GetAccountsAsync()).FirstOrDefault(a => a.Username == account.Address);
|
var storageProperties = new StorageCreationPropertiesBuilder(TokenCacheFileName, _applicationConfiguration.PublisherSharedFolderPath).Build();
|
||||||
|
var msalcachehelper = await MsalCacheHelper.CreateAsync(storageProperties);
|
||||||
|
msalcachehelper.RegisterCache(_publicClientApplication.UserTokenCache);
|
||||||
|
|
||||||
// Again, not expected at all...
|
isTokenCacheAttached = true;
|
||||||
// Force interactive login at this point.
|
}
|
||||||
|
|
||||||
if (cachedOutlookAccount == null)
|
var storedAccount = (await _publicClientApplication.GetAccountsAsync()).FirstOrDefault(a => a.Username == account.Address);
|
||||||
{
|
|
||||||
// What if interactive login info is for different account?
|
// TODO: Handle it from the server.
|
||||||
|
if (storedAccount == null) throw new AuthenticationAttentionException(account);
|
||||||
return await GenerateTokenAsync(account, true);
|
|
||||||
}
|
try
|
||||||
else
|
{
|
||||||
{
|
var authResult = await _publicClientApplication.AcquireTokenSilent(MailScope, storedAccount).ExecuteAsync();
|
||||||
// Silently refresh token from cache.
|
|
||||||
|
return authResult.CreateTokenInformation() ?? throw new Exception("Failed to get Outlook token.");
|
||||||
AuthenticationResult authResult = await _publicClientApplication.AcquireTokenSilent(MailScope, cachedOutlookAccount).ExecuteAsync();
|
}
|
||||||
|
catch (MsalUiRequiredException)
|
||||||
// Save refreshed token and return
|
{
|
||||||
var refreshedTokenInformation = authResult.CreateTokenInformation();
|
// Somehow MSAL is not able to refresh the token silently.
|
||||||
|
// Force interactive login.
|
||||||
await TokenService.SaveTokenInformationAsync(account.Id, refreshedTokenInformation);
|
return await GenerateTokenAsync(account, true);
|
||||||
|
}
|
||||||
return refreshedTokenInformation;
|
catch (Exception)
|
||||||
}
|
{
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
return cachedToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TokenInformation> GenerateTokenAsync(MailAccount account, bool saveToken)
|
public async Task<TokenInformation> GenerateTokenAsync(MailAccount account, bool saveToken)
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wino.Core.Domain.Entities;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
using Wino.Core.Services;
|
|
||||||
|
|
||||||
namespace Wino.Core.Authenticators
|
|
||||||
{
|
|
||||||
public class YahooAuthenticator : BaseAuthenticator, IAuthenticator
|
|
||||||
{
|
|
||||||
public YahooAuthenticator(ITokenService tokenService) : base(tokenService) { }
|
|
||||||
|
|
||||||
public override MailProviderType ProviderType => MailProviderType.Yahoo;
|
|
||||||
|
|
||||||
public string ClientId => throw new NotImplementedException();
|
|
||||||
|
|
||||||
public event EventHandler<string> InteractiveAuthenticationRequired;
|
|
||||||
|
|
||||||
public void CancelAuthorization()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ContinueAuthorization(Uri authorizationResponseUri)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<TokenInformation> GenerateTokenAsync(MailAccount account, bool saveToken)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<TokenInformation> GetTokenAsync(MailAccount account)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,9 +43,14 @@ namespace Wino.Core
|
|||||||
services.AddTransient<IFontService, FontService>();
|
services.AddTransient<IFontService, FontService>();
|
||||||
services.AddTransient<IUnsubscriptionService, UnsubscriptionService>();
|
services.AddTransient<IUnsubscriptionService, UnsubscriptionService>();
|
||||||
|
|
||||||
|
services.AddTransient<IOutlookAuthenticator, OutlookAuthenticator>();
|
||||||
|
services.AddTransient<IGmailAuthenticator, GmailAuthenticator>();
|
||||||
|
|
||||||
services.AddTransient<OutlookThreadingStrategy>();
|
services.AddTransient<OutlookThreadingStrategy>();
|
||||||
services.AddTransient<GmailThreadingStrategy>();
|
services.AddTransient<GmailThreadingStrategy>();
|
||||||
services.AddTransient<ImapThreadStrategy>();
|
services.AddTransient<ImapThreadStrategy>();
|
||||||
|
|
||||||
|
services.AddSingleton<ISynchronizerFactory, SynchronizerFactory>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,21 +8,15 @@ namespace Wino.Core.Extensions
|
|||||||
{
|
{
|
||||||
public static TokenInformation CreateTokenInformation(this AuthenticationResult clientBuilderResult)
|
public static TokenInformation CreateTokenInformation(this AuthenticationResult clientBuilderResult)
|
||||||
{
|
{
|
||||||
var expirationDate = clientBuilderResult.ExpiresOn.UtcDateTime;
|
// Plain access token info is not stored for Outlook in Wino's database.
|
||||||
var accesToken = clientBuilderResult.AccessToken;
|
// Here we store UniqueId and Access Token in memory only to compare the UniqueId returned from MSAL auth result.
|
||||||
var userName = clientBuilderResult.Account.Username;
|
|
||||||
|
|
||||||
// MSAL does not expose refresh token for security reasons.
|
|
||||||
// This token info will be created without refresh token.
|
|
||||||
// but OutlookIntegrator will ask for publicApplication to refresh it
|
|
||||||
// in case of expiration.
|
|
||||||
|
|
||||||
var tokenInfo = new TokenInformation()
|
var tokenInfo = new TokenInformation()
|
||||||
{
|
{
|
||||||
ExpiresAt = expirationDate,
|
Address = clientBuilderResult.Account.Username,
|
||||||
AccessToken = accesToken,
|
|
||||||
Address = userName,
|
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
|
UniqueId = clientBuilderResult.UniqueId,
|
||||||
|
AccessToken = clientBuilderResult.AccessToken
|
||||||
};
|
};
|
||||||
|
|
||||||
return tokenInfo;
|
return tokenInfo;
|
||||||
|
|||||||
49
Wino.Core/Integration/Json/ServerRequestTypeInfoResolver.cs
Normal file
49
Wino.Core/Integration/Json/ServerRequestTypeInfoResolver.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
using Wino.Core.Domain.Entities;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
|
using Wino.Core.Requests;
|
||||||
|
|
||||||
|
namespace Wino.Core.Integration.Json
|
||||||
|
{
|
||||||
|
public class ServerRequestTypeInfoResolver : DefaultJsonTypeInfoResolver
|
||||||
|
{
|
||||||
|
public ServerRequestTypeInfoResolver()
|
||||||
|
{
|
||||||
|
Modifiers.Add(new System.Action<JsonTypeInfo>(t =>
|
||||||
|
{
|
||||||
|
if (t.Type == typeof(IRequestBase))
|
||||||
|
{
|
||||||
|
t.PolymorphismOptions = new()
|
||||||
|
{
|
||||||
|
DerivedTypes =
|
||||||
|
{
|
||||||
|
new JsonDerivedType(typeof(AlwaysMoveToRequest), nameof(AlwaysMoveToRequest)),
|
||||||
|
new JsonDerivedType(typeof(ArchiveRequest), nameof(ArchiveRequest)),
|
||||||
|
new JsonDerivedType(typeof(ChangeFlagRequest), nameof(ChangeFlagRequest)),
|
||||||
|
new JsonDerivedType(typeof(CreateDraftRequest), nameof(CreateDraftRequest)),
|
||||||
|
new JsonDerivedType(typeof(DeleteRequest), nameof(DeleteRequest)),
|
||||||
|
new JsonDerivedType(typeof(EmptyFolderRequest), nameof(EmptyFolderRequest)),
|
||||||
|
new JsonDerivedType(typeof(MarkFolderAsReadRequest), nameof(MarkFolderAsReadRequest)),
|
||||||
|
new JsonDerivedType(typeof(MarkReadRequest), nameof(MarkReadRequest)),
|
||||||
|
new JsonDerivedType(typeof(MoveRequest), nameof(MoveRequest)),
|
||||||
|
new JsonDerivedType(typeof(MoveToFocusedRequest), nameof(MoveToFocusedRequest)),
|
||||||
|
new JsonDerivedType(typeof(RenameFolderRequest), nameof(RenameFolderRequest)),
|
||||||
|
new JsonDerivedType(typeof(SendDraftRequest), nameof(SendDraftRequest)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (t.Type == typeof(IMailItem))
|
||||||
|
{
|
||||||
|
t.PolymorphismOptions = new JsonPolymorphismOptions()
|
||||||
|
{
|
||||||
|
DerivedTypes =
|
||||||
|
{
|
||||||
|
new JsonDerivedType(typeof(MailCopy), nameof(MailCopy)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,6 +62,15 @@ namespace Wino.Core.Integration.Processors
|
|||||||
/// <returns>Whether the mime has b</returns>
|
/// <returns>Whether the mime has b</returns>
|
||||||
Task<bool> IsMailExistsAsync(string messageId);
|
Task<bool> IsMailExistsAsync(string messageId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the mail exists in the folder.
|
||||||
|
/// When deciding Create or Update existing mail, we need to check if the mail exists in the folder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageId">Message id</param>
|
||||||
|
/// <param name="folderId">Folder's local id.</param>
|
||||||
|
/// <returns>Whether mail exists in the folder or not.</returns>
|
||||||
|
Task<bool> IsMailExistsInFolderAsync(string messageId, Guid folderId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates Folder's delta synchronization identifier.
|
/// Updates Folder's delta synchronization identifier.
|
||||||
/// Only used in Outlook since it does per-folder sync.
|
/// Only used in Outlook since it does per-folder sync.
|
||||||
@@ -70,6 +79,24 @@ namespace Wino.Core.Integration.Processors
|
|||||||
/// <param name="synchronizationIdentifier">New synchronization identifier.</param>
|
/// <param name="synchronizationIdentifier">New synchronization identifier.</param>
|
||||||
/// <returns>New identifier if success.</returns>
|
/// <returns>New identifier if success.</returns>
|
||||||
Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string deltaSynchronizationIdentifier);
|
Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string deltaSynchronizationIdentifier);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Outlook may expire folder's delta token after a while.
|
||||||
|
/// Recommended action for this scenario is to reset token and do full sync.
|
||||||
|
/// This method resets the token for the given folder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folderId">Local folder id to reset token for.</param>
|
||||||
|
/// <returns>Empty string to assign folder delta sync for.</returns>
|
||||||
|
Task<string> ResetFolderDeltaTokenAsync(Guid folderId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Outlook may expire account's delta token after a while.
|
||||||
|
/// This will result returning 410 GONE response from the API for synchronizing folders.
|
||||||
|
/// This method resets the token for the given account for re-syncing folders.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountId">Account identifier to reset delta token for.</param>
|
||||||
|
/// <returns>Empty string to assign account delta sync for.</returns>
|
||||||
|
Task<string> ResetAccountDeltaTokenAsync(Guid accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IImapChangeProcessor : IDefaultChangeProcessor
|
public interface IImapChangeProcessor : IDefaultChangeProcessor
|
||||||
@@ -90,11 +117,11 @@ namespace Wino.Core.Integration.Processors
|
|||||||
protected IMailService MailService = mailService;
|
protected IMailService MailService = mailService;
|
||||||
|
|
||||||
protected IFolderService FolderService = folderService;
|
protected IFolderService FolderService = folderService;
|
||||||
private readonly IAccountService _accountService = accountService;
|
protected IAccountService AccountService = accountService;
|
||||||
private readonly IMimeFileService _mimeFileService = mimeFileService;
|
private readonly IMimeFileService _mimeFileService = mimeFileService;
|
||||||
|
|
||||||
public Task<string> UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string synchronizationDeltaIdentifier)
|
public Task<string> UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string synchronizationDeltaIdentifier)
|
||||||
=> _accountService.UpdateSynchronizationIdentifierAsync(accountId, synchronizationDeltaIdentifier);
|
=> AccountService.UpdateSynchronizationIdentifierAsync(accountId, synchronizationDeltaIdentifier);
|
||||||
|
|
||||||
public Task ChangeFlagStatusAsync(string mailCopyId, bool isFlagged)
|
public Task ChangeFlagStatusAsync(string mailCopyId, bool isFlagged)
|
||||||
=> MailService.ChangeFlagStatusAsync(mailCopyId, isFlagged);
|
=> MailService.ChangeFlagStatusAsync(mailCopyId, isFlagged);
|
||||||
|
|||||||
@@ -9,11 +9,29 @@ namespace Wino.Core.Integration.Processors
|
|||||||
IFolderService folderService,
|
IFolderService folderService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMimeFileService mimeFileService) : DefaultChangeProcessor(databaseService, folderService, mailService, accountService, mimeFileService), IOutlookChangeProcessor
|
IMimeFileService mimeFileService) : DefaultChangeProcessor(databaseService, folderService, mailService, accountService, mimeFileService)
|
||||||
|
, IOutlookChangeProcessor
|
||||||
{
|
{
|
||||||
public Task<bool> IsMailExistsAsync(string messageId)
|
public Task<bool> IsMailExistsAsync(string messageId)
|
||||||
=> MailService.IsMailExistsAsync(messageId);
|
=> MailService.IsMailExistsAsync(messageId);
|
||||||
|
|
||||||
|
public Task<bool> IsMailExistsInFolderAsync(string messageId, Guid folderId)
|
||||||
|
=> MailService.IsMailExistsAsync(messageId, folderId);
|
||||||
|
|
||||||
|
public Task<string> ResetAccountDeltaTokenAsync(Guid accountId)
|
||||||
|
=> AccountService.UpdateSynchronizationIdentifierAsync(accountId, null);
|
||||||
|
|
||||||
|
public async Task<string> ResetFolderDeltaTokenAsync(Guid folderId)
|
||||||
|
{
|
||||||
|
var folder = await FolderService.GetFolderAsync(folderId);
|
||||||
|
|
||||||
|
folder.DeltaToken = null;
|
||||||
|
|
||||||
|
await FolderService.UpdateFolderAsync(folder);
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string synchronizationIdentifier)
|
public Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string synchronizationIdentifier)
|
||||||
=> Connection.ExecuteAsync("UPDATE MailItemFolder SET DeltaToken = ? WHERE Id = ?", synchronizationIdentifier, folderId);
|
=> Connection.ExecuteAsync("UPDATE MailItemFolder SET DeltaToken = ? WHERE Id = ?", synchronizationIdentifier, folderId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Wino.Core.Messages.Mails
|
|
||||||
{
|
|
||||||
public record RefreshUnreadCountsMessage(Guid AccountId);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Wino.Core.Domain.Enums;
|
|
||||||
using Wino.Core.Synchronizers;
|
|
||||||
|
|
||||||
namespace Wino.Core.Messages.Synchronization
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Emitted when synchronizer state is updated.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="synchronizer">Account Synchronizer</param>
|
|
||||||
/// <param name="newState">New state.</param>
|
|
||||||
public record AccountSynchronizerStateChanged(IBaseSynchronizer Synchronizer, AccountSynchronizerState NewState);
|
|
||||||
}
|
|
||||||
@@ -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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
|
|||||||
23
Wino.Core/Requests/Bundles/ServerRequestPackage.cs
Normal file
23
Wino.Core/Requests/Bundles/ServerRequestPackage.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Models.Requests
|
||||||
|
{
|
||||||
|
///// <summary>
|
||||||
|
///// Encapsulates request to queue and account for synchronizer.
|
||||||
|
///// </summary>
|
||||||
|
///// <param name="AccountId"><inheritdoc/></param>
|
||||||
|
///// <param name="Request"></param>
|
||||||
|
///// <param name="Request">Prepared request for the server.</param>
|
||||||
|
///// <param name="AccountId">Which account to execute this request for.</param>
|
||||||
|
public class ServerRequestBundle(Guid accountId, IRequestBase request) : IClientMessage
|
||||||
|
{
|
||||||
|
public Guid AccountId { get; } = accountId;
|
||||||
|
|
||||||
|
public IRequestBase Request { get; } = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//public record ServerRequestPackage<TUserActionRequestType>(Guid AccountId, TUserActionRequestType Request)
|
||||||
|
// : ServerRequestBundle(AccountId), IClientMessage where TUserActionRequestType : IRequestBase;
|
||||||
|
}
|
||||||
@@ -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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ 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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
public record SendDraftRequest(SendDraftPreparationRequest Request)
|
public record SendDraftRequest(SendDraftPreparationRequest Request)
|
||||||
: RequestBase<BatchMarkReadRequest>(Request.MailItem, MailSynchronizerOperation.Send),
|
: RequestBase<BatchSendDraftRequestRequest>(Request.MailItem, MailSynchronizerOperation.Send),
|
||||||
ICustomFolderSynchronizationRequest
|
ICustomFolderSynchronizationRequest
|
||||||
{
|
{
|
||||||
public List<Guid> SynchronizationFolderIds
|
public List<Guid> SynchronizationFolderIds
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -11,8 +11,8 @@ 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.Extensions;
|
using Wino.Core.Extensions;
|
||||||
using Wino.Core.Messages.Accounts;
|
using Wino.Messaging.Client.Accounts;
|
||||||
using Wino.Core.Requests;
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Services
|
namespace Wino.Core.Services
|
||||||
{
|
{
|
||||||
@@ -382,7 +382,10 @@ namespace Wino.Core.Services
|
|||||||
if (customServerInformation != null)
|
if (customServerInformation != null)
|
||||||
await Connection.InsertAsync(customServerInformation);
|
await Connection.InsertAsync(customServerInformation);
|
||||||
|
|
||||||
if (tokenInformation != null)
|
// Outlook token cache is managed by MSAL.
|
||||||
|
// Don't save it to database.
|
||||||
|
|
||||||
|
if (tokenInformation != null && account.ProviderType != MailProviderType.Outlook)
|
||||||
await Connection.InsertAsync(tokenInformation);
|
await Connection.InsertAsync(tokenInformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
Wino.Core/Services/ApplicationConfiguration.cs
Normal file
13
Wino.Core/Services/ApplicationConfiguration.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,22 +11,23 @@ namespace Wino.Core.Services
|
|||||||
{
|
{
|
||||||
private readonly INativeAppService _nativeAppService;
|
private readonly INativeAppService _nativeAppService;
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
|
private readonly IApplicationConfiguration _applicationConfiguration;
|
||||||
|
|
||||||
public AuthenticationProvider(INativeAppService nativeAppService, ITokenService tokenService)
|
public AuthenticationProvider(INativeAppService nativeAppService, ITokenService tokenService, IApplicationConfiguration applicationConfiguration)
|
||||||
{
|
{
|
||||||
_nativeAppService = nativeAppService;
|
_nativeAppService = nativeAppService;
|
||||||
_tokenService = tokenService;
|
_tokenService = tokenService;
|
||||||
|
_applicationConfiguration = applicationConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAuthenticator GetAuthenticator(MailProviderType providerType)
|
public IAuthenticator GetAuthenticator(MailProviderType providerType)
|
||||||
{
|
{
|
||||||
|
// TODO: Move DI
|
||||||
return providerType switch
|
return providerType switch
|
||||||
{
|
{
|
||||||
MailProviderType.Outlook => new OutlookAuthenticator(_tokenService, _nativeAppService),
|
MailProviderType.Outlook => new OutlookAuthenticator(_tokenService, _nativeAppService, _applicationConfiguration),
|
||||||
MailProviderType.Office365 => new Office365Authenticator(_tokenService, _nativeAppService),
|
MailProviderType.Office365 => new Office365Authenticator(_tokenService, _nativeAppService, _applicationConfiguration),
|
||||||
MailProviderType.Gmail => new GmailAuthenticator(_tokenService, _nativeAppService),
|
MailProviderType.Gmail => new GmailAuthenticator(_tokenService, _nativeAppService),
|
||||||
MailProviderType.Yahoo => new YahooAuthenticator(_tokenService),
|
|
||||||
MailProviderType.IMAP4 => new CustomAuthenticator(_tokenService),
|
|
||||||
_ => throw new ArgumentException(Translator.Exception_UnsupportedProvider),
|
_ => throw new ArgumentException(Translator.Exception_UnsupportedProvider),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Services
|
namespace Wino.Core.Services
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.IO;
|
using Serilog;
|
||||||
using Serilog;
|
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Exceptions;
|
using Serilog.Exceptions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
@@ -8,8 +7,6 @@ namespace Wino.Core.Services
|
|||||||
{
|
{
|
||||||
public class LogInitializer : ILogInitializer
|
public class LogInitializer : ILogInitializer
|
||||||
{
|
{
|
||||||
public const string WinoLogFileName = "WinoDiagnostics.log";
|
|
||||||
|
|
||||||
private readonly LoggingLevelSwitch _levelSwitch = new LoggingLevelSwitch();
|
private readonly LoggingLevelSwitch _levelSwitch = new LoggingLevelSwitch();
|
||||||
private readonly IPreferencesService _preferencesService;
|
private readonly IPreferencesService _preferencesService;
|
||||||
|
|
||||||
@@ -25,13 +22,11 @@ namespace Wino.Core.Services
|
|||||||
_levelSwitch.MinimumLevel = _preferencesService.IsLoggingEnabled ? Serilog.Events.LogEventLevel.Debug : Serilog.Events.LogEventLevel.Fatal;
|
_levelSwitch.MinimumLevel = _preferencesService.IsLoggingEnabled ? Serilog.Events.LogEventLevel.Debug : Serilog.Events.LogEventLevel.Fatal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetupLogger(string logFolderPath)
|
public void SetupLogger(string fullLogFilePath)
|
||||||
{
|
{
|
||||||
string logFilePath = Path.Combine(logFolderPath, WinoLogFileName);
|
|
||||||
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.MinimumLevel.ControlledBy(_levelSwitch)
|
.MinimumLevel.ControlledBy(_levelSwitch)
|
||||||
.WriteTo.File(logFilePath)
|
.WriteTo.File(fullLogFilePath, retainedFileCountLimit: 3, rollOnFileSizeLimit: true, rollingInterval: RollingInterval.Day)
|
||||||
.WriteTo.Debug()
|
.WriteTo.Debug()
|
||||||
.Enrich.FromLogContext()
|
.Enrich.FromLogContext()
|
||||||
.Enrich.WithExceptionDetails()
|
.Enrich.WithExceptionDetails()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Kiota.Abstractions.Extensions;
|
using Microsoft.Kiota.Abstractions.Extensions;
|
||||||
@@ -10,11 +11,12 @@ using SqlKata;
|
|||||||
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;
|
||||||
|
using Wino.Core.Domain.Extensions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
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.UI;
|
||||||
|
|
||||||
namespace Wino.Core.Services
|
namespace Wino.Core.Services
|
||||||
{
|
{
|
||||||
@@ -52,10 +54,12 @@ namespace Wino.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MailCopy> CreateDraftAsync(MailAccount composerAccount,
|
public async Task<MailCopy> CreateDraftAsync(MailAccount composerAccount,
|
||||||
MimeMessage createdDraftMimeMessage,
|
string generatedReplyMimeMessageBase64,
|
||||||
MimeMessage replyingMimeMessage = null,
|
MimeMessage replyingMimeMessage = null,
|
||||||
IMailItem replyingMailItem = null)
|
IMailItem replyingMailItem = null)
|
||||||
{
|
{
|
||||||
|
var createdDraftMimeMessage = generatedReplyMimeMessageBase64.GetMimeMessageFromBase64();
|
||||||
|
|
||||||
bool isImapAccount = composerAccount.ServerInformation != null;
|
bool isImapAccount = composerAccount.ServerInformation != null;
|
||||||
|
|
||||||
string fromName;
|
string fromName;
|
||||||
@@ -625,7 +629,7 @@ namespace Wino.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MimeMessage> CreateDraftMimeMessageAsync(Guid accountId, DraftCreationOptions draftCreationOptions)
|
public async Task<string> CreateDraftMimeBase64Async(Guid accountId, DraftCreationOptions draftCreationOptions)
|
||||||
{
|
{
|
||||||
// This unique id is stored in mime headers for Wino to identify remote message with local copy.
|
// This unique id is stored in mime headers for Wino to identify remote message with local copy.
|
||||||
// Same unique id will be used for the local copy as well.
|
// Same unique id will be used for the local copy as well.
|
||||||
@@ -796,7 +800,14 @@ namespace Wino.Core.Services
|
|||||||
// Update TextBody from existing HtmlBody if exists.
|
// Update TextBody from existing HtmlBody if exists.
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
using MemoryStream memoryStream = new();
|
||||||
|
message.WriteTo(FormatOptions.Default, memoryStream);
|
||||||
|
byte[] buffer = memoryStream.GetBuffer();
|
||||||
|
int count = (int)memoryStream.Length;
|
||||||
|
|
||||||
|
return Convert.ToBase64String(buffer);
|
||||||
|
|
||||||
|
// return message;
|
||||||
|
|
||||||
// Generates html representation of To/Cc/From/Time and so on from referenced message.
|
// Generates html representation of To/Cc/From/Time and so on from referenced message.
|
||||||
string CreateHtmlForReferencingMessage(MimeMessage referenceMessage)
|
string CreateHtmlForReferencingMessage(MimeMessage referenceMessage)
|
||||||
@@ -919,5 +930,8 @@ namespace Wino.Core.Services
|
|||||||
|
|
||||||
public Task<bool> IsMailExistsAsync(string mailCopyId)
|
public Task<bool> IsMailExistsAsync(string mailCopyId)
|
||||||
=> Connection.ExecuteScalarAsync<bool>("SELECT EXISTS(SELECT 1 FROM MailCopy WHERE Id = ?)", mailCopyId);
|
=> Connection.ExecuteScalarAsync<bool>("SELECT EXISTS(SELECT 1 FROM MailCopy WHERE Id = ?)", mailCopyId);
|
||||||
|
|
||||||
|
public Task<bool> IsMailExistsAsync(string mailCopyId, Guid folderId)
|
||||||
|
=> Connection.ExecuteScalarAsync<bool>("SELECT EXISTS(SELECT 1 FROM MailCopy WHERE Id = ? AND FolderId = ?)", mailCopyId, folderId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
101
Wino.Core/Services/SynchronizerFactory.cs
Normal file
101
Wino.Core/Services/SynchronizerFactory.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wino.Core.Domain.Entities;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Integration.Processors;
|
||||||
|
using Wino.Core.Synchronizers;
|
||||||
|
|
||||||
|
namespace Wino.Core.Services
|
||||||
|
{
|
||||||
|
public class SynchronizerFactory : ISynchronizerFactory
|
||||||
|
{
|
||||||
|
private bool isInitialized = false;
|
||||||
|
|
||||||
|
private readonly IAccountService _accountService;
|
||||||
|
private readonly IOutlookChangeProcessor _outlookChangeProcessor;
|
||||||
|
private readonly IGmailChangeProcessor _gmailChangeProcessor;
|
||||||
|
private readonly IImapChangeProcessor _imapChangeProcessor;
|
||||||
|
private readonly IOutlookAuthenticator _outlookAuthenticator;
|
||||||
|
private readonly IGmailAuthenticator _gmailAuthenticator;
|
||||||
|
|
||||||
|
private readonly List<IBaseSynchronizer> synchronizerCache = new();
|
||||||
|
|
||||||
|
public SynchronizerFactory(IOutlookChangeProcessor outlookChangeProcessor,
|
||||||
|
IGmailChangeProcessor gmailChangeProcessor,
|
||||||
|
IImapChangeProcessor imapChangeProcessor,
|
||||||
|
IOutlookAuthenticator outlookAuthenticator,
|
||||||
|
IGmailAuthenticator gmailAuthenticator,
|
||||||
|
IAccountService accountService)
|
||||||
|
{
|
||||||
|
_outlookChangeProcessor = outlookChangeProcessor;
|
||||||
|
_gmailChangeProcessor = gmailChangeProcessor;
|
||||||
|
_imapChangeProcessor = imapChangeProcessor;
|
||||||
|
_outlookAuthenticator = outlookAuthenticator;
|
||||||
|
_gmailAuthenticator = gmailAuthenticator;
|
||||||
|
_accountService = accountService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IBaseSynchronizer> GetAccountSynchronizerAsync(Guid accountId)
|
||||||
|
{
|
||||||
|
var synchronizer = synchronizerCache.Find(a => a.Account.Id == accountId);
|
||||||
|
|
||||||
|
if (synchronizer == null)
|
||||||
|
{
|
||||||
|
var account = await _accountService.GetAccountAsync(accountId);
|
||||||
|
|
||||||
|
if (account != null)
|
||||||
|
{
|
||||||
|
synchronizer = CreateNewSynchronizer(account);
|
||||||
|
|
||||||
|
return await GetAccountSynchronizerAsync(accountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return synchronizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IBaseSynchronizer CreateIntegratorWithDefaultProcessor(MailAccount mailAccount)
|
||||||
|
{
|
||||||
|
var providerType = mailAccount.ProviderType;
|
||||||
|
|
||||||
|
switch (providerType)
|
||||||
|
{
|
||||||
|
case Domain.Enums.MailProviderType.Outlook:
|
||||||
|
case Domain.Enums.MailProviderType.Office365:
|
||||||
|
return new OutlookSynchronizer(mailAccount, _outlookAuthenticator, _outlookChangeProcessor);
|
||||||
|
case Domain.Enums.MailProviderType.Gmail:
|
||||||
|
return new GmailSynchronizer(mailAccount, _gmailAuthenticator, _gmailChangeProcessor);
|
||||||
|
case Domain.Enums.MailProviderType.IMAP4:
|
||||||
|
return new ImapSynchronizer(mailAccount, _imapChangeProcessor);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBaseSynchronizer CreateNewSynchronizer(MailAccount account)
|
||||||
|
{
|
||||||
|
var synchronizer = CreateIntegratorWithDefaultProcessor(account);
|
||||||
|
|
||||||
|
synchronizerCache.Add(synchronizer);
|
||||||
|
|
||||||
|
return synchronizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
if (isInitialized) return;
|
||||||
|
|
||||||
|
var accounts = await _accountService.GetAccountsAsync();
|
||||||
|
|
||||||
|
foreach (var account in accounts)
|
||||||
|
{
|
||||||
|
CreateNewSynchronizer(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
isInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ 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.Translations;
|
using Wino.Core.Domain.Models.Translations;
|
||||||
using Wino.Core.Messages.Shell;
|
using Wino.Messaging.Client.Shell;
|
||||||
|
|
||||||
namespace Wino.Core.Services
|
namespace Wino.Core.Services
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,26 +11,26 @@ 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.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
using Wino.Core.Messages.Synchronization;
|
|
||||||
using Wino.Core.Requests;
|
using Wino.Core.Requests;
|
||||||
|
using Wino.Messaging.Server;
|
||||||
|
|
||||||
namespace Wino.Core.Services
|
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;
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ namespace Wino.Core.Services
|
|||||||
{
|
{
|
||||||
foreach (var accountRequest in accountId)
|
foreach (var accountRequest in accountId)
|
||||||
{
|
{
|
||||||
QueueRequest(accountRequest, accountId.Key);
|
await QueueRequestAsync(accountRequest, accountId.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
QueueSynchronization(accountId.Key);
|
QueueSynchronization(accountId.Key);
|
||||||
@@ -107,43 +107,36 @@ namespace Wino.Core.Services
|
|||||||
|
|
||||||
if (request == null) return;
|
if (request == null) return;
|
||||||
|
|
||||||
QueueRequest(request, accountId);
|
await QueueRequestAsync(request, accountId);
|
||||||
QueueSynchronization(accountId);
|
QueueSynchronization(accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest)
|
public async Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest)
|
||||||
{
|
{
|
||||||
var request = new CreateDraftRequest(draftPreperationRequest);
|
var request = new CreateDraftRequest(draftPreperationRequest);
|
||||||
|
|
||||||
QueueRequest(request, draftPreperationRequest.Account.Id);
|
await QueueRequestAsync(request, draftPreperationRequest.Account.Id);
|
||||||
QueueSynchronization(draftPreperationRequest.Account.Id);
|
QueueSynchronization(draftPreperationRequest.Account.Id);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest)
|
public async Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest)
|
||||||
{
|
{
|
||||||
var request = new SendDraftRequest(sendDraftPreperationRequest);
|
var request = new SendDraftRequest(sendDraftPreperationRequest);
|
||||||
|
|
||||||
QueueRequest(request, sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
await QueueRequestAsync(request, sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
||||||
QueueSynchronization(sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
QueueSynchronization(sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueRequest(IRequestBase request, Guid accountId)
|
private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||||
{
|
{
|
||||||
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(accountId);
|
try
|
||||||
|
|
||||||
if (synchronizer == null)
|
|
||||||
{
|
{
|
||||||
_logger.Warning("Synchronizer not found for account {AccountId}.", accountId);
|
await _winoServerConnectionManager.QueueRequestAsync(request, accountId);
|
||||||
_logger.Warning("Skipping queueing request {Operation}.", request.Operation);
|
}
|
||||||
|
catch (WinoServerException serverException)
|
||||||
return;
|
{
|
||||||
|
_dialogService.InfoBarMessage("", serverException.Message, InfoBarMessageType.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronizer.QueueRequest(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueSynchronization(Guid accountId)
|
private void QueueSynchronization(Guid accountId)
|
||||||
@@ -154,7 +147,7 @@ namespace Wino.Core.Services
|
|||||||
Type = SynchronizationType.ExecuteRequests
|
Type = SynchronizationType.ExecuteRequests
|
||||||
};
|
};
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options));
|
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Client));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -92,6 +91,7 @@ namespace Wino.Core.Services
|
|||||||
|
|
||||||
var requests = new List<IRequest>();
|
var requests = new List<IRequest>();
|
||||||
|
|
||||||
|
// TODO: Fix: Collection was modified; enumeration operation may not execute
|
||||||
foreach (var item in preperationRequest.MailItems)
|
foreach (var item in preperationRequest.MailItems)
|
||||||
{
|
{
|
||||||
var singleRequest = await GetSingleRequestAsync(item, action, moveTargetStructure, preperationRequest.ToggleExecution);
|
var singleRequest = await GetSingleRequestAsync(item, action, moveTargetStructure, preperationRequest.ToggleExecution);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -14,57 +15,12 @@ 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.Domain.Models.Synchronization;
|
||||||
using Wino.Core.Integration;
|
using Wino.Core.Integration;
|
||||||
using Wino.Core.Messages.Mails;
|
|
||||||
using Wino.Core.Messages.Synchronization;
|
|
||||||
using Wino.Core.Misc;
|
using Wino.Core.Misc;
|
||||||
using Wino.Core.Requests;
|
using Wino.Core.Requests;
|
||||||
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
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);
|
||||||
@@ -88,7 +44,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
{
|
{
|
||||||
state = value;
|
state = value;
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new AccountSynchronizerStateChanged(this, value));
|
WeakReferenceMessenger.Default.Send(new AccountSynchronizerStateChanged(Account.Id, value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,18 +122,21 @@ namespace Wino.Core.Synchronizers
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
Logger.Warning("Synchronization cancelled.");
|
Logger.Warning("Synchronization canceled.");
|
||||||
|
|
||||||
return SynchronizationResult.Canceled;
|
return SynchronizationResult.Canceled;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Disable maybe?
|
Logger.Error(ex, "Synchronization failed for {Name}", Account.Name);
|
||||||
|
Debugger.Break();
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Reset account progress to hide the progress.
|
// Reset account progress to hide the progress.
|
||||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, 0);
|
PublishSynchronizationProgress(0);
|
||||||
|
|
||||||
State = AccountSynchronizerState.Idle;
|
State = AccountSynchronizerState.Idle;
|
||||||
synchronizationSemaphore.Release();
|
synchronizationSemaphore.Release();
|
||||||
@@ -191,6 +150,9 @@ namespace Wino.Core.Synchronizers
|
|||||||
private void PublishUnreadItemChanges()
|
private void PublishUnreadItemChanges()
|
||||||
=> WeakReferenceMessenger.Default.Send(new RefreshUnreadCountsMessage(Account.Id));
|
=> WeakReferenceMessenger.Default.Send(new RefreshUnreadCountsMessage(Account.Id));
|
||||||
|
|
||||||
|
public void PublishSynchronizationProgress(double progress)
|
||||||
|
=> WeakReferenceMessenger.Default.Send(new AccountSynchronizationProgressUpdatedMessage(Account.Id, progress));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 1. Group all requests by operation type.
|
/// 1. Group all requests by operation type.
|
||||||
/// 2. Group all individual operation type requests with equality check.
|
/// 2. Group all individual operation type requests with equality check.
|
||||||
@@ -302,20 +264,31 @@ namespace Wino.Core.Synchronizers
|
|||||||
/// <returns>New synchronization options with minimal HTTP effort.</returns>
|
/// <returns>New synchronization options with minimal HTTP effort.</returns>
|
||||||
private SynchronizationOptions GetSynchronizationOptionsAfterRequestExecution(IEnumerable<IRequestBase> requests)
|
private SynchronizationOptions GetSynchronizationOptionsAfterRequestExecution(IEnumerable<IRequestBase> requests)
|
||||||
{
|
{
|
||||||
bool isAllCustomSynchronizationRequests = requests.All(a => a is ICustomFolderSynchronizationRequest);
|
List<Guid> synchronizationFolderIds = new();
|
||||||
|
|
||||||
|
if (requests.All(a => a is IBatchChangeRequest))
|
||||||
|
{
|
||||||
|
var requestsInsideBatches = requests.Cast<IBatchChangeRequest>().SelectMany(b => b.Items);
|
||||||
|
|
||||||
|
// Gather FolderIds to synchronize.
|
||||||
|
synchronizationFolderIds = requestsInsideBatches
|
||||||
|
.Where(a => a is ICustomFolderSynchronizationRequest)
|
||||||
|
.Cast<ICustomFolderSynchronizationRequest>()
|
||||||
|
.SelectMany(a => a.SynchronizationFolderIds)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
var options = new SynchronizationOptions()
|
var options = new SynchronizationOptions()
|
||||||
{
|
{
|
||||||
AccountId = Account.Id,
|
AccountId = Account.Id,
|
||||||
Type = SynchronizationType.FoldersOnly
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isAllCustomSynchronizationRequests)
|
if (synchronizationFolderIds.Count > 0)
|
||||||
{
|
{
|
||||||
// Gather FolderIds to synchronize.
|
// Gather FolderIds to synchronize.
|
||||||
|
|
||||||
options.Type = SynchronizationType.Custom;
|
options.Type = SynchronizationType.Custom;
|
||||||
options.SynchronizationFolderIds = requests.Cast<ICustomFolderSynchronizationRequest>().SelectMany(a => a.SynchronizationFolderIds).ToList();
|
options.SynchronizationFolderIds = synchronizationFolderIds;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start downloading missing messages.
|
// Start downloading missing messages.
|
||||||
await BatchDownloadMessagesAsync(missingMessageIds, options.ProgressListener, cancellationToken).ConfigureAwait(false);
|
await BatchDownloadMessagesAsync(missingMessageIds, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// Map remote drafts to local drafts.
|
// Map remote drafts to local drafts.
|
||||||
await MapDraftIdsAsync(cancellationToken).ConfigureAwait(false);
|
await MapDraftIdsAsync(cancellationToken).ConfigureAwait(false);
|
||||||
@@ -353,7 +353,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageIds">Gmail message ids to download.</param>
|
/// <param name="messageIds">Gmail message ids to download.</param>
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
private async Task BatchDownloadMessagesAsync(IEnumerable<string> messageIds, ISynchronizationProgress progressListener = null, CancellationToken cancellationToken = default)
|
private async Task BatchDownloadMessagesAsync(IEnumerable<string> messageIds, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var totalDownloadCount = messageIds.Count();
|
var totalDownloadCount = messageIds.Count();
|
||||||
|
|
||||||
@@ -396,7 +396,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
var progressValue = downloadedItemCount * 100 / Math.Max(1, totalDownloadCount);
|
var progressValue = downloadedItemCount * 100 / Math.Max(1, totalDownloadCount);
|
||||||
|
|
||||||
progressListener?.AccountProgressUpdated(Account.Id, progressValue);
|
PublishSynchronizationProgress(progressValue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -407,21 +407,19 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
public override async Task<SynchronizationResult> SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default)
|
public override async Task<SynchronizationResult> SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// options.Type = SynchronizationType.FoldersOnly;
|
|
||||||
|
|
||||||
var downloadedMessageIds = new List<string>();
|
var downloadedMessageIds = new List<string>();
|
||||||
|
|
||||||
_logger.Information("Internal synchronization started for {Name}", Account.Name);
|
_logger.Information("Internal synchronization started for {Name}", Account.Name);
|
||||||
_logger.Information("Options: {Options}", options);
|
_logger.Information("Options: {Options}", options);
|
||||||
|
|
||||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, 1);
|
PublishSynchronizationProgress(1);
|
||||||
|
|
||||||
// Only do folder sync for these types.
|
bool shouldDoFolderSync = options.Type == SynchronizationType.Full || options.Type == SynchronizationType.FoldersOnly;
|
||||||
// Opening folder and checking their UidValidity is slow.
|
|
||||||
// Therefore this should be avoided as many times as possible.
|
|
||||||
|
|
||||||
// This may create some inconsistencies, but nothing we can do...
|
if (shouldDoFolderSync)
|
||||||
await SynchronizeFoldersAsync(cancellationToken).ConfigureAwait(false);
|
{
|
||||||
|
await SynchronizeFoldersAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.Type != SynchronizationType.FoldersOnly)
|
if (options.Type != SynchronizationType.FoldersOnly)
|
||||||
{
|
{
|
||||||
@@ -432,14 +430,14 @@ namespace Wino.Core.Synchronizers
|
|||||||
var folder = synchronizationFolders[i];
|
var folder = synchronizationFolders[i];
|
||||||
var progress = (int)Math.Round((double)(i + 1) / synchronizationFolders.Count * 100);
|
var progress = (int)Math.Round((double)(i + 1) / synchronizationFolders.Count * 100);
|
||||||
|
|
||||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, progress);
|
PublishSynchronizationProgress(progress);
|
||||||
|
|
||||||
var folderDownloadedMessageIds = await SynchronizeFolderInternalAsync(folder, cancellationToken).ConfigureAwait(false);
|
var folderDownloadedMessageIds = await SynchronizeFolderInternalAsync(folder, cancellationToken).ConfigureAwait(false);
|
||||||
downloadedMessageIds.AddRange(folderDownloadedMessageIds);
|
downloadedMessageIds.AddRange(folderDownloadedMessageIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, 100);
|
PublishSynchronizationProgress(100);
|
||||||
|
|
||||||
// Get all unread new downloaded items and return in the result.
|
// Get all unread new downloaded items and return in the result.
|
||||||
// This is primarily used in notifications.
|
// This is primarily used in notifications.
|
||||||
@@ -943,7 +941,12 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
foreach (var mailPackage in createdMailPackages)
|
foreach (var mailPackage in createdMailPackages)
|
||||||
{
|
{
|
||||||
await _imapChangeProcessor.CreateMailAsync(Account.Id, mailPackage).ConfigureAwait(false);
|
bool isCreated = await _imapChangeProcessor.CreateMailAsync(Account.Id, mailPackage).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (isCreated)
|
||||||
|
{
|
||||||
|
downloadedMessageIds.Add(mailPackage.Copy.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ using Microsoft.Graph;
|
|||||||
using Microsoft.Graph.Models;
|
using Microsoft.Graph.Models;
|
||||||
using Microsoft.Kiota.Abstractions;
|
using Microsoft.Kiota.Abstractions;
|
||||||
using Microsoft.Kiota.Abstractions.Authentication;
|
using Microsoft.Kiota.Abstractions.Authentication;
|
||||||
|
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware;
|
||||||
|
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using MoreLinq.Extensions;
|
using MoreLinq.Extensions;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@@ -73,19 +75,59 @@ namespace Wino.Core.Synchronizers
|
|||||||
{
|
{
|
||||||
var tokenProvider = new MicrosoftTokenProvider(Account, authenticator);
|
var tokenProvider = new MicrosoftTokenProvider(Account, authenticator);
|
||||||
|
|
||||||
// Add immutable id preffered client.
|
// Update request handlers for Graph client.
|
||||||
var handlers = GraphClientFactory.CreateDefaultHandlers();
|
var handlers = GraphClientFactory.CreateDefaultHandlers();
|
||||||
handlers.Add(new MicrosoftImmutableIdHandler());
|
|
||||||
|
handlers.Add(GetMicrosoftImmutableIdHandler());
|
||||||
|
|
||||||
|
// Remove existing RetryHandler and add a new one with custom options.
|
||||||
|
var existingRetryHandler = handlers.FirstOrDefault(a => a is RetryHandler);
|
||||||
|
if (existingRetryHandler != null)
|
||||||
|
handlers.Remove(existingRetryHandler);
|
||||||
|
|
||||||
|
// Add custom one.
|
||||||
|
handlers.Add(GetRetryHandler());
|
||||||
|
|
||||||
var httpClient = GraphClientFactory.Create(handlers);
|
var httpClient = GraphClientFactory.Create(handlers);
|
||||||
|
|
||||||
_graphClient = new GraphServiceClient(httpClient, new BaseBearerTokenAuthenticationProvider(tokenProvider));
|
_graphClient = new GraphServiceClient(httpClient, new BaseBearerTokenAuthenticationProvider(tokenProvider));
|
||||||
|
|
||||||
_outlookChangeProcessor = outlookChangeProcessor;
|
_outlookChangeProcessor = outlookChangeProcessor;
|
||||||
|
|
||||||
// Specify to use TLS 1.2 as default connection
|
// Specify to use TLS 1.2 as default connection
|
||||||
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region MS Graph Handlers
|
||||||
|
|
||||||
|
private MicrosoftImmutableIdHandler GetMicrosoftImmutableIdHandler() => new();
|
||||||
|
|
||||||
|
private RetryHandler GetRetryHandler()
|
||||||
|
{
|
||||||
|
var options = new RetryHandlerOption()
|
||||||
|
{
|
||||||
|
ShouldRetry = (delay, attempt, httpResponse) =>
|
||||||
|
{
|
||||||
|
var statusCode = httpResponse.StatusCode;
|
||||||
|
|
||||||
|
return statusCode switch
|
||||||
|
{
|
||||||
|
HttpStatusCode.ServiceUnavailable => true,
|
||||||
|
HttpStatusCode.GatewayTimeout => true,
|
||||||
|
(HttpStatusCode)429 => true,
|
||||||
|
HttpStatusCode.Unauthorized => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Delay = 3,
|
||||||
|
MaxRetry = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
return new RetryHandler(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
public override async Task<SynchronizationResult> SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default)
|
public override async Task<SynchronizationResult> SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var downloadedMessageIds = new List<string>();
|
var downloadedMessageIds = new List<string>();
|
||||||
@@ -95,7 +137,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, 1);
|
PublishSynchronizationProgress(1);
|
||||||
|
|
||||||
await SynchronizeFoldersAsync(cancellationToken).ConfigureAwait(false);
|
await SynchronizeFoldersAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -111,7 +153,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
var folder = synchronizationFolders[i];
|
var folder = synchronizationFolders[i];
|
||||||
var progress = (int)Math.Round((double)(i + 1) / synchronizationFolders.Count * 100);
|
var progress = (int)Math.Round((double)(i + 1) / synchronizationFolders.Count * 100);
|
||||||
|
|
||||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, progress);
|
PublishSynchronizationProgress(progress);
|
||||||
|
|
||||||
var folderDownloadedMessageIds = await SynchronizeFolderAsync(folder, cancellationToken).ConfigureAwait(false);
|
var folderDownloadedMessageIds = await SynchronizeFolderAsync(folder, cancellationToken).ConfigureAwait(false);
|
||||||
downloadedMessageIds.AddRange(folderDownloadedMessageIds);
|
downloadedMessageIds.AddRange(folderDownloadedMessageIds);
|
||||||
@@ -120,13 +162,14 @@ namespace Wino.Core.Synchronizers
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Synchronization failed for {Name}", Account.Name);
|
_logger.Error(ex, "Synchronizing folders for {Name}", Account.Name);
|
||||||
|
Debugger.Break();
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, 100);
|
PublishSynchronizationProgress(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all unred new downloaded items and return in the result.
|
// Get all unred new downloaded items and return in the result.
|
||||||
@@ -238,20 +281,12 @@ namespace Wino.Core.Synchronizers
|
|||||||
private bool IsResourceDeleted(IDictionary<string, object> additionalData)
|
private bool IsResourceDeleted(IDictionary<string, object> additionalData)
|
||||||
=> additionalData != null && additionalData.ContainsKey("@removed");
|
=> additionalData != null && additionalData.ContainsKey("@removed");
|
||||||
|
|
||||||
private bool IsResourceUpdated(IDictionary<string, object> additionalData)
|
|
||||||
=> additionalData == null || !additionalData.Any();
|
|
||||||
|
|
||||||
private async Task<bool> HandleFolderRetrievedAsync(MailFolder folder, OutlookSpecialFolderIdInformation outlookSpecialFolderIdInformation, CancellationToken cancellationToken = default)
|
private async Task<bool> HandleFolderRetrievedAsync(MailFolder folder, OutlookSpecialFolderIdInformation outlookSpecialFolderIdInformation, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (IsResourceDeleted(folder.AdditionalData))
|
if (IsResourceDeleted(folder.AdditionalData))
|
||||||
{
|
{
|
||||||
await _outlookChangeProcessor.DeleteFolderAsync(Account.Id, folder.Id).ConfigureAwait(false);
|
await _outlookChangeProcessor.DeleteFolderAsync(Account.Id, folder.Id).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (IsResourceUpdated(folder.AdditionalData))
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
Debugger.Break();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// New folder created.
|
// New folder created.
|
||||||
@@ -297,38 +332,45 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
await _outlookChangeProcessor.DeleteAssignmentAsync(Account.Id, item.Id, folder.RemoteFolderId).ConfigureAwait(false);
|
await _outlookChangeProcessor.DeleteAssignmentAsync(Account.Id, item.Id, folder.RemoteFolderId).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (IsResourceUpdated(item.AdditionalData))
|
|
||||||
{
|
|
||||||
// Some of the properties of the item are updated.
|
|
||||||
|
|
||||||
if (item.IsRead != null)
|
|
||||||
{
|
|
||||||
await _outlookChangeProcessor.ChangeMailReadStatusAsync(item.Id, item.IsRead.GetValueOrDefault()).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.Flag?.FlagStatus != null)
|
|
||||||
{
|
|
||||||
await _outlookChangeProcessor.ChangeFlagStatusAsync(item.Id, item.Flag.FlagStatus.GetValueOrDefault() == FollowupFlagStatus.Flagged)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Package may return null on some cases mapping the remote draft to existing local draft.
|
// If the item exists in the local database, it means that it's already downloaded. Process as an Update.
|
||||||
|
|
||||||
var newMailPackages = await CreateNewMailPackagesAsync(item, folder, cancellationToken);
|
var isMailExists = await _outlookChangeProcessor.IsMailExistsInFolderAsync(item.Id, folder.Id);
|
||||||
|
|
||||||
if (newMailPackages != null)
|
if (isMailExists)
|
||||||
{
|
{
|
||||||
foreach (var package in newMailPackages)
|
// Some of the properties of the item are updated.
|
||||||
{
|
|
||||||
// Only add to downloaded message ids if it's inserted successfuly.
|
|
||||||
// Updates should not be added to the list because they are not new.
|
|
||||||
bool isInserted = await _outlookChangeProcessor.CreateMailAsync(Account.Id, package).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (isInserted)
|
if (item.IsRead != null)
|
||||||
|
{
|
||||||
|
await _outlookChangeProcessor.ChangeMailReadStatusAsync(item.Id, item.IsRead.GetValueOrDefault()).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Flag?.FlagStatus != null)
|
||||||
|
{
|
||||||
|
await _outlookChangeProcessor.ChangeFlagStatusAsync(item.Id, item.Flag.FlagStatus.GetValueOrDefault() == FollowupFlagStatus.Flagged)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Package may return null on some cases mapping the remote draft to existing local draft.
|
||||||
|
|
||||||
|
var newMailPackages = await CreateNewMailPackagesAsync(item, folder, cancellationToken);
|
||||||
|
|
||||||
|
if (newMailPackages != null)
|
||||||
|
{
|
||||||
|
foreach (var package in newMailPackages)
|
||||||
{
|
{
|
||||||
downloadedMessageIds.Add(package.Copy.Id);
|
// Only add to downloaded message ids if it's inserted successfuly.
|
||||||
|
// Updates should not be added to the list because they are not new.
|
||||||
|
bool isInserted = await _outlookChangeProcessor.CreateMailAsync(Account.Id, package).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (isInserted)
|
||||||
|
{
|
||||||
|
downloadedMessageIds.Add(package.Copy.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,11 +381,12 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
private async Task SynchronizeFoldersAsync(CancellationToken cancellationToken = default)
|
private async Task SynchronizeFoldersAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Gather special folders by default.
|
// Gather special folders by default.
|
||||||
// Others will be other type.
|
// Others will be other type.
|
||||||
|
|
||||||
// Get well known folder ids by batch.
|
// Get well known folder ids by batch.
|
||||||
|
|
||||||
|
retry:
|
||||||
var wellKnownFolderIdBatch = new BatchRequestContentCollection(_graphClient);
|
var wellKnownFolderIdBatch = new BatchRequestContentCollection(_graphClient);
|
||||||
|
|
||||||
var inboxRequest = _graphClient.Me.MailFolders[INBOX_NAME].ToGetRequestInformation((t) => { t.QueryParameters.Select = ["id"]; });
|
var inboxRequest = _graphClient.Me.MailFolders[INBOX_NAME].ToGetRequestInformation((t) => { t.QueryParameters.Select = ["id"]; });
|
||||||
@@ -394,9 +437,19 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
deltaRequest.UrlTemplate = deltaRequest.UrlTemplate.Insert(deltaRequest.UrlTemplate.Length - 1, ",%24deltaToken");
|
deltaRequest.UrlTemplate = deltaRequest.UrlTemplate.Insert(deltaRequest.UrlTemplate.Length - 1, ",%24deltaToken");
|
||||||
deltaRequest.QueryParameters.Add("%24deltaToken", currentDeltaLink);
|
deltaRequest.QueryParameters.Add("%24deltaToken", currentDeltaLink);
|
||||||
graphFolders = await _graphClient.RequestAdapter.SendAsync(deltaRequest,
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
graphFolders = await _graphClient.RequestAdapter.SendAsync(deltaRequest,
|
||||||
Microsoft.Graph.Me.MailFolders.Delta.DeltaGetResponse.CreateFromDiscriminatorValue,
|
Microsoft.Graph.Me.MailFolders.Delta.DeltaGetResponse.CreateFromDiscriminatorValue,
|
||||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (ApiException apiException) when (apiException.ResponseStatusCode == 410)
|
||||||
|
{
|
||||||
|
Account.SynchronizationDeltaIdentifier = await _outlookChangeProcessor.ResetAccountDeltaTokenAsync(Account.Id);
|
||||||
|
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var iterator = PageIterator<MailFolder, Microsoft.Graph.Me.MailFolders.Delta.DeltaGetResponse>.CreatePageIterator(_graphClient, graphFolders, (folder) =>
|
var iterator = PageIterator<MailFolder, Microsoft.Graph.Me.MailFolders.Delta.DeltaGetResponse>.CreatePageIterator(_graphClient, graphFolders, (folder) =>
|
||||||
@@ -686,6 +739,8 @@ namespace Wino.Core.Synchronizers
|
|||||||
HttpResponseMessage httpResponseMessage,
|
HttpResponseMessage httpResponseMessage,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (httpResponseMessage == null) return;
|
||||||
|
|
||||||
if (!httpResponseMessage.IsSuccessStatusCode)
|
if (!httpResponseMessage.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
throw new SynchronizerException(string.Format(Translator.Exception_SynchronizerFailureHTTP, httpResponseMessage.StatusCode));
|
throw new SynchronizerException(string.Format(Translator.Exception_SynchronizerFailureHTTP, httpResponseMessage.StatusCode));
|
||||||
|
|||||||
@@ -5,8 +5,13 @@
|
|||||||
<RootNamespace>Wino.Core</RootNamespace>
|
<RootNamespace>Wino.Core</RootNamespace>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
<LangVersion>12</LangVersion>
|
<LangVersion>12</LangVersion>
|
||||||
|
<Platforms>AnyCPU;x64;x86</Platforms>
|
||||||
</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 +23,13 @@
|
|||||||
<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.56.0" />
|
||||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.47.2" />
|
<PackageReference Include="Microsoft.Identity.Client" Version="4.62.0" />
|
||||||
<PackageReference Include="MimeKit" Version="4.6.0" />
|
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.62.0" />
|
||||||
|
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.62.0" />
|
||||||
|
<PackageReference Include="MimeKit" Version="4.7.1" />
|
||||||
<PackageReference Include="morelinq" Version="4.1.0" />
|
<PackageReference Include="morelinq" Version="4.1.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||||
@@ -33,10 +40,10 @@
|
|||||||
<PackageReference Include="SkiaSharp" Version="2.88.8" />
|
<PackageReference Include="SkiaSharp" Version="2.88.8" />
|
||||||
<PackageReference Include="SqlKata" Version="2.4.0" />
|
<PackageReference Include="SqlKata" Version="2.4.0" />
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.8.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
@@ -72,12 +72,13 @@ namespace Wino.Mail.ViewModels
|
|||||||
|
|
||||||
private Task ShareProtocolLogAsync()
|
private Task ShareProtocolLogAsync()
|
||||||
=> SaveLogInternalAsync(ImapTestService.ProtocolLogFileName);
|
=> SaveLogInternalAsync(ImapTestService.ProtocolLogFileName);
|
||||||
|
|
||||||
private Task ShareWinoLogAsync()
|
private Task ShareWinoLogAsync()
|
||||||
=> SaveLogInternalAsync(LogInitializer.WinoLogFileName);
|
=> SaveLogInternalAsync(Constants.ClientLogFile);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user