Skip to content

Commit 45626d3

Browse files
authored
Pass request Task to FileDownloadDelegate reportHead and reportProgress closures (#681)
### Motivation If the HTTP response status is not 2xx or the file being downloaded becomes too big, it is desirable to cancel the file download eagerly. This is currently quite hard because the `reportHead` and `reportProgress` closures do not have direct access to the `HTTPClient.Task`. ### Modifications - Pass `HTTPClient.Task` additionally to `reportHead` and `reportProgress` closures ### Result A file download can now easily be cancelled at any time.
1 parent 5b4f03d commit 45626d3

File tree

1 file changed

+62
-26
lines changed

1 file changed

+62
-26
lines changed

Sources/AsyncHTTPClient/FileDownloadDelegate.swift

+62-26
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
3131

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

3737
private var fileHandleFuture: EventLoopFuture<NIOFileHandle>?
3838
private var writeFuture: EventLoopFuture<Void>?
@@ -41,63 +41,99 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
4141
///
4242
/// - parameters:
4343
/// - path: Path to a file you'd like to write the download to.
44-
/// - pool: A thread pool to use for asynchronous file I/O.
44+
/// - pool: A thread pool to use for asynchronous file I/O. If nil, a shared thread pool will be used. Defaults to nil.
4545
/// - reportHead: A closure called when the response head is available.
4646
/// - reportProgress: A closure called when a body chunk has been downloaded, with
4747
/// the total byte count and download byte count passed to it as arguments. The callbacks
4848
/// will be invoked in the same threading context that the delegate itself is invoked,
4949
/// as controlled by `EventLoopPreference`.
50-
public convenience init(
50+
public init(
5151
path: String,
52-
pool: NIOThreadPool,
53-
reportHead: ((HTTPResponseHead) -> Void)? = nil,
54-
reportProgress: ((Progress) -> Void)? = nil
52+
pool: NIOThreadPool? = nil,
53+
reportHead: ((HTTPClient.Task<Response>, HTTPResponseHead) -> Void)? = nil,
54+
reportProgress: ((HTTPClient.Task<Response>, Progress) -> Void)? = nil
5555
) throws {
56-
try self.init(path: path, pool: .some(pool), reportHead: reportHead, reportProgress: reportProgress)
56+
if let pool = pool {
57+
self.fileIOThreadPool = pool
58+
} else {
59+
// we should use the shared thread pool from the HTTPClient which
60+
// we will get from the `HTTPClient.Task`
61+
self.fileIOThreadPool = nil
62+
}
63+
64+
self.filePath = path
65+
66+
self.reportHead = reportHead
67+
self.reportProgress = reportProgress
5768
}
5869

59-
/// Initializes a new file download delegate and uses the shared thread pool of the ``HTTPClient`` for file I/O.
70+
/// Initializes a new file download delegate.
6071
///
6172
/// - parameters:
6273
/// - path: Path to a file you'd like to write the download to.
74+
/// - pool: A thread pool to use for asynchronous file I/O.
6375
/// - reportHead: A closure called when the response head is available.
6476
/// - reportProgress: A closure called when a body chunk has been downloaded, with
6577
/// the total byte count and download byte count passed to it as arguments. The callbacks
6678
/// will be invoked in the same threading context that the delegate itself is invoked,
6779
/// as controlled by `EventLoopPreference`.
6880
public convenience init(
6981
path: String,
82+
pool: NIOThreadPool,
7083
reportHead: ((HTTPResponseHead) -> Void)? = nil,
7184
reportProgress: ((Progress) -> Void)? = nil
7285
) throws {
73-
try self.init(path: path, pool: nil, reportHead: reportHead, reportProgress: reportProgress)
86+
try self.init(
87+
path: path,
88+
pool: .some(pool),
89+
reportHead: reportHead.map { reportHead in
90+
return { _, head in
91+
reportHead(head)
92+
}
93+
},
94+
reportProgress: reportProgress.map { reportProgress in
95+
return { _, head in
96+
reportProgress(head)
97+
}
98+
}
99+
)
74100
}
75101

76-
private init(
102+
/// Initializes a new file download delegate and uses the shared thread pool of the ``HTTPClient`` for file I/O.
103+
///
104+
/// - parameters:
105+
/// - path: Path to a file you'd like to write the download to.
106+
/// - reportHead: A closure called when the response head is available.
107+
/// - reportProgress: A closure called when a body chunk has been downloaded, with
108+
/// the total byte count and download byte count passed to it as arguments. The callbacks
109+
/// will be invoked in the same threading context that the delegate itself is invoked,
110+
/// as controlled by `EventLoopPreference`.
111+
public convenience init(
77112
path: String,
78-
pool: NIOThreadPool?,
79113
reportHead: ((HTTPResponseHead) -> Void)? = nil,
80114
reportProgress: ((Progress) -> Void)? = nil
81115
) throws {
82-
if let pool = pool {
83-
self.fileIOThreadPool = pool
84-
} else {
85-
// we should use the shared thread pool from the HTTPClient which
86-
// we will get from the `HTTPClient.Task`
87-
self.fileIOThreadPool = nil
88-
}
89-
90-
self.filePath = path
91-
92-
self.reportHead = reportHead
93-
self.reportProgress = reportProgress
116+
try self.init(
117+
path: path,
118+
pool: nil,
119+
reportHead: reportHead.map { reportHead in
120+
return { _, head in
121+
reportHead(head)
122+
}
123+
},
124+
reportProgress: reportProgress.map { reportProgress in
125+
return { _, head in
126+
reportProgress(head)
127+
}
128+
}
129+
)
94130
}
95131

96132
public func didReceiveHead(
97133
task: HTTPClient.Task<Response>,
98134
_ head: HTTPResponseHead
99135
) -> EventLoopFuture<Void> {
100-
self.reportHead?(head)
136+
self.reportHead?(task, head)
101137

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

126162
let writeFuture: EventLoopFuture<Void>
127163
if let fileHandleFuture = self.fileHandleFuture {

0 commit comments

Comments
 (0)