Skip to content

Add FileDownloadDelegate for simple file downloads #275

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 19 commits into from
Sep 10, 2020
Merged
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
Create separate Progress struct, async open file
  • Loading branch information
MaxDesiatov committed Sep 8, 2020
commit b091019dda5c7f948c6ea8b77bd3aeaeaef5ae00
45 changes: 30 additions & 15 deletions Sources/AsyncHTTPClient/FileDownloadDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ import NIOHTTP1
public final class FileDownloadDelegate: HTTPClientResponseDelegate {
/// The response type for this delegate: the total count of bytes as reported by the response
/// "Content-Length" header (if available) and the count of bytes downloaded.
public typealias Response = (totalBytes: Int?, receivedBytes: Int)
public struct Progress {
public var totalBytes: Int?
public var receivedBytes: Int
}

private var progress = Progress(totalBytes: nil, receivedBytes: 0)

private var totalBytes: Int?
private var receivedBytes = 0
public typealias Response = Progress

private let handle: NIOFileHandle
private let handle: EventLoopFuture<NIOFileHandle>
private let io: NonBlockingFileIO
private let reportHeaders: ((HTTPHeaders) -> Void)?
private let reportProgress: ((_ totalBytes: Int?, _ receivedBytes: Int) -> Void)?
private let reportProgress: ((Progress) -> Void)?

private var writeFuture: EventLoopFuture<Void>?

Expand All @@ -44,9 +48,13 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
path: String,
pool: NIOThreadPool = NIOThreadPool(numberOfThreads: 1),
reportHeaders: ((HTTPHeaders) -> Void)? = nil,
reportProgress: ((_ totalBytes: Int?, _ receivedBytes: Int) -> Void)? = nil
reportProgress: ((Progress) -> Void)? = nil
) throws {
self.handle = try NIOFileHandle(path: path, mode: .write, flags: .allowFileCreation())
self.handle = try self.io.openFile(
path: path,
mode: .write,
flags: .allowFileCreation()
)
pool.start()
self.io = NonBlockingFileIO(threadPool: pool)

Expand All @@ -62,7 +70,7 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {

if let totalBytesString = head.headers.first(name: "Content-Length"),
let totalBytes = Int(totalBytesString) {
self.totalBytes = totalBytes
self.progress.totalBytes = totalBytes
}

return task.eventLoop.makeSucceededFuture(())
Expand All @@ -72,19 +80,26 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
task: HTTPClient.Task<Response>,
_ buffer: ByteBuffer
) -> EventLoopFuture<Void> {
self.receivedBytes += buffer.readableBytes
self.reportProgress?(self.totalBytes, self.receivedBytes)
self.progress.receivedBytes += buffer.readableBytes
self.reportProgress?(self.progress)

let writeFuture = self.handle.flatMap {
self.io.write(fileHandle: $0, buffer: buffer, eventLoop: task.eventLoop)
}

let writeFuture = self.io.write(fileHandle: self.handle, buffer: buffer, eventLoop: task.eventLoop)
self.writeFuture = writeFuture
return writeFuture
}

public func didFinishRequest(task: HTTPClient.Task<Response>) throws -> Response {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test on the error path? I think we're missing cleanup in the didReceiveError case.

self.writeFuture?.whenComplete { _ in
try! self.handle.close()
self.writeFuture = nil
if let writeFuture = self.writeFuture {
writeFuture.whenComplete { _ in
self.handle.whenSuccess { try! $0.close() }
self.writeFuture = nil
}
} else {
self.handle.whenSuccess { try! $0.close() }
}
return (self.totalBytes, self.receivedBytes)
return self.progress
}
}