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

Commit b2009b5

Browse files
authored
Update installApp.swift
1 parent f24a573 commit b2009b5

1 file changed

Lines changed: 31 additions & 84 deletions

File tree

Sources/prostore/install/installApp.swift

Lines changed: 31 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,88 +3,57 @@ import Foundation
33
import IDeviceSwift
44
import Combine
55

6-
/// Phase-aware install progress reporting.
7-
public enum InstallPhase: String {
8-
case uploading = "📤 Uploading"
9-
case installing = "📲 Installing"
10-
case finalizing = "⏳ Finalizing"
11-
case completed = "✅ Completed"
12-
}
13-
146
/// Installs a signed IPA on the device using InstallationProxy
15-
/// - Returns: an AsyncThrowingStream that yields `(phase: InstallPhase, progress: Double, status: String)`
16-
/// where `progress` is phase-relative (0.0...1.0).
17-
public func installApp(from ipaURL: URL) async throws -> AsyncThrowingStream<(phase: InstallPhase, progress: Double, status: String), Error> {
7+
public func installApp(from ipaURL: URL) async throws -> AsyncThrowingStream<(progress: Double, status: String), Error> {
188
print("Installing app from: \(ipaURL.path)")
199

2010
return AsyncThrowingStream { continuation in
21-
// Keep cancellables outside of the Task so termination handler can access them.
22-
var cancellables = Set<AnyCancellable>()
23-
24-
// The actual installation work runs on a Task so we can cancel it if the stream is terminated.
25-
let installTask = Task {
11+
Task {
2612
// Start heartbeat to keep connection alive during long install
2713
HeartbeatManager.shared.start()
2814

2915
// Create view model to receive installation status updates
3016
let viewModel = InstallerStatusViewModel()
31-
32-
// Observe progress updates (phase-aware)
33-
// uploadProgress and installProgress are assumed to be 0.0...1.0 published Doubles.
17+
18+
// Observe progress updates
19+
var cancellables = Set<AnyCancellable>()
20+
21+
// Combine progress updates into a single stream
3422
viewModel.$uploadProgress
3523
.combineLatest(viewModel.$installProgress)
3624
.sink { uploadProgress, installProgress in
37-
// Decide which phase is active and yield phase-relative progress
25+
let overallProgress = (uploadProgress + installProgress) / 2.0
26+
let currentStage: String
27+
3828
if uploadProgress < 1.0 {
39-
// Uploading phase
40-
let status = "\(InstallPhase.uploading.rawValue)... (\(Int(uploadProgress * 100))%)"
41-
DispatchQueue.main.async {
42-
continuation.yield((phase: .uploading, progress: uploadProgress, status: status))
43-
}
29+
currentStage = "Uploading..."
4430
} else if installProgress < 1.0 {
45-
// Installing phase
46-
let status = "\(InstallPhase.installing.rawValue)... (\(Int(installProgress * 100))%)"
47-
DispatchQueue.main.async {
48-
continuation.yield((phase: .installing, progress: installProgress, status: status))
49-
}
31+
currentStage = "Installing..."
5032
} else {
51-
// Finalizing phase — both reported progress are 1.0, so show finalizing
52-
let status = "\(InstallPhase.finalizing.rawValue)..."
53-
DispatchQueue.main.async {
54-
continuation.yield((phase: .finalizing, progress: 1.0, status: status))
55-
}
33+
currentStage = "Finalizing..."
5634
}
35+
36+
continuation.yield((progress: overallProgress, status: currentStage))
5737
}
5838
.store(in: &cancellables)
59-
60-
// Observe installer status for completion/failure
39+
40+
// Handle completion
6141
viewModel.$status
6242
.sink { installerStatus in
6343
switch installerStatus {
6444
case .completed(.success):
65-
// Report completed (phase-relative = 1.0)
66-
let status = "\(InstallPhase.completed.rawValue) Successfully installed app!"
67-
DispatchQueue.main.async {
68-
continuation.yield((phase: .completed, progress: 1.0, status: status))
69-
continuation.finish()
70-
}
71-
// cleanup
45+
continuation.yield((progress: 1.0, status: "Successfully installed app!"))
46+
continuation.finish()
7247
cancellables.removeAll()
73-
48+
7449
case .completed(.failure(let error)):
75-
// Forward the error and finish the stream
76-
DispatchQueue.main.async {
77-
continuation.finish(throwing: error)
78-
}
50+
continuation.finish(throwing: error)
7951
cancellables.removeAll()
80-
52+
8153
case .broken(let error):
82-
// Broken connection or other fatal error
83-
DispatchQueue.main.async {
84-
continuation.finish(throwing: error)
85-
}
54+
continuation.finish(throwing: error)
8655
cancellables.removeAll()
87-
56+
8857
default:
8958
break
9059
}
@@ -94,40 +63,18 @@ public func installApp(from ipaURL: URL) async throws -> AsyncThrowingStream<(ph
9463
do {
9564
// Create the installation proxy
9665
let installer = await InstallationProxy(viewModel: viewModel)
97-
66+
9867
// Perform the actual installation
9968
try await installer.install(at: ipaURL)
100-
101-
// Allow a short grace period for final signals to arrive
102-
try await Task.sleep(nanoseconds: 500_000_000) // 0.5s
103-
104-
// NOTE: viewModel.$status should drive the final .completed event;
105-
// if for some reason it didn't, ensure we finish here.
106-
// (If status already finished the continuation, these calls are no-ops.)
107-
DispatchQueue.main.async {
108-
// If not yet finished, mark completed.
109-
continuation.yield((phase: .completed, progress: 1.0, status: "\(InstallPhase.completed.rawValue)"))
110-
continuation.finish()
111-
}
112-
113-
cancellables.removeAll()
69+
70+
// Wait a moment for completion
71+
try await Task.sleep(nanoseconds: 1_000_000_000) // 1 second
72+
73+
print("Installation completed successfully!")
11474
} catch {
115-
// On error, forward and cleanup
116-
DispatchQueue.main.async {
117-
continuation.finish(throwing: error)
118-
}
75+
continuation.finish(throwing: error)
11976
cancellables.removeAll()
12077
}
121-
} // End Task
122-
123-
// If the consumer cancels/terminates the stream, cancel the install task and cleanup.
124-
continuation.onTermination = { @Sendable _ in
125-
installTask.cancel()
126-
cancellables.removeAll()
12778
}
12879
}
12980
}
130-
131-
132-
133-

0 commit comments

Comments
 (0)