From 413e13aeea1b7a7852e2ef52066b4005a56eec84 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Wed, 23 Apr 2025 14:33:47 +1000 Subject: [PATCH] feat: add create progress to file sync window --- App/Services/MutagenController.cs | 12 +++++++++-- App/ViewModels/FileSyncListViewModel.cs | 22 +++++++++++++++++++-- App/Views/Pages/FileSyncListMainPage.xaml | 14 +++++++++++-- Tests.App/Services/MutagenControllerTest.cs | 18 +++++++++++++---- 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/App/Services/MutagenController.cs b/App/Services/MutagenController.cs index dd489df..3a68962 100644 --- a/App/Services/MutagenController.cs +++ b/App/Services/MutagenController.cs @@ -85,7 +85,7 @@ public interface ISyncSessionController : IAsyncDisposable /// Task RefreshState(CancellationToken ct = default); - Task CreateSyncSession(CreateSyncSessionRequest req, CancellationToken ct = default); + Task CreateSyncSession(CreateSyncSessionRequest req, Action progressCallback, CancellationToken ct = default); Task PauseSyncSession(string identifier, CancellationToken ct = default); Task ResumeSyncSession(string identifier, CancellationToken ct = default); Task TerminateSyncSession(string identifier, CancellationToken ct = default); @@ -200,12 +200,15 @@ public async Task RefreshState(CancellationToke return state; } - public async Task CreateSyncSession(CreateSyncSessionRequest req, CancellationToken ct = default) + public async Task CreateSyncSession(CreateSyncSessionRequest req, Action? progressCallback = null, CancellationToken ct = default) { using var _ = await _lock.LockAsync(ct); var client = await EnsureDaemon(ct); await using var prompter = await Prompter.Create(client, true, ct); + if (progressCallback != null) + prompter.OnProgress += (_, progress) => progressCallback(progress); + var createRes = await client.Synchronization.CreateAsync(new CreateRequest { Prompter = prompter.Identifier, @@ -603,6 +606,8 @@ private async Task StopDaemon(CancellationToken ct) private class Prompter : IAsyncDisposable { + public event EventHandler? OnProgress; + private readonly AsyncDuplexStreamingCall _dup; private readonly CancellationTokenSource _cts; private readonly Task _handleRequestsTask; @@ -684,6 +689,9 @@ private async Task HandleRequests(CancellationToken ct) if (response.Message == null) throw new InvalidOperationException("Prompting.Host response stream returned a null message"); + if (!response.IsPrompt) + OnProgress?.Invoke(this, response.Message); + // Currently we only reply to SSH fingerprint messages with // "yes" and send an empty reply for everything else. var reply = ""; diff --git a/App/ViewModels/FileSyncListViewModel.cs b/App/ViewModels/FileSyncListViewModel.cs index 7fdd881..d01338c 100644 --- a/App/ViewModels/FileSyncListViewModel.cs +++ b/App/ViewModels/FileSyncListViewModel.cs @@ -67,6 +67,9 @@ public partial class FileSyncListViewModel : ObservableObject public partial string NewSessionRemotePath { get; set; } = ""; // TODO: NewSessionRemotePathDialogOpen for remote path + [ObservableProperty] + public partial string NewSessionStatus { get; set; } = ""; + public bool NewSessionCreateEnabled { get @@ -187,6 +190,7 @@ private void ClearNewForm() NewSessionLocalPath = ""; NewSessionRemoteHost = ""; NewSessionRemotePath = ""; + NewSessionStatus = ""; } [RelayCommand] @@ -263,13 +267,26 @@ private void CancelNewSession() ClearNewForm(); } + private void OnCreateSessionProgress(string message) + { + // Ensure we're on the UI thread. + if (_dispatcherQueue == null) return; + if (!_dispatcherQueue.HasThreadAccess) + { + _dispatcherQueue.TryEnqueue(() => OnCreateSessionProgress(message)); + return; + } + + NewSessionStatus = message; + } + [RelayCommand] private async Task ConfirmNewSession() { if (OperationInProgress || !NewSessionCreateEnabled) return; OperationInProgress = true; - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(120)); try { // The controller will send us a state changed event. @@ -286,7 +303,7 @@ await _syncSessionController.CreateSyncSession(new CreateSyncSessionRequest Host = NewSessionRemoteHost, Path = NewSessionRemotePath, }, - }, cts.Token); + }, OnCreateSessionProgress, cts.Token); ClearNewForm(); } @@ -304,6 +321,7 @@ await _syncSessionController.CreateSyncSession(new CreateSyncSessionRequest finally { OperationInProgress = false; + NewSessionStatus = ""; } } diff --git a/App/Views/Pages/FileSyncListMainPage.xaml b/App/Views/Pages/FileSyncListMainPage.xaml index d38bc29..5a96898 100644 --- a/App/Views/Pages/FileSyncListMainPage.xaml +++ b/App/Views/Pages/FileSyncListMainPage.xaml @@ -274,8 +274,11 @@ - - + + @@ -340,6 +343,13 @@ HorizontalAlignment="Stretch" Text="{x:Bind ViewModel.NewSessionRemotePath, Mode=TwoWay}" /> + + + diff --git a/Tests.App/Services/MutagenControllerTest.cs b/Tests.App/Services/MutagenControllerTest.cs index 1605f1c..c834009 100644 --- a/Tests.App/Services/MutagenControllerTest.cs +++ b/Tests.App/Services/MutagenControllerTest.cs @@ -112,6 +112,13 @@ public async Task Ok(CancellationToken ct) // Ensure the daemon is stopped because all sessions are terminated. await AssertDaemonStopped(dataDirectory, ct); + var progressMessages = new List(); + void OnProgress(string message) + { + TestContext.Out.WriteLine("Create session progress: " + message); + progressMessages.Add(message); + } + var session1 = await controller.CreateSyncSession(new CreateSyncSessionRequest { Alpha = new CreateSyncSessionRequest.Endpoint @@ -124,7 +131,10 @@ public async Task Ok(CancellationToken ct) Protocol = CreateSyncSessionRequest.Endpoint.ProtocolKind.Local, Path = betaDirectory.FullName, }, - }, ct); + }, OnProgress, ct); + + // There should've been at least one progress message. + Assert.That(progressMessages, Is.Not.Empty); state = controller.GetState(); Assert.That(state.SyncSessions, Has.Count.EqualTo(1)); @@ -142,7 +152,7 @@ public async Task Ok(CancellationToken ct) Protocol = CreateSyncSessionRequest.Endpoint.ProtocolKind.Local, Path = betaDirectory.FullName, }, - }, ct); + }, null, ct); state = controller.GetState(); Assert.That(state.SyncSessions, Has.Count.EqualTo(2)); @@ -225,7 +235,7 @@ await controller.CreateSyncSession(new CreateSyncSessionRequest Protocol = CreateSyncSessionRequest.Endpoint.ProtocolKind.Local, Path = betaDirectory.FullName, }, - }, ct); + }, null, ct); } await AssertDaemonStopped(dataDirectory, ct); @@ -265,7 +275,7 @@ await controller1.CreateSyncSession(new CreateSyncSessionRequest Protocol = CreateSyncSessionRequest.Endpoint.ProtocolKind.Local, Path = betaDirectory.FullName, }, - }, ct); + }, null, ct); controller2 = new MutagenController(_mutagenBinaryPath, dataDirectory); await controller2.RefreshState(ct); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy