@@ -30,22 +30,42 @@ public enum DownloaderError: Error {
30
30
/// The `Downloader` protocol abstract away the download of a file with a progress report.
31
31
public protocol Downloader {
32
32
33
+ /// The progress closure type. The first arguments contains the number of bytes downloaded, and the second argument
34
+ /// contains the total number of bytes to download, if known.
35
+ typealias Progress = ( Int64 , Int64 ? ) -> Void
36
+
37
+ /// The completion closure type. The only argument contains the result type containing the
38
+ /// `DownloaderError` encountered on failure.
39
+ typealias Completion = ( Result < Void , DownloaderError > ) -> Void
40
+
33
41
/// Downloads a file and keeps the caller updated on the progress and completion.
34
42
///
35
43
/// - Parameters:
36
44
/// - url: The `URL` to the file to download.
37
45
/// - destination: The `AbsolutePath` to download the file to.
38
- /// - progress: A closure to receive the download's progress as a fractional value between `0.0` and `1.0`.
39
- /// - completion: A closure to be notifed of the completion of the download as a `Result` type containing the
40
- /// `DownloaderError` encountered on failure.
46
+ /// - progress: A closure to receive the download's progress as number of bytes.
47
+ /// - completion: A closure to be notifed of the completion of the download.
41
48
func downloadFile(
42
49
at url: Foundation . URL ,
43
50
to destination: AbsolutePath ,
44
- progress: @escaping ( Double ) -> Void ,
45
- completion: @escaping ( Result < Void , DownloaderError > ) -> Void
51
+ progress: @escaping Progress ,
52
+ completion: @escaping Completion
46
53
)
47
54
}
48
55
56
+ extension DownloaderError : LocalizedError {
57
+ public var errorDescription : String ? {
58
+ switch self {
59
+ case . clientError( let error) :
60
+ return ( error as? LocalizedError ) ? . errorDescription ?? error. localizedDescription
61
+ case . serverError( let statusCode) :
62
+ return " invalid status code \( statusCode) "
63
+ case . fileSystemError( let error) :
64
+ return ( error as? LocalizedError ) ? . errorDescription ?? error. localizedDescription
65
+ }
66
+ }
67
+ }
68
+
49
69
/// A `Downloader` conformance that uses Foundation's `URLSession`.
50
70
public final class FoundationDownloader : NSObject , Downloader {
51
71
@@ -56,8 +76,8 @@ public final class FoundationDownloader: NSObject, Downloader {
56
76
fileprivate struct Download {
57
77
let task : URLSessionDownloadTask
58
78
let destination : AbsolutePath
59
- let progress : ( Double ) -> Void
60
- let completion : ( Result < Void , DownloaderError > ) -> Void
79
+ let progress : Downloader . Progress
80
+ let completion : Downloader . Completion
61
81
}
62
82
63
83
/// The `URLSession` used for all downloads.
@@ -89,8 +109,8 @@ public final class FoundationDownloader: NSObject, Downloader {
89
109
public func downloadFile(
90
110
at url: Foundation . URL ,
91
111
to destination: AbsolutePath ,
92
- progress: @escaping ( Double ) -> Void ,
93
- completion: @escaping ( Result < Void , DownloaderError > ) -> Void
112
+ progress: @escaping Downloader . Progress ,
113
+ completion: @escaping Downloader . Completion
94
114
) {
95
115
queue. addOperation {
96
116
let task = self . session. downloadTask ( with: url)
@@ -114,8 +134,9 @@ extension FoundationDownloader: URLSessionDownloadDelegate {
114
134
totalBytesExpectedToWrite: Int64
115
135
) {
116
136
let download = self . download ( for: downloadTask)
117
- let progress = Double ( totalBytesWritten) / Double( totalBytesExpectedToWrite)
118
- download. notifyProgress ( progress)
137
+ let totalBytesToDownload = totalBytesExpectedToWrite != NSURLSessionTransferSizeUnknown ?
138
+ totalBytesExpectedToWrite : nil
139
+ download. notifyProgress ( bytesDownloaded: totalBytesWritten, totalBytesToDownload: totalBytesToDownload)
119
140
}
120
141
121
142
public func urlSession(
@@ -162,9 +183,9 @@ extension FoundationDownloader {
162
183
}
163
184
164
185
extension FoundationDownloader . Download {
165
- func notifyProgress( _ progress : Double ) {
186
+ func notifyProgress( bytesDownloaded : Int64 , totalBytesToDownload : Int64 ? ) {
166
187
DispatchQueue . global ( ) . async {
167
- self . progress ( progress )
188
+ self . progress ( bytesDownloaded , totalBytesToDownload )
168
189
}
169
190
}
170
191
0 commit comments