Resume download

This commit is contained in:
Chris Li 2016-09-18 14:05:28 -04:00
parent 8479324f67
commit c8dd4ca05f
8 changed files with 122 additions and 102 deletions

View File

@ -31,7 +31,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Register notification
let settings = UIUserNotificationSettings(forTypes: [.Sound, .Alert, .Badge], categories: nil) // Here are the notification permission the app wants
application.registerUserNotificationSettings(settings)
// application.registerUserNotificationSettings(settings)
// Set background refresh interval
application.setMinimumBackgroundFetchInterval(86400)

View File

@ -177,37 +177,38 @@ class DownloadTasksController: UITableViewController, NSFetchedResultsController
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
guard let downloadTask = self.fetchedResultController.objectAtIndexPath(indexPath) as? DownloadTask else {return nil}
guard let downloadTask = self.fetchedResultController.objectAtIndexPath(indexPath) as? DownloadTask,
let bookID = downloadTask.book?.id else {return nil}
var actions = [UITableViewRowAction]()
switch downloadTask.state {
case .Downloading:
let pause = UITableViewRowAction(style: .Normal, title: "Pause") { (action, indexPath) in
guard let bookID = downloadTask.book?.id else {return}
let operation = PauseBookDwonloadOperation(bookID: bookID)
GlobalQueue.shared.addOperation(operation)
tableView.setEditing(false, animated: true)
}
actions.insert(pause, atIndex: 0)
case .Paused:
if let book = downloadTask.book,
let resumeData = Preference.resumeData[book.id] {
let resume = UITableViewRowAction(style: .Normal, title: "Resume") { (action, indexPath) in
let task = Network.shared.session.downloadTaskWithResumeData(resumeData)
let operation = DownloadBookOperation(downloadTask: task)
Network.shared.queue.addOperation(operation)
let operation = ResumeBookDwonloadOperation(bookID: bookID)
GlobalQueue.shared.addOperation(operation)
tableView.setEditing(false, animated: true)
}
actions.insert(resume, atIndex: 0)
} else {
let restart = UITableViewRowAction(style: .Normal, title: "Restart") { (action, indexPath) in
guard let bookID = downloadTask.book?.id,
let operation = DownloadBookOperation(bookID: bookID) else {return}
Network.shared.queue.addOperation(operation)
tableView.setEditing(false, animated: true)
}
actions.insert(restart, atIndex: 0)
}
//
// if let book = downloadTask.book,
// let resumeData = Preference.resumeData[book.id] {
//
// } else {
// let restart = UITableViewRowAction(style: .Normal, title: "Restart") { (action, indexPath) in
// guard let bookID = downloadTask.book?.id,
// let operation = DownloadBookOperation(bookID: bookID) else {return}
// Network.shared.queue.addOperation(operation)
// tableView.setEditing(false, animated: true)
// }
// actions.insert(restart, atIndex: 0)
// }
default:
break
}

View File

@ -49,7 +49,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1.8.669</string>
<string>1.8.725</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@ -21,7 +21,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.8.672</string>
<string>1.8.728</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionMainStoryboard</key>

View File

