@@ -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