import Foundation import Combine enum BackupStatus: Equatable { case idle case preparing case running case paused case completed case failed(String) case cancelled var isActive: Bool { switch self { case .running, .paused, .preparing: return true default: return false } } } @MainActor final class BackupJob: ObservableObject { @Published private(set) var status: BackupStatus = .idle @Published private(set) var totalFiles: Int = 0 @Published private(set) var uploadedFiles: Int = 0 @Published private(set) var skippedFiles: Int = 0 @Published private(set) var failedFiles: Int = 0 @Published private(set) var currentFileName: String = "" @Published private(set) var currentFileSizeBytes: Int64 = 0 @Published private(set) var bytesTransferred: Int64 = 0 @Published private(set) var totalBytes: Int64 = 0 @Published private(set) var speedBytesPerSecond: Double = 0 @Published private(set) var startDate: Date? @Published private(set) var endDate: Date? var progress: Double { guard totalFiles > 0 else { return 0 } return min(1.0, Double(uploadedFiles + skippedFiles + failedFiles) / Double(totalFiles)) } var eta: TimeInterval? { guard speedBytesPerSecond > 0, totalBytes > bytesTransferred else { return nil } return Double(totalBytes - bytesTransferred) / speedBytesPerSecond } func prepare() { status = .preparing } func start(totalFiles: Int, totalBytes: Int64) { self.totalFiles = totalFiles self.totalBytes = totalBytes self.uploadedFiles = 0 self.skippedFiles = 0 self.failedFiles = 0 self.bytesTransferred = 0 self.startDate = Date() self.endDate = nil self.status = .running } func updateProgress(fileName: String, fileSize: Int64, bytesTransferred: Int64, speed: Double) { self.currentFileName = fileName self.currentFileSizeBytes = fileSize self.bytesTransferred = bytesTransferred self.speedBytesPerSecond = speed } func fileCompleted(skipped: Bool = false) { if skipped { skippedFiles += 1 } else { uploadedFiles += 1 } } func fileFailed() { failedFiles += 1 } func finish(result: BackupResult) { endDate = Date() status = result.failedCount == 0 ? .completed : .failed("Completed with \(result.failedCount) error\(result.failedCount == 1 ? "" : "s")") } func fail(error: BackupError) { endDate = Date() status = .failed(error.errorDescription ?? error.localizedDescription) } func cancel() { endDate = Date() status = .cancelled } func pause() { if case .running = status { status = .paused } } func resume() { if case .paused = status { status = .running } } }