import Vapor extension AsyncSequence where Element == ByteBuffer { /// Get tracker to print and track transfer rates func tracker(_ tracker: TransferAmountTrackerActor) -> TransferAmountTrackerStream { return TransferAmountTrackerStream(sequence: self, tracker: tracker) } } final actor TransferAmountTrackerActor { private let tracker: TransferAmountTracker var transfered: Int { tracker.transfered } public init(logger: Logger, totalSize: Int?, name: String = "Transfer") { self.tracker = TransferAmountTracker(logger: logger, totalSize: totalSize, name: name) } /// Print status from time to time func set(_ bytes: Int) { tracker.set(bytes) } /// Print status from time to time func add(_ bytes: Int) { tracker.add(bytes) } /// Print end statistics func finish() { tracker.finish() } } /// Sum up transfered amount and print statistics every couple of seconds struct TransferAmountTrackerStream: AsyncSequence where T.Element == ByteBuffer { public typealias Element = AsyncIterator.Element let sequence: T let tracker: TransferAmountTrackerActor public init(sequence: T, tracker: TransferAmountTrackerActor) { self.sequence = sequence self.tracker = tracker } public final class AsyncIterator: AsyncIteratorProtocol { private var tracker: TransferAmountTrackerActor private var iterator: T.AsyncIterator fileprivate init(tracker: TransferAmountTrackerActor, iterator: T.AsyncIterator) { self.tracker = tracker self.iterator = iterator } public func next() async throws -> ByteBuffer? { guard let data = try await self.iterator.next() else { return nil } await tracker.add(data.readableBytes) return data } } public func makeAsyncIterator() -> AsyncIterator { AsyncIterator(tracker: tracker, iterator: sequence.makeAsyncIterator()) } } extension Int { /// Format number of bytes to a human readable format like `5.5 MB` var bytesHumanReadable: String { if self > 5 * 1024*1024*1024 { return "\((Double(self)/1024/1024/1024).round(digits: 1)) GB" } if self > 1 * 1024*1024*1024 { return "\((Double(self)/1024/1024/1024).round(digits: 2)) GB" } if self > 5 * 1024*1024 { return "\((Double(self)/1024/1024).round(digits: 1)) MB" } if self > 1 * 1024*1024 { return "\((Double(self)/1024/1024).round(digits: 2)) MB" } if self > 5 * 1024 { return "\((Double(self)/1024).round(digits: 1)) KB" } if self > 1 * 1024 { return "\((Double(self)/1024).round(digits: 2)) KB" } return "\(self) bytes" } }