X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/53b27422d140022594fc241cca91c3183be57bca..48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff:/iOS/Pods/MZDownloadManager/MZDownloadManager/Classes/MZDownloadManager.swift diff --git a/iOS/Pods/MZDownloadManager/MZDownloadManager/Classes/MZDownloadManager.swift b/iOS/Pods/MZDownloadManager/MZDownloadManager/Classes/MZDownloadManager.swift new file mode 100644 index 0000000..ccdda6b --- /dev/null +++ b/iOS/Pods/MZDownloadManager/MZDownloadManager/Classes/MZDownloadManager.swift @@ -0,0 +1,446 @@ +// +// MZDownloadManager.swift +// MZDownloadManager +// +// Created by Muhammad Zeeshan on 19/04/2016. +// Copyright © 2016 ideamakerz. All rights reserved. +// + +import UIKit +fileprivate func < (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (l?, r?): + return l < r + case (nil, _?): + return true + default: + return false + } +} + +fileprivate func > (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (l?, r?): + return l > r + default: + return rhs < lhs + } +} + + +@objc public protocol MZDownloadManagerDelegate: class { + /**A delegate method called each time whenever any download task's progress is updated + */ + @objc func downloadRequestDidUpdateProgress(_ downloadModel: MZDownloadModel, index: Int) + /**A delegate method called when interrupted tasks are repopulated + */ + @objc func downloadRequestDidPopulatedInterruptedTasks(_ downloadModel: [MZDownloadModel]) + /**A delegate method called each time whenever new download task is start downloading + */ + @objc optional func downloadRequestStarted(_ downloadModel: MZDownloadModel, index: Int) + /**A delegate method called each time whenever running download task is paused. If task is already paused the action will be ignored + */ + @objc optional func downloadRequestDidPaused(_ downloadModel: MZDownloadModel, index: Int) + /**A delegate method called each time whenever any download task is resumed. If task is already downloading the action will be ignored + */ + @objc optional func downloadRequestDidResumed(_ downloadModel: MZDownloadModel, index: Int) + /**A delegate method called each time whenever any download task is resumed. If task is already downloading the action will be ignored + */ + @objc optional func downloadRequestDidRetry(_ downloadModel: MZDownloadModel, index: Int) + /**A delegate method called each time whenever any download task is cancelled by the user + */ + @objc optional func downloadRequestCanceled(_ downloadModel: MZDownloadModel, index: Int) + /**A delegate method called each time whenever any download task is finished successfully + */ + @objc optional func downloadRequestFinished(_ downloadModel: MZDownloadModel, index: Int) + /**A delegate method called each time whenever any download task is failed due to any reason + */ + @objc optional func downloadRequestDidFailedWithError(_ error: NSError, downloadModel: MZDownloadModel, index: Int) + /**A delegate method called each time whenever specified destination does not exists. It will be called on the session queue. It provides the opportunity to handle error appropriately + */ + @objc optional func downloadRequestDestinationDoestNotExists(_ downloadModel: MZDownloadModel, index: Int, location: URL) + +} + +open class MZDownloadManager: NSObject { + + fileprivate var sessionManager: URLSession! + + fileprivate var backgroundSessionCompletionHandler: (() -> Void)? + + fileprivate let TaskDescFileNameIndex = 0 + fileprivate let TaskDescFileURLIndex = 1 + fileprivate let TaskDescFileDestinationIndex = 2 + + fileprivate weak var delegate: MZDownloadManagerDelegate? + + open var downloadingArray: [MZDownloadModel] = [] + + public convenience init(session sessionIdentifer: String, delegate: MZDownloadManagerDelegate) { + self.init() + + self.delegate = delegate + self.sessionManager = backgroundSession(identifier: sessionIdentifer) + self.populateOtherDownloadTasks() + } + + public convenience init(session sessionIdentifer: String, delegate: MZDownloadManagerDelegate, completion: (() -> Void)?) { + self.init(session: sessionIdentifer, delegate: delegate) + self.backgroundSessionCompletionHandler = completion + } + + fileprivate func backgroundSession(identifier: String) -> URLSession { + let sessionConfiguration = URLSessionConfiguration.background(withIdentifier: identifier) + let session = Foundation.URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil) + return session + } +} + +// MARK: Private Helper functions + +extension MZDownloadManager { + + fileprivate func downloadTasks() -> [URLSessionDownloadTask] { + var tasks: [URLSessionDownloadTask] = [] + let semaphore : DispatchSemaphore = DispatchSemaphore(value: 0) + sessionManager.getTasksWithCompletionHandler { (dataTasks, uploadTasks, downloadTasks) -> Void in + tasks = downloadTasks + semaphore.signal() + } + + let _ = semaphore.wait(timeout: DispatchTime.distantFuture) + + debugPrint("MZDownloadManager: pending tasks \(tasks)") + + return tasks + } + + fileprivate func populateOtherDownloadTasks() { + + let downloadTasks = self.downloadTasks() + + for downloadTask in downloadTasks { + let taskDescComponents: [String] = downloadTask.taskDescription!.components(separatedBy: ",") + let fileName = taskDescComponents[TaskDescFileNameIndex] + let fileURL = taskDescComponents[TaskDescFileURLIndex] + let destinationPath = taskDescComponents[TaskDescFileDestinationIndex] + + let downloadModel = MZDownloadModel.init(fileName: fileName, fileURL: fileURL, destinationPath: destinationPath) + downloadModel.task = downloadTask + downloadModel.startTime = Date() + + if downloadTask.state == .running { + downloadModel.status = TaskStatus.downloading.description() + downloadingArray.append(downloadModel) + } else if(downloadTask.state == .suspended) { + downloadModel.status = TaskStatus.paused.description() + downloadingArray.append(downloadModel) + } else { + downloadModel.status = TaskStatus.failed.description() + } + } + } + + fileprivate func isValidResumeData(_ resumeData: Data?) -> Bool { + + guard resumeData != nil || resumeData?.count > 0 else { + return false + } + + do { + var resumeDictionary : AnyObject! + resumeDictionary = try PropertyListSerialization.propertyList(from: resumeData!, options: PropertyListSerialization.MutabilityOptions(), format: nil) as AnyObject! + var localFilePath = (resumeDictionary?["NSURLSessionResumeInfoLocalPath"] as? String) + + if localFilePath == nil || localFilePath?.count < 1 { + localFilePath = (NSTemporaryDirectory() as String) + (resumeDictionary["NSURLSessionResumeInfoTempFileName"] as! String) + } + + let fileManager : FileManager! = FileManager.default + debugPrint("resume data file exists: \(fileManager.fileExists(atPath: localFilePath! as String))") + return fileManager.fileExists(atPath: localFilePath! as String) + } catch let error as NSError { + debugPrint("resume data is nil: \(error)") + return false + } + } +} + +extension MZDownloadManager: URLSessionDownloadDelegate { + + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + for (index, downloadModel) in self.downloadingArray.enumerated() { + if downloadTask.isEqual(downloadModel.task) { + DispatchQueue.main.async(execute: { () -> Void in + + let receivedBytesCount = Double(downloadTask.countOfBytesReceived) + let totalBytesCount = Double(downloadTask.countOfBytesExpectedToReceive) + let progress = Float(receivedBytesCount / totalBytesCount) + + let taskStartedDate = downloadModel.startTime! + let timeInterval = taskStartedDate.timeIntervalSinceNow + let downloadTime = TimeInterval(-1 * timeInterval) + + let speed = Float(totalBytesWritten) / Float(downloadTime) + + let remainingContentLength = totalBytesExpectedToWrite - totalBytesWritten + + let remainingTime = remainingContentLength / Int64(speed) + let hours = Int(remainingTime) / 3600 + let minutes = (Int(remainingTime) - hours * 3600) / 60 + let seconds = Int(remainingTime) - hours * 3600 - minutes * 60 + + let totalFileSize = MZUtility.calculateFileSizeInUnit(totalBytesExpectedToWrite) + let totalFileSizeUnit = MZUtility.calculateUnit(totalBytesExpectedToWrite) + + let downloadedFileSize = MZUtility.calculateFileSizeInUnit(totalBytesWritten) + let downloadedSizeUnit = MZUtility.calculateUnit(totalBytesWritten) + + let speedSize = MZUtility.calculateFileSizeInUnit(Int64(speed)) + let speedUnit = MZUtility.calculateUnit(Int64(speed)) + + downloadModel.remainingTime = (hours, minutes, seconds) + downloadModel.file = (totalFileSize, totalFileSizeUnit as String) + downloadModel.downloadedFile = (downloadedFileSize, downloadedSizeUnit as String) + downloadModel.speed = (speedSize, speedUnit as String) + downloadModel.progress = progress + + self.downloadingArray[index] = downloadModel + + self.delegate?.downloadRequestDidUpdateProgress(downloadModel, index: index) + }) + break + } + } + } + + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + for (index, downloadModel) in downloadingArray.enumerated() { + if downloadTask.isEqual(downloadModel.task) { + let fileName = downloadModel.fileName as NSString + let basePath = downloadModel.destinationPath == "" ? MZUtility.baseFilePath : downloadModel.destinationPath + let destinationPath = (basePath as NSString).appendingPathComponent(fileName as String) + + let fileManager : FileManager = FileManager.default + + //If all set just move downloaded file to the destination + if fileManager.fileExists(atPath: basePath) { + let fileURL = URL(fileURLWithPath: destinationPath as String) + debugPrint("directory path = \(destinationPath)") + + do { + try fileManager.moveItem(at: location, to: fileURL) + } catch let error as NSError { + debugPrint("Error while moving downloaded file to destination path:\(error)") + DispatchQueue.main.async(execute: { () -> Void in + self.delegate?.downloadRequestDidFailedWithError?(error, downloadModel: downloadModel, index: index) + }) + } + } else { + //Opportunity to handle the folder doesnot exists error appropriately. + //Move downloaded file to destination + //Delegate will be called on the session queue + //Otherwise blindly give error Destination folder does not exists + + if let _ = self.delegate?.downloadRequestDestinationDoestNotExists { + self.delegate?.downloadRequestDestinationDoestNotExists?(downloadModel, index: index, location: location) + } else { + let error = NSError(domain: "FolderDoesNotExist", code: 404, userInfo: [NSLocalizedDescriptionKey : "Destination folder does not exists"]) + self.delegate?.downloadRequestDidFailedWithError?(error, downloadModel: downloadModel, index: index) + } + } + + break + } + } + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + debugPrint("task id: \(task.taskIdentifier)") + /***** Any interrupted tasks due to any reason will be populated in failed state after init *****/ + + DispatchQueue.main.async { + + let err = error as NSError? + + if (err?.userInfo[NSURLErrorBackgroundTaskCancelledReasonKey] as? NSNumber)?.intValue == NSURLErrorCancelledReasonUserForceQuitApplication || (err?.userInfo[NSURLErrorBackgroundTaskCancelledReasonKey] as? NSNumber)?.intValue == NSURLErrorCancelledReasonBackgroundUpdatesDisabled { + + let downloadTask = task as! URLSessionDownloadTask + let taskDescComponents: [String] = downloadTask.taskDescription!.components(separatedBy: ",") + let fileName = taskDescComponents[self.TaskDescFileNameIndex] + let fileURL = taskDescComponents[self.TaskDescFileURLIndex] + let destinationPath = taskDescComponents[self.TaskDescFileDestinationIndex] + + let downloadModel = MZDownloadModel.init(fileName: fileName, fileURL: fileURL, destinationPath: destinationPath) + downloadModel.status = TaskStatus.failed.description() + downloadModel.task = downloadTask + + let resumeData = err?.userInfo[NSURLSessionDownloadTaskResumeData] as? Data + + var newTask = downloadTask + if self.isValidResumeData(resumeData) == true { + newTask = self.sessionManager.downloadTask(withResumeData: resumeData!) + } else { + newTask = self.sessionManager.downloadTask(with: URL(string: fileURL as String)!) + } + + newTask.taskDescription = downloadTask.taskDescription + downloadModel.task = newTask + + self.downloadingArray.append(downloadModel) + + self.delegate?.downloadRequestDidPopulatedInterruptedTasks(self.downloadingArray) + + } else { + for(index, object) in self.downloadingArray.enumerated() { + let downloadModel = object + if task.isEqual(downloadModel.task) { + if err?.code == NSURLErrorCancelled || err == nil { + self.downloadingArray.remove(at: index) + + if err == nil { + self.delegate?.downloadRequestFinished?(downloadModel, index: index) + } else { + self.delegate?.downloadRequestCanceled?(downloadModel, index: index) + } + + } else { + let resumeData = err?.userInfo[NSURLSessionDownloadTaskResumeData] as? Data + var newTask = task + if self.isValidResumeData(resumeData) == true { + newTask = self.sessionManager.downloadTask(withResumeData: resumeData!) + } else { + newTask = self.sessionManager.downloadTask(with: URL(string: downloadModel.fileURL)!) + } + + newTask.taskDescription = task.taskDescription + downloadModel.status = TaskStatus.failed.description() + downloadModel.task = newTask as? URLSessionDownloadTask + + self.downloadingArray[index] = downloadModel + + if let error = err { + self.delegate?.downloadRequestDidFailedWithError?(error, downloadModel: downloadModel, index: index) + } else { + let error: NSError = NSError(domain: "MZDownloadManagerDomain", code: 1000, userInfo: [NSLocalizedDescriptionKey : "Unknown error occurred"]) + + self.delegate?.downloadRequestDidFailedWithError?(error, downloadModel: downloadModel, index: index) + } + } + break; + } + } + } + } + } + + public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + if let backgroundCompletion = self.backgroundSessionCompletionHandler { + DispatchQueue.main.async(execute: { + backgroundCompletion() + }) + } + debugPrint("All tasks are finished") + } +} + +//MARK: Public Helper Functions + +extension MZDownloadManager { + + @objc public func addDownloadTask(_ fileName: String, fileURL: String, destinationPath: String) { + + let url = URL(string: fileURL as String)! + let request = URLRequest(url: url) + + let downloadTask = sessionManager.downloadTask(with: request) + downloadTask.taskDescription = [fileName, fileURL, destinationPath].joined(separator: ",") + downloadTask.resume() + + debugPrint("session manager:\(sessionManager) url:\(url) request:\(request)") + + let downloadModel = MZDownloadModel.init(fileName: fileName, fileURL: fileURL, destinationPath: destinationPath) + downloadModel.startTime = Date() + downloadModel.status = TaskStatus.downloading.description() + downloadModel.task = downloadTask + + downloadingArray.append(downloadModel) + delegate?.downloadRequestStarted?(downloadModel, index: downloadingArray.count - 1) + } + + @objc public func addDownloadTask(_ fileName: String, fileURL: String) { + addDownloadTask(fileName, fileURL: fileURL, destinationPath: "") + } + + @objc public func pauseDownloadTaskAtIndex(_ index: Int) { + + let downloadModel = downloadingArray[index] + + guard downloadModel.status != TaskStatus.paused.description() else { + return + } + + let downloadTask = downloadModel.task + downloadTask!.suspend() + downloadModel.status = TaskStatus.paused.description() + downloadModel.startTime = Date() + + downloadingArray[index] = downloadModel + + delegate?.downloadRequestDidPaused?(downloadModel, index: index) + } + + @objc public func resumeDownloadTaskAtIndex(_ index: Int) { + + let downloadModel = downloadingArray[index] + + guard downloadModel.status != TaskStatus.downloading.description() else { + return + } + + let downloadTask = downloadModel.task + downloadTask!.resume() + downloadModel.status = TaskStatus.downloading.description() + + downloadingArray[index] = downloadModel + + delegate?.downloadRequestDidResumed?(downloadModel, index: index) + } + + @objc public func retryDownloadTaskAtIndex(_ index: Int) { + let downloadModel = downloadingArray[index] + + guard downloadModel.status != TaskStatus.downloading.description() else { + return + } + + let downloadTask = downloadModel.task + + downloadTask!.resume() + downloadModel.status = TaskStatus.downloading.description() + downloadModel.startTime = Date() + downloadModel.task = downloadTask + + downloadingArray[index] = downloadModel + } + + @objc public func cancelTaskAtIndex(_ index: Int) { + let downloadInfo = downloadingArray[index] + let downloadTask = downloadInfo.task + downloadTask!.cancel() + } + + @objc public func presentNotificationForDownload(_ notifAction: String, notifBody: String) { + let application = UIApplication.shared + let applicationState = application.applicationState + + if applicationState == UIApplicationState.background { + let localNotification = UILocalNotification() + localNotification.alertBody = notifBody + localNotification.alertAction = notifAction + localNotification.soundName = UILocalNotificationDefaultSoundName + localNotification.applicationIconBadgeNumber += 1 + application.presentLocalNotificationNow(localNotification) + } + } +}