···27272828 func applicationDidEnterBackground(_ application: UIApplication) {
2929 // Request background processing time
3030- var backgroundTask: UIBackgroundTaskIdentifier = .invalid
3131- backgroundTask = application.beginBackgroundTask {
3232- application.endBackgroundTask(backgroundTask)
3030+ let backgroundTask = application.beginBackgroundTask {
3131+ // Expiration handler - called if we run out of time
3332 }
3434-3333+3534 // You get ~30 seconds to update Live Activities
3635 Task {
3736 // Note: We could update Live Activities here but we don't have
+5-19
AtProtoBackup/BlobDownloader.swift
···105105 }
106106 }
107107108108- func urlSession(
109109- _ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
110110- completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
111111- ) {
112112- // Store response for download tasks
113113- if let downloadTask = dataTask as? URLSessionDownloadTask {
114114- completionQueue.sync {
115115- taskResponses[downloadTask] = response
116116- }
117117- }
118118- completionHandler(.allow)
119119- }
120120-121108 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
122109 {
123110 completionQueue.sync {
···169156170157 // Initialize and restore any pending background downloads
171158 public init() {
172172- // Force creation of the session to reconnect with any existing downloads
173173- _ = backgroundSession
159159+ // Note: backgroundSession will be lazily created on first use
160160+ // which will automatically reconnect with any existing downloads
174161 }
175162176163 // Static method to handle background session events (call from AppDelegate)
···255242 // Create all download tasks immediately so they continue in background
256243 var results: [String: URL] = [:]
257244 var newDownloadCount = 0
258258- let resultsLock = NSLock()
259245260246 // Semaphore to limit concurrent downloads
261247 let semaphore = AsyncSemaphore(value: maxConcurrent)
···290276 var downloadedCount = 0
291277 for try await result in group {
292278 if let (cid, url, wasNewDownload) = result {
293293- resultsLock.lock()
294279 results[cid] = url
295280 downloadedCount += 1
296281 if wasNewDownload {
297282 newDownloadCount += 1
298283 }
299299- resultsLock.unlock()
300284301285 progressHandler?(downloadedCount, cids.count)
302286 }
···628612 )
629613630614 if let fileEnumerator = fileEnumerator {
631631- for case let fileURL as URL in fileEnumerator {
615615+ // Convert to array to avoid async iteration issues
616616+ let files = fileEnumerator.allObjects.compactMap { $0 as? URL }
617617+ for fileURL in files {
632618 let fileName = fileURL.deletingPathExtension().lastPathComponent
633619 if fileName == cid {
634620 print("Blob \(cid) already exists at \(fileURL.path), skipping download")
-40
AtProtoBackup/JSONResponseSection.swift
···11-//
22-// JSONResponseSection.swift
33-// AtProtoBackup
44-//
55-// Created by Corey Alexander on 8/25/25.
66-//
77-88-import SwiftUI
99-1010-struct JSONResponseSection: View {
1111- let jsonData: Data?
1212-1313- var body: some View {
1414- if let jsonData = jsonData {
1515- VStack(alignment: .leading, spacing: 8) {
1616- Text("JSON Response:")
1717- .font(.headline)
1818-1919- ScrollView {
2020- if let jsonObject = try? JSONSerialization.jsonObject(with: jsonData),
2121- let prettyData = try? JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted]),
2222- let prettyString = String(data: prettyData, encoding: .utf8) {
2323- Text(prettyString)
2424- .font(.system(.body, design: .monospaced))
2525- .textSelection(.enabled)
2626- .padding()
2727- .background(Color.gray.opacity(0.1))
2828- .cornerRadius(8)
2929- } else {
3030- Text("Unable to decode JSON data")
3131- .foregroundColor(.secondary)
3232- }
3333- }
3434- }
3535- } else {
3636- Text("No JSON response available")
3737- .foregroundColor(.secondary)
3838- }
3939- }
4040-}