some changes for progress
This commit is contained in:
@@ -20,14 +20,14 @@ public partial class MergedAccountMenuItem : MenuItemBase<MergedInbox, IMenuItem
|
|||||||
/// Total items to sync across all merged accounts.
|
/// Total items to sync across all merged accounts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedFor(nameof(SynchronizationProgress))]
|
[NotifyPropertyChangedFor(nameof(SynchronizationProgress), nameof(IsSynchronizationProgressVisible), nameof(IsProgressIndeterminate))]
|
||||||
public partial int TotalItemsToSync { get; set; }
|
public partial int TotalItemsToSync { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remaining items to sync across all merged accounts.
|
/// Remaining items to sync across all merged accounts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedFor(nameof(SynchronizationProgress))]
|
[NotifyPropertyChangedFor(nameof(SynchronizationProgress), nameof(IsSynchronizationProgressVisible), nameof(IsProgressIndeterminate))]
|
||||||
public partial int RemainingItemsToSync { get; set; }
|
public partial int RemainingItemsToSync { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -50,6 +50,17 @@ public partial class MergedAccountMenuItem : MenuItemBase<MergedInbox, IMenuItem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether synchronization progress should be visible.
|
||||||
|
/// Visible when there's active synchronization (TotalItemsToSync > 0 or RemainingItemsToSync > 0).
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSynchronizationProgressVisible => TotalItemsToSync > 0 || RemainingItemsToSync > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether progress should be indeterminate.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsProgressIndeterminate => TotalItemsToSync == 0 && IsSynchronizationProgressVisible;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string mergedAccountName;
|
private string mergedAccountName;
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
if (shouldSynchronizeFolders)
|
if (shouldSynchronizeFolders)
|
||||||
{
|
{
|
||||||
_logger.Information("Synchronizing folders for {Name}", Account.Name);
|
_logger.Information("Synchronizing folders for {Name}", Account.Name);
|
||||||
|
UpdateSyncProgress(0, 0, "Synchronizing folders...");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -169,6 +170,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
}
|
}
|
||||||
|
|
||||||
_logger.Information("Synchronizing folders for {Name} is completed", Account.Name);
|
_logger.Information("Synchronizing folders for {Name} is completed", Account.Name);
|
||||||
|
UpdateSyncProgress(0, 0, "Folders synchronized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is no specific folder synchronization in Gmail.
|
// There is no specific folder synchronization in Gmail.
|
||||||
@@ -186,24 +188,30 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
|
|
||||||
if (Account.SynchronizationStatus == InitialSynchronizationStatus.None)
|
if (Account.SynchronizationStatus == InitialSynchronizationStatus.None)
|
||||||
{
|
{
|
||||||
|
UpdateSyncProgress(0, 0, "Fetching email IDs...");
|
||||||
await FetchAllEmailIdsAsync().ConfigureAwait(false);
|
await FetchAllEmailIdsAsync().ConfigureAwait(false);
|
||||||
await CompleteAccountSyncStatusAsync();
|
await CompleteAccountSyncStatusAsync();
|
||||||
|
UpdateSyncProgress(0, 0, "Email IDs fetched");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Account.SynchronizationDeltaIdentifier))
|
if (!string.IsNullOrEmpty(Account.SynchronizationDeltaIdentifier))
|
||||||
{
|
{
|
||||||
|
UpdateSyncProgress(0, 0, "Synchronizing changes...");
|
||||||
await SynchronizeDeltaAsync(options, cancellationToken).ConfigureAwait(false);
|
await SynchronizeDeltaAsync(options, cancellationToken).ConfigureAwait(false);
|
||||||
await CompleteAccountSyncStatusAsync();
|
await CompleteAccountSyncStatusAsync();
|
||||||
|
UpdateSyncProgress(0, 0, "Changes synchronized");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Account.SynchronizationStatus == InitialSynchronizationStatus.IdsFetched)
|
if (Account.SynchronizationStatus == InitialSynchronizationStatus.IdsFetched)
|
||||||
{
|
{
|
||||||
|
UpdateSyncProgress(0, 0, "Processing email metadata...");
|
||||||
await ProcessEmailMetadataFromQueueAsync(cancellationToken);
|
await ProcessEmailMetadataFromQueueAsync(cancellationToken);
|
||||||
|
UpdateSyncProgress(0, 0, "Email metadata processed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Account.SynchronizationStatus == InitialSynchronizationStatus.Completed)
|
if (Account.SynchronizationStatus == InitialSynchronizationStatus.Completed)
|
||||||
{
|
{
|
||||||
|
UpdateSyncProgress(0, 0, "Synchronization completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
return MailSynchronizationResult.Completed(new List<MailCopy>());
|
return MailSynchronizationResult.Completed(new List<MailCopy>());
|
||||||
@@ -220,12 +228,15 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
await _gmailChangeProcessor.ClearMailItemQueueAsync(Account.Id).ConfigureAwait(false);
|
await _gmailChangeProcessor.ClearMailItemQueueAsync(Account.Id).ConfigureAwait(false);
|
||||||
|
|
||||||
var totalFetched = 0;
|
var totalFetched = 0;
|
||||||
string? pageToken = null;
|
string pageToken = null;
|
||||||
|
var pageCount = 0;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
pageCount++;
|
||||||
|
|
||||||
// Use maximum page size of 500 for efficiency
|
// Use maximum page size of 500 for efficiency
|
||||||
var request = _gmailService.Users.Messages.List("me");
|
var request = _gmailService.Users.Messages.List("me");
|
||||||
request.MaxResults = 500;
|
request.MaxResults = 500;
|
||||||
@@ -248,6 +259,9 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
await _gmailChangeProcessor.AddMailItemQueueItemsAsync(queueEntries1).ConfigureAwait(false);
|
await _gmailChangeProcessor.AddMailItemQueueItemsAsync(queueEntries1).ConfigureAwait(false);
|
||||||
|
|
||||||
totalFetched += queueEntries1.Count();
|
totalFetched += queueEntries1.Count();
|
||||||
|
|
||||||
|
// Update progress - we don't know total count, so show indeterminate with status
|
||||||
|
UpdateSyncProgress(0, 0, $"Fetched {totalFetched} email IDs (page {pageCount})");
|
||||||
}
|
}
|
||||||
|
|
||||||
pageToken = response.NextPageToken;
|
pageToken = response.NextPageToken;
|
||||||
@@ -261,8 +275,11 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
continue; // Retry the same page
|
continue; // Retry the same page
|
||||||
}
|
}
|
||||||
} while (!string.IsNullOrEmpty(pageToken));
|
} while (!string.IsNullOrEmpty(pageToken));
|
||||||
|
|
||||||
|
// Final update with total count
|
||||||
|
UpdateSyncProgress(0, 0, $"Fetched {totalFetched} email IDs total");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -327,6 +344,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
{
|
{
|
||||||
// Get total count for progress tracking
|
// Get total count for progress tracking
|
||||||
var totalInQueue = await _gmailChangeProcessor.GetMailItemQueueCountAsync(Account.Id).ConfigureAwait(false);
|
var totalInQueue = await _gmailChangeProcessor.GetMailItemQueueCountAsync(Account.Id).ConfigureAwait(false);
|
||||||
|
var processedCount = 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -365,6 +383,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
{
|
{
|
||||||
queueItem.IsProcessed = true;
|
queueItem.IsProcessed = true;
|
||||||
queueItem.ProcessedAt = DateTime.UtcNow;
|
queueItem.ProcessedAt = DateTime.UtcNow;
|
||||||
|
processedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -376,11 +395,19 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
queueItem.ProcessedAt = null;
|
queueItem.ProcessedAt = null;
|
||||||
queueItem.FailedCount++;
|
queueItem.FailedCount++;
|
||||||
totalFailed++;
|
totalFailed++;
|
||||||
|
processedCount++; // Count failed items as processed for progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _gmailChangeProcessor.UpdateMailItemQueueAsync(mailItemQueue).ConfigureAwait(false);
|
await _gmailChangeProcessor.UpdateMailItemQueueAsync(mailItemQueue).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Update progress based on processed items
|
||||||
|
if (totalInQueue > 0)
|
||||||
|
{
|
||||||
|
var remainingItems = totalInQueue - processedCount;
|
||||||
|
UpdateSyncProgress(totalInQueue, remainingItems, $"Processing emails: {processedCount}/{totalInQueue}");
|
||||||
|
}
|
||||||
|
|
||||||
// If too many failures, pause to avoid hitting rate limits
|
// If too many failures, pause to avoid hitting rate limits
|
||||||
if (totalFailed > 85) await Task.Delay(TimeSpan.FromSeconds(10));
|
if (totalFailed > 85) await Task.Delay(TimeSpan.FromSeconds(10));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -514,9 +514,10 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
// Group items by their grouping key and add them in a single UI thread call
|
// Group items by their grouping key and add them in a single UI thread call
|
||||||
if (itemsToAdd.Count > 0)
|
if (itemsToAdd.Count > 0)
|
||||||
{
|
{
|
||||||
var groupedItems = itemsToAdd
|
// Pre-compute grouping on background thread to reduce UI thread work
|
||||||
|
var groupedItems = await Task.Run(() => itemsToAdd
|
||||||
.GroupBy(GetGroupingKey)
|
.GroupBy(GetGroupingKey)
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
.ToDictionary(g => g.Key, g => g.ToList())).ConfigureAwait(false);
|
||||||
|
|
||||||
await ExecuteUIThread(() =>
|
await ExecuteUIThread(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -571,7 +571,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var viewModels = PrepareMailViewModels(items);
|
var viewModels = await PrepareMailViewModelsAsync(items).ConfigureAwait(false);
|
||||||
|
|
||||||
await MailCollection.AddRangeAsync(viewModels, false);
|
await MailCollection.AddRangeAsync(viewModels, false);
|
||||||
await ExecuteUIThread(() => { IsInitializingFolder = false; });
|
await ExecuteUIThread(() => { IsInitializingFolder = false; });
|
||||||
@@ -738,9 +738,19 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MailItemViewModel> PrepareMailViewModels(IEnumerable<MailCopy> mailItems)
|
private async Task<List<MailItemViewModel>> PrepareMailViewModelsAsync(IEnumerable<MailCopy> mailItems, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return mailItems.Select(a => new MailItemViewModel(a)).ToList();
|
// Run ViewModel creation on background thread to avoid blocking UI
|
||||||
|
return await Task.Run(() =>
|
||||||
|
{
|
||||||
|
var viewModels = new List<MailItemViewModel>();
|
||||||
|
foreach (var mailItem in mailItems)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
viewModels.Add(new MailItemViewModel(mailItem));
|
||||||
|
}
|
||||||
|
return viewModels;
|
||||||
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -764,7 +774,11 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
if (ActiveFolder == null)
|
if (ActiveFolder == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await ExecuteUIThread(() => { IsInitializingFolder = true; });
|
await ExecuteUIThread(() => {
|
||||||
|
IsInitializingFolder = true;
|
||||||
|
// Show initial loading progress
|
||||||
|
UpdateBarMessage(InfoBarMessageType.Information, ActiveFolder.FolderName, "Loading emails...");
|
||||||
|
});
|
||||||
|
|
||||||
// Folder is changed during initialization.
|
// Folder is changed during initialization.
|
||||||
// Just cancel the existing one and wait for new initialization.
|
// Just cancel the existing one and wait for new initialization.
|
||||||
@@ -855,11 +869,21 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
if (!listManipulationCancellationTokenSource.IsCancellationRequested)
|
if (!listManipulationCancellationTokenSource.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
// Update progress: Creating view models
|
||||||
|
await ExecuteUIThread(() => {
|
||||||
|
UpdateBarMessage(InfoBarMessageType.Information, ActiveFolder.FolderName, $"Processing {items.Count} emails...");
|
||||||
|
});
|
||||||
|
|
||||||
// Here they are already threaded if needed.
|
// Here they are already threaded if needed.
|
||||||
// We don't need to insert them one by one.
|
// We don't need to insert them one by one.
|
||||||
// Just create VMs and do bulk insert.
|
// Just create VMs and do bulk insert.
|
||||||
|
|
||||||
var viewModels = PrepareMailViewModels(items);
|
var viewModels = await PrepareMailViewModelsAsync(items, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Update progress: Adding to collection
|
||||||
|
await ExecuteUIThread(() => {
|
||||||
|
UpdateBarMessage(InfoBarMessageType.Information, ActiveFolder.FolderName, "Finalizing...");
|
||||||
|
});
|
||||||
|
|
||||||
await MailCollection.AddRangeAsync(viewModels, clearIdCache: true);
|
await MailCollection.AddRangeAsync(viewModels, clearIdCache: true);
|
||||||
|
|
||||||
@@ -895,6 +919,9 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
OnPropertyChanged(nameof(CanSynchronize));
|
OnPropertyChanged(nameof(CanSynchronize));
|
||||||
NotifyItemFoundState();
|
NotifyItemFoundState();
|
||||||
|
|
||||||
|
// Clear the loading message after completion
|
||||||
|
IsBarOpen = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,12 +76,25 @@
|
|||||||
MaxLines="1"
|
MaxLines="1"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
Text="{x:Bind Parameter.Address, Mode=OneWay}"
|
Text="{x:Bind Parameter.Address, Mode=OneWay}"
|
||||||
TextTrimming="CharacterEllipsis" />
|
TextTrimming="CharacterEllipsis"
|
||||||
|
Visibility="{x:Bind IsSynchronizationProgressVisible, Converter={StaticResource ReverseBooleanToVisibilityConverter}, Mode=OneWay}" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
x:Name="SyncStatusText"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
MaxLines="1"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{x:Bind SynchronizationStatus, Mode=OneWay}"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<PathIcon
|
<PathIcon
|
||||||
x:Name="AttentionIcon"
|
x:Name="AttentionIcon"
|
||||||
Grid.Column="2"
|
Grid.Column="1"
|
||||||
|
Width="16"
|
||||||
|
Height="16"
|
||||||
|
Margin="8,0"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
x:Load="{x:Bind IsAttentionRequired, Mode=OneWay}"
|
x:Load="{x:Bind IsAttentionRequired, Mode=OneWay}"
|
||||||
@@ -90,18 +103,15 @@
|
|||||||
|
|
||||||
<muxc:ProgressRing
|
<muxc:ProgressRing
|
||||||
x:Name="SynchronizationProgressBar"
|
x:Name="SynchronizationProgressBar"
|
||||||
Grid.ColumnSpan="3"
|
Grid.Column="2"
|
||||||
Width="10"
|
Width="16"
|
||||||
Height="10"
|
Height="16"
|
||||||
HorizontalAlignment="Right"
|
Margin="8,0"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Background="{ThemeResource AppBarItemBackgroundThemeBrush}"
|
|
||||||
Foreground="{ThemeResource AppBarItemForegroundThemeBrush}"
|
|
||||||
IsActive="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
IsActive="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
||||||
IsIndeterminate="{x:Bind IsProgressIndeterminate}"
|
IsIndeterminate="{x:Bind IsProgressIndeterminate, Mode=OneWay}"
|
||||||
Maximum="100"
|
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
|
||||||
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
|
||||||
Value="{x:Bind SynchronizationProgress, Mode=OneWay}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</controls:AccountNavigationItem>
|
</controls:AccountNavigationItem>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
@@ -251,6 +261,10 @@
|
|||||||
</coreControls:WinoNavigationViewItem.Icon>
|
</coreControls:WinoNavigationViewItem.Icon>
|
||||||
|
|
||||||
<Grid Height="50">
|
<Grid Height="50">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
<StackPanel VerticalAlignment="Center" Spacing="0">
|
<StackPanel VerticalAlignment="Center" Spacing="0">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Name="AccountNameTextblock"
|
x:Name="AccountNameTextblock"
|
||||||
@@ -262,12 +276,34 @@
|
|||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
MaxLines="1"
|
MaxLines="1"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
TextTrimming="CharacterEllipsis">
|
Text="{x:Bind SynchronizationStatus, Mode=OneWay}"
|
||||||
<Run Text="{x:Bind MergedAccountCount}" /><Run Text="{x:Bind domain:Translator.MenuMergedAccountItemAccountsSuffix}" />
|
TextTrimming="CharacterEllipsis"
|
||||||
|
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
FontSize="12"
|
||||||
|
MaxLines="1"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(IsSynchronizationProgressVisible), Mode=OneWay}">
|
||||||
|
<Run Text="{x:Bind MergedAccountCount, Mode=OneWay}" /><Run Text="{x:Bind domain:Translator.MenuMergedAccountItemAccountsSuffix}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<muxc:ProgressRing
|
||||||
|
x:Name="SynchronizationProgressBar"
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="16"
|
||||||
|
Height="16"
|
||||||
|
Margin="8,0"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsActive="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
|
||||||
|
IsIndeterminate="{x:Bind IsProgressIndeterminate, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</controls:AccountNavigationItem>
|
</controls:AccountNavigationItem>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid MaxWidth="700">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SQLite;
|
using SQLite;
|
||||||
using Wino.Core.Domain.Entities.Calendar;
|
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
@@ -44,6 +43,10 @@ public class DatabaseService : IDatabaseService
|
|||||||
|
|
||||||
private async Task CreateTablesAsync()
|
private async Task CreateTablesAsync()
|
||||||
{
|
{
|
||||||
|
//typeof(AccountCalendar),
|
||||||
|
// typeof(CalendarEventAttendee),
|
||||||
|
// typeof(CalendarItem),
|
||||||
|
// typeof(Reminder),
|
||||||
await Connection.CreateTablesAsync(CreateFlags.None,
|
await Connection.CreateTablesAsync(CreateFlags.None,
|
||||||
typeof(MailCopy),
|
typeof(MailCopy),
|
||||||
typeof(MailItemFolder),
|
typeof(MailItemFolder),
|
||||||
@@ -54,10 +57,6 @@ public class DatabaseService : IDatabaseService
|
|||||||
typeof(MergedInbox),
|
typeof(MergedInbox),
|
||||||
typeof(MailAccountPreferences),
|
typeof(MailAccountPreferences),
|
||||||
typeof(MailAccountAlias),
|
typeof(MailAccountAlias),
|
||||||
typeof(AccountCalendar),
|
|
||||||
typeof(CalendarEventAttendee),
|
|
||||||
typeof(CalendarItem),
|
|
||||||
typeof(Reminder),
|
|
||||||
typeof(Thumbnail),
|
typeof(Thumbnail),
|
||||||
typeof(KeyboardShortcut),
|
typeof(KeyboardShortcut),
|
||||||
typeof(MailItemQueue)
|
typeof(MailItemQueue)
|
||||||
|
|||||||
@@ -252,33 +252,35 @@ public class MailService : BaseDatabaseService, IMailService
|
|||||||
return [.. mails];
|
return [.. mails];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include other mails in the same threads
|
// Include other mails in the same threads - batch process to reduce DB calls
|
||||||
var expandedMails = new List<MailCopy>(mails);
|
var expandedMails = new List<MailCopy>(mails);
|
||||||
var processedThreadIds = new HashSet<string>();
|
var uniqueThreadIds = mails
|
||||||
|
.Where(m => !string.IsNullOrEmpty(m.ThreadId))
|
||||||
|
.Select(m => m.ThreadId)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
foreach (var mail in mails)
|
if (uniqueThreadIds.Count > 0)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(mail.ThreadId) && !processedThreadIds.Contains(mail.ThreadId))
|
// Get all thread mails in a single DB call
|
||||||
|
var existingMailIds = expandedMails.Select(m => m.Id).ToHashSet();
|
||||||
|
var allThreadMails = await GetMailsByThreadIdsAsync(uniqueThreadIds, existingMailIds).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (allThreadMails?.Count > 0)
|
||||||
{
|
{
|
||||||
processedThreadIds.Add(mail.ThreadId);
|
// Process thread mails in parallel to improve performance
|
||||||
|
var tasks = allThreadMails.Select(async threadMail =>
|
||||||
// Get all other mails with the same ThreadId that are not already in the result
|
|
||||||
var existingMailIds = expandedMails.Select(m => m.Id).ToHashSet();
|
|
||||||
var threadMails = await GetMailsByThreadIdAsync(mail.ThreadId, existingMailIds).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (threadMails?.Any() == true)
|
|
||||||
{
|
{
|
||||||
// Load assigned properties for the thread mails
|
await LoadAssignedPropertiesWithCacheAsync(threadMail, folderCache, accountCache, contactCache).ConfigureAwait(false);
|
||||||
foreach (var threadMail in threadMails)
|
return threadMail;
|
||||||
{
|
});
|
||||||
await LoadAssignedPropertiesWithCacheAsync(threadMail, folderCache, accountCache, contactCache).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove items that have no assigned account or folder
|
var processedThreadMails = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
threadMails.RemoveAll(a => a.AssignedAccount == null || a.AssignedFolder == null);
|
|
||||||
|
// Filter out items with no assigned account or folder
|
||||||
expandedMails.AddRange(threadMails);
|
var validThreadMails = processedThreadMails.Where(m => m.AssignedAccount != null && m.AssignedFolder != null);
|
||||||
}
|
|
||||||
|
expandedMails.AddRange(validThreadMails);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
@@ -301,6 +303,20 @@ public class MailService : BaseDatabaseService, IMailService
|
|||||||
return await Connection.QueryAsync<MailCopy>(query);
|
return await Connection.QueryAsync<MailCopy>(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<List<MailCopy>> GetMailsByThreadIdsAsync(List<string> threadIds, HashSet<string> excludeMailIds)
|
||||||
|
{
|
||||||
|
if (threadIds?.Count == 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var query = new Query("MailCopy")
|
||||||
|
.WhereIn("ThreadId", threadIds)
|
||||||
|
.WhereNotIn("Id", excludeMailIds)
|
||||||
|
.SelectRaw("MailCopy.*")
|
||||||
|
.GetRawQuery();
|
||||||
|
|
||||||
|
return await Connection.QueryAsync<MailCopy>(query).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method should used for operations with multiple mailItems. Don't use this for single mail items.
|
/// This method should used for operations with multiple mailItems. Don't use this for single mail items.
|
||||||
/// Called method should provide own instances for caches.
|
/// Called method should provide own instances for caches.
|
||||||
|
|||||||
Reference in New Issue
Block a user