| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- import Foundation
- import SwiftUI
- /// Observable manager for tracking active cloud track downloads.
- /// Coordinates single, album-level, and playlist-level downloads with bounded concurrency.
- @MainActor
- @Observable
- final class DownloadManager {
- static let shared = DownloadManager()
- /// Per-track download progress (0.0–1.0). Keyed by Track persistent model ID.
- var trackProgress: [UUID: Double] = [:]
- /// Active download tasks, keyed by Track ID. Used for cancellation.
- @ObservationIgnored
- private var activeTasks: [UUID: Task<Void, Never>] = [:]
- // MARK: - Single Track
- func download(track: Track, apiClient: ChadMusicAPIClient) {
- guard track.downloadState != .downloading else { return }
- guard track.downloadState != .downloaded else { return }
- let trackId = track.id
- trackProgress[trackId] = 0
- let task = Task {
- do {
- _ = try await DownloadService.downloadPersistent(
- track: track,
- apiClient: apiClient
- ) { [weak self] progress in
- Task { @MainActor in
- self?.trackProgress[trackId] = progress
- }
- }
- trackProgress.removeValue(forKey: trackId)
- activeTasks.removeValue(forKey: trackId)
- } catch {
- if Task.isCancelled {
- track.downloadState = .none
- }
- trackProgress.removeValue(forKey: trackId)
- activeTasks.removeValue(forKey: trackId)
- }
- }
- activeTasks[trackId] = task
- }
- // MARK: - Cancel
- func cancel(track: Track) {
- let trackId = track.id
- activeTasks[trackId]?.cancel()
- activeTasks.removeValue(forKey: trackId)
- trackProgress.removeValue(forKey: trackId)
- track.downloadState = .none
- }
- // MARK: - Remove
- func removeDownload(track: Track) {
- cancel(track: track)
- DownloadService.removeDownload(track: track)
- }
- // MARK: - Batch Download (album / playlist)
- /// Download multiple tracks with bounded concurrency (max 3).
- func downloadBatch(tracks: [Track], apiClient: ChadMusicAPIClient) {
- let toDownload = tracks.filter { $0.isCloud && $0.downloadState != .downloaded && $0.downloadState != .downloading }
- guard !toDownload.isEmpty else { return }
- for track in toDownload {
- download(track: track, apiClient: apiClient)
- }
- }
- /// Cancel all active batch downloads for the given tracks.
- func cancelBatch(tracks: [Track]) {
- for track in tracks {
- if activeTasks[track.id] != nil {
- cancel(track: track)
- }
- }
- }
- // MARK: - Query
- func isDownloading(track: Track) -> Bool {
- activeTasks[track.id] != nil
- }
- func progress(for track: Track) -> Double {
- trackProgress[track.id] ?? 0
- }
- }
|