···2728 func applicationDidEnterBackground(_ application: UIApplication) {
29 // Request background processing time
30- var backgroundTask: UIBackgroundTaskIdentifier = .invalid
31- backgroundTask = application.beginBackgroundTask {
32- application.endBackgroundTask(backgroundTask)
33 }
34-35 // You get ~30 seconds to update Live Activities
36 Task {
37 // Note: We could update Live Activities here but we don't have
···2728 func applicationDidEnterBackground(_ application: UIApplication) {
29 // Request background processing time
30+ let backgroundTask = application.beginBackgroundTask {
31+ // Expiration handler - called if we run out of time
032 }
33+34 // You get ~30 seconds to update Live Activities
35 Task {
36 // Note: We could update Live Activities here but we don't have
+5-19
AtProtoBackup/BlobDownloader.swift
···105 }
106 }
107108- func urlSession(
109- _ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
110- completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
111- ) {
112- // Store response for download tasks
113- if let downloadTask = dataTask as? URLSessionDownloadTask {
114- completionQueue.sync {
115- taskResponses[downloadTask] = response
116- }
117- }
118- completionHandler(.allow)
119- }
120-121 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
122 {
123 completionQueue.sync {
···169170 // Initialize and restore any pending background downloads
171 public init() {
172- // Force creation of the session to reconnect with any existing downloads
173- _ = backgroundSession
174 }
175176 // Static method to handle background session events (call from AppDelegate)
···255 // Create all download tasks immediately so they continue in background
256 var results: [String: URL] = [:]
257 var newDownloadCount = 0
258- let resultsLock = NSLock()
259260 // Semaphore to limit concurrent downloads
261 let semaphore = AsyncSemaphore(value: maxConcurrent)
···290 var downloadedCount = 0
291 for try await result in group {
292 if let (cid, url, wasNewDownload) = result {
293- resultsLock.lock()
294 results[cid] = url
295 downloadedCount += 1
296 if wasNewDownload {
297 newDownloadCount += 1
298 }
299- resultsLock.unlock()
300301 progressHandler?(downloadedCount, cids.count)
302 }
···628 )
629630 if let fileEnumerator = fileEnumerator {
631- for case let fileURL as URL in fileEnumerator {
00632 let fileName = fileURL.deletingPathExtension().lastPathComponent
633 if fileName == cid {
634 print("Blob \(cid) already exists at \(fileURL.path), skipping download")
···105 }
106 }
1070000000000000108 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
109 {
110 completionQueue.sync {
···156157 // Initialize and restore any pending background downloads
158 public init() {
159+ // Note: backgroundSession will be lazily created on first use
160+ // which will automatically reconnect with any existing downloads
161 }
162163 // Static method to handle background session events (call from AppDelegate)
···242 // Create all download tasks immediately so they continue in background
243 var results: [String: URL] = [:]
244 var newDownloadCount = 0
0245246 // Semaphore to limit concurrent downloads
247 let semaphore = AsyncSemaphore(value: maxConcurrent)
···276 var downloadedCount = 0
277 for try await result in group {
278 if let (cid, url, wasNewDownload) = result {
0279 results[cid] = url
280 downloadedCount += 1
281 if wasNewDownload {
282 newDownloadCount += 1
283 }
0284285 progressHandler?(downloadedCount, cids.count)
286 }
···612 )
613614 if let fileEnumerator = fileEnumerator {
615+ // Convert to array to avoid async iteration issues
616+ let files = fileEnumerator.allObjects.compactMap { $0 as? URL }
617+ for fileURL in files {
618 let fileName = fileURL.deletingPathExtension().lastPathComponent
619 if fileName == cid {
620 print("Blob \(cid) already exists at \(fileURL.path), skipping download")
-40
AtProtoBackup/JSONResponseSection.swift
···1-//
2-// JSONResponseSection.swift
3-// AtProtoBackup
4-//
5-// Created by Corey Alexander on 8/25/25.
6-//
7-8-import SwiftUI
9-10-struct JSONResponseSection: View {
11- let jsonData: Data?
12-13- var body: some View {
14- if let jsonData = jsonData {
15- VStack(alignment: .leading, spacing: 8) {
16- Text("JSON Response:")
17- .font(.headline)
18-19- ScrollView {
20- if let jsonObject = try? JSONSerialization.jsonObject(with: jsonData),
21- let prettyData = try? JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted]),
22- let prettyString = String(data: prettyData, encoding: .utf8) {
23- Text(prettyString)
24- .font(.system(.body, design: .monospaced))
25- .textSelection(.enabled)
26- .padding()
27- .background(Color.gray.opacity(0.1))
28- .cornerRadius(8)
29- } else {
30- Text("Unable to decode JSON data")
31- .foregroundColor(.secondary)
32- }
33- }
34- }
35- } else {
36- Text("No JSON response available")
37- .foregroundColor(.secondary)
38- }
39- }
40-}