CCCPaste

6ac05fa5402f5

//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()
}