Skip to content

Commit

Permalink
Support for background downloads.
Browse files Browse the repository at this point in the history
  • Loading branch information
SagarSDagdu committed Mar 17, 2019
1 parent 3bb33af commit c5c36c5
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 27 deletions.
1 change: 1 addition & 0 deletions .swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4.2
6 changes: 4 additions & 2 deletions SDDownloadManager.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'SDDownloadManager'
s.version = '1.0.3'
s.version = '1.1.0'
s.summary = 'A simple, robust and elegant download manager written in Swift'

s.description = <<-DESC
Expand All @@ -12,7 +12,9 @@ s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Sagar Dagdu' => '[email protected]' }
s.source = { :git => 'https://github.com/SagarSDagdu/SDDownloadManager.git', :tag => s.version.to_s }

s.ios.deployment_target = '11.2'
s.swift_version = '4.2'
s.ios.deployment_target = '10.0'
s.framework = 'UserNotifications'
s.source_files = 'SDDownloadManager/Classes/*.swift'

end
10 changes: 8 additions & 2 deletions SDDownloadManager.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,13 @@
TargetAttributes = {
ACE3B6801F35D5BA00684BE6 = {
CreatedOnToolsVersion = 8.3.3;
LastSwiftMigration = 1010;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 0;
};
};
};
};
};
Expand Down Expand Up @@ -289,7 +295,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.sagar.thinkinfinite.SDDownloadManager;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Debug;
};
Expand All @@ -302,7 +308,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.sagar.thinkinfinite.SDDownloadManager;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Release;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
type = "1"
version = "2.0">
</Bucket>
14 changes: 12 additions & 2 deletions SDDownloadManager/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,22 @@
// THE SOFTWARE.

import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let center = UNUserNotificationCenter.current()
// Request permission to display alerts and play sounds.
center.requestAuthorization(options: [.alert, .sound])
{ (granted, error) in
// Enable or disable features based on authorization.
}
return true
}

Expand All @@ -58,6 +65,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}


func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
debugPrint("handleEventsForBackgroundURLSession: \(identifier)")
SDDownloadManager.shared.backgroundCompletionHandler = completionHandler
}
}

74 changes: 69 additions & 5 deletions SDDownloadManager/Classes/SDDownloadManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,46 @@
// THE SOFTWARE.

import UIKit
import UserNotifications

final public class SDDownloadManager: NSObject {

public typealias DownloadCompletionBlock = (_ error : Error?, _ fileUrl:URL?) -> Void
public typealias DownloadProgressBlock = (_ progress : CGFloat) -> Void
public typealias BackgroundDownloadCompletionHandler = () -> Void

// MARK :- Properties

var session: URLSession = URLSession()
var ongoingDownloads: [String : SDDownloadObject] = [:]
private var session: URLSession!
private var ongoingDownloads: [String : SDDownloadObject] = [:]
private var backgroundSession: URLSession!

public var backgroundCompletionHandler: BackgroundDownloadCompletionHandler?
public var showLocalNotificationOnBackgroundDownloadDone = true
public var localNotificationText: String?

public static let shared: SDDownloadManager = { return SDDownloadManager() }()

//MARK:- Public methods

public func dowloadFile(withRequest request: URLRequest,
public func downloadFile(withRequest request: URLRequest,
inDirectory directory: String? = nil,
withName fileName: String? = nil,
shouldDownloadInBackground: Bool = false,
onProgress progressBlock:DownloadProgressBlock? = nil,
onCompletion completionBlock:@escaping DownloadCompletionBlock) -> String? {

if let _ = self.ongoingDownloads[(request.url?.absoluteString)!] {
print("Already in progress")
debugPrint("Already in progress")
return nil
}
var downloadTask: URLSessionDownloadTask
if shouldDownloadInBackground {
downloadTask = self.backgroundSession.downloadTask(with: request)
} else{
downloadTask = self.session.downloadTask(with: request)
}

let downloadTask = self.session.downloadTask(with: request)
let download = SDDownloadObject(downloadTask: downloadTask,
progressBlock: progressBlock,
completionBlock: completionBlock,
Expand Down Expand Up @@ -109,6 +122,8 @@ final public class SDDownloadManager: NSObject {
super.init()
let sessionConfiguration = URLSessionConfiguration.default
self.session = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
let backgroundConfiguration = URLSessionConfiguration.background(withIdentifier: Bundle.main.bundleIdentifier!)
self.backgroundSession = URLSession(configuration: backgroundConfiguration, delegate: self, delegateQueue: OperationQueue())
}

private func isDownloadInProgress(forUniqueKey key:String?) -> (Bool, SDDownloadObject?) {
Expand All @@ -121,6 +136,29 @@ final public class SDDownloadManager: NSObject {
return (false, nil)
}

private func showLocalNotification(withText text:String) {
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getNotificationSettings { (settings) in
guard settings.authorizationStatus == .authorized else {
debugPrint("Not authorized to schedule notification")
return
}

let content = UNMutableNotificationContent()
content.title = text
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1,
repeats: false)
let identifier = "SDDownloadManagerNotification"
let request = UNNotificationRequest(identifier: identifier,
content: content, trigger: trigger)
notificationCenter.add(request, withCompletionHandler: { (error) in
if let error = error {
debugPrint("Could not schedule notification, error : \(error)")
}
})
}
}
}

extension SDDownloadManager : URLSessionDelegate, URLSessionDownloadDelegate {
Expand Down Expand Up @@ -164,6 +202,10 @@ extension SDDownloadManager : URLSessionDelegate, URLSessionDownloadDelegate {
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
guard totalBytesExpectedToWrite > 0 else {
debugPrint("Could not calculate progress as totalBytesExpectedToWrite is less than 0")
return;
}

if let download = self.ongoingDownloads[(downloadTask.originalRequest?.url?.absoluteString)!],
let progressBlock = download.progressBlock {
Expand All @@ -190,4 +232,26 @@ extension SDDownloadManager : URLSessionDelegate, URLSessionDownloadDelegate {
}
}

public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
session.getTasksWithCompletionHandler { (dataTasks, uploadTasks, downloadTasks) in
if downloadTasks.count == 0 {
OperationQueue.main.addOperation({
if let completion = self.backgroundCompletionHandler {
completion()
}

if self.showLocalNotificationOnBackgroundDownloadDone {
var notificationText = "Download completed"
if let userNotificationText = self.localNotificationText {
notificationText = userNotificationText
}

self.showLocalNotification(withText: notificationText)
}

self.backgroundCompletionHandler = nil
})
}
}
}
}
10 changes: 5 additions & 5 deletions SDDownloadManager/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand All @@ -34,10 +39,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
52 changes: 41 additions & 11 deletions SDDownloadManager/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,37 @@ class ViewController: UIViewController {
@IBOutlet weak var progressLabel: UILabel!
@IBOutlet weak var finalUrlLabel: UILabel!

private let downloadManager = SDDownloadManager.shared
let directoryName : String = "TestDirectory"

let fiveMBUrl = "https://sample-videos.com/video123/mp4/480/big_buck_bunny_480p_5mb.mp4"
let tenMBUrl = "https://sample-videos.com/video123/mp4/480/big_buck_bunny_480p_10mb.mp4"

//MARK:- Lifecycle

override func viewDidLoad() {
super.viewDidLoad()

self.setupUI()
self.foregrounDownloadDemo()
self.backgroundDownloadDemo()
}

private func setupUI() {
self.progressView.setProgress(0, animated: false)
self.progressLabel.text = "0.0 %"
self.finalUrlLabel.text = ""
}

private func foregrounDownloadDemo() {
let request = URLRequest(url: URL(string: self.fiveMBUrl)!)

let request = URLRequest.init(url: URL.init(string: "http://www.sample-videos.com/video/3gp/144/big_buck_bunny_144p_5mb.3gp")!)

let downloadKey = SDDownloadManager.shared.dowloadFile(withRequest: request,
inDirectory: directoryName,
withName: nil,
onProgress: { [weak self] (progress) in
let percentage = String(format: "%.1f %", (progress * 100))
self?.progressView.setProgress(Float(progress), animated: true)
self?.progressLabel.text = "\(percentage) %"
let downloadKey = self.downloadManager.downloadFile(withRequest: request,
inDirectory: directoryName,
onProgress: { [weak self] (progress) in
let percentage = String(format: "%.1f %", (progress * 100))
self?.progressView.setProgress(Float(progress), animated: true)
self?.progressLabel.text = "\(percentage) %"
}) { [weak self] (error, url) in
if let error = error {
print("Error is \(error as NSError)")
Expand All @@ -65,10 +76,29 @@ class ViewController: UIViewController {
}

print("The key is \(downloadKey!)")
}

private func backgroundDownloadDemo() {
let request = URLRequest(url: URL(string: self.tenMBUrl)!)

self.downloadManager.showLocalNotificationOnBackgroundDownloadDone = true
self.downloadManager.localNotificationText = "All background downloads complete"

let downloadKey = self.downloadManager.downloadFile(withRequest: request, inDirectory: directoryName, withName: directoryName, shouldDownloadInBackground: true, onProgress: { (progress) in
let percentage = String(format: "%.1f %", (progress * 100))
debugPrint("Background progress : \(percentage)")
}) { [weak self] (error, url) in
if let error = error {
print("Error is \(error as NSError)")
} else {
if let url = url {
print("Downloaded file's url is \(url.path)")
self?.finalUrlLabel.text = url.path
}
}
}

print("The key is \(downloadKey!)")
}


}

0 comments on commit c5c36c5

Please sign in to comment.