using System; using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Accounts; using Wino.Core.Requests.Bundles; using Wino.Messaging.UI; namespace Wino.Core.Synchronizers; public abstract partial class BaseSynchronizer : ObservableObject, IBaseSynchronizer { protected SemaphoreSlim synchronizationSemaphore = new(1); protected CancellationToken activeSynchronizationCancellationToken; protected List changeRequestQueue = []; protected readonly IMessenger Messenger; public MailAccount Account { get; } private AccountSynchronizerState state; public AccountSynchronizerState State { get { return state; } set { state = value; // Send state changed message with current progress information Messenger.Send(new AccountSynchronizerStateChanged( Account.Id, value, TotalItemsToSync, RemainingItemsToSync, SynchronizationStatus)); } } /// /// Current synchronization status message. /// [ObservableProperty] public partial string SynchronizationStatus { get; set; } = string.Empty; /// /// Total items to download/sync in current operation. /// 0 means no active download or indeterminate progress. /// [ObservableProperty] public partial int TotalItemsToSync { get; set; } /// /// Remaining items to download/sync in current operation. /// [ObservableProperty] public partial int RemainingItemsToSync { get; set; } /// /// Calculated progress percentage (0-100) based on TotalItemsToSync and RemainingItemsToSync. /// Returns -1 for indeterminate progress (when both are 0). /// public double SynchronizationProgress { get { if (TotalItemsToSync == 0 || RemainingItemsToSync == 0) return -1; // Indeterminate return ((double)(TotalItemsToSync - RemainingItemsToSync) / TotalItemsToSync) * 100; } } protected BaseSynchronizer(MailAccount account, IMessenger messenger) { Account = account; Messenger = messenger ?? WeakReferenceMessenger.Default; } /// /// Resets synchronization progress to default state. /// protected void ResetSyncProgress() { TotalItemsToSync = 0; RemainingItemsToSync = 0; SynchronizationStatus = string.Empty; OnPropertyChanged(nameof(SynchronizationProgress)); } /// /// Updates synchronization progress with current item counts. /// /// Total items to sync /// Remaining items to sync /// Optional status message protected void UpdateSyncProgress(int total, int remaining, string status = "") { TotalItemsToSync = total; RemainingItemsToSync = remaining; SynchronizationStatus = status; OnPropertyChanged(nameof(SynchronizationProgress)); // Send progress update message Messenger.Send(new AccountSynchronizerStateChanged( Account.Id, State, TotalItemsToSync, RemainingItemsToSync, SynchronizationStatus)); } /// /// Queues a single request to be executed in the next synchronization. /// /// Request to execute. public void QueueRequest(IRequestBase request) => changeRequestQueue.Add(request); /// /// Runs existing queued requests in the queue. /// /// Batched requests to execute. Integrator methods will only receive batched requests. /// Cancellation token public abstract Task ExecuteNativeRequestsAsync(List> batchedRequests, CancellationToken cancellationToken = default); /// /// Refreshes remote mail account profile if possible. /// Profile picture, sender name and mailbox settings (todo) will be handled in this step. /// public virtual Task GetProfileInformationAsync() => default; /// /// Safely updates account's profile information. /// Database changes are reflected after this call. /// protected async Task SynchronizeProfileInformationInternalAsync() { var profileInformation = await GetProfileInformationAsync(); if (profileInformation != null) { Account.SenderName = profileInformation.SenderName; Account.Base64ProfilePictureData = profileInformation.Base64ProfilePictureData; if (!string.IsNullOrEmpty(profileInformation.AccountAddress)) { Account.Address = profileInformation.AccountAddress; } } return profileInformation; } /// /// Returns the base64 encoded profile picture of the account from the given URL. /// /// URL to retrieve picture from. /// base64 encoded profile picture protected async Task GetProfilePictureBase64EncodedAsync(string url) { using var client = new HttpClient(); var response = await client.GetAsync(url).ConfigureAwait(false); var byteContent = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); return Convert.ToBase64String(byteContent); } public List> ForEachRequest(IEnumerable requests, Func action) where TWinoRequestType : IRequestBase { List> ret = []; foreach (var request in requests) ret.Add(new HttpRequestBundle(action(request), request, request)); return ret; } }