Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit05b5e1f

Browse files
committed
feat: support restarting file sync sessions
1 parent28e2003 commit05b5e1f

File tree

5 files changed

+104
-50
lines changed

5 files changed

+104
-50
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])asyncthrows(VPNLib.DaemonError){}
2828

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

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

Lines changed: 79 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
1010
@StateprivatevareditingSession:FileSyncSession?
1111

1212
@Stateprivatevarloading:Bool=false
13-
@StateprivatevardeleteError:DaemonError?
13+
@StateprivatevaractionError:DaemonError?
1414
@StateprivatevarisVisible:Bool=false
1515
@StateprivatevardontRetry: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:{ isPresentedin
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
@@ -120,58 +120,90 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
120120
addingNewSession=true
121121
} label:{
122122
Image(systemName:"plus")
123-
.frame(width:24, height:24)
123+
.frame(width:24, height:24).help("Create")
124124
}.disabled(vpn.menuState.agents.isEmpty)
125-
Divider()
126-
Button{
127-
Task{
128-
loading=true
129-
defer{ loading=false}
130-
dothrows(DaemonError){
131-
// TODO: Support selecting & deleting multiple sessions at once
132-
tryawait fileSync.deleteSessions(ids:[selection!])
133-
if fileSync.sessionState.isEmpty{
134-
// Last session was deleted, stop the daemon
135-
await fileSync.stop()
136-
}
137-
} catch{
138-
deleteError= error
125+
sessionControls
126+
}
127+
.buttonStyle(.borderless)
128+
}
129+
.background(.primary.opacity(0.04))
130+
.fixedSize(horizontal:false, vertical:true)
131+
}
132+
133+
varsessionControls:someView{
134+
Group{
135+
iflet selection{
136+
iflet selectedSession= fileSync.sessionState.first(where:{ $0.id== selection}){
137+
Divider()
138+
Button{Task{awaitdelete(session: selectedSession)}}
139+
label:{
140+
Image(systemName:"minus").frame(width:24, height:24).help("Terminate")
139141
}
140-
selection=nil
141-
}
142-
} label:{
143-
Image(systemName:"minus").frame(width:24, height:24)
144-
}.disabled(selection==nil)
145-
iflet selection{
146-
iflet selectedSession= fileSync.sessionState.first(where:{ $0.id== selection}){
147-
Divider()
148-
Button{
149-
Task{
150-
// TODO: Support pausing & resuming multiple sessions at once
151-
loading=true
152-
defer{ loading=false}
153-
switch selectedSession.status{
154-
case.paused:
155-
tryawait fileSync.resumeSessions(ids:[selectedSession.id])
156-
default:
157-
tryawait fileSync.pauseSessions(ids:[selectedSession.id])
158-
}
159-
}
160-
} label:{
142+
Divider()
143+
Button{Task{awaitpauseResume(session: selectedSession)}}
144+
label:{
161145
switch selectedSession.status{
162-
case.paused:
163-
Image(systemName:"play").frame(width:24, height:24)
146+
case.paused,.error(.haltedOnRootEmptied),
147+
.error(.haltedOnRootDeletion),
148+
.error(.haltedOnRootTypeChange):
149+
Image(systemName:"play").frame(width:24, height:24).help("Pause")
164150
default:
165-
Image(systemName:"pause").frame(width:24, height:24)
151+
Image(systemName:"pause").frame(width:24, height:24).help("Resume")
166152
}
167153
}
168-
}
154+
Divider()
155+
Button{Task{awaitreset(session: selectedSession)}}
156+
label:{
157+
Image(systemName:"arrow.clockwise").frame(width:24, height:24).help("Reset")
158+
}
169159
}
170160
}
171-
.buttonStyle(.borderless)
172161
}
173-
.background(.primary.opacity(0.04))
174-
.fixedSize(horizontal:false, vertical:true)
162+
}
163+
164+
// TODO: Support selecting & deleting multiple sessions at once
165+
func delete(session _:FileSyncSession)async{
166+
loading=true
167+
defer{ loading=false}
168+
dothrows(DaemonError){
169+
tryawait fileSync.deleteSessions(ids:[selection!])
170+
if fileSync.sessionState.isEmpty{
171+
// Last session was deleted, stop the daemon
172+
await fileSync.stop()
173+
}
174+
} catch{
175+
actionError= error
176+
}
177+
selection=nil
178+
}
179+
180+
// TODO: Support pausing & resuming multiple sessions at once
181+
func pauseResume(session:FileSyncSession)async{
182+
loading=true
183+
defer{ loading=false}
184+
dothrows(DaemonError){
185+
switch session.status{
186+
case.paused,.error(.haltedOnRootEmptied),
187+
.error(.haltedOnRootDeletion),
188+
.error(.haltedOnRootTypeChange):
189+
tryawait fileSync.resumeSessions(ids:[session.id])
190+
default:
191+
tryawait fileSync.pauseSessions(ids:[session.id])
192+
}
193+
} catch{
194+
actionError= error
195+
}
196+
}
197+
198+
// TODO: Support restarting multiple sessions at once
199+
func reset(session:FileSyncSession)async{
200+
loading=true
201+
defer{ loading=false}
202+
dothrows(DaemonError){
203+
tryawait fileSync.resetSessions(ids:[session.id])
204+
} catch{
205+
actionError= error
206+
}
175207
}
176208
}
177209

‎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])asyncthrows(VPNLib.DaemonError){}
5353

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

5759
extensionInspection:@uncheckedSendable,@retroactiveInspectionEmissary{}

‎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])asyncthrows(DaemonError)
1919
func pauseSessions(ids:[String])asyncthrows(DaemonError)
2020
func resumeSessions(ids:[String])asyncthrows(DaemonError)
21+
func resetSessions(ids:[String])asyncthrows(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])asyncthrows(DaemonError){
103-
// Resuming sessions does not require prompting, according to the
104-
// Mutagen CLI
105-
let(stream, promptID)=tryawaithost(allowPrompts:false)
103+
// Resuming sessions does use prompting, as it may start a new SSH connection
104+
let(stream, promptID)=tryawaithost(allowPrompts:true)
106105
defer{ stream.cancel()}
107106
guard case.running= stateelse{return}
108107
do{
@@ -117,4 +116,22 @@ public extension MutagenDaemon {
117116
}
118117
awaitrefreshSessions()
119118
}
119+
120+
func resetSessions(ids:[String])asyncthrows(DaemonError){
121+
// Resetting a session involves pausing & resuming, so it does use prompting
122+
let(stream, promptID)=tryawaithost(allowPrompts:true)
123+
defer{ stream.cancel()}
124+
guard case.running= stateelse{return}
125+
do{
126+
_=tryawait client!.sync.reset(Synchronization_ResetRequest.with{ reqin
127+
req.prompter= promptID
128+
req.selection=.with{ selectionin
129+
selection.specifications= ids
130+
}
131+
}, callOptions:.init(timeLimit:.timeout(sessionMgmtReqTimeout)))
132+
}catch{
133+
throw.grpcFailure(error)
134+
}
135+
awaitrefreshSessions()
136+
}
120137
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp