@@ -41,6 +41,10 @@ struct AppUpdatesView: View {
4141
4242 // Get preferences or default values
4343 @StateObject var preferences = Preferences ( )
44+
45+ // Update cancel hover state
46+ @State private var hoveredCancelButton : Bool = false
47+ @State private var hoveredItem : String ?
4448
4549 var body : some View {
4650
@@ -71,13 +75,22 @@ struct AppUpdatesView: View {
7175
7276 if appCatalogController. updateDetails. count > 0 {
7377 Button ( action: {
74- for app in appCatalogController . updateDetails {
75- Task {
78+ Task {
79+ for app in appCatalogController . updateDetails {
7680 // Validate Catalog Agent code requirement
7781 guard verifyAppCatalogCodeRequirement ( ) else {
7882 return
7983 }
80- await updateApp ( bundleID: app. id)
84+
85+ // Append app to queue
86+ await MainActor . run ( ) {
87+ appCatalogController. appsQueued. append ( app. id)
88+ }
89+
90+ // Update app
91+ await InstallTaskQueue . shared. submit ( id: app. id) {
92+ await updateApp ( bundleID: app. id)
93+ }
8194 }
8295 }
8396 } ) {
@@ -202,14 +215,40 @@ struct AppUpdatesView: View {
202215 guard verifyAppCatalogCodeRequirement ( ) else {
203216 return
204217 }
205- await updateApp ( bundleID: update. id)
218+
219+ // Append app to queue
220+ await MainActor . run ( ) {
221+ appCatalogController. appsQueued. append ( update. id)
222+ }
223+
224+ // Update app
225+ await InstallTaskQueue . shared. submit ( id: update. id) {
226+ await updateApp ( bundleID: update. id)
227+ }
206228 }
207229 } ) {
208230 if appCatalogController. appsUpdating. contains ( update. id) {
209231 ProgressView ( )
210232 . scaleEffect ( 0.6 )
211233 . frame ( width: 26 , height: 26 )
212234 . padding ( . leading, 10 )
235+ } else if appCatalogController. appsQueued. contains ( update. id) {
236+ Image ( systemName: hoveredCancelButton && ( hoveredItem == update. id) ? " xmark.circle.fill " : " clock " )
237+ . font ( . system( size: 16 ) )
238+ . frame ( width: 26 , height: 26 )
239+ . onHover { hover in
240+ hoveredCancelButton = hover
241+ }
242+ . animation ( . easeOut( duration: 0.2 ) , value: hoveredCancelButton && ( hoveredItem == update. id) )
243+ . onTapGesture {
244+ Task {
245+ await InstallTaskQueue . shared. cancel ( taskID: update. id)
246+ await MainActor . run {
247+ appCatalogController. appsQueued. removeAll ( where: { $0 == update. id } )
248+ }
249+ appCatalogController. logger. debug ( " App \( update. id, privacy: . public) update cancelled " )
250+ }
251+ }
213252 } else {
214253 Image ( systemName: " icloud.and.arrow.down " )
215254 . font ( . system( size: 16 , weight: . medium) )
@@ -220,7 +259,9 @@ struct AppUpdatesView: View {
220259 . buttonStyle ( . plain)
221260
222261 }
223-
262+ . onHover { _ in
263+ hoveredItem = update. id
264+ }
224265 }
225266
226267 // Show update schedule information when configured
@@ -328,58 +369,57 @@ struct AppUpdatesView: View {
328369 // MARK: - Function to update app using App Catalog
329370 func updateApp( bundleID: String ) async {
330371
372+ appCatalogController. logger. debug ( " App \( bundleID, privacy: . public) added to update queue " )
373+
331374 // Command to update app
332375 let command = " '/usr/local/bin/catalog --install \( bundleID) --update-action --support-app' "
333376
377+ // Remove Bundle ID from queued array
378+ await MainActor . run {
379+ appCatalogController. appsQueued. removeAll ( where: { $0 == bundleID } )
380+ }
381+
334382 // Add bundle ID to apps currently updating
335383 appCatalogController. appsUpdating. append ( bundleID)
336384
337385 do {
338- try ExecutionService . executeScript ( command: command) { exitCode in
339-
340- if exitCode == 0 {
341- appCatalogController. logger. log ( " App \( bundleID, privacy: . public) successfully updated " )
342-
343- // Temporarily drop app from updates array so it will not show once completed. Then we check updates again to verify the update was really successful
344- if appCatalogController. updateDetails. contains ( where: { $0. id == bundleID } ) {
345- if let index = appCatalogController. updateDetails. firstIndex ( where: { $0. id == bundleID } ) {
346- DispatchQueue . main. async {
347- appCatalogController. updateDetails. remove ( at: index)
348- }
349- }
350- }
351-
352- } else {
353- appCatalogController. logger. error ( " Failed to update app \( bundleID, privacy: . public) " )
386+
387+ let exitCode : NSNumber = try await withCheckedThrowingContinuation { continuation in
388+ try ? ExecutionService . executeScript ( command: command) { exitCode in
389+ continuation. resume ( returning: exitCode)
354390 }
391+ }
392+
393+ if exitCode == 0 {
394+ appCatalogController. logger. log ( " App \( bundleID, privacy: . public) successfully updated " )
355395
356- // Stop update spinner
357- if appCatalogController. appsUpdating. contains ( bundleID) {
358- if let index = appCatalogController. appsUpdating. firstIndex ( of: bundleID) {
359- DispatchQueue . main. async {
360- appCatalogController. appsUpdating. remove ( at: index)
361-
362- // Check for updates again when apps currently updating is empty
363- if appCatalogController. appsUpdating. isEmpty {
364- // Trigger check for app updates
365- appCatalogController. ignoreUpdateChange = true
366- appCatalogController. getAppUpdates ( )
367- }
368- }
369- }
396+ // Temporarily drop app from updates array so it will not show once completed. Then we check updates again to verify the update was really successful
397+ await MainActor . run {
398+ appCatalogController. updateDetails. removeAll ( where: { $0. id == bundleID } )
370399 }
371400
401+ } else {
402+ appCatalogController. logger. error ( " Failed to update app \( bundleID, privacy: . public) " )
372403 }
404+
405+ // Stop update spinner
406+ await MainActor . run {
407+ appCatalogController. appsUpdating. removeAll ( where: { $0 == bundleID } )
408+
409+ // Check for updates again when apps currently updating is empty
410+ if appCatalogController. appsUpdating. isEmpty {
411+ // Trigger check for app updates
412+ appCatalogController. ignoreUpdateChange = true
413+ appCatalogController. getAppUpdates ( )
414+ }
415+ }
416+
373417 } catch {
374418 appCatalogController. logger. log ( " Failed to update app \( bundleID, privacy: . public) " )
375419
376420 // Stop update spinner
377- if appCatalogController. appsUpdating. contains ( bundleID) {
378- if let index = appCatalogController. appsUpdating. firstIndex ( of: bundleID) {
379- DispatchQueue . main. async {
380- appCatalogController. appsUpdating. remove ( at: index)
381- }
382- }
421+ await MainActor . run {
422+ appCatalogController. appsUpdating. removeAll ( where: { $0 == bundleID } )
383423 }
384424
385425 // Trigger check for app updates
0 commit comments