From adee0696baa333eb8288c607efc8efc3277cf822 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sun, 20 Sep 2026 18:03:33 +0200 Subject: [PATCH] (feature): index keyword and meaning progress across all mailboxes --- CHANGELOG.md | 8 +++ data/eu.postclient.desktop.metainfo.xml | 5 ++ src/Directory.Build.props | 2 +- .../SemanticSearchService.cs | 12 +++- .../MailArchiveCatalog.cs | 8 +++ .../ViewModels/MainViewModel.cs | 55 ++++++++++++------- 6 files changed, 66 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13233bd..4888365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ The format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic ## [Unreleased] +## [0.3.5] - 2026-09-20 + +Index status covers every mailbox, not one folder or one account. + +### Changed + +- Status bar keyword and meaning index lines show one done / total across all mailboxes, not the folder or mailbox currently being scanned. + ## [0.3.4] - 2026-09-20 Status bar index lines no longer flash between batches. diff --git a/data/eu.postclient.desktop.metainfo.xml b/data/eu.postclient.desktop.metainfo.xml index 8783736..8953350 100644 --- a/data/eu.postclient.desktop.metainfo.xml +++ b/data/eu.postclient.desktop.metainfo.xml @@ -30,6 +30,11 @@ 768 + + +

Keyword and meaning index status shows one done / total across all mailboxes, not the current folder or a single account.

+
+

Status bar indexing and meaning-index lines stay visible between batches instead of flashing.

