Skip to content

Commit ce6cdcc

Browse files
committed
feat: support restarting file sync sessions
1 parent be70ade commit ce6cdcc

File tree

5 files changed

+79
-28
lines changed

5 files changed

+79
-28
lines changed

Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ final class PreviewFileSync: FileSyncDaemon {
2727
func pauseSessions(ids _: [String]) async throws(VPNLib.DaemonError) {}
2828

2929
func resumeSessions(ids _: [String]) async throws(VPNLib.DaemonError) {}
30+
31+
func resetSessions(ids _: [String]) async throws(VPNLib.DaemonError) {}
3032
}

Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
1010
@State private var editingSession: FileSyncSession?
1111

1212
@State private var loading: Bool = false
13-
@State private var deleteError: DaemonError?
13+
@State private var actionError: DaemonError?
1414
@State private var isVisible: Bool = false
1515
@State private var dontRetry: Bool = false
1616

@@ -50,14 +50,14 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
5050
FileSyncSessionModal<VPN, FS>(existingSession: session)
5151
.frame(width: 700)
5252
}.alert("Error", isPresented: Binding(
53-
get: { deleteError != nil },
53+
get: { actionError != nil },
5454
set: { isPresented in
5555
if !isPresented {
56-
deleteError = nil
56+
actionError = nil
5757
}
5858
}
5959
)) {} message: {
60-
Text(deleteError?.description ?? "An unknown error occurred.")
60+
Text(actionError?.description ?? "An unknown error occurred.")
6161
}.alert("Error", isPresented: Binding(
6262
// We only show the alert if the file config window is open
6363
// Users will see the alert symbol on the menu bar to prompt them to
@@ -118,7 +118,7 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
118118
addingNewSession = true
119119
} label: {
120120
Image(systemName: "plus")
121-
.frame(width: 24, height: 24)
121+
.frame(width: 24, height: 24).help("Create")
122122
}.disabled(vpn.menuState.agents.isEmpty)
123123
Divider()
124124
Button {
@@ -133,43 +133,72 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
133133
await fileSync.stop()
134134
}
135135
} catch {
136-
deleteError = error
136+
actionError = error
137137
}
138138
selection = nil
139139
}
140140
} label: {
141-
Image(systemName: "minus").frame(width: 24, height: 24)
141+
Image(systemName: "minus").frame(width: 24, height: 24).help("Delete")
142142
}.disabled(selection == nil)
143-
if let selection {
144-
if let selectedSession = fileSync.sessionState.first(where: { $0.id == selection }) {
145-
Divider()
146-
Button {
147-
Task {
148-
// TODO: Support pausing & resuming multiple sessions at once
149-
loading = true
150-
defer { loading = false }
143+
sessionControls
144+
}
145+
.buttonStyle(.borderless)
146+
}
147+
.background(.primary.opacity(0.04))
148+
.fixedSize(horizontal: false, vertical: true)
149+
}
150+
151+
var sessionControls: some View {
152+
Group {
153+
if let selection {
154+
if let selectedSession = fileSync.sessionState.first(where: { $0.id == selection }) {
155+
Divider()
156+
Button {
157+
Task {
158+
// TODO: Support pausing & resuming multiple sessions at once
159+
loading = true
160+
defer { loading = false }
161+
do throws(DaemonError) {
151162
switch selectedSession.status {
152-
case .paused:
163+
case .paused, .error(.haltedOnRootEmptied),
164+
.error(.haltedOnRootDeletion),
165+
.error(.haltedOnRootTypeChange):
153166
try await fileSync.resumeSessions(ids: [selectedSession.id])
154167
default:
155168
try await fileSync.pauseSessions(ids: [selectedSession.id])
156169
}
170+
} catch {
171+
actionError = error
157172
}
158-
} label: {
159-
switch selectedSession.status {
160-
case .paused:
161-
Image(systemName: "play").frame(width: 24, height: 24)
162-
default:
163-
Image(systemName: "pause").frame(width: 24, height: 24)
173+
}
174+
} label: {
175+
switch selectedSession.status {
176+
case .paused, .error(.haltedOnRootEmptied),
177+
.error(.haltedOnRootDeletion),
178+
.error(.haltedOnRootTypeChange):
179+
Image(systemName: "play").frame(width: 24, height: 24).help("Pause")
180+
default:
181+
Image(systemName: "pause").frame(width: 24, height: 24).help("Resume")
182+
}
183+
}
184+
Divider()
185+
Button {
186+
Task {
187+
// TODO: Support restarting multiple sessions at once
188+
loading = true
189+
defer { loading = false }
190+
do throws(DaemonError) {
191+
try await fileSync.resetSessions(ids: [selectedSession.id])
192+
} catch {
193+
actionError = error
164194
}
165195
}
196+
} label: {
197+
Image(systemName: "arrow.clockwise").frame(width: 24, height: 24).help("Reset")
166198
}
167199
}
168200
}
169-
.buttonStyle(.borderless)
170201
}
171-
.background(.primary.opacity(0.04))
172-
.fixedSize(horizontal: false, vertical: true)
173202
}
174203
}
175204

Coder-Desktop/Coder-DesktopTests/Util.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ class MockFileSyncDaemon: FileSyncDaemon {
5252
func pauseSessions(ids _: [String]) async throws(VPNLib.DaemonError) {}
5353

5454
func resumeSessions(ids _: [String]) async throws(VPNLib.DaemonError) {}
55+
56+
func resetSessions(ids _: [String]) async throws(VPNLib.DaemonError) {}
5557
}
5658

5759
extension Inspection: @unchecked Sendable, @retroactive InspectionEmissary {}

Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public protocol FileSyncDaemon: ObservableObject {
1818
func deleteSessions(ids: [String]) async throws(DaemonError)
1919
func pauseSessions(ids: [String]) async throws(DaemonError)
2020
func resumeSessions(ids: [String]) async throws(DaemonError)
21+
func resetSessions(ids: [String]) async throws(DaemonError)
2122
}
2223

2324
@MainActor

Coder-Desktop/VPNLib/FileSync/FileSyncManagement.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,8 @@ public extension MutagenDaemon {
100100
}
101101

102102
func resumeSessions(ids: [String]) async throws(DaemonError) {
103-
// Resuming sessions does not require prompting, according to the
104-
// Mutagen CLI
105-
let (stream, promptID) = try await host(allowPrompts: false)
103+
// Resuming sessions does use prompting, as it may start a new SSH connection
104+
let (stream, promptID) = try await host(allowPrompts: true)
106105
defer { stream.cancel() }
107106
guard case .running = state else { return }
108107
do {
@@ -117,4 +116,22 @@ public extension MutagenDaemon {
117116
}
118117
await refreshSessions()
119118
}
119+
120+
func resetSessions(ids: [String]) async throws(DaemonError) {
121+
// Resetting a session involves pausing & resuming, so it does use prompting
122+
let (stream, promptID) = try await host(allowPrompts: true)
123+
defer { stream.cancel() }
124+
guard case .running = state else { return }
125+
do {
126+
_ = try await client!.sync.reset(Synchronization_ResetRequest.with { req in
127+
req.prompter = promptID
128+
req.selection = .with { selection in
129+
selection.specifications = ids
130+
}
131+
}, callOptions: .init(timeLimit: .timeout(sessionMgmtReqTimeout)))
132+
} catch {
133+
throw .grpcFailure(error)
134+
}
135+
await refreshSessions()
136+
}
120137
}

0 commit comments

Comments
 (0)
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