diff --git a/Kiwix-iOS/AppDelegate.swift b/Kiwix-iOS/AppDelegate.swift
index 480e4319..15c60c31 100644
--- a/Kiwix-iOS/AppDelegate.swift
+++ b/Kiwix-iOS/AppDelegate.swift
@@ -57,7 +57,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, OperationQueueDelegate {
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: #selector(AppDelegate.recordActiveSession), userInfo: nil, repeats: false)
- ZimMultiReader.sharedInstance.scan()
}
func applicationWillTerminate(application: UIApplication) {
diff --git a/Kiwix-iOS/Controller/MainVC.swift b/Kiwix-iOS/Controller/MainVC.swift
index a71c33e8..a8822746 100644
--- a/Kiwix-iOS/Controller/MainVC.swift
+++ b/Kiwix-iOS/Controller/MainVC.swift
@@ -56,6 +56,7 @@ class MainVC: UIViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
+
// showGetStarted()
}
diff --git a/Kiwix-iOS/Controller/MainVCDelegates.swift b/Kiwix-iOS/Controller/MainVCDelegates.swift
index 7dc5a329..c6afb542 100644
--- a/Kiwix-iOS/Controller/MainVCDelegates.swift
+++ b/Kiwix-iOS/Controller/MainVCDelegates.swift
@@ -45,7 +45,8 @@ extension MainVC: LPTBarButtonItemDelegate, TableOfContentsDelegate, ZimMultiRea
// MARK: - ZimMultiReaderDelegate
- func firstBookAdded(id: ZimID) {
+ func firstBookAdded() {
+ guard let id = ZimMultiReader.sharedInstance.readers.keys.first else {return}
loadMainPage(id)
}
diff --git a/Kiwix-iOS/Model/Network.swift b/Kiwix-iOS/Model/Network.swift
index 1fc63b67..11e0e089 100644
--- a/Kiwix-iOS/Model/Network.swift
+++ b/Kiwix-iOS/Model/Network.swift
@@ -163,7 +163,6 @@ class Network: NSObject, NSURLSessionDelegate, NSURLSessionDownloadDelegate, NSU
let bookDownloadTask = book.downloadTask else {return}
context.performBlockAndWait { () -> Void in
- book.isLocal = true
self.context.deleteObject(bookDownloadTask)
}
diff --git a/Kiwix-iOS/Storyboard/Main.storyboard b/Kiwix-iOS/Storyboard/Main.storyboard
index 14830c8d..496f89c5 100644
--- a/Kiwix-iOS/Storyboard/Main.storyboard
+++ b/Kiwix-iOS/Storyboard/Main.storyboard
@@ -348,6 +348,7 @@
+
diff --git a/Kiwix-iOS/View/ShadowView.swift b/Kiwix-iOS/View/ShadowViews.swift
similarity index 100%
rename from Kiwix-iOS/View/ShadowView.swift
rename to Kiwix-iOS/View/ShadowViews.swift
diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj
index cb900e51..2f1c3064 100644
--- a/Kiwix.xcodeproj/project.pbxproj
+++ b/Kiwix.xcodeproj/project.pbxproj
@@ -180,7 +180,7 @@
97E609F11D103DED00EBCB9D /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97E609F01D103DED00EBCB9D /* NotificationCenter.framework */; };
97E609F41D103DED00EBCB9D /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97E609F31D103DED00EBCB9D /* TodayViewController.swift */; };
97E609F71D103DED00EBCB9D /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97E609F51D103DED00EBCB9D /* MainInterface.storyboard */; };
- 97E60A021D10423A00EBCB9D /* ShadowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97E60A011D10423A00EBCB9D /* ShadowView.swift */; };
+ 97E60A021D10423A00EBCB9D /* ShadowViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97E60A011D10423A00EBCB9D /* ShadowViews.swift */; };
97E60A061D10504000EBCB9D /* LibraryBackupTBVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97E60A051D10504000EBCB9D /* LibraryBackupTBVC.swift */; };
97E850CB1D2DA5B300A9F688 /* About.html in Resources */ = {isa = PBXBuildFile; fileRef = 97E850CA1D2DA5B300A9F688 /* About.html */; };
97E891691CA976E90001CA32 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97E891681CA976E90001CA32 /* FileManager.swift */; };
@@ -432,7 +432,7 @@
97E609F31D103DED00EBCB9D /* TodayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewController.swift; sourceTree = ""; };
97E609F61D103DED00EBCB9D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; };
97E609F81D103DED00EBCB9D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 97E60A011D10423A00EBCB9D /* ShadowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowView.swift; sourceTree = ""; };
+ 97E60A011D10423A00EBCB9D /* ShadowViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowViews.swift; sourceTree = ""; };
97E60A051D10504000EBCB9D /* LibraryBackupTBVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LibraryBackupTBVC.swift; path = "Kiwix-iOS/Controller/LibraryBackupTBVC.swift"; sourceTree = SOURCE_ROOT; };
97E850CA1D2DA5B300A9F688 /* About.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = About.html; path = Kiwix/HelpDocuments/About.html; sourceTree = SOURCE_ROOT; };
97E891681CA976E90001CA32 /* FileManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FileManager.swift; path = Kiwix/FileManager.swift; sourceTree = ""; };
@@ -702,7 +702,7 @@
971A10281D022AD5007FC62C /* LTBarButtonItem.swift */,
971A10291D022AD5007FC62C /* RefreshHUD.swift */,
971A102A1D022AD5007FC62C /* SearchBar.swift */,
- 97E60A011D10423A00EBCB9D /* ShadowView.swift */,
+ 97E60A011D10423A00EBCB9D /* ShadowViews.swift */,
);
path = View;
sourceTree = "";
@@ -1580,7 +1580,7 @@
971A106F1D022E62007FC62C /* DownloadProgress.swift in Sources */,
971A102E1D022AD5007FC62C /* TableViewCells.swift in Sources */,
971A105A1D022DAD007FC62C /* LibraryLocalTBVC.swift in Sources */,
- 97E60A021D10423A00EBCB9D /* ShadowView.swift in Sources */,
+ 97E60A021D10423A00EBCB9D /* ShadowViews.swift in Sources */,
9779A1CC1D34225E0071EFAB /* SearchOperation.swift in Sources */,
971A10671D022E0A007FC62C /* MainVCDelegates.swift in Sources */,
978C58981C1CD86E0077AE47 /* Book.swift in Sources */,
diff --git a/Kiwix.xcworkspace/xcuserdata/chrisli.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Kiwix.xcworkspace/xcuserdata/chrisli.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
index c1699228..0e8d4107 100644
--- a/Kiwix.xcworkspace/xcuserdata/chrisli.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
+++ b/Kiwix.xcworkspace/xcuserdata/chrisli.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -9,29 +9,13 @@
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
- filePath = "Kiwix-iOS/Model/KiwixURLProtocol.swift"
- timestampString = "489951010.904281"
+ filePath = "Kiwix/ZimMultiReader/ZimMultiReader.swift"
+ timestampString = "490129771.550315"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
- startingLineNumber = "24"
- endingLineNumber = "24"
- landmarkName = "startLoading()"
- landmarkType = "5">
-
-
-
-
diff --git a/Kiwix/CoreData/Book.swift b/Kiwix/CoreData/Book.swift
index 45dac05c..7d2ce92c 100644
--- a/Kiwix/CoreData/Book.swift
+++ b/Kiwix/CoreData/Book.swift
@@ -14,8 +14,6 @@ import CoreData
import AppKit
#endif
-
-
class Book: NSManagedObject {
// MARK: - Add Book
@@ -95,6 +93,20 @@ class Book: NSManagedObject {
return fetch(fetchRequest, type: Book.self, context: context) ?? [Book]()
}
+ class func fetchLocal(context: NSManagedObjectContext) -> [ZimID: Book] {
+ let fetchRequest = NSFetchRequest(entityName: "Book")
+ let predicate = NSPredicate(format: "isLocal = true")
+ fetchRequest.predicate = predicate
+ let localBooks = fetch(fetchRequest, type: Book.self, context: context) ?? [Book]()
+
+ var books = [ZimID: Book]()
+ for book in localBooks {
+ guard let id = book.id else {continue}
+ books[id] = book
+ }
+ return books
+ }
+
class func fetch(id: String, context: NSManagedObjectContext) -> Book? {
let fetchRequest = NSFetchRequest(entityName: "Book")
fetchRequest.predicate = NSPredicate(format: "id = %@", id)
diff --git a/Kiwix/Operations/ScanLocalBookOperation.swift b/Kiwix/Operations/ScanLocalBookOperation.swift
index 4bf4a524..ec955472 100644
--- a/Kiwix/Operations/ScanLocalBookOperation.swift
+++ b/Kiwix/Operations/ScanLocalBookOperation.swift
@@ -6,8 +6,150 @@
// Copyright © 2016 Chris. All rights reserved.
//
-import UIKit
+import CoreData
+import PSOperations
-class ScanLocalBookOperation: NSObject {
+class ScanLocalBookOperation: Operation {
+ private let context: NSManagedObjectContext
+ private var firstBookAdded = false
+
+ private var lastZimFileURLSnapshot: Set
+ private var currentZimFileURLSnapshot = Set()
+ private let lastIndexFolderURLSnapshot: Set
+ private var currentIndexFolderURLSnapshot = Set()
+
+ private var completionHandler: ((currentZimFileURLSnapshot: Set, currentIndexFolderURLSnapshot: Set, firstBookAdded: Bool) -> Void)
+
+ init(lastZimFileURLSnapshot: Set, lastIndexFolderURLSnapshot: Set,
+ completionHandler: ((currentZimFileURLSnapshot: Set, currentIndexFolderURLSnapshot: Set, firstBookAdded: Bool) -> Void)) {
+ self.context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
+ context.parentContext = NSManagedObjectContext.mainQueueContext
+ context.mergePolicy = NSOverwriteMergePolicy
+
+ self.lastZimFileURLSnapshot = lastZimFileURLSnapshot
+ self.lastIndexFolderURLSnapshot = lastIndexFolderURLSnapshot
+
+ self.completionHandler = completionHandler
+ super.init()
+ name = String(self)
+ }
+
+ override func execute() {
+ defer {finish()}
+
+ currentZimFileURLSnapshot = ScanLocalBookOperation.getCurrentZimFileURLsInDocDir()
+ currentIndexFolderURLSnapshot = ScanLocalBookOperation.getCurrentIndexFolderURLsInDocDir()
+
+ let zimFileHasChanges = lastZimFileURLSnapshot != currentZimFileURLSnapshot
+ let indexFolderHasDeletions = lastIndexFolderURLSnapshot.subtract(currentIndexFolderURLSnapshot).count > 0
+
+ guard zimFileHasChanges || indexFolderHasDeletions else {return}
+
+ if indexFolderHasDeletions {
+ lastZimFileURLSnapshot.removeAll()
+ }
+
+ updateReaders()
+ updateCoreData()
+ }
+
+ override func finished(errors: [NSError]) {
+ context.performBlockAndWait {self.context.saveIfNeeded()}
+ NSManagedObjectContext.mainQueueContext.performBlockAndWait {NSManagedObjectContext.mainQueueContext.saveIfNeeded()}
+ NSOperationQueue.mainQueue().addOperationWithBlock {
+ self.completionHandler(currentZimFileURLSnapshot: self.currentZimFileURLSnapshot,
+ currentIndexFolderURLSnapshot: self.currentIndexFolderURLSnapshot, firstBookAdded: self.firstBookAdded)
+ }
+ }
+
+ private func updateReaders() {
+ let addedZimFileURLs = currentZimFileURLSnapshot.subtract(lastZimFileURLSnapshot)
+ let removedZimFileURLs = lastZimFileURLSnapshot.subtract(currentZimFileURLSnapshot)
+
+ guard addedZimFileURLs.count > 0 || removedZimFileURLs.count > 0 else {return}
+ ZimMultiReader.sharedInstance.removeReaders(removedZimFileURLs)
+ ZimMultiReader.sharedInstance.addReaders(addedZimFileURLs)
+ }
+
+ private func updateCoreData() {
+ let localBooks = Book.fetchLocal(context)
+ let zimReaderIDs = Set(ZimMultiReader.sharedInstance.readers.keys)
+ let addedZimFileIDs = zimReaderIDs.subtract(Set(localBooks.keys))
+ let removedZimFileIDs = Set(localBooks.keys).subtract(zimReaderIDs)
+
+ for id in removedZimFileIDs {
+ guard let book = localBooks[id] else {continue}
+ if let _ = book.meta4URL {
+ book.isLocal = false
+ } else {
+ context.deleteObject(book)
+ }
+ }
+
+ for id in addedZimFileIDs {
+ guard let reader = ZimMultiReader.sharedInstance.readers[id] else {return}
+ let book: Book? = {
+ let book = Book.fetch(id, context: NSManagedObjectContext.mainQueueContext)
+ return book ?? Book.add(reader.metaData, context: NSManagedObjectContext.mainQueueContext)
+ }()
+ book?.isLocal = true
+ book?.hasIndex = reader.hasIndex()
+ book?.hasPic = !reader.fileURL.absoluteString.containsString("nopic")
+ }
+
+ for (id, book) in localBooks {
+ guard !context.deletedObjects.contains(book) else {continue}
+ guard let reader = ZimMultiReader.sharedInstance.readers[id] else {return}
+ book.hasIndex = reader.hasIndex()
+ }
+
+ if localBooks.count == 0 && addedZimFileIDs.count == 1 {
+ firstBookAdded = true
+ }
+ }
+
+ // MARK: - Helper
+
+ private class func getCurrentZimFileURLsInDocDir() -> Set {
+ let fileURLs = FileManager.contentsOfDirectory(FileManager.docDirURL) ?? [NSURL]()
+ var zimURLs = Set()
+ for url in fileURLs {
+ do {
+ var isDirectory: AnyObject? = nil
+ try url.getResourceValue(&isDirectory, forKey: NSURLIsDirectoryKey)
+ if let isDirectory = (isDirectory as? NSNumber)?.boolValue {
+ if !isDirectory {
+ guard let pathExtension = url.pathExtension?.lowercaseString else {continue}
+ guard pathExtension.containsString("zim") else {continue}
+ zimURLs.insert(url)
+ }
+ }
+ } catch {
+ continue
+ }
+ }
+ return zimURLs
+ }
+
+ private class func getCurrentIndexFolderURLsInDocDir() -> Set {
+ let fileURLs = FileManager.contentsOfDirectory(FileManager.docDirURL) ?? [NSURL]()
+ var folderURLs = Set()
+ for url in fileURLs {
+ do {
+ var isDirectory: AnyObject? = nil
+ try url.getResourceValue(&isDirectory, forKey: NSURLIsDirectoryKey)
+ if let isDirectory = (isDirectory as? NSNumber)?.boolValue {
+ if isDirectory {
+ guard let pathExtension = url.pathExtension?.lowercaseString else {continue}
+ guard pathExtension == "idx" else {continue}
+ folderURLs.insert(url)
+ }
+ }
+ } catch {
+ continue
+ }
+ }
+ return folderURLs
+ }
}
diff --git a/Kiwix/ZimMultiReader/ZimMultiReader.swift b/Kiwix/ZimMultiReader/ZimMultiReader.swift
index 450aaa94..a4fe547e 100644
--- a/Kiwix/ZimMultiReader/ZimMultiReader.swift
+++ b/Kiwix/ZimMultiReader/ZimMultiReader.swift
@@ -11,26 +11,21 @@ import PSOperations
class ZimMultiReader: NSObject, DirectoryMonitorDelegate {
static let sharedInstance = ZimMultiReader()
- let searchQueue = OperationQueue()
+
weak var delegate: ZimMultiReaderDelegate?
+ private weak var scanOperation: ScanLocalBookOperation?
- private(set) var readers = [ZimID: ZimReader]() {
- didSet {
- if readers.count == 1 {
- guard let id = readers.keys.first else {return}
- delegate?.firstBookAdded(id)
- }
- }
- }
-
+ let searchQueue = OperationQueue()
+ private(set) var isScanning = false
+ private(set) var readers = [ZimID: ZimReader]()
private let monitor = DirectoryMonitor(URL: FileManager.docDirURL)
- private var zimURLs = Set()
- private var zimAdded = Set()
- private var zimRemoved = Set()
- private var indexFolders = Set()
+ private var lastZimFileURLSnapshot = Set()
+ private var lastIndexFolderURLSnapshot = Set()
override init() {
super.init()
+
+ startScan()
monitor.delegate = self
monitor.startMonitoring()
}
@@ -39,117 +34,54 @@ class ZimMultiReader: NSObject, DirectoryMonitorDelegate {
monitor.stopMonitoring()
}
- // MARK: - DirectoryMonitorDelegate
-
- func directoryMonitorDidObserveChange() {
- scan()
- }
-
- // MARK: - Scan
-
- func scan() {
- /*
- If list of idx folders changes, reinitialize all zim readers,
- because currently ZimMultiReader cannot find out which ZimReader's index folder is added or deleted
-
- Note: when a idx folder is added, the content of that idx folder will not finish copying, which makes it meanless to detect idx folder addition.
- Because, with a incompletely copied idx folder, the xapian initializer is guranteed to fail. So here only check for idx folder deletion.
- If user added a idx folder, he or she needs to manaually call rescan.
- */
- let newIndexFolders = Set(indexFolderURLsInDocDir)
- let deletedIdxFolder = indexFolders.subtract(newIndexFolders)
-
- // Check for idx folder deletion
- if deletedIdxFolder.count > 0 {
- zimURLs.removeAll()
- }
- indexFolders = newIndexFolders
-
- // Below are the lines required when not considering idx folders, aka only detect zim files
- let newZimURLs = Set(zimFileURLsInDocDir)
- zimAdded = newZimURLs.subtract(zimURLs)
- zimRemoved = zimURLs.subtract(newZimURLs)
- removeOld()
- addNew()
- zimAdded.removeAll()
- zimRemoved.removeAll()
- zimURLs = newZimURLs
- }
-
- private func removeOld() {
- for (id, reader) in readers {
- guard zimRemoved.contains(reader.fileURL) else {continue}
- readers[id] = nil
-
- guard let book = Book.fetch(id, context: NSManagedObjectContext.mainQueueContext) else {return}
- if let _ = book.meta4URL {
- book.isLocal = false
- } else {
- NSManagedObjectContext.mainQueueContext.deleteObject(book)
+ func startScan() {
+ isScanning = true
+ let scanOperation = ScanLocalBookOperation(lastZimFileURLSnapshot: lastZimFileURLSnapshot, lastIndexFolderURLSnapshot: lastIndexFolderURLSnapshot) { (currentZimFileURLSnapshot, currentIndexFolderURLSnapshot, firstBookAdded) in
+ self.lastZimFileURLSnapshot = currentZimFileURLSnapshot
+ self.lastIndexFolderURLSnapshot = currentIndexFolderURLSnapshot
+ self.isScanning = false
+ if firstBookAdded {
+ self.delegate?.firstBookAdded()
}
}
+ GlobalOperationQueue.sharedInstance.addOperation(scanOperation)
+ self.scanOperation = scanOperation
}
- private func addNew() {
- for url in zimAdded {
+ // MARK: - Reader Addition / Deletion
+
+ func addReaders(urls: Set) {
+ for url in urls {
guard let reader = ZimReader(ZIMFileURL: url) else {continue}
let id = reader.getID()
readers[id] = reader
-
- let book: Book? = {
- let book = Book.fetch(id, context: NSManagedObjectContext.mainQueueContext)
- return book ?? Book.add(reader.metaData, context: NSManagedObjectContext.mainQueueContext)
- }()
- book?.isLocal = true
- book?.hasIndex = reader.hasIndex()
- book?.hasPic = !reader.fileURL.absoluteString.containsString("nopic")
}
}
- private var zimFileURLsInDocDir: [NSURL] {
- let fileURLs = FileManager.contentsOfDirectory(FileManager.docDirURL) ?? [NSURL]()
- var zimURLs = [NSURL]()
- for url in fileURLs {
- do {
- var isDirectory: AnyObject? = nil
- try url.getResourceValue(&isDirectory, forKey: NSURLIsDirectoryKey)
- if let isDirectory = (isDirectory as? NSNumber)?.boolValue {
- if !isDirectory {
- guard let pathExtension = url.pathExtension?.lowercaseString else {continue}
- guard pathExtension.containsString("zim") else {continue}
- zimURLs.append(url)
- }
- }
- } catch {
- continue
- }
+ func removeReaders(urls: Set) {
+ for (id, reader) in readers {
+ guard urls.contains(reader.fileURL) else {continue}
+ readers[id] = nil
}
- return zimURLs
}
- private var indexFolderURLsInDocDir: [NSURL] {
- let fileURLs = FileManager.contentsOfDirectory(FileManager.docDirURL) ?? [NSURL]()
- var folderURLs = [NSURL]()
- for url in fileURLs {
- do {
- var isDirectory: AnyObject? = nil
- try url.getResourceValue(&isDirectory, forKey: NSURLIsDirectoryKey)
- if let isDirectory = (isDirectory as? NSNumber)?.boolValue {
- if isDirectory {
- guard let pathExtension = url.pathExtension?.lowercaseString else {continue}
- guard pathExtension == "idx" else {continue}
- folderURLs.append(url)
- }
- }
- } catch {
- continue
- }
- }
- return folderURLs
+ // MARK: - DirectoryMonitorDelegate
+
+ func directoryMonitorDidObserveChange() {
+ startScan()
}
// MARK: - Search
+ func startSearch(searchOperation: SearchOperation) {
+ if let scanOperation = scanOperation {
+ searchOperation.addDependency(scanOperation)
+ }
+ searchQueue.addOperation(searchOperation)
+ }
+
+ // MARK: Search (Old)
+
func search(searchTerm: String, zimFileID: String) -> [(id: String, articleTitle: String)] {
var resultTuples = [(id: String, articleTitle: String)]()
let firstCharRange = searchTerm.startIndex...searchTerm.startIndex
@@ -200,6 +132,6 @@ class ZimMultiReader: NSObject, DirectoryMonitorDelegate {
}
protocol ZimMultiReaderDelegate: class {
- func firstBookAdded(id: ZimID)
+ func firstBookAdded()
}