@ -43,6 +43,7 @@
97219DBD1D383A00009FDFF1 /* BookmarkController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97219DBC1D383A00009FDFF1 /* BookmarkController.swift */; };
9722122B1D3FCCE200C0DCF2 /* MainControllerShowHide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9722122A1D3FCCE200C0DCF2 /* MainControllerShowHide.swift */; };
972659191D8AE4B400D1DFFB /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972659181D8AE4B400D1DFFB /* Alerts.swift */; };
9726591B1D8DB91200D1DFFB /* DownloadProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9726591A1D8DB91200D1DFFB /* DownloadProgress.swift */; };
9734E54E1D289D060061C39B /* Welcome.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9734E54D1D289D060061C39B /* Welcome.storyboard */; };
973BCCEC1CEB3FA400F10B44 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 973BCCEB1CEB3FA400F10B44 /* AppDelegate.swift */; };
973BCCF31CEB3FA400F10B44 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 973BCCF21CEB3FA400F10B44 /* Assets.xcassets */; };
@ -108,7 +109,6 @@
97A1FD431D6F728200A80EE2 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A1FD3E1D6F728200A80EE2 /* FileManager.swift */; };
97A1FD441D6F728200A80EE2 /* Preference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A1FD401D6F728200A80EE2 /* Preference.swift */; };
97A1FD451D6F728200A80EE2 /* StringTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A1FD411D6F728200A80EE2 /* StringTools.swift */; };
97A461B71D74819000AC3DED /* DownloadProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A461B61D74819000AC3DED /* DownloadProgress.swift */; };
97A7017F1D2C59CA00AAE2D8 /* GetStartedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A7017E1D2C59CA00AAE2D8 /* GetStartedController.swift */; };
97A8AD841D6C951A00584ED1 /* LocalBooksController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A8AD831D6C951A00584ED1 /* LocalBooksController.swift */; };
97A8AD871D6CF38000584ED1 /* EmptyTableConfigExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A8AD861D6CF38000584ED1 /* EmptyTableConfigExtension.swift */; };
@ -256,6 +256,7 @@
97219DBC1D383A00009FDFF1 /* BookmarkController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BookmarkController.swift; path = "Kiwix-iOS/Controller/Bookmark/BookmarkController.swift"; sourceTree = SOURCE_ROOT; };
9722122A1D3FCCE200C0DCF2 /* MainControllerShowHide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MainControllerShowHide.swift; path = "Kiwix-iOS/Controller/Main/MainControllerShowHide.swift"; sourceTree = SOURCE_ROOT; };
972659181D8AE4B400D1DFFB /* Alerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Alerts.swift; sourceTree = "<group>"; };
9726591A1D8DB91200D1DFFB /* DownloadProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadProgress.swift; sourceTree = "<group>"; };
9734E54D1D289D060061C39B /* Welcome.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Welcome.storyboard; path = "Kiwix-iOS/Storyboard/Welcome.storyboard"; sourceTree = SOURCE_ROOT; };
973BCCE91CEB3FA400F10B44 /* Kiwix.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Kiwix.app; sourceTree = BUILT_PRODUCTS_DIR; };
973BCCEB1CEB3FA400F10B44 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = "Kiwix-OSX/AppDelegate.swift"; sourceTree = SOURCE_ROOT; };
@ -340,7 +341,6 @@
97A2AB881C1B80FF00052E74 /* Kiwix.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Kiwix.app; sourceTree = BUILT_PRODUCTS_DIR; };
97A2AB9F1C1B80FF00052E74 /* Kiwix-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Kiwix-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
97A2ABAA1C1B810000052E74 /* Kiwix-iOSUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Kiwix-iOSUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
97A461B61D74819000AC3DED /* DownloadProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadProgress.swift; sourceTree = "<group>"; };
97A7017E1D2C59CA00AAE2D8 /* GetStartedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GetStartedController.swift; path = "Kiwix-iOS/Controller/Welcome/GetStartedController.swift"; sourceTree = SOURCE_ROOT; };
97A8AD831D6C951A00584ED1 /* LocalBooksController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalBooksController.swift; sourceTree = "<group>"; };
97A8AD861D6CF38000584ED1 /* EmptyTableConfigExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyTableConfigExtension.swift; sourceTree = "<group>"; };
@ -852,7 +852,6 @@
97A1FD3C1D6F728200A80EE2 /* Tools */ = {
isa = PBXGroup;
children = (
97A461B61D74819000AC3DED /* DownloadProgress.swift */,
97A1FD3D1D6F728200A80EE2 /* Extensions.swift */,
97A1FD3E1D6F728200A80EE2 /* FileManager.swift */,
97C601DD1D7F342100362D4F /* HTMLHeading.swift */,
@ -967,6 +966,7 @@
isa = PBXGroup;
children = (
97DF259F1D6F996B001648A3 /* Network.swift */,
9726591A1D8DB91200D1DFFB /* DownloadProgress.swift */,
97A1FD1B1D6F71D800A80EE2 /* URLResponseCache.swift */,
);
path = Network;
@ -1520,6 +1520,7 @@
97A8AD871D6CF38000584ED1 /* EmptyTableConfigExtension.swift in Sources */,
971A102E1D022AD5007FC62C /* TableViewCells.swift in Sources */,
97DF259D1D6F9053001648A3 /* URLSessionDownloadTaskOperation.swift in Sources */,
9726591B1D8DB91200D1DFFB /* DownloadProgress.swift in Sources */,
97E60A021D10423A00EBCB9D /* ShadowViews.swift in Sources */,
970E68BA1D3809A3001E8514 /* MainController.swift in Sources */,
970C65501D398D5A007032F8 /* BookmarkControllerAnimator.swift in Sources */,
@ -1574,7 +1575,6 @@
971A103B1D022C2C007FC62C /* AdjustLayoutTBVC.swift in Sources */,
97219DBD1D383A00009FDFF1 /* BookmarkController.swift in Sources */,
97D6812E1D6F70DE00E5FA99 /* Kiwix.xcdatamodeld in Sources */,
97A461B71D74819000AC3DED /* DownloadProgress.swift in Sources */,
971A10341D022AEC007FC62C /* BookmarkTBVC.swift in Sources */,
97A1FD1D1D6F71D800A80EE2 /* URLResponseCache.swift in Sources */,
9764F5971D8339D500E0B1C4 /* JSInjection.swift in Sources */,

View File

@ -14,19 +14,13 @@ class DownloadProgress: NSProgress {
private let timePointMinCount: Int = 20
private let timePointMaxCount: Int = 200
init(completedUnitCount: Int64 = 0, totalUnitCount: Int64) {
init(completedUnitCount: Int64, totalUnitCount: Int64) {
super.init(parent: nil, userInfo: [NSProgressFileOperationKindKey: NSProgressFileOperationKindDownloading])
self.kind = NSProgressKindFile
self.totalUnitCount = totalUnitCount
self.completedUnitCount = completedUnitCount
}
override var completedUnitCount: Int64 {
didSet {
add(completedUnitCount)
}
}
// MARK: - Descriptions
var fractionCompletedDescription: String? {
@ -60,7 +54,8 @@ class DownloadProgress: NSProgress {
setUserInfoObject(NSNumber(double: remainingSeconds), forKey: NSProgressEstimatedTimeRemainingKey)
}
private func add(completedUnitCount: Int64) {
func addObservation(totalBytesWritten: Int64) {
completedUnitCount = totalBytesWritten
let timeStamp = NSDate().timeIntervalSince1970
if let lastPoint = timePoints.last {
guard timeStamp - lastPoint.timeStamp > 0.2 else {return}

View File

@ -74,7 +74,7 @@ class Network: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSe
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
guard let bookID = downloadTask.taskDescription,
let operation = operations[bookID] else {return}
operation.progress.completedUnitCount = totalBytesWritten
operation.progress.addObservation(totalBytesWritten)
context.performBlock {
guard let downloadTask = Book.fetch(bookID, context: self.context)?.downloadTask where downloadTask.state == .Queued else {return}

View File

@ -11,7 +11,6 @@ import CoreData
import Operations
class DownloadBookOperation: URLSessionDownloadTaskOperation {
let bookID: String?
let progress: DownloadProgress
@ -20,6 +19,33 @@ class DownloadBookOperation: URLSessionDownloadTaskOperation {
bookID = downloadTask.taskDescription
super.init(downloadTask: downloadTask)
name = downloadTask.taskDescription
// Update Coredata
let context = NSManagedObjectContext.mainQueueContext
context.performBlockAndWait {
guard let bookID = self.bookID,
let book = Book.fetch(bookID, context: context),
let downloadTask = DownloadTask.addOrUpdate(book, context: context) else {return}
book.isLocal = nil
downloadTask.state = .Queued
// Overwrite progress
self.progress.completedUnitCount = book.downloadTask?.totalBytesWritten ?? 0
self.progress.totalUnitCount = book.fileSize
}
}
convenience init?(bookID: String, resumeData: NSData) {
if #available(iOS 10.0, *) {
guard let data = DownloadBookOperation.correctFuckingResumeData(resumeData) else {return nil}
let downloadTask = Network.shared.session.downloadTaskWithResumeData(data)
downloadTask.taskDescription = bookID
self.init(downloadTask: downloadTask)
} else {
let downloadTask = Network.shared.session.downloadTaskWithResumeData(resumeData)
downloadTask.taskDescription = bookID
self.init(downloadTask: downloadTask)
}
}
convenience init?(bookID: String) {
@ -30,13 +56,6 @@ class DownloadBookOperation: URLSessionDownloadTaskOperation {
let task = Network.shared.session.downloadTaskWithURL(url)
task.taskDescription = bookID
self.init(downloadTask: task)
let downloadTask = DownloadTask.addOrUpdate(book, context: context)
downloadTask?.state = .Queued
book.isLocal = nil
progress.completedUnitCount = book.downloadTask?.totalBytesWritten ?? 0
progress.totalUnitCount = book.fileSize
}
override func operationWillCancel(errors: [ErrorType]) {
@ -45,76 +64,26 @@ class DownloadBookOperation: URLSessionDownloadTaskOperation {
override func operationDidCancel() {
print("Download Task did cancel")
guard let bookID = bookID else {return}
// Update CoreData
let context = NSManagedObjectContext.mainQueueContext
context.performBlockAndWait {
let book = Book.fetch(bookID, context: context)
if !self.produceResumeData {book?.isLocal = false}
guard let bookID = self.bookID,
let book = Book.fetch(bookID, context: context) else {return}
if !self.produceResumeData {book.isLocal = false}
guard let downloadTask = book?.downloadTask else {return}
guard let downloadTask = book.downloadTask else {return}
if self.produceResumeData {
downloadTask.state = .Paused
} else {
context.deleteObject(downloadTask)
}
}
}
}
class RemoveBookOperation: Operation {
// MARK: - Helper
let bookID: String
init(bookID: String) {
self.bookID = bookID
super.init()
}
override func execute() {
let context = NSManagedObjectContext.mainQueueContext
context.performBlockAndWait {
guard let zimFileURL = ZimMultiReader.shared.readers[self.bookID]?.fileURL else {return}
_ = try? NSFileManager.defaultManager().removeItemAtURL(zimFileURL)
// Core data is updated by scan book operation
// Article removal is handled by cascade relationship
guard let idxFolderURL = ZimMultiReader.shared.readers[self.bookID]?.idxFolderURL else {return}
_ = try? NSFileManager.defaultManager().removeItemAtURL(idxFolderURL)
}
finish()
}
}
class PauseBookDwonloadOperation: Operation {
let bookID: String
init(bookID: String) {
self.bookID = bookID
super.init()
}
override func execute() {
Network.shared.operations[bookID]?.cancel(produceResumeData: true)
finish()
}
}
class ResumeBookDwonloadOperation: Operation {
let bookID: String
init(bookID: String) {
self.bookID = bookID
super.init()
}
override func execute() {
}
private func correctFuckingResumeData(data: NSData?) -> NSData? {
private class func correctFuckingResumeData(data: NSData?) -> NSData? {
let kResumeCurrentRequest = "NSURLSessionResumeCurrentRequest"
let kResumeOriginalRequest = "NSURLSessionResumeOriginalRequest"
@ -129,7 +98,7 @@ class ResumeBookDwonloadOperation: Operation {
return result
}
private func correctFuckingRequestData(data: NSData?) -> NSData? {
private class func correctFuckingRequestData(data: NSData?) -> NSData? {
guard let data = data else {
return nil
}
@ -174,3 +143,58 @@ class ResumeBookDwonloadOperation: Operation {
return result
}
}
class RemoveBookOperation: Operation {
let bookID: String
init(bookID: String) {
self.bookID = bookID
super.init()
}
override func execute() {
let context = NSManagedObjectContext.mainQueueContext
context.performBlockAndWait {
guard let zimFileURL = ZimMultiReader.shared.readers[self.bookID]?.fileURL else {return}
_ = try? NSFileManager.defaultManager().removeItemAtURL(zimFileURL)
// Core data is updated by scan book operation
// Article removal is handled by cascade relationship
guard let idxFolderURL = ZimMultiReader.shared.readers[self.bookID]?.idxFolderURL else {return}
_ = try? NSFileManager.defaultManager().removeItemAtURL(idxFolderURL)
}
finish()
}
}
class PauseBookDwonloadOperation: Operation {
let bookID: String
init(bookID: String) {
self.bookID = bookID
super.init()
}
override func execute() {
Network.shared.operations[bookID]?.cancel(produceResumeData: true)
finish()
}
}
class ResumeBookDwonloadOperation: Operation {
let bookID: String
init(bookID: String) {
self.bookID = bookID
super.init()
name = "Resume Book Dwonload Operation, bookID = \(bookID)"
}
override func execute() {
guard let data: NSData = Preference.resumeData[bookID],
let operation = DownloadBookOperation(bookID: bookID, resumeData: data) else {return}
Network.shared.queue.addOperation(operation)
finish()
}
}