Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

Commit 223a18c

Browse files
authored
Refactor error handling and installation logic
1 parent fee7f5d commit 223a18c

1 file changed

Lines changed: 32 additions & 29 deletions

File tree

Sources/prostore/install/installApp.swift

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -119,52 +119,57 @@ public func installApp(from ipaURL: URL) async throws
119119

120120
print("Installing app from: \(ipaURL.path)")
121121

122-
return AsyncThrowingStream { continuation in
123-
// Keep track of subscriptions & a cancellation token
122+
// === IMPORTANT: explicitly specify element and failure types so the compiler selects
123+
// the continuation-style initializer (the one that passes `continuation` to the closure).
124+
typealias InstallUpdate = (progress: Double, status: String)
125+
typealias StreamContinuation = AsyncThrowingStream<InstallUpdate, Error>.Continuation
126+
127+
return AsyncThrowingStream<InstallUpdate, Error> { continuation in
124128
var cancellables = Set<AnyCancellable>()
125129
var installTask: Task<Void, Never>?
126130

127-
// Ensure cleanup when the stream ends for whatever reason
128-
continuation.onTermination = { @Sendable _ in
129-
print("Install stream terminated — cleaning up.")
131+
// Explicitly annotate the termination parameter type so compiler is happy
132+
continuation.onTermination = { @Sendable (reason: StreamContinuation.Termination) in
133+
print("Install stream terminated: \(reason)")
130134
cancellables.removeAll()
131-
// cancel the installation task if still running
135+
// cancel running install task if still active
132136
installTask?.cancel()
133137
}
134138

135-
// Start the async work on a Task so we can await inside
136139
installTask = Task {
137140
// Start heartbeat to keep connection alive
138141
HeartbeatManager.shared.start()
139142

140-
// initialize view model the same way UI does (important)
143+
// initialize view model same as UI code (important)
141144
let isIdevice = UserDefaults.standard.integer(forKey: "Feather.installationMethod") == 1
142145
let viewModel = InstallerStatusViewModel(isIdevice: isIdevice)
143146

144-
// Log useful updates to console (debug)
145-
viewModel.$status.sink { status in
146-
print("[Installer] status ->", status)
147-
}.store(in: &cancellables)
147+
// Debug logging for status changes
148+
viewModel.$status
149+
.sink { status in
150+
print("[Installer] status ->", status)
151+
}
152+
.store(in: &cancellables)
148153

154+
// Progress stream (combine upload & install progress)
149155
viewModel.$uploadProgress
150156
.combineLatest(viewModel.$installProgress)
151157
.sink { uploadProgress, installProgress in
152158
let overall = (uploadProgress + installProgress) / 2.0
153-
let status: String
159+
let statusText: String
154160
if uploadProgress < 1.0 {
155-
status = "📤 Uploading..."
161+
statusText = "📤 Uploading..."
156162
} else if installProgress < 1.0 {
157-
status = "📲 Installing..."
163+
statusText = "📲 Installing..."
158164
} else {
159-
status = "🏁 Finalizing..."
165+
statusText = "🏁 Finalizing..."
160166
}
161-
// debug
162167
print("[Installer] progress upload:\(uploadProgress) install:\(installProgress) overall:\(overall)")
163-
continuation.yield((overall, status))
168+
continuation.yield((overall, statusText))
164169
}
165170
.store(in: &cancellables)
166171

167-
// Watch for completion via published isCompleted (robust across enum shapes)
172+
// Robust completion detection: watch isCompleted
168173
viewModel.$isCompleted
169174
.sink { completed in
170175
if completed {
@@ -177,21 +182,20 @@ public func installApp(from ipaURL: URL) async throws
177182
.store(in: &cancellables)
178183

179184
do {
180-
// Create the installer tied to the view model
181185
let installer = await InstallationProxy(viewModel: viewModel)
182186

183-
// If you need same behaviour as UI, pass the suspend flag like the UI does:
184-
// let suspend = (Bundle.main.bundleIdentifier == someIdentifier) // adapt as needed
185-
// try await installer.install(at: ipaURL, suspend: suspend)
186-
187-
// For now, call the simpler signature – change to include 'suspend:' if needed
187+
// If your UI calls install(at: suspend:) when updating itself,
188+
// replicate that logic here if you need that behaviour.
188189
try await installer.install(at: ipaURL)
189190

190-
// tiny pause so progress updates propagate
191+
// small delay to let final progress propagate
191192
try await Task.sleep(nanoseconds: 500_000_000)
192193

193-
print("Installation call returned without throwing — waiting for viewModel to report completion.")
194-
// Don't force finish here: wait for the published isCompleted to fire (above)
194+
print("Installation call returned — waiting for viewModel to report completion.")
195+
196+
// Note: we intentionally don't call continuation.finish() here -
197+
// we rely on viewModel.$isCompleted to finish the stream so the
198+
// installer has its normal lifecycle.
195199

196200
} catch {
197201
print("[Installer] install threw error ->", error)
@@ -201,4 +205,3 @@ public func installApp(from ipaURL: URL) async throws
201205
}
202206
}
203207
}
204-

0 commit comments

Comments
 (0)