@@ -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 {
5050FileSyncSessionModal < VPN , FS > ( existingSession: session)
5151. frame ( width: 700 )
5252} . alert ( " Error " , isPresented: Binding (
53- get: { deleteError != nil } ,
53+ get: { actionError != nil } ,
5454 set: { isPresentedin
5555if !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
@@ -89,7 +89,7 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
8989Text ( """
9090 File sync daemon failed. The daemon log file at \n \( fileSync. logFile. path) \n has been opened.
9191""" ) . onAppear {
92- //Open the log file inthe default editor
92+ //Opens the log file inConsole
9393NSWorkspace . shared. open ( fileSync. logFile)
9494}
9595} . task {
@@ -120,58 +120,90 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
120120 addingNewSession= true
121121} label: {
122122Image ( 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- do throws ( DaemonError) {
131- // TODO: Support selecting & deleting multiple sessions at once
132- try await 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+ var sessionControls : some View {
134+ Group {
135+ if let selection{
136+ if let selectedSession= fileSync. sessionState. first ( where: { $0. id== selection} ) {
137+ Divider ( )
138+ Button { Task { await delete ( 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- if let selection{
146- if let 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- try await fileSync. resumeSessions ( ids: [ selectedSession. id] )
156- default :
157- try await fileSync. pauseSessions ( ids: [ selectedSession. id] )
158- }
159- }
160- } label: {
142+ Divider ( )
143+ Button { Task { await pauseResume ( session: selectedSession) } }
144+ label: {
161145switch 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 " )
164150default :
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 { await reset ( 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+ do throws ( DaemonError) {
169+ try await 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+ do throws ( DaemonError) {
185+ switch session. status{
186+ case . paused, . error( . haltedOnRootEmptied) ,
187+ . error( . haltedOnRootDeletion) ,
188+ . error( . haltedOnRootTypeChange) :
189+ try await fileSync. resumeSessions ( ids: [ session. id] )
190+ default :
191+ try await 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+ do throws ( DaemonError) {
203+ try await fileSync. resetSessions ( ids: [ session. id] )
204+ } catch{
205+ actionError= error
206+ }
175207}
176208}
177209