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

Commit 1503356

Browse files
authored
Enhance error handling and message display
Added error handling for missing download URL and certificate selection. Introduced a timer to hide temporary error messages after 5 seconds.
1 parent 526adf0 commit 1503356

1 file changed

Lines changed: 103 additions & 81 deletions

File tree

Sources/prostore/signing/DownloadSignManager.swift

Lines changed: 103 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,60 @@ class DownloadSignManager: ObservableObject {
77
@Published var status: String = ""
88
@Published var isProcessing: Bool = false
99
@Published var showSuccess: Bool = false
10-
10+
1111
private var downloadTask: URLSessionDownloadTask?
1212
private var cancellables = Set<AnyCancellable>()
13-
13+
private var hideTimer: Timer?
14+
1415
func downloadAndSign(app: AltApp) {
1516
guard let downloadURL = app.downloadURL else {
16-
self.status = "No download URL available"
17+
self.showTemporaryError("No download URL available")
1718
return
1819
}
19-
20+
2021
guard let selectedCertFolder = UserDefaults.standard.string(forKey: "selectedCertificateFolder") else {
21-
self.status = "No certificate selected"
22+
self.showTemporaryError("Please add and select a certificate first!")
2223
return
2324
}
24-
25+
2526
self.isProcessing = true
2627
self.progress = 0.0
2728
self.status = "Starting download..."
2829
self.showSuccess = false
29-
30+
3031
DispatchQueue.global(qos: .userInitiated).async {
3132
self.performDownloadAndSign(downloadURL: downloadURL, appName: app.name, certFolder: selectedCertFolder)
3233
}
3334
}
3435

36+
private func showTemporaryError(_ message: String) {
37+
DispatchQueue.main.async {
38+
self.isProcessing = true
39+
self.progress = 0.0
40+
self.status = message
41+
self.showSuccess = false
42+
43+
// Cancel any existing timer
44+
self.hideTimer?.invalidate()
45+
46+
// Hide the bar after 5 seconds
47+
self.hideTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in
48+
DispatchQueue.main.async {
49+
self.isProcessing = false
50+
self.progress = 0.0
51+
self.status = ""
52+
self.showSuccess = false
53+
}
54+
}
55+
}
56+
}
57+
3558
private func performDownloadAndSign(downloadURL: URL, appName: String, certFolder: String) {
3659
// Step 1: Setup directories
3760
let fm = FileManager.default
3861
let appFolder = self.getAppFolder()
3962
let tempDir = appFolder.appendingPathComponent("temp")
40-
63+
4164
do {
4265
if !fm.fileExists(atPath: tempDir.path) {
4366
try fm.createDirectory(at: tempDir, withIntermediateDirectories: true)
@@ -49,13 +72,13 @@ class DownloadSignManager: ObservableObject {
4972
}
5073
return
5174
}
52-
75+
5376
let tempIPAURL = tempDir.appendingPathComponent("\(UUID().uuidString).ipa")
54-
77+
5578
// Step 2: Download the IPA
5679
self.downloadIPA(from: downloadURL, to: tempIPAURL) { [weak self] result in
5780
guard let self = self else { return }
58-
81+
5982
switch result {
6083
case .success:
6184
// Step 3: Get certificate files
@@ -66,38 +89,38 @@ class DownloadSignManager: ObservableObject {
6689
}
6790
return
6891
}
69-
92+
7093
// Step 4: Sign the IPA
7194
self.signIPA(ipaURL: tempIPAURL, p12URL: p12URL, provURL: provURL, password: password, appName: appName)
72-
95+
7396
case .failure(let error):
7497
DispatchQueue.main.async {
7598
self.status = "Download failed: \(error.localizedDescription)"
7699
self.isProcessing = false
77100
}
78-
101+
79102
// Clean up temp file if it exists
80103
try? fm.removeItem(at: tempIPAURL)
81104
}
82105
}
83106
}
84-
107+
85108
private func downloadIPA(from url: URL, to destination: URL, completion: @escaping (Result<Void, Error>) -> Void) {
86109
let semaphore = DispatchSemaphore(value: 0)
87-
110+
88111
let task = URLSession.shared.downloadTask(with: url) { tempURL, response, error in
89112
defer { semaphore.signal() }
90-
113+
91114
if let error = error {
92115
completion(.failure(error))
93116
return
94117
}
95-
118+
96119
guard let tempURL = tempURL else {
97120
completion(.failure(NSError(domain: "Download", code: -1, userInfo: [NSLocalizedDescriptionKey: "No temp URL returned"])))
98121
return
99122
}
100-
123+
101124
do {
102125
let fm = FileManager.default
103126
if fm.fileExists(atPath: destination.path) {
@@ -109,7 +132,7 @@ class DownloadSignManager: ObservableObject {
109132
completion(.failure(error))
110133
}
111134
}
112-
135+
113136
// Observe download progress
114137
var observation: NSKeyValueObservation?
115138
observation = task.progress.observe(\.fractionCompleted) { [weak self] progress, _ in
@@ -120,103 +143,104 @@ class DownloadSignManager: ObservableObject {
120143
self?.status = "Downloading... (\(percent)%)"
121144
}
122145
}
123-
146+
124147
self.downloadTask = task
125148
task.resume()
126-
149+
127150
// Wait for download to complete
128151
DispatchQueue.global(qos: .userInitiated).async {
129152
semaphore.wait()
130153
observation?.invalidate()
131154
}
132155
}
133-
134-
private func getCertificateFiles(for folderName: String) -> (p12URL: URL, provURL: URL, password: String)? {
156+
157+
private func getCertificateFiles(for folderName: String) -> (p12URL: URL, provURL: URL, password: String)? {
135158
let fm = FileManager.default
136159
let certsDir = CertificateFileManager.shared.certificatesDirectory.appendingPathComponent(folderName)
137-
160+
138161
let p12URL = certsDir.appendingPathComponent("certificate.p12")
139162
let provURL = certsDir.appendingPathComponent("profile.mobileprovision")
140163
let passwordURL = certsDir.appendingPathComponent("password.txt")
141-
164+
142165
guard fm.fileExists(atPath: p12URL.path),
143166
fm.fileExists(atPath: provURL.path),
144167
fm.fileExists(atPath: passwordURL.path) else {
145168
return nil
146169
}
147-
170+
148171
do {
149172
let password = try String(contentsOf: passwordURL, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines)
150173
return (p12URL, provURL, password)
151174
} catch {
152175
return nil
153176
}
154177
}
155-
156-
private func signIPA(ipaURL: URL, p12URL: URL, provURL: URL, password: String, appName: String) {
157-
DispatchQueue.main.async {
158-
self.status = "Starting signing process..."
159-
self.progress = 0.5
160-
}
161-
162-
signer.sign(
163-
ipaURL: ipaURL,
164-
p12URL: p12URL,
165-
provURL: provURL,
166-
p12Password: password,
167-
progressUpdate: { [weak self] status, progress in
168-
DispatchQueue.main.async {
169-
let overallProgress = 0.5 + (progress * 0.5)
170-
self?.progress = overallProgress
171-
let percent = Int(overallProgress * 100)
172-
self?.status = "\(status) (\(percent)%)"
173-
}
174-
},
175-
completion: { [weak self] result in
176-
DispatchQueue.main.async {
177-
switch result {
178-
case .success(let signedIPAURL):
179-
self?.progress = 1.0
180-
self?.status = "✅ Successfully signed ipa! Installing app now..."
181-
self?.showSuccess = true
182-
183-
Task {
184-
do {
185-
try await installApp(from: signedIPAURL)
186-
} catch {
187-
self?.status = "❌ Install failed: \(error.localizedDescription)"
178+
179+
private func signIPA(ipaURL: URL, p12URL: URL, provURL: URL, password: String, appName: String) {
180+
DispatchQueue.main.async {
181+
self.status = "Starting signing process..."
182+
self.progress = 0.5
183+
}
184+
185+
signer.sign(
186+
ipaURL: ipaURL,
187+
p12URL: p12URL,
188+
provURL: provURL,
189+
p12Password: password,
190+
progressUpdate: { [weak self] status, progress in
191+
DispatchQueue.main.async {
192+
let overallProgress = 0.5 + (progress * 0.5)
193+
self?.progress = overallProgress
194+
let percent = Int(overallProgress * 100)
195+
self?.status = "\(status) (\(percent)%)"
196+
}
197+
},
198+
completion: { [weak self] result in
199+
DispatchQueue.main.async {
200+
switch result {
201+
case .success(let signedIPAURL):
202+
self?.progress = 1.0
203+
self?.status = "✅ Successfully signed ipa! Installing app now..."
204+
self?.showSuccess = true
205+
206+
Task {
207+
do {
208+
try await installApp(from: signedIPAURL)
209+
} catch {
210+
self?.status = "❌ Install failed: \(error.localizedDescription)"
211+
}
188212
}
189-
}
190-
191-
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
213+
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
214+
self?.isProcessing = false
215+
self?.showSuccess = false
216+
self?.progress = 0.0
217+
self?.status = ""
218+
}
219+
220+
// Clean up original downloaded IPA
221+
try? FileManager.default.removeItem(at: ipaURL)
222+
223+
case .failure(let error):
224+
self?.status = "❌ Signing failed: \(error.localizedDescription)"
192225
self?.isProcessing = false
193-
self?.showSuccess = false
194-
self?.progress = 0.0
195-
self?.status = ""
226+
try? FileManager.default.removeItem(at: ipaURL)
196227
}
197-
198-
// Clean up original downloaded IPA
199-
try? FileManager.default.removeItem(at: ipaURL)
200-
201-
case .failure(let error):
202-
self?.status = "❌ Signing failed: \(error.localizedDescription)"
203-
self?.isProcessing = false
204-
try? FileManager.default.removeItem(at: ipaURL)
205228
}
206229
}
207-
}
208-
)
209-
}
210-
230+
)
231+
}
232+
211233
func cancel() {
212234
downloadTask?.cancel()
235+
hideTimer?.invalidate()
213236
DispatchQueue.main.async {
214237
self.isProcessing = false
215238
self.status = "Cancelled"
216239
self.progress = 0.0
240+
self.showSuccess = false
217241
}
218242
}
219-
243+
220244
private func getAppFolder() -> URL {
221245
let fm = FileManager.default
222246
let documents = fm.urls(for: .documentDirectory, in: .userDomainMask).first!
@@ -226,6 +250,4 @@ private func signIPA(ipaURL: URL, p12URL: URL, provURL: URL, password: String, a
226250
}
227251
return appFolder
228252
}
229-
230253
}
231-

0 commit comments

Comments
 (0)