diff --git a/.gitignore b/.gitignore
index de72aeb7..4a60bc43 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,67 @@
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## Build generated
+build/
+DerivedData/
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+
+## Other
+*.moved-aside
*.xcuserstate
-Pods
-Kiwix/libkiwix/C&C++
-Kiwix/libkiwix/include
-Kiwix/libkiwix/shared
-Kiwix/libkiwix/static
-*.a
-Kiwix/libkiwix/iOS
-Kiwix/libkiwix/macOS
+
+## Obj-C/Swift specific
+*.hmap
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
+
+# Swift Package Manager
+#
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
+# Packages/
+.build/
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+Pods/
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
+# screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+
+Kiwix/libkiwix/
\ No newline at end of file
diff --git a/Kiwix-iOS/Controller/Library/CloudBooksController.swift b/Kiwix-iOS/Controller/Library/CloudBooksController.swift
index 30072101..bf096b6a 100644
--- a/Kiwix-iOS/Controller/Library/CloudBooksController.swift
+++ b/Kiwix-iOS/Controller/Library/CloudBooksController.swift
@@ -274,8 +274,10 @@ class CloudBooksController: CoreDataTableBaseController, UITableViewDelegate, UI
switch book.spaceState {
case .enough:
let action = UITableViewRowAction(style: UITableViewRowActionStyle.normal, title: LocalizedStrings.download, handler: { _ in
-// guard let download = DownloadBookOperation(bookID: book.id) else {return}
-// Network.shared.queue.addOperation(download)
+ guard let url = book.url else {return}
+ let download = BookDownloadProcedure(session: DownloadManager.shared.session, bookID: book.id, url: url)
+ DownloadManager.shared.queue.add(operation: download)
+ self.tableView.setEditing(false, animated: true)
})
action.backgroundColor = UIColor.defaultTint
return [action]
diff --git a/Kiwix-iOS/Info.plist b/Kiwix-iOS/Info.plist
index 6a58f533..e2426d5b 100644
--- a/Kiwix-iOS/Info.plist
+++ b/Kiwix-iOS/Info.plist
@@ -49,7 +49,7 @@
CFBundleVersion
- 1.8.3683
+ 1.8.3835
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/Kiwix-iOSWidgets/Bookmarks/Info.plist b/Kiwix-iOSWidgets/Bookmarks/Info.plist
index 7296d298..c58076dd 100644
--- a/Kiwix-iOSWidgets/Bookmarks/Info.plist
+++ b/Kiwix-iOSWidgets/Bookmarks/Info.plist
@@ -21,7 +21,7 @@
CFBundleSignature
????
CFBundleVersion
- 1.8.3701
+ 1.8.3853
NSExtension
NSExtensionMainStoryboard
diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj
index ddcfa388..253632ad 100644
--- a/Kiwix.xcodeproj/project.pbxproj
+++ b/Kiwix.xcodeproj/project.pbxproj
@@ -63,6 +63,8 @@
975227CD1D0227E8001D1DDE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 975227CA1D0227E8001D1DDE /* Main.storyboard */; };
975227CE1D0227E8001D1DDE /* Setting.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 975227CB1D0227E8001D1DDE /* Setting.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 */; };
975B90FE1CEB909100D13906 /* iOSExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 975B90FD1CEB909100D13906 /* iOSExtensions.swift */; };
9764CBD11D806AD800072D6A /* RefreshLibControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9764CBD01D806AD800072D6A /* RefreshLibControl.swift */; };
9764F5931D830EF200E0B1C4 /* liblzma.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9764F5921D830EF200E0B1C4 /* liblzma.tbd */; };
@@ -217,6 +219,8 @@
975227CA1D0227E8001D1DDE /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = "Kiwix-iOS/Storyboard/Main.storyboard"; sourceTree = SOURCE_ROOT; };
975227CB1D0227E8001D1DDE /* Setting.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Setting.storyboard; path = "Kiwix-iOS/Storyboard/Setting.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 = ""; };
975B90FD1CEB909100D13906 /* iOSExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = iOSExtensions.swift; path = "Kiwix-iOS/iOSExtensions.swift"; sourceTree = SOURCE_ROOT; };
9763275D1D64FE0F0034F120 /* BookDetailController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookDetailController.swift; sourceTree = ""; };
9764CBD01D806AD800072D6A /* RefreshLibControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshLibControl.swift; sourceTree = ""; };
@@ -751,6 +755,7 @@
isa = PBXGroup;
children = (
97DF259F1D6F996B001648A3 /* Network.swift */,
+ 9757C7491E10660B008A9469 /* DownloadManager.swift */,
9726591A1D8DB91200D1DFFB /* DownloadProgress.swift */,
);
path = Network;
@@ -773,6 +778,7 @@
children = (
97D6811C1D6F70AC00E5FA99 /* GlobalQueue.swift */,
9764CBD21D8083AA00072D6A /* ArticleOperation.swift */,
+ 9757C74B1E106958008A9469 /* BackgroundDownload.swift */,
970A2A211DD562CB0078BB7C /* BookOperations.swift */,
973A5C981DEBC54800C7804C /* CloudKit.swift */,
973208281DD223DB00EDD3DC /* RefreshLibrary.swift */,
@@ -1111,6 +1117,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 9757C74C1E106958008A9469 /* BackgroundDownload.swift in Sources */,
970E7F771D9DBEA900741290 /* SettingController.swift in Sources */,
973A5C951DEA6DD000C7804C /* URLResponseCache.swift in Sources */,
973207A51DD1984700EDD3DC /* SearchBooksController.swift in Sources */,
@@ -1137,6 +1144,7 @@
97A1FD3A1D6F724E00A80EE2 /* reader.cpp in Sources */,
973207A31DD1983D00EDD3DC /* LanguageFilterController.swift in Sources */,
97D681371D6F711A00E5FA99 /* Article.swift in Sources */,
+ 9757C74A1E10660B008A9469 /* DownloadManager.swift in Sources */,
972F81571DDBFC79008D7289 /* Search.swift in Sources */,
970E7F831DA0305000741290 /* WelcomeController.swift in Sources */,
97A1FD3B1D6F724E00A80EE2 /* stringTools.cpp in Sources */,
diff --git a/Kiwix.xcodeproj/xcuserdata/Chrisli.xcuserdatad/xcschemes/xcschememanagement.plist b/Kiwix.xcodeproj/xcuserdata/Chrisli.xcuserdatad/xcschemes/xcschememanagement.plist
index efbae53c..3c9b9b3c 100644
--- a/Kiwix.xcodeproj/xcuserdata/Chrisli.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Kiwix.xcodeproj/xcuserdata/Chrisli.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,7 +7,7 @@
Bookmarks.xcscheme
orderHint
- 2
+ 1
Kiwix-iOS.xcscheme
@@ -20,57 +20,57 @@
9722121A1D3ECCFE00C0DCF2
primary
-
+
973BCCE81CEB3FA400F10B44
primary
-
+
973BCCFB1CEB3FA400F10B44
primary
-
+
973BCD061CEB3FA500F10B44
primary
-
+
9779C2FD1D4574280064CC8E
primary
-
+
9779C3121D4575AD0064CC8E
primary
-
+
97A2AB871C1B80FF00052E74
primary
-
+
97A2AB9E1C1B80FF00052E74
primary
-
+
97A2ABA91C1B810000052E74
primary
-
+
97CF3EEA1D428F9600AE82FE
primary
-
+
97E609EE1D103DED00EBCB9D
primary
-
+
diff --git a/Kiwix/CoreData/Classes/DownloadTask.swift b/Kiwix/CoreData/Classes/DownloadTask.swift
index 03a06903..d1f041ae 100644
--- a/Kiwix/CoreData/Classes/DownloadTask.swift
+++ b/Kiwix/CoreData/Classes/DownloadTask.swift
@@ -12,7 +12,7 @@ import CoreData
class DownloadTask: NSManagedObject {
- class func fetch(_ book: Book, context: NSManagedObjectContext) -> DownloadTask? {
+ class func fetch(book: Book, context: NSManagedObjectContext) -> DownloadTask? {
let fetchRequest = NSFetchRequest(entityName: "DownloadTask")
fetchRequest.predicate = NSPredicate(format: "book = %@", book)
let downloadTask = DownloadTask.fetch(fetchRequest, type: DownloadTask.self, context: context)?.first ?? insert(DownloadTask.self, context: context)
diff --git a/Kiwix/Network/DownloadManager.swift b/Kiwix/Network/DownloadManager.swift
new file mode 100644
index 00000000..a35eb5bf
--- /dev/null
+++ b/Kiwix/Network/DownloadManager.swift
@@ -0,0 +1,62 @@
+//
+// 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/Operations/BackgroundDownload.swift b/Kiwix/Operations/BackgroundDownload.swift
new file mode 100644
index 00000000..09c4c691
--- /dev/null
+++ b/Kiwix/Operations/BackgroundDownload.swift
@@ -0,0 +1,80 @@
+//
+// BookDownload.swift
+// Kiwix
+//
+// Created by Chris Li on 12/25/16.
+// Copyright © 2016 Chris Li. All rights reserved.
+//
+
+import ProcedureKit
+
+class BackgroundDownloadProcedure: Procedure {
+ let task: URLSessionDownloadTask
+ let resumeDataProcessing: (Data?) -> Void
+ private let stateLock = NSLock()
+ private var produceResumeData = false
+
+ init(task: URLSessionDownloadTask, resumeData: @escaping (Data?) -> Void) {
+ self.task = task
+ self.resumeDataProcessing = resumeData
+ super.init()
+
+ add(observer: NetworkObserver())
+ addDidCancelBlockObserver { procedure, errors in
+ procedure.stateLock.withCriticalScope {
+ if procedure.produceResumeData {
+ procedure.task.cancel(byProducingResumeData: self.resumeDataProcessing)
+ } else {
+ procedure.task.cancel()
+ }
+ }
+ }
+ }
+
+ override func execute() {
+ stateLock.withCriticalScope {
+ guard !isCancelled, task.state == .suspended else { return }
+ task.resume()
+ }
+ }
+
+ func pause() {
+ produceResumeData = true
+ cancel()
+ }
+}
+
+class BookDownloadProcedure: BackgroundDownloadProcedure {
+
+ init(task: URLSessionDownloadTask) {
+ super.init(task: task) { data in
+ print("cancelled, resume data length = \(data?.count)")
+ }
+ addDidFinishBlockObserver { (procedure, errors) in
+ print("Download has finished")
+ }
+ }
+
+ convenience init(session: URLSession, bookID: String, url: URL) {
+ let task = session.downloadTask(with: url)
+ task.taskDescription = bookID
+ self.init(task: task)
+ }
+
+ convenience init(session: URLSession, bookID: String, resumeData: Data) {
+ let task = session.downloadTask(withResumeData: resumeData)
+ task.taskDescription = bookID
+ self.init(task: task)
+ }
+
+ override func execute() {
+ let context = AppDelegate.persistentContainer.viewContext
+ context.performAndWait {
+ guard let bookID = self.task.taskDescription,
+ let book = Book.fetch(bookID, context: context),
+ let downloadTask = DownloadTask.fetch(book: book, context: context) else {return}
+ downloadTask.state = .queued
+ }
+ super.execute()
+ }
+}