this repo has no description
at main 4.0 kB view raw
1// 2// BackgroundDownloadTracker.swift 3// AtProtoBackup 4// 5// Created by Corey Alexander on 8/29/25. 6// 7 8import Foundation 9#if os(iOS) 10import ActivityKit 11#endif 12 13@MainActor 14class BackgroundDownloadTracker { 15 static let shared = BackgroundDownloadTracker() 16 17 private var downloadProgress: [String: DownloadProgress] = [:] 18 #if os(iOS) 19 private var liveActivities: [String: Activity<DownloadActivityAttributes>] = [:] 20 private var lastUpdateCounts: [String: Int] = [:] 21 private let updateBatchSize = 10 22 #endif 23 24 private struct DownloadProgress { 25 var downloadedBlobs: Int 26 var totalBlobs: Int 27 var lastUpdated: Date 28 } 29 30 private init() {} 31 32 func registerDownload(accountDid: String, totalBlobs: Int) { 33 downloadProgress[accountDid] = DownloadProgress( 34 downloadedBlobs: 0, 35 totalBlobs: totalBlobs, 36 lastUpdated: Date() 37 ) 38 #if os(iOS) 39 lastUpdateCounts[accountDid] = 0 40 #endif 41 } 42 43 #if os(iOS) 44 func registerLiveActivity(_ activity: Activity<DownloadActivityAttributes>, for accountDid: String) { 45 liveActivities[accountDid] = activity 46 } 47 #endif 48 49 func incrementProgress(for accountDid: String) async { 50 guard var progress = downloadProgress[accountDid] else { return } 51 52 progress.downloadedBlobs += 1 53 progress.lastUpdated = Date() 54 downloadProgress[accountDid] = progress 55 56 #if os(iOS) 57 // Check if we should update the Live Activity (every 10 downloads) 58 let currentCount = lastUpdateCounts[accountDid] ?? 0 59 let newCount = currentCount + 1 60 lastUpdateCounts[accountDid] = newCount 61 62 if newCount >= updateBatchSize || progress.downloadedBlobs == progress.totalBlobs { 63 // Reset counter and update Live Activity 64 lastUpdateCounts[accountDid] = 0 65 await updateLiveActivityIfNeeded(accountDid: accountDid, progress: progress) 66 } 67 #endif 68 } 69 70 #if os(iOS) 71 private func updateLiveActivityIfNeeded(accountDid: String, progress: DownloadProgress) async { 72 guard let activity = liveActivities[accountDid] else { return } 73 74 let progressPercent = Double(progress.downloadedBlobs) / Double(progress.totalBlobs) 75 let status: DownloadActivityAttributes.ContentState.DownloadStatus = 76 progress.downloadedBlobs == progress.totalBlobs ? .completed : .downloading 77 78 let updatedState = DownloadActivityAttributes.ContentState( 79 progress: progressPercent, 80 downloadedBlobs: progress.downloadedBlobs, 81 totalBlobs: progress.totalBlobs, 82 accountHandle: activity.attributes.accountHandle, 83 isPaused: false, 84 status: status 85 ) 86 87 await activity.update(using: updatedState) 88 print("[BackgroundTracker] Updated activity for \(accountDid) - Blobs: \(progress.downloadedBlobs)/\(progress.totalBlobs)") 89 90 // If completed, end the activity after a delay 91 if status == .completed { 92 Task { 93 try? await Task.sleep(nanoseconds: 3_000_000_000) // 3 seconds 94 await activity.end(dismissalPolicy: .immediate) 95 liveActivities.removeValue(forKey: accountDid) 96 downloadProgress.removeValue(forKey: accountDid) 97 lastUpdateCounts.removeValue(forKey: accountDid) 98 } 99 } 100 } 101 #endif 102 103 func getProgress(for accountDid: String) -> (downloaded: Int, total: Int)? { 104 guard let progress = downloadProgress[accountDid] else { return nil } 105 return (progress.downloadedBlobs, progress.totalBlobs) 106 } 107 108 func cleanup(for accountDid: String) { 109 downloadProgress.removeValue(forKey: accountDid) 110 #if os(iOS) 111 liveActivities.removeValue(forKey: accountDid) 112 lastUpdateCounts.removeValue(forKey: accountDid) 113 #endif 114 } 115}