From e2ff8f60f4238c5f5a1cdf2bab7adaf393ec69ea Mon Sep 17 00:00:00 2001 From: Chris Li Date: Tue, 24 Jan 2017 17:30:18 -0500 Subject: [PATCH] basic for downloading system --- Kiwix.xcodeproj/project.pbxproj | 12 +-- Kiwix/Network/DownloadManager.swift | 62 ----------- Kiwix/Network/Network-old.swift | 153 ++++++++++++++++++++++++++++ Kiwix/Network/Network.swift | 149 +-------------------------- 4 files changed, 164 insertions(+), 212 deletions(-) delete mode 100644 Kiwix/Network/DownloadManager.swift create mode 100644 Kiwix/Network/Network-old.swift diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index a4e6393d..e3d49b24 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -54,11 +54,11 @@ 974C49681DA4266200E276E1 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 974C49671DA4266200E276E1 /* CloudKit.framework */; }; 975227CD1D0227E8001D1DDE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 975227CA1D0227E8001D1DDE /* Main.storyboard */; }; 975227D01D022814001D1DDE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 975227CF1D022814001D1DDE /* LaunchScreen.storyboard */; }; - 9757C74A1E10660B008A9469 /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9757C7491E10660B008A9469 /* DownloadManager.swift */; }; 9757C74C1E106958008A9469 /* BackgroundDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9757C74B1E106958008A9469 /* BackgroundDownload.swift */; }; 97599AA21E26D3B000BA15EF /* BookmarkBooksController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97599AA11E26D3B000BA15EF /* BookmarkBooksController.swift */; }; 97599AE21E28193D00BA15EF /* BookmarkCollectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97599AE11E28193D00BA15EF /* BookmarkCollectionController.swift */; }; 975B90FE1CEB909100D13906 /* iOSExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 975B90FD1CEB909100D13906 /* iOSExtensions.swift */; }; + 97642B991E380CC0003E2D0B /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97642B981E380CC0003E2D0B /* Network.swift */; }; 9764CBD11D806AD800072D6A /* RefreshLibControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9764CBD01D806AD800072D6A /* RefreshLibControl.swift */; }; 9764F5931D830EF200E0B1C4 /* liblzma.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9764F5921D830EF200E0B1C4 /* liblzma.tbd */; }; 9764F5991D833F2B00E0B1C4 /* KiwixURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9764F5981D833F2B00E0B1C4 /* KiwixURL.swift */; }; @@ -213,11 +213,11 @@ 974C49671DA4266200E276E1 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 975227CA1D0227E8001D1DDE /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = "Kiwix-iOS/Storyboard/Main.storyboard"; sourceTree = SOURCE_ROOT; }; 975227CF1D022814001D1DDE /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = "Kiwix-iOS/Storyboard/LaunchScreen.storyboard"; sourceTree = SOURCE_ROOT; }; - 9757C7491E10660B008A9469 /* DownloadManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = ""; }; 9757C74B1E106958008A9469 /* BackgroundDownload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundDownload.swift; sourceTree = ""; }; 97599AA11E26D3B000BA15EF /* BookmarkBooksController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkBooksController.swift; sourceTree = ""; }; 97599AE11E28193D00BA15EF /* BookmarkCollectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkCollectionController.swift; sourceTree = ""; }; 975B90FD1CEB909100D13906 /* iOSExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = iOSExtensions.swift; path = "Kiwix-iOS/iOSExtensions.swift"; sourceTree = SOURCE_ROOT; }; + 97642B981E380CC0003E2D0B /* Network.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; 9764CBD01D806AD800072D6A /* RefreshLibControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshLibControl.swift; sourceTree = ""; }; 9764CBD21D8083AA00072D6A /* ArticleOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleOperation.swift; sourceTree = ""; }; 9764F5921D830EF200E0B1C4 /* liblzma.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = liblzma.tbd; path = usr/lib/liblzma.tbd; sourceTree = SDKROOT; }; @@ -286,7 +286,7 @@ 97D6813D1D6F712800E5FA99 /* DownloadTask+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "DownloadTask+CoreDataProperties.swift"; path = "Classes/DownloadTask+CoreDataProperties.swift"; sourceTree = ""; }; 97D6813E1D6F712800E5FA99 /* Language+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Language+CoreDataProperties.swift"; path = "Classes/Language+CoreDataProperties.swift"; sourceTree = ""; }; 97DB65D91D4576B600A2CC42 /* BookmarkWidgetCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkWidgetCell.swift; sourceTree = ""; }; - 97DF259F1D6F996B001648A3 /* Network.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; + 97DF259F1D6F996B001648A3 /* Network-old.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Network-old.swift"; sourceTree = ""; }; 97E609F01D103DED00EBCB9D /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; 97E60A011D10423A00EBCB9D /* Others.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Others.swift; sourceTree = ""; }; 97E850CA1D2DA5B300A9F688 /* About.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = About.html; path = Kiwix/HelpDocuments/About.html; sourceTree = SOURCE_ROOT; }; @@ -733,8 +733,8 @@ 97DF259E1D6F9942001648A3 /* Network */ = { isa = PBXGroup; children = ( - 97DF259F1D6F996B001648A3 /* Network.swift */, - 9757C7491E10660B008A9469 /* DownloadManager.swift */, + 97DF259F1D6F996B001648A3 /* Network-old.swift */, + 97642B981E380CC0003E2D0B /* Network.swift */, 9726591A1D8DB91200D1DFFB /* DownloadProgress.swift */, ); path = Network; @@ -1082,7 +1082,6 @@ 97A1FD421D6F728200A80EE2 /* Extensions.swift in Sources */, 97A1FD3A1D6F724E00A80EE2 /* reader.cpp in Sources */, 97D681371D6F711A00E5FA99 /* Article.swift in Sources */, - 9757C74A1E10660B008A9469 /* DownloadManager.swift in Sources */, 976C1DD41E300695005EDEC4 /* UIProcedure.swift in Sources */, 972F81571DDBFC79008D7289 /* Search.swift in Sources */, 970E7F831DA0305000741290 /* WelcomeController.swift in Sources */, @@ -1110,6 +1109,7 @@ 971A10521D022D9D007FC62C /* AppDelegate.swift in Sources */, 9764F5991D833F2B00E0B1C4 /* KiwixURL.swift in Sources */, 97A127CC1D777CF100FB204D /* SearchResultController.swift in Sources */, + 97642B991E380CC0003E2D0B /* Network.swift in Sources */, 973208241DD217B600EDD3DC /* BookmarkHUD.swift in Sources */, 976C1DDA1E32A7B3005EDEC4 /* NotificationSettingController.swift in Sources */, 971A102F1D022AD5007FC62C /* Logo.swift in Sources */, diff --git a/Kiwix/Network/DownloadManager.swift b/Kiwix/Network/DownloadManager.swift deleted file mode 100644 index a35eb5bf..00000000 --- a/Kiwix/Network/DownloadManager.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// DownloadManager.swift -// Kiwix -// -// Created by Chris Li on 12/25/16. -// Copyright © 2016 Chris Li. All rights reserved. -// - -import ProcedureKit - -class DownloadManager: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate { - static let shared = DownloadManager() - let queue = ProcedureQueue() - private override init() { - super.init() - } - - private(set) lazy var session: Foundation.URLSession = { - let configuration = URLSessionConfiguration.background(withIdentifier: "org.kiwix.www") - configuration.allowsCellularAccess = false - configuration.isDiscretionary = false - return URLSession(configuration: configuration, delegate: self, delegateQueue: nil) - }() - - - - // MARK: - URLSessionDelegate - - func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { - - } - - // MARK: - URLSessionTaskDelegate - - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - guard let operation = queue.operations.flatMap({$0 as? BookDownloadProcedure}) - .filter({$0.task.taskDescription == task.taskDescription}) - .first else { - if let bookID = task.taskDescription, - let resumeData = (error as? NSError)?.userInfo[NSURLSessionDownloadTaskResumeData] as? Data { - let operation = BookDownloadProcedure(session: session, bookID: bookID, resumeData: resumeData) - DownloadManager.shared.queue.add(operation: operation) - } - return - } - if let error = error { - operation.finish(withError: error) - } else { - operation.finish() - } - } - - // MARK: - URLSessionDownloadDelegate - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { - - } - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { - - } -} diff --git a/Kiwix/Network/Network-old.swift b/Kiwix/Network/Network-old.swift new file mode 100644 index 00000000..c4b0b9b4 --- /dev/null +++ b/Kiwix/Network/Network-old.swift @@ -0,0 +1,153 @@ +// +// Network.swift +// Kiwix +// +// Created by Chris Li on 8/25/16. +// Copyright © 2016 Chris Li. All rights reserved. +// + +import UIKit +import CoreData +import ProcedureKit +import UserNotifications + +class Network: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate, ProcedureQueueDelegate { + static let shared = Network() + let queue = OperationQueue() + let context = NSManagedObjectContext.mainQueueContext + + fileprivate(set) var operations = [String: DownloadBookOperation]() + fileprivate var downloadedBookTitle = [String]() + fileprivate var completionHandler: (()-> Void)? + + fileprivate override init() { + super.init() + queue.delegate = self + session.getAllTasks { _ in } + } + + lazy var session: Foundation.URLSession = { + let configuration = URLSessionConfiguration.background(withIdentifier: "org.kiwix.www") + configuration.allowsCellularAccess = false + configuration.isDiscretionary = false + return Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: nil) + }() + + func rejoinSessionWithIdentifier(_ identifier: String, completionHandler: @escaping ()-> Void) { + guard identifier == session.configuration.identifier else {return} + self.completionHandler = completionHandler + } + + // MARK: - OperationQueueDelegate + + func operationQueue(_ queue: ProcedureQueue, willAddOperation operation: Procedure) { + guard let bookID = operation.name, + let operation = operation as? DownloadBookOperation else {return} + operations[bookID] = operation + } + + func operationQueue(_ queue: ProcedureQueue, didFinishOperation operation: Procedure, withErrors errors: [Error]) { + guard let bookID = operation.name else {return} + operations[bookID] = nil + } + + func operationQueue(_ queue: ProcedureQueue, willFinishOperation operation: Procedure, withErrors errors: [Error]) {} + + func operationQueue(_ queue: ProcedureQueue, willProduceOperation operation: Procedure) {} + + // MARK: - NSURLSessionDelegate + + func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + OperationQueue.main.addOperation { + self.completionHandler?() + + let title = NSLocalizedString("Book download finished", comment: "Notification: Book download finished") + let body: String = { + switch self.downloadedBookTitle.count { + case 0: + return NSLocalizedString("All download tasks are finished.", comment: "Notification: Book download finished") + case 1: + return String(format: NSLocalizedString("%@ has been downloaded", comment: "Notification: Book download finished"), self.downloadedBookTitle[0]) + case 2: + return String(format: NSLocalizedString("%@ and %@ have been downloaded", comment: "Notification: Book download finished"), + self.downloadedBookTitle[0], self.downloadedBookTitle[1]) + default: + return String(format: NSLocalizedString("%@ and %d others have been downloaded", comment: "Notification: Book download finished"), + self.downloadedBookTitle[0], self.downloadedBookTitle.count - 1) + } + }() + + if #available(iOS 10, *) { + UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { (settings) in + guard settings.alertSetting == .enabled else {return} + let content = UNMutableNotificationContent() + content.title = title + content.body = body + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false) + let request = UNNotificationRequest(identifier: "org.kiwix.downloadFinished", content: content, trigger: trigger) + UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) + }) + } else { + let notification = UILocalNotification() + notification.alertTitle = title + notification.alertBody = body + notification.soundName = UILocalNotificationDefaultSoundName + UIApplication.shared.presentLocalNotificationNow(notification) + } + } + } + + // MARK: - NSURLSessionTaskDelegate + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + if let error = error {print(error.localizedDescription)} + + let context = NSManagedObjectContext.mainQueueContext + guard let error = error, + let bookID = task.taskDescription, + let downloadTask = Book.fetch(bookID, context: context)?.downloadTask else {return} + + if let resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData] as? Data { + Preference.resumeData[bookID] = resumeData + downloadTask.state = .paused + downloadTask.totalBytesWritten = task.countOfBytesReceived + } else { + downloadTask.state = .error + } + } + + // MARK: - NSURLSessionDownloadDelegate + + func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + guard let bookID = downloadTask.taskDescription, + let operation = operations[bookID] else {return} + operation.progress.addObservation(totalBytesWritten) + + context.perform { + guard let downloadTask = Book.fetch(bookID, context: self.context)?.downloadTask, downloadTask.state == .queued else {return} + downloadTask.state = .downloading + } + } + + func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + guard let bookID = downloadTask.taskDescription else {return} + + // Save downloaded zim file + var fileName: String = downloadTask.response?.suggestedFilename ?? bookID + if !fileName.hasSuffix(".zim") {fileName += ".zim"} + guard let destination = FileManager.docDirURL.appendingPathComponent(fileName) else {return} + _ = try? FileManager.default.moveItem(at: location, to: destination) + + // Scanner Operation will updated Book object status + + // - Remove cache, if any + // - Delete Download task Object + context.performAndWait { + guard let book = Book.fetch(bookID, context: self.context) else {return} + if let title = book.title {self.downloadedBookTitle.append(title)} + book.removeResumeData() + guard let downloadTask = book.downloadTask else {return} + self.context.delete(downloadTask) + } + } +} diff --git a/Kiwix/Network/Network.swift b/Kiwix/Network/Network.swift index c4b0b9b4..fa039aad 100644 --- a/Kiwix/Network/Network.swift +++ b/Kiwix/Network/Network.swift @@ -1,153 +1,14 @@ // -// Network.swift +// Downloader.swift // Kiwix // -// Created by Chris Li on 8/25/16. -// Copyright © 2016 Chris Li. All rights reserved. +// Created by Chris Li on 1/24/17. +// Copyright © 2017 Chris Li. All rights reserved. // import UIKit -import CoreData -import ProcedureKit -import UserNotifications -class Network: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate, ProcedureQueueDelegate { +class Network: NSObject { static let shared = Network() - let queue = OperationQueue() - let context = NSManagedObjectContext.mainQueueContext - - fileprivate(set) var operations = [String: DownloadBookOperation]() - fileprivate var downloadedBookTitle = [String]() - fileprivate var completionHandler: (()-> Void)? - - fileprivate override init() { - super.init() - queue.delegate = self - session.getAllTasks { _ in } - } - - lazy var session: Foundation.URLSession = { - let configuration = URLSessionConfiguration.background(withIdentifier: "org.kiwix.www") - configuration.allowsCellularAccess = false - configuration.isDiscretionary = false - return Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: nil) - }() - - func rejoinSessionWithIdentifier(_ identifier: String, completionHandler: @escaping ()-> Void) { - guard identifier == session.configuration.identifier else {return} - self.completionHandler = completionHandler - } - - // MARK: - OperationQueueDelegate - - func operationQueue(_ queue: ProcedureQueue, willAddOperation operation: Procedure) { - guard let bookID = operation.name, - let operation = operation as? DownloadBookOperation else {return} - operations[bookID] = operation - } - - func operationQueue(_ queue: ProcedureQueue, didFinishOperation operation: Procedure, withErrors errors: [Error]) { - guard let bookID = operation.name else {return} - operations[bookID] = nil - } - - func operationQueue(_ queue: ProcedureQueue, willFinishOperation operation: Procedure, withErrors errors: [Error]) {} - - func operationQueue(_ queue: ProcedureQueue, willProduceOperation operation: Procedure) {} - - // MARK: - NSURLSessionDelegate - - func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { - OperationQueue.main.addOperation { - self.completionHandler?() - - let title = NSLocalizedString("Book download finished", comment: "Notification: Book download finished") - let body: String = { - switch self.downloadedBookTitle.count { - case 0: - return NSLocalizedString("All download tasks are finished.", comment: "Notification: Book download finished") - case 1: - return String(format: NSLocalizedString("%@ has been downloaded", comment: "Notification: Book download finished"), self.downloadedBookTitle[0]) - case 2: - return String(format: NSLocalizedString("%@ and %@ have been downloaded", comment: "Notification: Book download finished"), - self.downloadedBookTitle[0], self.downloadedBookTitle[1]) - default: - return String(format: NSLocalizedString("%@ and %d others have been downloaded", comment: "Notification: Book download finished"), - self.downloadedBookTitle[0], self.downloadedBookTitle.count - 1) - } - }() - - if #available(iOS 10, *) { - UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { (settings) in - guard settings.alertSetting == .enabled else {return} - let content = UNMutableNotificationContent() - content.title = title - content.body = body - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false) - let request = UNNotificationRequest(identifier: "org.kiwix.downloadFinished", content: content, trigger: trigger) - UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) - }) - } else { - let notification = UILocalNotification() - notification.alertTitle = title - notification.alertBody = body - notification.soundName = UILocalNotificationDefaultSoundName - UIApplication.shared.presentLocalNotificationNow(notification) - } - } - } - - // MARK: - NSURLSessionTaskDelegate - - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - if let error = error {print(error.localizedDescription)} - - let context = NSManagedObjectContext.mainQueueContext - guard let error = error, - let bookID = task.taskDescription, - let downloadTask = Book.fetch(bookID, context: context)?.downloadTask else {return} - - if let resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData] as? Data { - Preference.resumeData[bookID] = resumeData - downloadTask.state = .paused - downloadTask.totalBytesWritten = task.countOfBytesReceived - } else { - downloadTask.state = .error - } - } - - // MARK: - NSURLSessionDownloadDelegate - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { - guard let bookID = downloadTask.taskDescription, - let operation = operations[bookID] else {return} - operation.progress.addObservation(totalBytesWritten) - - context.perform { - guard let downloadTask = Book.fetch(bookID, context: self.context)?.downloadTask, downloadTask.state == .queued else {return} - downloadTask.state = .downloading - } - } - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { - guard let bookID = downloadTask.taskDescription else {return} - - // Save downloaded zim file - var fileName: String = downloadTask.response?.suggestedFilename ?? bookID - if !fileName.hasSuffix(".zim") {fileName += ".zim"} - guard let destination = FileManager.docDirURL.appendingPathComponent(fileName) else {return} - _ = try? FileManager.default.moveItem(at: location, to: destination) - - // Scanner Operation will updated Book object status - - // - Remove cache, if any - // - Delete Download task Object - context.performAndWait { - guard let book = Book.fetch(bookID, context: self.context) else {return} - if let title = book.title {self.downloadedBookTitle.append(title)} - book.removeResumeData() - guard let downloadTask = book.downloadTask else {return} - self.context.delete(downloadTask) - } - } + private override init() {} }