//Just to see the time on the logs struct Logger { private static let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd hh:mm:ss.SSSSSZ" return formatter }() static func log(_ object: Any) { let date = Self.dateFormatter.string(from: Date()) print("\(date): - \(object)") } } actor FileDownloader { static var fileIdCounter = 0 let fileId: Int var onDidDownload: (() -> Void)? //In case we want to trigger something init(onDidDownload: (() -> Void)? = nil) { self.fileId = Self.fileIdCounter Self.fileIdCounter += 1 self.onDidDownload = onDidDownload } func download() async throws { let rng = Int.random(in: 5...15) try? await Task.sleep(nanoseconds: UInt64(rng * 100_000_000)) Logger.log("Download finished for fileId: \(fileId)") } } //Handle 5 downloads max concurrently, when on finished, the next one is directly launched. // This avoid using Groups, that need the full downloads of the group to finish before handling the next one. //There is currently no Cancel management, just a hard stop //`add(_:)` could start the whole download process if it isn't started //States "started, paused etc". could be added. actor DownloadManager { //Whole list of wdw var queue: [FileDownloader] = [] var maxConcurrentDownloads: Int = 5 var currentDownloads: [FileDownloader] = [] //In case of downloads that trigger errors, currently just blacklisted withing app life, but could be handled differently ofc var blacklist: [FileDownloader] = [] func prioritize(_ fileDownloaders: [FileDownloader]) { fileDownloaders.forEach { prioritize($0) } } func prioritize(_ fileDownloader: FileDownloader) { Logger.log("Prioritize \(fileDownloader.fileId)") guard !currentDownloads.contains(where: { $0.fileId == fileDownloader.fileId }) else { //Already downloading Logger.log("Dwd \(fileDownloader.fileId) is already being downloaded") return } guard !blacklist.contains(where: { $0.fileId == fileDownloader.fileId }) else { //Already tried to download this time Logger.log("Dwd \(fileDownloader.fileId) is blacklisted") return } if let queuedIndex = queue.firstIndex(where: { $0.fileId == fileDownloader.fileId }) { queue.remove(at: queuedIndex) queue.insert(fileDownloader, at: 0) Logger.log("Dwd \(fileDownloader.fileId) is priorized with success") } } func add(_ fileDownloader: FileDownloader) { queue.append(fileDownloader) } func add(_ fileDownloaders: [FileDownloader]) { queue.append(contentsOf: fileDownloaders) } func download() async { guard !queue.isEmpty else { Logger.log("Nothing to download, will set a timer to check in X minutes") return } let freeQueuesCount = maxConcurrentDownloads - currentDownloads.count guard freeQueuesCount > 0 else { Logger.log("Max concurrent reached with \(currentDownloads.map { "\($0.fileId)" }.joined(separator: ", "))") return } for _ in 0.. Prioritize last!") //It's also check if we can mess with the order Task { await manager.prioritize(last) } })) for _ in 0...49 { dwds.append(FileDownloader()) } await manager.add(dwds) Task { //Quick test on "Stop" try? await Task.sleep(nanoseconds: 25_000_000_000) await manager.stop() } await manager.download() }