diff --git a/Kiwix-iOS/AppDelegate.swift b/Kiwix-iOS/AppDelegate.swift index da915ac1..c0db8be7 100644 --- a/Kiwix-iOS/AppDelegate.swift +++ b/Kiwix-iOS/AppDelegate.swift @@ -34,7 +34,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { application.registerUserNotificationSettings(settings) // Set background refresh interval - application.setMinimumBackgroundFetchInterval(60 * 60 * 24) + application.setMinimumBackgroundFetchInterval(86400) return true } @@ -150,7 +150,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } let notification = UILocalNotification() - notification.alertTitle = "[debug] Library was refreshed" + notification.alertTitle = "[DEBUG] Library was refreshed" notification.alertBody = NSDate().description notification.soundName = UILocalNotificationDefaultSoundName UIApplication.sharedApplication().presentLocalNotificationNow(notification) diff --git a/Kiwix-iOS/Controller/Library/DownloadTasksController.swift b/Kiwix-iOS/Controller/Library/DownloadTasksController.swift index 566ec20e..e1c8d463 100644 --- a/Kiwix-iOS/Controller/Library/DownloadTasksController.swift +++ b/Kiwix-iOS/Controller/Library/DownloadTasksController.swift @@ -12,6 +12,8 @@ import DZNEmptyDataSet class DownloadTasksController: UITableViewController, NSFetchedResultsControllerDelegate, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate { + var timer: NSTimer? + // MARK: - Override required init?(coder aDecoder: NSCoder) { @@ -33,6 +35,29 @@ class DownloadTasksController: UITableViewController, NSFetchedResultsController override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) tabBarController?.navigationItem.rightBarButtonItem = nil + timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(DownloadTasksController.refreshProgress), userInfo: nil, repeats: true) + } + + override func viewWillDisappear(animated: Bool) { + super.viewWillDisappear(animated) + timer?.invalidate() + timer = nil + } + + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + guard let identifier = segue.identifier else {return} + switch identifier { + case "ShowBookDetail": + guard let navController = segue.destinationViewController as? UINavigationController, + let bookDetailController = navController.topViewController as? BookDetailController, + let cell = sender as? UITableViewCell, + let indexPath = tableView.indexPathForCell(cell), + let downloadTask = fetchedResultController.objectAtIndexPath(indexPath) as? DownloadTask, + let book = downloadTask.book else {return} + bookDetailController.book = book + default: + break + } } override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) { @@ -43,6 +68,15 @@ class DownloadTasksController: UITableViewController, NSFetchedResultsController tableView.scrollIndicatorInsets = inset } + // MARK: - Methods + + func refreshProgress() { + tableView.visibleCells.forEach { (cell) in + guard let indexPath = tableView.indexPathForCell(cell) else {return} + configureCell(cell, atIndexPath: indexPath) + } + } + // MARK: - TableView Data Source override func numberOfSectionsInTableView(tableView: UITableView) -> Int { @@ -67,19 +101,10 @@ class DownloadTasksController: UITableViewController, NSFetchedResultsController cell.titleLabel.text = book.title cell.favIcon.image = UIImage(data: book.favIcon ?? NSData()) -// -// guard let progress = Network.shared.progresses[id] else {return} -// cell.progressView.progress = Float(progress.fractionCompleted) -// switch downloadTask.state { -// case .Queued, .Downloading: -// cell.accessoryImageView.highlighted = false -// cell.accessoryImageTintColor = UIColor.orangeColor().colorWithAlphaComponent(0.75) -// case .Paused, .Error: -// cell.accessoryImageView.highlighted = true -// cell.accessoryHighlightedImageTintColor = UIColor.greenColor().colorWithAlphaComponent(0.75) -// } -// cell.subtitleLabel.text = progress.description + guard let progress = Network.shared.operations[id]?.progress else {return} + cell.progressView.progress = Float(progress.fractionCompleted) + cell.detailLabel.text = progress.localizedAdditionalDescription.stringByReplacingOccurrencesOfString(" – ", withString: "\n") } // MARK: Other Data Source @@ -114,27 +139,21 @@ class DownloadTasksController: UITableViewController, NSFetchedResultsController // header.textLabel?.font = UIFont.boldSystemFontOfSize(14) // } // -// override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { -// return true -// } -// -// override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {} -// -// override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? { -// let remove = UITableViewRowAction(style: .Destructive, title: LocalizedStrings.remove) { (action, indexPath) -> Void in -// guard let downloadTask = self.fetchedResultController.objectAtIndexPath(indexPath) as? DownloadTask else {return} -// let context = UIApplication.appDelegate.managedObjectContext -// if let book = downloadTask.book { -// Network.sharedInstance.cancel(book) -// FileManager.removeResumeData(book) -// } -// context.performBlockAndWait({ () -> Void in -// downloadTask.book?.isLocal = false -// context.deleteObject(downloadTask) -// }) -// } -// return [remove] -// } + override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { + return true + } + + override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {} + + override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? { + let remove = UITableViewRowAction(style: .Destructive, title: LocalizedStrings.remove) { (action, indexPath) -> Void in + guard let downloadTask = self.fetchedResultController.objectAtIndexPath(indexPath) as? DownloadTask, + let bookID = downloadTask.book?.id else {return} + let operation = CancelBookDownloadOperation(bookID: bookID) + GlobalOperationQueue.sharedInstance.addOperation(operation) + } + return [remove] + } // MARK: - Fetched Results Controller diff --git a/Kiwix-iOS/Info.plist b/Kiwix-iOS/Info.plist index 7ca73986..da824076 100644 --- a/Kiwix-iOS/Info.plist +++ b/Kiwix-iOS/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 1.7.1386 + 1.7.1423 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/Kiwix-iOS/Storyboard/Library.storyboard b/Kiwix-iOS/Storyboard/Library.storyboard index 18623880..8db82d1c 100644 --- a/Kiwix-iOS/Storyboard/Library.storyboard +++ b/Kiwix-iOS/Storyboard/Library.storyboard @@ -469,7 +469,7 @@ - + @@ -515,6 +515,7 @@ + @@ -686,6 +687,6 @@ - + diff --git a/Kiwix-iOSWidgets/Bookmarks/Info.plist b/Kiwix-iOSWidgets/Bookmarks/Info.plist index e0819875..4e6c67cd 100644 --- a/Kiwix-iOSWidgets/Bookmarks/Info.plist +++ b/Kiwix-iOSWidgets/Bookmarks/Info.plist @@ -21,7 +21,7 @@ CFBundleSignature ???? CFBundleVersion - 1.7.1673 + 1.7.1742 NSExtension NSExtensionMainStoryboard diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index 2854f846..11ed82d3 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -38,7 +38,6 @@ 971A104A1D022CBE007FC62C /* SearchResultTBVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 971A10471D022CBE007FC62C /* SearchResultTBVC.swift */; }; 971A104B1D022CBE007FC62C /* SearchBooksVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 971A10481D022CBE007FC62C /* SearchBooksVC.swift */; }; 971A10521D022D9D007FC62C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 971A10511D022D9D007FC62C /* AppDelegate.swift */; }; - 971A106F1D022E62007FC62C /* DownloadProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 971A106D1D022E62007FC62C /* DownloadProgress.swift */; }; 971A10771D022F05007FC62C /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 971A10791D022F05007FC62C /* Localizable.stringsdict */; }; 971A107E1D022F74007FC62C /* DownloaderLearnMore.html in Resources */ = {isa = PBXBuildFile; fileRef = 971A107A1D022F74007FC62C /* DownloaderLearnMore.html */; }; 971A107F1D022F74007FC62C /* ImportBookLearnMore.html in Resources */ = {isa = PBXBuildFile; fileRef = 971A107B1D022F74007FC62C /* ImportBookLearnMore.html */; }; @@ -131,7 +130,7 @@ 97D681421D6F712800E5FA99 /* Language+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D6813E1D6F712800E5FA99 /* Language+CoreDataProperties.swift */; }; 97D681441D6F713200E5FA99 /* CoreDataExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D681431D6F713200E5FA99 /* CoreDataExtension.swift */; }; 97DB65DA1D4576B600A2CC42 /* BookmarkWidgetCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DB65D91D4576B600A2CC42 /* BookmarkWidgetCell.swift */; }; - 97DF259C1D6F7613001648A3 /* DownloadBookOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DF259B1D6F7612001648A3 /* DownloadBookOperation.swift */; }; + 97DF259C1D6F7613001648A3 /* BookOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DF259B1D6F7612001648A3 /* BookOperation.swift */; }; 97DF259D1D6F9053001648A3 /* URLSessionDownloadTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D681221D6F70AC00E5FA99 /* URLSessionDownloadTaskOperation.swift */; }; 97DF25A01D6F996B001648A3 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DF259F1D6F996B001648A3 /* Network.swift */; }; 97E60A021D10423A00EBCB9D /* ShadowViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97E60A011D10423A00EBCB9D /* ShadowViews.swift */; }; @@ -357,7 +356,7 @@ 97D6813E1D6F712800E5FA99 /* Language+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Language+CoreDataProperties.swift"; path = "Classes/Language+CoreDataProperties.swift"; sourceTree = ""; }; 97D681431D6F713200E5FA99 /* CoreDataExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CoreDataExtension.swift; path = Classes/CoreDataExtension.swift; sourceTree = ""; }; 97DB65D91D4576B600A2CC42 /* BookmarkWidgetCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkWidgetCell.swift; sourceTree = ""; }; - 97DF259B1D6F7612001648A3 /* DownloadBookOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadBookOperation.swift; sourceTree = ""; }; + 97DF259B1D6F7612001648A3 /* BookOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookOperation.swift; sourceTree = ""; }; 97DF259F1D6F996B001648A3 /* Network.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; 97E609F01D103DED00EBCB9D /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; 97E60A011D10423A00EBCB9D /* ShadowViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowViews.swift; sourceTree = ""; }; @@ -969,7 +968,7 @@ 97E5712A1CA0525300FF4F1D /* Operation */ = { isa = PBXGroup; children = ( - 97DF259B1D6F7612001648A3 /* DownloadBookOperation.swift */, + 97DF259B1D6F7612001648A3 /* BookOperation.swift */, 97D6811C1D6F70AC00E5FA99 /* GlobalOperationQueue.swift */, 97D6811D1D6F70AC00E5FA99 /* RefreshLibraryOperation.swift */, 97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */, @@ -1499,7 +1498,6 @@ 97A7017F1D2C59CA00AAE2D8 /* GetStartedController.swift in Sources */, 97A8AD871D6CF38000584ED1 /* EmptyTableConfigExtension.swift in Sources */, 97D452BC1D16FF010033666F /* RecentSearchCVC.swift in Sources */, - 971A106F1D022E62007FC62C /* DownloadProgress.swift in Sources */, 971A102E1D022AD5007FC62C /* TableViewCells.swift in Sources */, 97DF259D1D6F9053001648A3 /* URLSessionDownloadTaskOperation.swift in Sources */, 97E60A021D10423A00EBCB9D /* ShadowViews.swift in Sources */, @@ -1555,7 +1553,7 @@ 97A1FD441D6F728200A80EE2 /* Preference.swift in Sources */, 97D681311D6F70EC00E5FA99 /* 1.5.xcmappingmodel in Sources */, 97D681441D6F713200E5FA99 /* CoreDataExtension.swift in Sources */, - 97DF259C1D6F7613001648A3 /* DownloadBookOperation.swift in Sources */, + 97DF259C1D6F7613001648A3 /* BookOperation.swift in Sources */, 97A1FD181D6F71CE00A80EE2 /* SearchResult.swift in Sources */, 9763275E1D64FE0F0034F120 /* BookDetailController.swift in Sources */, 973DD4281D36E3E4009D45DB /* SettingSingleSwitchTBVC.swift in Sources */, diff --git a/Kiwix/Network/Network.swift b/Kiwix/Network/Network.swift index 7c89b098..f5953c7e 100644 --- a/Kiwix/Network/Network.swift +++ b/Kiwix/Network/Network.swift @@ -9,10 +9,11 @@ import UIKit import Operations -// , NSURLSessionDownloadDelegate, NSURLSessionTaskDelegate -class Network: NSObject, NSURLSessionDelegate, OperationQueueDelegate { +// , NSURLSessionTaskDelegate +class Network: NSObject, NSURLSessionDelegate, NSURLSessionDownloadDelegate, OperationQueueDelegate { static let shared = Network() let queue = OperationQueue() + private(set) var operations = [String: DownloadBookOperation]() private override init() { super.init() @@ -26,15 +27,34 @@ class Network: NSObject, NSURLSessionDelegate, OperationQueueDelegate { return NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil) }() + // MARK: - OperationQueueDelegate + func operationQueue(queue: OperationQueue, willAddOperation operation: NSOperation) { - print(queue.operationCount) + print("DEBUG: Network Queue will add" + (operation.name ?? "Unknown OP")) + guard let bookID = operation.name, + let operation = operation as? DownloadBookOperation else {return} + operations[bookID] = operation } func operationQueue(queue: OperationQueue, willFinishOperation operation: NSOperation, withErrors errors: [ErrorType]) {} func operationQueue(queue: OperationQueue, didFinishOperation operation: NSOperation, withErrors errors: [ErrorType]) { - print(queue.operationCount) + print("DEBUG: Network Queue did finish" + (operation.name ?? "Unknown OP")) + guard let bookID = operation.name else {return} + operations[bookID] = nil } func operationQueue(queue: OperationQueue, willProduceOperation operation: NSOperation) {} + + // MARK: - NSURLSessionDownloadDelegate + + 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 + } + + func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) { + + } } diff --git a/Kiwix/Operations/BookOperation.swift b/Kiwix/Operations/BookOperation.swift new file mode 100644 index 00000000..16f31f9d --- /dev/null +++ b/Kiwix/Operations/BookOperation.swift @@ -0,0 +1,76 @@ +// +// DownloadBookOperation.swift +// Kiwix +// +// Created by Chris Li on 8/25/16. +// Copyright © 2016 Chris. All rights reserved. +// + +import UIKit +import CoreData +import Operations + +class DownloadBookOperation: URLSessionDownloadTaskOperation { + + let progress: DownloadProgress + + override init(downloadTask: NSURLSessionDownloadTask) { + progress = DownloadProgress(completedUnitCount: downloadTask.countOfBytesReceived, totalUnitCount: downloadTask.countOfBytesExpectedToReceive) + super.init(downloadTask: downloadTask) + name = downloadTask.taskDescription + } + + convenience init?(bookID: String) { + let context = NSManagedObjectContext.mainQueueContext + guard let book = Book.fetch(bookID, context: context), + let url = book.url else { return nil } + + let task = Network.shared.session.downloadTaskWithURL(url) + task.taskDescription = bookID + self.init(downloadTask: task) + + let downloadTask = DownloadTask.addOrUpdate(book, context: context) + downloadTask?.state = .Queued + + progress.completedUnitCount = book.downloadTask?.totalBytesWritten ?? 0 + progress.totalUnitCount = book.fileSize + } + +} + +class CancelBookDownloadOperation: Operation { + + let bookID: String + + init(bookID: String) { + self.bookID = bookID + super.init() + } + + override func execute() { + Network.shared.operations[bookID]?.cancel(produceResumeData: false) + + let context = NSManagedObjectContext.mainQueueContext + context.performBlockAndWait { + guard let book = Book.fetch(self.bookID, context: context) else {return} + if let _ = book.meta4URL { + book.isLocal = false + } else{ + context.deleteObject(book) + } + + guard let downloadTask = book.downloadTask else {return} + context.deleteObject(downloadTask) + } + finish() + } +} + +class DownloadProgress: NSProgress { + init(completedUnitCount: Int64, totalUnitCount: Int64) { + super.init(parent: nil, userInfo: [NSProgressFileOperationKindKey: NSProgressFileOperationKindDownloading]) + self.kind = NSProgressKindFile + self.totalUnitCount = totalUnitCount + self.completedUnitCount = completedUnitCount + } +} diff --git a/Kiwix/Operations/DownloadBookOperation.swift b/Kiwix/Operations/DownloadBookOperation.swift deleted file mode 100644 index 50ef78fd..00000000 --- a/Kiwix/Operations/DownloadBookOperation.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// DownloadBookOperation.swift -// Kiwix -// -// Created by Chris Li on 8/25/16. -// Copyright © 2016 Chris. All rights reserved. -// - -import UIKit -import CoreData -import Operations - -class DownloadBookOperation: URLSessionDownloadTaskOperation { - - convenience init?(bookID: String) { - let context = NSManagedObjectContext.mainQueueContext - guard let book = Book.fetch(bookID, context: context), - let url = book.url else { return nil } - -// book - let task = Network.shared.session.downloadTaskWithURL(url) - - let downloadTask = DownloadTask.addOrUpdate(book, context: context) - downloadTask?.state = .Queued - - task.taskDescription = bookID - self.init(downloadTask: task) - } - - override func operationDidFinish(errors: [ErrorType]) { - guard let bookID = task.taskDescription else {return} - print("Book download op finished") - } -} - diff --git a/Kiwix/Operations/URLSessionDownloadTaskOperation.swift b/Kiwix/Operations/URLSessionDownloadTaskOperation.swift index 8f219c5f..b75cbcd9 100644 --- a/Kiwix/Operations/URLSessionDownloadTaskOperation.swift +++ b/Kiwix/Operations/URLSessionDownloadTaskOperation.swift @@ -16,7 +16,7 @@ class URLSessionDownloadTaskOperation: Operation { let task: NSURLSessionTask - private var produceResumeData = false + private(set) var produceResumeData = false private var removedObserved = false private let lock = NSLock()