@@ -16,81 +16,93 @@ class InstallationQueueManager {
1616 /// Queue of pending installations
1717 private var installationQueue : [ ( RegistryItem , ( Result < Void , Error > ) -> Void ) ] = [ ]
1818 /// Currently running installations
19- private var runningInstallations : Int = 0
19+ private var runningInstallations : Set < String > = [ ]
2020 /// Installation status dictionary
2121 private var installationStatus : [ String : PackageInstallationStatus ] = [ : ]
2222
23- private init ( ) { }
24-
2523 /// Add a package to the installation queue
2624 func queueInstallation( package : RegistryItem , completion: @escaping ( Result < Void , Error > ) -> Void ) {
27- installationStatus [ package . name] = . queued
28- installationQueue. append ( ( package , completion) )
29- processNextInstallations ( )
25+ // If we're already at max capacity and this isn't already running, mark as queued
26+ if runningInstallations. count >= maxConcurrentInstallations && !runningInstallations. contains ( package . name) {
27+ installationStatus [ package . name] = . queued
28+ installationQueue. append ( ( package , completion) )
3029
31- // Notify UI that package is queued
30+ // Notify UI that package is queued
31+ DispatchQueue . main. async {
32+ NotificationCenter . default. post (
33+ name: . installationStatusChanged,
34+ object: nil ,
35+ userInfo: [ " packageName " : package . name, " status " : PackageInstallationStatus . queued]
36+ )
37+ }
38+ } else {
39+ startInstallation ( package : package , completion: completion)
40+ }
41+ }
42+
43+ /// Starts the actual installation process for a package
44+ private func startInstallation( package : RegistryItem , completion: @escaping ( Result < Void , Error > ) -> Void ) {
45+ installationStatus [ package . name] = . installing
46+ runningInstallations. insert ( package . name)
47+
48+ // Notify UI that installation is now in progress
3249 DispatchQueue . main. async {
3350 NotificationCenter . default. post (
3451 name: . installationStatusChanged,
3552 object: nil ,
36- userInfo: [ " packageName " : package . name, " status " : PackageInstallationStatus . queued ]
53+ userInfo: [ " packageName " : package . name, " status " : PackageInstallationStatus . installing ]
3754 )
3855 }
56+
57+ Task {
58+ do {
59+ try await RegistryManager . shared. installPackage ( package : package )
60+
61+ // Notify UI that installation is complete
62+ installationStatus [ package . name] = . installed
63+ DispatchQueue . main. async {
64+ NotificationCenter . default. post (
65+ name: . installationStatusChanged,
66+ object: nil ,
67+ userInfo: [ " packageName " : package . name, " status " : PackageInstallationStatus . installed]
68+ )
69+ completion ( . success( ( ) ) )
70+ }
71+ } catch {
72+ // Notify UI that installation failed
73+ installationStatus [ package . name] = . failed( error)
74+ DispatchQueue . main. async {
75+ NotificationCenter . default. post (
76+ name: . installationStatusChanged,
77+ object: nil ,
78+ userInfo: [ " packageName " : package . name, " status " : PackageInstallationStatus . failed ( error) ]
79+ )
80+ completion ( . failure( error) )
81+ }
82+ }
83+
84+ runningInstallations. remove ( package . name)
85+ processNextInstallations ( )
86+ }
3987 }
4088
4189 /// Process next installations from the queue if possible
4290 private func processNextInstallations( ) {
43- while runningInstallations < maxConcurrentInstallations && !installationQueue. isEmpty {
91+ while runningInstallations. count < maxConcurrentInstallations && !installationQueue. isEmpty {
4492 let ( package , completion) = installationQueue. removeFirst ( )
45- runningInstallations += 1
46- installationStatus [ package . name] = . installing
47-
48- // Notify UI that installation is now in progress
49- DispatchQueue . main. async {
50- NotificationCenter . default. post (
51- name: . installationStatusChanged,
52- object: nil ,
53- userInfo: [ " packageName " : package . name, " status " : PackageInstallationStatus . installing]
54- )
93+ if runningInstallations. contains ( package . name) {
94+ continue
5595 }
5696
57- Task {
58- do {
59- try await RegistryManager . shared. installPackage ( package : package )
60-
61- // Notify UI that installation is complete
62- installationStatus [ package . name] = . installed
63- DispatchQueue . main. async {
64- NotificationCenter . default. post (
65- name: . installationStatusChanged,
66- object: nil ,
67- userInfo: [ " packageName " : package . name, " status " : PackageInstallationStatus . installed]
68- )
69- completion ( . success( ( ) ) )
70- }
71- } catch {
72- // Notify UI that installation failed
73- installationStatus [ package . name] = . failed( error)
74- DispatchQueue . main. async {
75- NotificationCenter . default. post (
76- name: . installationStatusChanged,
77- object: nil ,
78- userInfo: [ " packageName " : package . name, " status " : PackageInstallationStatus . failed ( error) ]
79- )
80- completion ( . failure( error) )
81- }
82- }
83-
84- runningInstallations -= 1
85- processNextInstallations ( )
86- }
97+ startInstallation ( package : package , completion: completion)
8798 }
8899 }
89100
90101 /// Cancel an installation if it's in the queue
91102 func cancelInstallation( packageName: String ) {
92103 installationQueue. removeAll { $0. 0 . name == packageName }
93104 installationStatus [ packageName] = . cancelled
105+ runningInstallations. remove ( packageName)
94106
95107 // Notify UI that installation was cancelled
96108 DispatchQueue . main. async {
@@ -100,12 +112,43 @@ class InstallationQueueManager {
100112 userInfo: [ " packageName " : packageName, " status " : PackageInstallationStatus . cancelled]
101113 )
102114 }
115+ processNextInstallations ( )
103116 }
104117
105118 /// Get the current status of an installation
106119 func getInstallationStatus( packageName: String ) -> PackageInstallationStatus {
107120 return installationStatus [ packageName] ?? . notQueued
108121 }
122+
123+ /// Cleans up installation status by removing completed or failed installations
124+ func cleanUpInstallationStatus( ) {
125+ let statusKeys = installationStatus. keys. map { $0 }
126+ for packageName in statusKeys {
127+ if let status = installationStatus [ packageName] {
128+ switch status {
129+ case . installed, . failed, . cancelled:
130+ installationStatus. removeValue ( forKey: packageName)
131+ case . queued, . installing, . notQueued:
132+ break
133+ }
134+ }
135+ }
136+
137+ // If an item is in runningInstallations but not in an active state in the status dictionary,
138+ // it might be a stale reference
139+ let currentRunning = runningInstallations. map { $0 }
140+ for packageName in currentRunning {
141+ let status = installationStatus [ packageName]
142+ if status != . installing {
143+ runningInstallations. remove ( packageName)
144+ }
145+ }
146+
147+ // Check for orphaned queue items
148+ installationQueue = installationQueue. filter { item, _ in
149+ return installationStatus [ item. name] == . queued
150+ }
151+ }
109152}
110153
111154/// Status of a package installation
0 commit comments