Skip to content
Open
Show file tree
Hide file tree
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
40 changes: 20 additions & 20 deletions Source/TCBlobDownload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ public class TCBlobDownload {
public let downloadTask: NSURLSessionDownloadTask

/// An optional delegate to get notified of events.
public weak var delegate: TCBlobDownloadDelegate?
weak var delegate: TCBlobDownloadDelegate?

/// An optional progression closure periodically executed when a chunk of data has been received.
public var progression: progressionHandler
var progression: progressionHandler

/// An optional completion closure executed when a download was completed by the download task.
public var completion: completionHandler
var completion: completionHandler

/// An optional file name set by the user.
private let preferedFileName: String?
Expand All @@ -48,16 +48,16 @@ public class TCBlobDownload {
public var destinationURL: NSURL {
let destinationPath = self.directory ?? NSURL(fileURLWithPath: NSTemporaryDirectory())

return NSURL(string: self.fileName!, relativeToURL: destinationPath!)!.URLByStandardizingPath!
return NSURL(string: self.fileName!, relativeToURL: destinationPath)!.URLByStandardizingPath!
}

/**
Initialize a new download assuming the `NSURLSessionDownloadTask` was already created.

:param: downloadTask The underlying download task for this download.
:param: directory The directory where to move the downloaded file once completed.
:param: fileName The preferred file name once the download is completed.
:param: delegate An optional delegate for this download.
- parameter downloadTask: The underlying download task for this download.
- parameter directory: The directory where to move the downloaded file once completed.
- parameter fileName: The preferred file name once the download is completed.
- parameter delegate: An optional delegate for this download.
*/
init(downloadTask: NSURLSessionDownloadTask, toDirectory directory: NSURL?, fileName: String?, delegate: TCBlobDownloadDelegate?) {
self.downloadTask = downloadTask
Expand Down Expand Up @@ -109,9 +109,9 @@ public class TCBlobDownload {
:see: `TCBlobDownloadManager -downloadFileWithResumeData`
:see: `NSURLSessionDownloadTask -cancelByProducingResumeData`

:param: completionHandler A completion handler that is called when the download has been successfully canceled. If the download is resumable, the completion handler is provided with a resumeData object.
- parameter completionHandler: A completion handler that is called when the download has been successfully canceled. If the download is resumable, the completion handler is provided with a resumeData object.
*/
public func cancelWithResumeData(completionHandler: (NSData!) -> Void) {
public func cancelWithResumeData(completionHandler: (NSData?) -> Void) {
self.downloadTask.cancelByProducingResumeData(completionHandler)
}

Expand All @@ -125,10 +125,10 @@ public protocol TCBlobDownloadDelegate: class {

:see: `NSURLSession -URLSession:dataTask:didReceiveData:`

:param: download The download that received a chunk of data.
:param: progress The current progress of the download, between 0 and 1. 0 means nothing was received and 1 means the download is completed.
:param: totalBytesWritten The total number of bytes the download has currently written to the disk.
:param: totalBytesExpectedToWrite The total number of bytes the download will write to the disk once completed.
- parameter download: The download that received a chunk of data.
- parameter progress: The current progress of the download, between 0 and 1. 0 means nothing was received and 1 means the download is completed.
- parameter totalBytesWritten: The total number of bytes the download has currently written to the disk.
- parameter totalBytesExpectedToWrite: The total number of bytes the download will write to the disk once completed.
*/
func download(download: TCBlobDownload, didProgress progress: Float, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)

Expand All @@ -137,16 +137,16 @@ public protocol TCBlobDownloadDelegate: class {

:see: `NSURLSession -URLSession:task:didCompleteWithError:`

:param: download The download that received a chunk of data.
:param: error An eventual error. If `nil`, consider the download as being successful.
:param: location The location where the downloaded file can be found.
- parameter download: The download that received a chunk of data.
- parameter error: An eventual error. If `nil`, consider the download as being successful.
- parameter location: The location where the downloaded file can be found.
*/
func download(download: TCBlobDownload, didFinishWithError error: NSError?, atLocation location: NSURL?)
}

// MARK: Printable

extension TCBlobDownload: Printable {
extension TCBlobDownload: CustomStringConvertible {
public var description: String {
var parts: [String] = []
var state: String
Expand All @@ -159,11 +159,11 @@ extension TCBlobDownload: Printable {
}

parts.append("TCBlobDownload")
parts.append("URL: \(self.downloadTask.originalRequest.URL)")
parts.append("URL: \(self.downloadTask.originalRequest!.URL)")
parts.append("Download task state: \(state)")
parts.append("destinationPath: \(self.directory)")
parts.append("fileName: \(self.fileName)")

return join(" | ", parts)
return parts.joinWithSeparator(" | ")
}
}
111 changes: 58 additions & 53 deletions Source/TCBlobDownloadManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class TCBlobDownloadManager {
/**
Custom `NSURLSessionConfiguration` init.

:param: config The configuration used to manage the underlying session.
- parameter config: The configuration used to manage the underlying session.
*/
public init(config: NSURLSessionConfiguration) {
self.delegate = DownloadDelegate()
Expand All @@ -57,7 +57,7 @@ public class TCBlobDownloadManager {
/**
Base method to start a download, called by other download methods.

:param: download Download to start.
- parameter download: Download to start.
*/
private func downloadWithDownload(download: TCBlobDownload) -> TCBlobDownload {
self.delegate.downloads[download.downloadTask.taskIdentifier] = download
Expand All @@ -72,10 +72,10 @@ public class TCBlobDownloadManager {
/**
Start downloading the file at the given URL.

:param: url NSURL of the file to download.
:param: directory Directory Where to copy the file once the download is completed. If `nil`, the file will be downloaded in the current user temporary directory/
:param: name Name to give to the file once the download is completed.
:param: delegate An eventual delegate for this download.
- parameter url: NSURL of the file to download.
- parameter directory: Directory Where to copy the file once the download is completed. If `nil`, the file will be downloaded in the current user temporary directory/
- parameter name: Name to give to the file once the download is completed.
- parameter delegate: An eventual delegate for this download.

:return: A `TCBlobDownload` instance.
*/
Expand All @@ -89,11 +89,11 @@ public class TCBlobDownloadManager {
/**
Start downloading the file at the given URL.

:param: url NSURL of the file to download.
:param: directory Directory Where to copy the file once the download is completed. If `nil`, the file will be downloaded in the current user temporary directory/
:param: name Name to give to the file once the download is completed.
:param: progression A closure executed periodically when a chunk of data is received.
:param: completion A closure executed when the download has been completed.
- parameter url: NSURL of the file to download.
- parameter directory: Directory Where to copy the file once the download is completed. If `nil`, the file will be downloaded in the current user temporary directory/
- parameter name: Name to give to the file once the download is completed.
- parameter progression: A closure executed periodically when a chunk of data is received.
- parameter completion: A closure executed when the download has been completed.

:return: A `TCBlobDownload` instance.
*/
Expand All @@ -109,10 +109,10 @@ public class TCBlobDownloadManager {

:see: `TCBlobDownload -cancelWithResumeData:` to produce this data.

:param: resumeData Data blob produced by a previous download cancellation.
:param: directory Directory Where to copy the file once the download is completed. If `nil`, the file will be downloaded in the current user temporary directory/
:param: name Name to give to the file once the download is completed.
:param: delegate An eventual delegate for this download.
- parameter resumeData: Data blob produced by a previous download cancellation.
- parameter directory: Directory Where to copy the file once the download is completed. If `nil`, the file will be downloaded in the current user temporary directory/
- parameter name: Name to give to the file once the download is completed.
- parameter delegate: An eventual delegate for this download.

:return: A `TCBlobDownload` instance.
*/
Expand All @@ -126,7 +126,7 @@ public class TCBlobDownloadManager {
/**
Gets the downloads in a given state currently being processed by the instance of `TCBlobDownloadManager`.

:param: state The state by which to filter the current downloads.
- parameter state: The state by which to filter the current downloads.

:return: An `Array` of all current downloads with the given state.
*/
Expand All @@ -150,64 +150,69 @@ class DownloadDelegate: NSObject, NSURLSessionDownloadDelegate {
let acceptableStatusCodes: Range<Int> = 200...299

func validateResponse(response: NSHTTPURLResponse) -> Bool {
return contains(self.acceptableStatusCodes, response.statusCode)
return self.acceptableStatusCodes.contains(response.statusCode)
}

// MARK: NSURLSessionDownloadDelegate

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
println("Resume at offset: \(fileOffset) total expected: \(expectedTotalBytes)")
print("Resume at offset: \(fileOffset) total expected: \(expectedTotalBytes)")
}

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
let download = self.downloads[downloadTask.taskIdentifier]!
let progress = totalBytesExpectedToWrite == NSURLSessionTransferSizeUnknown ? -1 : Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
if let download = self.downloads[downloadTask.taskIdentifier] {
let progress = totalBytesExpectedToWrite == NSURLSessionTransferSizeUnknown ? -1 : Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)

download.progress = progress
download.progress = progress

dispatch_async(dispatch_get_main_queue()) {
download.delegate?.download(download, didProgress: progress, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
download.progression?(progress: progress, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
return
dispatch_async(dispatch_get_main_queue()) {
download.delegate?.download(download, didProgress: progress, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
download.progression?(progress: progress, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
return
}
}
}

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
let download = self.downloads[downloadTask.taskIdentifier]!
var fileError: NSError?
var resultingURL: NSURL?

if NSFileManager.defaultManager().replaceItemAtURL(download.destinationURL, withItemAtURL: location, backupItemName: nil, options: nil, resultingItemURL: &resultingURL, error: &fileError) {
download.resultingURL = resultingURL
} else {
download.error = fileError
if let download = self.downloads[downloadTask.taskIdentifier] {
var fileError: NSError?
var resultingURL: NSURL?

do {
try NSFileManager.defaultManager().replaceItemAtURL(download.destinationURL, withItemAtURL: location, backupItemName: nil, options: [], resultingItemURL: &resultingURL)
download.resultingURL = resultingURL
} catch let error1 as NSError {
fileError = error1
download.error = fileError
}
}
}

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError sessionError: NSError?) {
let download = self.downloads[task.taskIdentifier]!
var error: NSError? = sessionError ?? download.error
// Handle possible HTTP errors
if let response = task.response as? NSHTTPURLResponse {
// NSURLErrorDomain errors are not supposed to be reported by this delegate
// according to https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html
// so let's ignore them as they sometimes appear there for now. (But WTF?)
if !validateResponse(response) && (error == nil || error!.domain == NSURLErrorDomain) {
error = NSError(domain: kTCBlobDownloadErrorDomain,
code: TCBlobDownloadError.TCBlobDownloadHTTPError.rawValue,
userInfo: [kTCBlobDownloadErrorDescriptionKey: "Erroneous HTTP status code: \(response.statusCode)",
kTCBlobDownloadErrorFailingURLKey: task.originalRequest.URL!,
kTCBlobDownloadErrorHTTPStatusKey: response.statusCode])
if let download = self.downloads[task.taskIdentifier] {
var error: NSError? = sessionError ?? download.error
// Handle possible HTTP errors
if let response = task.response as? NSHTTPURLResponse {
// NSURLErrorDomain errors are not supposed to be reported by this delegate
// according to https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html
// so let's ignore them as they sometimes appear there for now. (But WTF?)
if !validateResponse(response) && (error == nil || error!.domain == NSURLErrorDomain) {
error = NSError(domain: kTCBlobDownloadErrorDomain,
code: TCBlobDownloadError.TCBlobDownloadHTTPError.rawValue,
userInfo: [kTCBlobDownloadErrorDescriptionKey: "Erroneous HTTP status code: \(response.statusCode)",
kTCBlobDownloadErrorFailingURLKey: task.originalRequest!.URL!,
kTCBlobDownloadErrorHTTPStatusKey: response.statusCode])
}
}
}

// Remove the reference to the download
self.downloads.removeValueForKey(task.taskIdentifier)
// Remove the reference to the download
self.downloads.removeValueForKey(task.taskIdentifier)

dispatch_async(dispatch_get_main_queue()) {
download.delegate?.download(download, didFinishWithError: error, atLocation: download.resultingURL)
download.completion?(error: error, location: download.resultingURL)
return
dispatch_async(dispatch_get_main_queue()) {
download.delegate?.download(download, didFinishWithError: error, atLocation: download.resultingURL)
download.completion?(error: error, location: download.resultingURL)
return
}
}
}
}