diff --git a/src/Directory.Build.props b/src/Directory.Build.props index f9bddac..5109b0d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ latest enable enable - 0.3.4 + 0.3.5 Postclient Postclient Maksym Sadovnychyy diff --git a/src/MaksIT.PostClient.Client/SemanticSearchService.cs b/src/MaksIT.PostClient.Client/SemanticSearchService.cs index e28c0c7..6a955cd 100644 --- a/src/MaksIT.PostClient.Client/SemanticSearchService.cs +++ b/src/MaksIT.PostClient.Client/SemanticSearchService.cs @@ -197,13 +197,21 @@ public sealed class SemanticSearchService : ISemanticSearchService { await DelayAsync(TimeSpan.FromMilliseconds(800), token).ConfigureAwait(false); if (_archive.PendingEmbeddings(EmbeddingModelSpec.Id, 1, ready).Count > 0) continue; + var leftAll = _archive.EmbeddingPendingCount(EmbeddingModelSpec.Id); + var doneAll = _archive.EmbeddingCount(EmbeddingModelSpec.Id); + if (leftAll > 0) { + SetStatus(MailIndexProgress.MeaningLine(doneAll, doneAll + leftAll), ready: true); + await WaitAsync(token).ConfigureAwait(false); + return; + } + SetStatus("", ready: true); await WaitAsync(token).ConfigureAwait(false); return; } - var left = _archive.EmbeddingPendingCount(EmbeddingModelSpec.Id, ready); - var done = _archive.EmbeddingCount(EmbeddingModelSpec.Id, ready); + var left = _archive.EmbeddingPendingCount(EmbeddingModelSpec.Id); + var done = _archive.EmbeddingCount(EmbeddingModelSpec.Id); SetStatus(MailIndexProgress.MeaningLine(done, done + left), ready: true); foreach (var item in pending) { token.ThrowIfCancellationRequested(); diff --git a/src/MaksIT.PostClient.Shared/MailArchiveCatalog.cs b/src/MaksIT.PostClient.Shared/MailArchiveCatalog.cs index 21d6990..c8fc464 100644 --- a/src/MaksIT.PostClient.Shared/MailArchiveCatalog.cs +++ b/src/MaksIT.PostClient.Shared/MailArchiveCatalog.cs @@ -105,6 +105,14 @@ public sealed class MailArchiveCatalog : IDisposable { public int MessageCount(string mailboxId) => OffUi(() => TryGet(mailboxId)?.MessageCount(mailboxId) ?? 0); + public int TotalMessageCount() => + OffUi(() => { + var count = 0; + foreach (var pair in _stores) + count += pair.Value.MessageCount(pair.Key); + return count; + }); + public int RebuildKeywordIndex(string mailboxId) => OffUi(() => TryGet(mailboxId)?.RebuildKeywordIndex() ?? 0); diff --git a/src/MaksIT.PostClient.UI/ViewModels/MainViewModel.cs b/src/MaksIT.PostClient.UI/ViewModels/MainViewModel.cs index 6ee5231..1954523 100644 --- a/src/MaksIT.PostClient.UI/ViewModels/MainViewModel.cs +++ b/src/MaksIT.PostClient.UI/ViewModels/MainViewModel.cs @@ -38,7 +38,6 @@ public partial class MainViewModel : ObservableObject, IAsyncDisposable { private readonly HashSet _catalogFolders = new(StringComparer.OrdinalIgnoreCase); private const int IndexCatalogChunk = 2_000; private const int IndexUiChunk = 250; - private int _indexBodyTotal; private readonly StatusLineHold _indexHold; private readonly StatusLineHold _semanticHold; private bool _uiReady; @@ -3756,10 +3755,10 @@ public partial class MainViewModel : ObservableObject, IAsyncDisposable { } private void EnsureIndexing(string mailboxId) { - if (string.IsNullOrWhiteSpace(mailboxId)) - return; - _indexMailboxes.Add(mailboxId); - if (_backfill is not null) + if (!string.IsNullOrWhiteSpace(mailboxId)) + _indexMailboxes.Add(mailboxId); + TrackConnectedMailboxes(); + if (_backfill is not null || _indexMailboxes.Count == 0) return; StartIndexing(); } @@ -3799,7 +3798,10 @@ public partial class MainViewModel : ObservableObject, IAsyncDisposable { try { while (!token.IsCancellationRequested) { await WaitIfIndexingPausedAsync(token).ConfigureAwait(false); - var snapshot = await Dispatcher.UIThread.InvokeAsync(CaptureIndexSnapshot); + var (snapshot, readyIds) = await Dispatcher.UIThread.InvokeAsync(() => { + TrackConnectedMailboxes(); + return (CaptureIndexSnapshot(), ReadyMailboxIds()); + }); await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); if (snapshot.Count == 0) break; @@ -3824,11 +3826,6 @@ public partial class MainViewModel : ObservableObject, IAsyncDisposable { pendingFolders = true; cataloged = true; await WaitIfIndexingPausedAsync(token).ConfigureAwait(false); - var folderDone = box.CatalogedFolders.Count; - await SetIndexLineAsync( - MailIndexProgress.Line(folderDone, Math.Max(box.Folders.Count, 1), folder.Name), - token) - .ConfigureAwait(false); var known = _archive.Uids(box.Id, folder.FullName); var listed = await box.Session .ListMessagesAsync(folder.FullName, known, token, IndexCatalogChunk) @@ -3837,6 +3834,8 @@ public partial class MainViewModel : ObservableObject, IAsyncDisposable { continue; var sync = listed.Value ?? new MailFolderSync(); ApplyFolderSync(box.Id, folder.FullName, sync); + await SetIndexLineAsync(GlobalIndexLine(), token) + .ConfigureAwait(false); var counts = _archive.FolderCounts(box.Id, folder.FullName); await UiAsync(() => RefreshIndexedFolderCounts(box.Id, folder.FullName, counts)) .ConfigureAwait(false); @@ -3855,10 +3854,6 @@ public partial class MainViewModel : ObservableObject, IAsyncDisposable { } await WaitIfIndexingPausedAsync(token).ConfigureAwait(false); - var readyIds = snapshot - .Where(s => IsInitialSyncComplete(s.Id)) - .Select(s => s.Id) - .ToList(); var pending = readyIds.Count == 0 ? [] : _archive.MissingBodies(readyIds, 16); if (pending.Count == 0) { if (!cataloged) @@ -3866,11 +3861,7 @@ public partial class MainViewModel : ObservableObject, IAsyncDisposable { continue; } - var left = _archive.MissingBodyCount(readyIds); - if (_indexBodyTotal < left) - _indexBodyTotal = left; - var done = Math.Max(0, _indexBodyTotal - left); - await SetIndexLineAsync(MailIndexProgress.Line(done, _indexBodyTotal), token) + await SetIndexLineAsync(GlobalIndexLine(), token) .ConfigureAwait(false); var fetched = 0; foreach (var item in pending) { @@ -3916,7 +3907,6 @@ public partial class MainViewModel : ObservableObject, IAsyncDisposable { var again = _indexMailboxes.Any(id => SessionFor(MailboxById(id)) is { IsConnected: true }); if (!again) { _indexHold.Set(""); - _indexBodyTotal = 0; return; } @@ -3962,6 +3952,29 @@ public partial class MainViewModel : ObservableObject, IAsyncDisposable { _indexHold.Set(line); }).ConfigureAwait(false); + private void TrackConnectedMailboxes() { + foreach (var box in Mailboxes) { + if (SessionFor(box) is { IsConnected: true }) + _indexMailboxes.Add(box.Id); + } + } + + private List ReadyMailboxIds() { + var ids = new List(); + foreach (var box in Mailboxes) { + if (IsInitialSyncComplete(box.Id)) + ids.Add(box.Id); + } + + return ids; + } + + private string GlobalIndexLine() { + var total = _archive.TotalMessageCount(); + var missing = _archive.MissingBodyCount(); + return MailIndexProgress.Line(Math.Max(0, total - missing), total); + } + private MailboxAccount? MailboxById(string mailboxId) => Mailboxes.FirstOrDefault(m => m.Id.Equals(mailboxId, StringComparison.OrdinalIgnoreCase)) ?? _files.Current.FindMailbox(mailboxId);