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