Skip to content

Pass request Task to FileDownloadDelegate reportHead and reportProgress closures #681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 13, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 62 additions & 26 deletions Sources/AsyncHTTPClient/FileDownloadDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {

private let filePath: String
private(set) var fileIOThreadPool: NIOThreadPool?
private let reportHead: ((HTTPResponseHead) -> Void)?
private let reportProgress: ((Progress) -> Void)?
private let reportHead: ((HTTPClient.Task<Progress>, HTTPResponseHead) -> Void)?
private let reportProgress: ((HTTPClient.Task<Progress>, Progress) -> Void)?

private var fileHandleFuture: EventLoopFuture<NIOFileHandle>?
private var writeFuture: EventLoopFuture<Void>?
Expand All @@ -41,63 +41,99 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
///
/// - parameters:
/// - path: Path to a file you'd like to write the download to.
/// - pool: A thread pool to use for asynchronous file I/O.
/// - pool: A thread pool to use for asynchronous file I/O. If nil, a shared thread pool will be used. Defaults to nil.
/// - reportHead: A closure called when the response head is available.
/// - reportProgress: A closure called when a body chunk has been downloaded, with
/// the total byte count and download byte count passed to it as arguments. The callbacks
/// will be invoked in the same threading context that the delegate itself is invoked,
/// as controlled by `EventLoopPreference`.
public convenience init(
public init(
path: String,
pool: NIOThreadPool,
reportHead: ((HTTPResponseHead) -> Void)? = nil,
reportProgress: ((Progress) -> Void)? = nil
pool: NIOThreadPool? = nil,
reportHead: ((HTTPClient.Task<Response>, HTTPResponseHead) -> Void)? = nil,
reportProgress: ((HTTPClient.Task<Response>, Progress) -> Void)? = nil
) throws {
try self.init(path: path, pool: .some(pool), reportHead: reportHead, reportProgress: reportProgress)
if let pool = pool {
self.fileIOThreadPool = pool
} else {
// we should use the shared thread pool from the HTTPClient which
// we will get from the `HTTPClient.Task`
self.fileIOThreadPool = nil
}

self.filePath = path

self.reportHead = reportHead
self.reportProgress = reportProgress
}

/// Initializes a new file download delegate and uses the shared thread pool of the ``HTTPClient`` for file I/O.
/// Initializes a new file download delegate.
///
/// - parameters:
/// - path: Path to a file you'd like to write the download to.
/// - pool: A thread pool to use for asynchronous file I/O.
/// - reportHead: A closure called when the response head is available.
/// - reportProgress: A closure called when a body chunk has been downloaded, with
/// the total byte count and download byte count passed to it as arguments. The callbacks
/// will be invoked in the same threading context that the delegate itself is invoked,
/// as controlled by `EventLoopPreference`.
public convenience init(
path: String,
pool: NIOThreadPool,
reportHead: ((HTTPResponseHead) -> Void)? = nil,
reportProgress: ((Progress) -> Void)? = nil
) throws {
try self.init(path: path, pool: nil, reportHead: reportHead, reportProgress: reportProgress)
try self.init(
path: path,
pool: .some(pool),
reportHead: reportHead.map { reportHead in
return { _, head in
reportHead(head)
}
},
reportProgress: reportProgress.map { reportProgress in
return { _, head in
reportProgress(head)
}
}
)
}

private init(
/// Initializes a new file download delegate and uses the shared thread pool of the ``HTTPClient`` for file I/O.
///
/// - parameters:
/// - path: Path to a file you'd like to write the download to.
/// - reportHead: A closure called when the response head is available.
/// - reportProgress: A closure called when a body chunk has been downloaded, with
/// the total byte count and download byte count passed to it as arguments. The callbacks
/// will be invoked in the same threading context that the delegate itself is invoked,
/// as controlled by `EventLoopPreference`.
public convenience init(
path: String,
pool: NIOThreadPool?,
reportHead: ((HTTPResponseHead) -> Void)? = nil,
reportProgress: ((Progress) -> Void)? = nil
) throws {
if let pool = pool {
self.fileIOThreadPool = pool
} else {
// we should use the shared thread pool from the HTTPClient which
// we will get from the `HTTPClient.Task`
self.fileIOThreadPool = nil
}

self.filePath = path

self.reportHead = reportHead
self.reportProgress = reportProgress
try self.init(
path: path,
pool: nil,
reportHead: reportHead.map { reportHead in
return { _, head in
reportHead(head)
}
},
reportProgress: reportProgress.map { reportProgress in
return { _, head in
reportProgress(head)
}
}
)
}

public func didReceiveHead(
task: HTTPClient.Task<Response>,
_ head: HTTPResponseHead
) -> EventLoopFuture<Void> {
self.reportHead?(head)
self.reportHead?(task, head)

if let totalBytesString = head.headers.first(name: "Content-Length"),
let totalBytes = Int(totalBytesString) {
Expand All @@ -121,7 +157,7 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
}()
let io = NonBlockingFileIO(threadPool: threadPool)
self.progress.receivedBytes += buffer.readableBytes
self.reportProgress?(self.progress)
self.reportProgress?(task, self.progress)

let writeFuture: EventLoopFuture<Void>
if let fileHandleFuture = self.fileHandleFuture {
Expand Down