Desktop: stop decrypting the whole keychain on a sync pass that changed nothing

This commit is contained in:
jaap-jan committed 2026-10-02 14:08:08 +02:00
1 parent 873405e591
commit 441709f1e9
2 files changed
+59 -3

No files matched your search

@@ -6367,7 +6367,7 @@ internal sealed partial class VaultViewModel(
{
// Before the sync, so a vault admitted here is one of the vaults that pass then pulls. The
// other order would show a newly shared vault as an empty one until the minute after.
await AdmitNewVaultsAsync(server.Account, cancellationToken).ConfigureAwait(true);
var vaultsChanged = await AdmitNewVaultsAsync(server.Account, cancellationToken).ConfigureAwait(true);
// Every vault this session can read, not only the one new items are filed into. A team's
// vault that never synced would show its hosts exactly once — at the unlock that first
@@ -6380,6 +6380,15 @@ internal sealed partial class VaultViewModel(
// that had just failed to sync, which is exactly the lie that flag exists to prevent.
LastSyncFailed = report.Any(vault => !vault.Succeeded);
if (!vaultsChanged && !MayHaveChangedTheKeychain(report))
{
// Nothing to redraw, so nothing is decrypted: see MayHaveChangedTheKeychain. The one thing a
// rebuild did that still has to happen is the ago-text on each card, which is a minute older.
RestringLastConnected();
return report;
}
await ReloadAsync(cancellationToken).ConfigureAwait(true);
// Host key trust arrives with the rest of the vault, and the store the SSH handshake asks holds a
@@ -6423,7 +6432,7 @@ internal sealed partial class VaultViewModel(
/// this list, and doing that on every quiet pass would rebuild a menu once a minute for nothing.
/// </para>
/// </remarks>
private async Task AdmitNewVaultsAsync(IAccountApi api, CancellationToken cancellationToken)
private async Task<bool> AdmitNewVaultsAsync(IAccountApi api, CancellationToken cancellationToken)
{
var before = session.Vaults.Count;
@@ -6431,12 +6440,39 @@ internal sealed partial class VaultViewModel(
if (admitted == 0 && session.Vaults.Count == before)
{
return;
return false;
}
VaultsChanged?.Invoke(this, EventArgs.Empty);
return true;
}
/// <summary>Whether a pass could have left the keychain different from the lists on screen.</summary>
/// <remarks>
/// <para>
/// Every pass used to end by reading the whole keychain back — decrypting every host, key, password,
/// bucket and snippet and rebuilding every list from them — once a minute, whether or not a single item
/// had moved. Almost every pass moves nothing, so almost all of that was spent redrawing what was already
/// drawn.
/// </para>
/// <para>
/// ◆ Conservative on purpose. A failed vault counts as changed, because a pull can apply a page and then
/// fail on the next one, and a report with no counts is no evidence that nothing landed. Log entries do
/// not count — a machine reads back the ones it just wrote, and nothing on these lists is drawn from the
/// log; see <see cref="SyncReport.PulledItems"/>.
/// </para>
/// </remarks>
private static bool MayHaveChangedTheKeychain(IReadOnlyList<VaultSyncReport> report) =>
report.Any(vault => vault.Report is not { } pass
|| pass.PulledItems > 0
|| pass.PushedItems > 0
|| pass.Merged > 0
|| pass.Resurrected > 0
|| pass.DeletesAbandoned > 0
|| pass.Parked > 0
|| pass.ResyncedFromStart);
/// <summary>
/// Raised when a synchronisation pass found that the vaults this account can reach have changed.
/// </summary>