fix(sync): allow server change at any time, even during an active sync
When IsSyncing and the user requests a different server, cancel the
running CancellationTokenSource instead of silently ignoring the click.
OperationCanceledException is caught separately (not an error), and
ConnectAndSync restarts automatically with the new server after the
cancelled task unwinds.
The "Connect" button no longer has IsEnabled="{Binding !IsSyncing}":
it is always clickable, and clicking it with the same server sets
_resyncRequested as before. CancellationToken is now passed to both
ElectrumClient.ConnectAsync and WalletSynchronizer.SyncOnceAsync.
This commit is contained in:
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
@@ -55,6 +56,8 @@ public partial class MainWindowViewModel
|
||||
[ObservableProperty]
|
||||
private bool isSyncing;
|
||||
|
||||
private CancellationTokenSource _syncCts = new();
|
||||
|
||||
public ObservableCollection<KnownServer> KnownServers { get; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -165,11 +168,23 @@ public partial class MainWindowViewModel
|
||||
{
|
||||
if (IsSyncing)
|
||||
{
|
||||
var (newHost, newPort) = ParseServer();
|
||||
bool serverChanged = _client is null
|
||||
|| _client.Host != newHost
|
||||
|| _client.Port != newPort
|
||||
|| _client.UseSsl != UseSsl;
|
||||
if (serverChanged)
|
||||
_syncCts.Cancel();
|
||||
else
|
||||
_resyncRequested = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_syncCts = new CancellationTokenSource();
|
||||
var ct = _syncCts.Token;
|
||||
IsSyncing = true;
|
||||
StatusMessage = "";
|
||||
bool cancelled = false;
|
||||
try
|
||||
{
|
||||
var (host, port) = ParseServer();
|
||||
@@ -194,12 +209,13 @@ public partial class MainWindowViewModel
|
||||
Exception? lastError = null;
|
||||
foreach (var (h, p) in candidates)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
ConnectionStatus = $"{Loc.Tr("conn.connectingto")} {h}:{p}…";
|
||||
ConnectionStatusShort = Loc.Tr("conn.connectingto") + "…";
|
||||
try
|
||||
{
|
||||
var pins = new CertificatePinStore(AppPaths.CertificatePinsPath(Net));
|
||||
_client = await ElectrumClient.ConnectAsync(h, p, UseSsl, pins);
|
||||
_client = await ElectrumClient.ConnectAsync(h, p, UseSsl, pins, ct);
|
||||
_client.NotificationReceived += OnServerNotification;
|
||||
_client.Disconnected += _ => Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
@@ -226,6 +242,10 @@ public partial class MainWindowViewModel
|
||||
lastError = null;
|
||||
break;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastError = ex;
|
||||
@@ -258,7 +278,7 @@ public partial class MainWindowViewModel
|
||||
do
|
||||
{
|
||||
_resyncRequested = false;
|
||||
var result = await _synchronizer.SyncOnceAsync();
|
||||
var result = await _synchronizer.SyncOnceAsync(ct);
|
||||
_lastTransactions = result.Transactions;
|
||||
|
||||
var (rawHex, verifiedAt, blockHeaders) = _synchronizer.ExportCaches(
|
||||
@@ -282,7 +302,16 @@ public partial class MainWindowViewModel
|
||||
_syncFailed = false;
|
||||
StatusMessage = $"{Loc.Tr("msg.synced")}: {Loc.Tr("msg.height")} {result.TipHeight}, " +
|
||||
$"{result.History.Count} {Loc.Tr("msg.synced.detail")}";
|
||||
} while (_resyncRequested);
|
||||
} while (_resyncRequested && !ct.IsCancellationRequested);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Intentional cancellation due to server change request — not an error.
|
||||
cancelled = true;
|
||||
IsConnected = false;
|
||||
ConnectionStatus = Loc.Tr("conn.none");
|
||||
ConnectionStatusShort = Loc.Tr("conn.none");
|
||||
StatusMessage = "";
|
||||
}
|
||||
catch (CertificatePinMismatchException ex)
|
||||
{
|
||||
@@ -309,6 +338,10 @@ public partial class MainWindowViewModel
|
||||
{
|
||||
IsSyncing = false;
|
||||
}
|
||||
|
||||
// If cancelled due to a server change, restart immediately with the new server.
|
||||
if (cancelled)
|
||||
_ = ConnectAndSync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -1233,14 +1233,14 @@
|
||||
<!-- Actions — Desktop: in a row -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" IsVisible="{Binding IsDesktop}">
|
||||
<Button Content="{Binding Loc[wallet.connect]}" Classes="accent"
|
||||
Command="{Binding ConnectAndSyncCommand}" IsEnabled="{Binding !IsSyncing}"/>
|
||||
Command="{Binding ConnectAndSyncCommand}"/>
|
||||
<Button Content="{Binding Loc[wallet.resetcert]}"
|
||||
Command="{Binding ResetCertificatesCommand}"/>
|
||||
</StackPanel>
|
||||
<!-- Actions — Mobile: stacked -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsMobile}">
|
||||
<Button Content="{Binding Loc[wallet.connect]}" Classes="accent"
|
||||
Command="{Binding ConnectAndSyncCommand}" IsEnabled="{Binding !IsSyncing}"
|
||||
Command="{Binding ConnectAndSyncCommand}"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"/>
|
||||
<Button Content="{Binding Loc[wallet.resetcert]}"
|
||||
Command="{Binding ResetCertificatesCommand}"
|
||||
|
||||
Reference in New Issue
Block a user