@@ -16,6 +16,7 @@ private let installPath = homeDirectory
1616 . appending ( path: " CodeEdit " )
1717 . appending ( path: " language-servers " )
1818
19+ @MainActor
1920final class RegistryManager {
2021 static let shared : RegistryManager = . init( )
2122
@@ -47,8 +48,11 @@ final class RegistryManager {
4748 cleanupTimer = Timer . scheduledTimer (
4849 withTimeInterval: CachedRegistry . expirationInterval, repeats: false
4950 ) { [ weak self] _ in
50- self ? . cachedRegistry = nil
51- self ? . cleanupTimer = nil
51+ Task { @MainActor in
52+ guard let self = self else { return }
53+ self . cachedRegistry = nil
54+ self . cleanupTimer = nil
55+ }
5256 }
5357 return items
5458 }
@@ -65,44 +69,63 @@ final class RegistryManager {
6569
6670 /// Downloads the latest registry and saves to "~/Library/Application Support/CodeEdit/extensions"
6771 func update( ) async {
68- async let zipDataTask = download ( from: registryURL)
69- async let checksumsTask = download ( from: checksumURL)
72+ // swiftlint:disable:next large_tuple
73+ let result = await Task . detached ( priority: . userInitiated) { ( ) -> (
74+ registryData: Data ? , checksumData: Data ? , error: Error ?
75+ ) in
76+ do {
77+ async let zipDataTask = Self . download ( from: self . registryURL)
78+ async let checksumsTask = Self . download ( from: self . checksumURL)
79+
80+ let ( registryData, checksumData) = try await ( zipDataTask, checksumsTask)
81+ return ( registryData, checksumData, nil )
82+ } catch {
83+ return ( nil , nil , error)
84+ }
85+ } . value
86+
87+ if let error = result. error {
88+ handleUpdateError ( error)
89+ return
90+ }
91+
92+ guard let registryData = result. registryData, let checksumData = result. checksumData else {
93+ return
94+ }
7095
7196 do {
7297 // Make sure the extensions folder exists first
7398 try FileManager . default. createDirectory ( at: installPath, withIntermediateDirectories: true )
7499
75- let ( registryData, checksumData) = try await ( zipDataTask, checksumsTask)
76-
77100 let tempZipURL = installPath. appending ( path: " temp.zip " )
78101 let checksumDestination = installPath. appending ( path: " checksums.txt " )
79102
80- do {
81- // Delete existing zip data if it exists
82- if FileManager . default. fileExists ( atPath: tempZipURL. path) {
83- try FileManager . default. removeItem ( at: tempZipURL)
84- }
85- let registryJsonPath = installPath. appending ( path: " registry.json " ) . path
86- if FileManager . default. fileExists ( atPath: registryJsonPath) {
87- try FileManager . default. removeItem ( atPath: registryJsonPath)
88- }
89-
90- // Write the zip data to a temporary file, then unzip
91- try registryData. write ( to: tempZipURL)
92- try FileManager . default. unzipItem ( at: tempZipURL, to: installPath)
103+ // Delete existing zip data if it exists
104+ if FileManager . default. fileExists ( atPath: tempZipURL. path) {
93105 try FileManager . default. removeItem ( at: tempZipURL)
106+ }
107+ let registryJsonPath = installPath. appending ( path: " registry.json " ) . path
108+ if FileManager . default. fileExists ( atPath: registryJsonPath) {
109+ try FileManager . default. removeItem ( atPath: registryJsonPath)
110+ }
94111
95- try checksumData. write ( to: checksumDestination)
112+ // Write the zip data to a temporary file, then unzip
113+ try registryData. write ( to: tempZipURL)
114+ try FileManager . default. unzipItem ( at: tempZipURL, to: installPath)
115+ try FileManager . default. removeItem ( at: tempZipURL)
96116
97- DispatchQueue . main. async {
98- NotificationCenter . default. post ( name: . RegistryUpdatedNotification, object: nil )
99- }
100- } catch {
101- print ( " Error details: \( error) " )
102- throw RegistryManagerError . writeFailed ( error: error)
103- }
104- } catch let error as RegistryManagerError {
105- switch error {
117+ try checksumData. write ( to: checksumDestination)
118+
119+ NotificationCenter . default. post ( name: . RegistryUpdatedNotification, object: nil )
120+ } catch {
121+ print ( " Error details: \( error) " )
122+ handleUpdateError ( RegistryManagerError . writeFailed ( error: error) )
123+ }
124+ }
125+
126+ private func handleUpdateError( _ error: Error ) {
127+ if let regError = error as? RegistryManagerError {
128+ switch regError {
106129 case . invalidResponse( let statusCode) :
107130 print ( " Invalid response received: \( statusCode) " )
108131 case let . downloadFailed( url, error) :
@@ -112,50 +135,56 @@ final class RegistryManager {
112135 case let . writeFailed( error) :
113136 print ( " Failed to write files to disk: \( error. localizedDescription) " )
114137 }
115- } catch {
138+ } else {
116139 print ( " Unexpected registry error: \( error. localizedDescription) " )
117140 }
118141 }
119142
120143 func installPackage( package entry: RegistryItem ) async throws {
121- let method = Self . parseRegistryEntry ( entry)
122- guard let manager = Self . createPackageManager ( for: method) else {
123- throw PackageManagerError . invalidConfiguration
124- }
144+ return try await Task . detached ( priority: . userInitiated) { ( ) in
145+ let method = await Self . parseRegistryEntry ( entry)
146+ guard let manager = await Self . createPackageManager ( for: method, installPath) else {
147+ throw PackageManagerError . invalidConfiguration
148+ }
125149
126- // Add to activity viewer
127- let activityTitle = " \( entry. name) \( " @ " + ( method. version ?? " latest " ) ) "
128- NotificationCenter . default. post (
129- name: . taskNotification,
130- object: nil ,
131- userInfo: [
132- " id " : entry. name,
133- " action " : " create " ,
134- " title " : " Installing \( activityTitle) "
135- ]
136- )
150+ // Add to activity viewer
151+ let activityTitle = " \( entry. name) \( " @ " + ( method. version ?? " latest " ) ) "
152+ await MainActor . run {
153+ NotificationCenter . default. post (
154+ name: . taskNotification,
155+ object: nil ,
156+ userInfo: [
157+ " id " : entry. name,
158+ " action " : " create " ,
159+ " title " : " Installing \( activityTitle) "
160+ ]
161+ )
162+ }
137163
138- do {
139- try await manager. install ( method: method)
140- } catch {
141- Self . updateActivityViewer ( entry. name, activityTitle, fail: true )
142- // Throw error again so the UI can catch it
143- throw error
144- }
164+ do {
165+ try await manager. install ( method: method)
166+ } catch {
167+ await MainActor . run {
168+ Self . updateActivityViewer ( entry. name, activityTitle, fail: true )
169+ }
170+ // Throw error again so the UI can catch it
171+ throw error
172+ }
145173
146- // Save to settings
147- DispatchQueue . main. async { [ weak self] in
148- self ? . installedLanguageServers [ entry. name] = . init(
149- packageName: entry. name,
150- isEnabled: true ,
151- version: method. version ?? " "
152- )
153- }
154- Self . updateActivityViewer ( entry. name, activityTitle, fail: false )
174+ // Update settings on the main thread
175+ await MainActor . run {
176+ self . installedLanguageServers [ entry. name] = . init(
177+ packageName: entry. name,
178+ isEnabled: true ,
179+ version: method. version ?? " "
180+ )
181+ Self . updateActivityViewer ( entry. name, activityTitle, fail: false )
182+ }
183+ } . value
155184 }
156185
157186 /// Attempts downloading from `url`, with error handling and a retry policy
158- private func download( from url: URL , attempt: Int = 1 ) async throws -> Data {
187+ private static func download( from url: URL , attempt: Int = 1 ) async throws -> Data {
159188 do {
160189 let ( data, response) = try await URLSession . shared. data ( from: url)
161190
@@ -210,48 +239,8 @@ final class RegistryManager {
210239 }
211240 }
212241
213- /// Parse a registry entry and create the appropriate installation method
214- private static func parseRegistryEntry( _ entry: RegistryItem ) -> InstallationMethod {
215- let sourceId = entry. source. id
216- if sourceId. hasPrefix ( " pkg:cargo/ " ) {
217- return PackageSourceParser . parseCargoPackage ( entry)
218- } else if sourceId. hasPrefix ( " pkg:npm/ " ) {
219- return PackageSourceParser . parseNpmPackage ( entry)
220- } else if sourceId. hasPrefix ( " pkg:pypi/ " ) {
221- return PackageSourceParser . parsePythonPackage ( entry)
222- } else if sourceId. hasPrefix ( " pkg:gem/ " ) {
223- return PackageSourceParser . parseRubyGem ( entry)
224- } else if sourceId. hasPrefix ( " pkg:golang/ " ) {
225- return PackageSourceParser . parseGolangPackage ( entry)
226- } else if sourceId. hasPrefix ( " pkg:github/ " ) {
227- return PackageSourceParser . parseGithubPackage ( entry)
228- } else {
229- return . unknown
230- }
231- }
232-
233- /// Create the appropriate package manager for the given installation method
234- private static func createPackageManager( for method: InstallationMethod ) -> PackageManagerProtocol ? {
235- switch method. packageManagerType {
236- case . npm:
237- return NPMPackageManager ( installationDirectory: installPath)
238- case . cargo:
239- return CargoPackageManager ( installationDirectory: installPath)
240- case . pip:
241- return PipPackageManager ( installationDirectory: installPath)
242- case . golang:
243- return GolangPackageManager ( installationDirectory: installPath)
244- case . github, . sourceBuild:
245- return GithubPackageManager ( installationDirectory: installPath)
246- case . nuget, . opam, . gem, . composer:
247- // TODO: IMPLEMENT OTHER PACKAGE MANAGERS
248- return nil
249- case . none:
250- return nil
251- }
252- }
253-
254242 /// Updates the activity viewer with the status of the language server installation
243+ @MainActor
255244 private static func updateActivityViewer(
256245 _ id: String ,
257246 _ activityName: String ,
0 commit comments