@@ -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
@@ -89,7 +89,7 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
8989 Text ( """
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 in the default editor
92+ // Opens the log file in Console
9393 NSWorkspace . shared. open ( fileSync. logFile)
9494 }
9595 } . task {
@@ -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- 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: {
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 { 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
0 commit comments