From bf3adbe58ab01c6da5404692bd051b0399274cd7 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Mon, 12 Sep 2016 17:03:03 -0400 Subject: [PATCH] Operations --- Kiwix-iOS/AppDelegate.swift | 3 +- .../Controller/Search/SearchResultTBVC.swift | 23 ++-- Kiwix-iOS/Info.plist | 2 +- Kiwix-iOSWidgets/Bookmarks/Info.plist | 2 +- Kiwix.xcodeproj/project.pbxproj | 22 ++-- Kiwix/Operations/GlobalQueue.swift | 25 ++++ Kiwix/Operations/ScanLocalBookOperation.swift | 16 +-- Kiwix/Operations/SearchOperation-old.swift | 110 ++++++++++++++++++ Kiwix/Operations/SearchOperation.swift | 101 +--------------- Kiwix/ZimMultiReader/Extension.swift | 32 +++++ .../ExtensionAndTypealias.swift | 76 ------------ Kiwix/ZimMultiReader/ZimMultiReader.swift | 42 +++---- 12 files changed, 218 insertions(+), 236 deletions(-) create mode 100644 Kiwix/Operations/SearchOperation-old.swift create mode 100644 Kiwix/ZimMultiReader/Extension.swift delete mode 100644 Kiwix/ZimMultiReader/ExtensionAndTypealias.swift diff --git a/Kiwix-iOS/AppDelegate.swift b/Kiwix-iOS/AppDelegate.swift index c4165bbf..73e556c3 100644 --- a/Kiwix-iOS/AppDelegate.swift +++ b/Kiwix-iOS/AppDelegate.swift @@ -27,7 +27,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { NSURLProtocol.registerClass(KiwixURLProtocol) -// Network.shared.restoreProgresses() + //Network.shared.restoreProgresses() // Register notification let settings = UIUserNotificationSettings(forTypes: [.Sound, .Alert, .Badge], categories: nil) // Here are the notification permission the app wants @@ -64,7 +64,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } else { return false } - } // MARK: - Active diff --git a/Kiwix-iOS/Controller/Search/SearchResultTBVC.swift b/Kiwix-iOS/Controller/Search/SearchResultTBVC.swift index 0da2ea66..43a521d6 100644 --- a/Kiwix-iOS/Controller/Search/SearchResultTBVC.swift +++ b/Kiwix-iOS/Controller/Search/SearchResultTBVC.swift @@ -7,6 +7,7 @@ // import UIKit +import Operations class SearchResultTBVC: UIViewController, UITableViewDataSource, UITableViewDelegate { @@ -120,15 +121,19 @@ class SearchResultTBVC: UIViewController, UITableViewDataSource, UITableViewDele tableView.reloadData() return } - let operation = SearchOperation(searchTerm: searchText) { (results) in - guard let results = results else {return} - self.searchResults = results - self.tableView.reloadData() - if results.count > 0 { - self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: .Top, animated: true) - } - } - ZimMultiReader.sharedInstance.startSearch(operation) +// let operation = SearchOperation(searchTerm: searchText) { [unowned self] (results) in +// guard let results = results else {return} +// self.searchResults = results +// self.tableView.reloadData() +// if results.count > 0 { +// self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: .Top, animated: true) +// } +// } + let operation = SearchOperation(searchTerm: searchText) + operation.addObserver(DidFinishObserver {(operation, errors) in + print("search op did finish, result injection") + }) + GlobalQueue.shared.add(search: operation) } } diff --git a/Kiwix-iOS/Info.plist b/Kiwix-iOS/Info.plist index 6dc9babf..795878aa 100644 --- a/Kiwix-iOS/Info.plist +++ b/Kiwix-iOS/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 1.8.229 + 1.8.277 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/Kiwix-iOSWidgets/Bookmarks/Info.plist b/Kiwix-iOSWidgets/Bookmarks/Info.plist index 07c06b3f..a049057f 100644 --- a/Kiwix-iOSWidgets/Bookmarks/Info.plist +++ b/Kiwix-iOSWidgets/Bookmarks/Info.plist @@ -21,7 +21,7 @@ CFBundleSignature ???? CFBundleVersion - 1.8.231 + 1.8.280 NSExtension NSExtensionMainStoryboard diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index a5b84007..49096e25 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -93,7 +93,7 @@ 97A127CB1D777CF100FB204D /* SearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A127C71D777CF100FB204D /* SearchController.swift */; }; 97A127CC1D777CF100FB204D /* SearchResultTBVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A127C81D777CF100FB204D /* SearchResultTBVC.swift */; }; 97A1FD161D6F71CE00A80EE2 /* DirectoryMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A1FD121D6F71CE00A80EE2 /* DirectoryMonitor.swift */; }; - 97A1FD171D6F71CE00A80EE2 /* ExtensionAndTypealias.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A1FD131D6F71CE00A80EE2 /* ExtensionAndTypealias.swift */; }; + 97A1FD171D6F71CE00A80EE2 /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A1FD131D6F71CE00A80EE2 /* Extension.swift */; }; 97A1FD181D6F71CE00A80EE2 /* SearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A1FD141D6F71CE00A80EE2 /* SearchResult.swift */; }; 97A1FD191D6F71CE00A80EE2 /* ZimMultiReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A1FD151D6F71CE00A80EE2 /* ZimMultiReader.swift */; }; 97A1FD1C1D6F71D800A80EE2 /* KiwixURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97A1FD1A1D6F71D800A80EE2 /* KiwixURLProtocol.swift */; }; @@ -118,12 +118,12 @@ 97C601DC1D7F15C400362D4F /* Bookmark.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C601DB1D7F15C400362D4F /* Bookmark.storyboard */; }; 97C601DE1D7F342100362D4F /* HTMLHeading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97C601DD1D7F342100362D4F /* HTMLHeading.swift */; }; 97D452BE1D1723FF0033666F /* CollectionViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D452BD1D1723FF0033666F /* CollectionViewCells.swift */; }; + 97D4D64F1D874E6E00C1B065 /* SearchOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D4D64E1D874E6E00C1B065 /* SearchOperation.swift */; }; 97D55EF61D2075180081B523 /* TableOfContentsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D55EF51D2075180081B523 /* TableOfContentsController.swift */; }; 97D6811B1D6E2A7100E5FA99 /* DownloadTasksController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D6811A1D6E2A7100E5FA99 /* DownloadTasksController.swift */; }; 97D681231D6F70AC00E5FA99 /* GlobalQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D6811C1D6F70AC00E5FA99 /* GlobalQueue.swift */; }; 97D681241D6F70AC00E5FA99 /* RefreshLibraryOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D6811D1D6F70AC00E5FA99 /* RefreshLibraryOperation.swift */; }; 97D681251D6F70AC00E5FA99 /* ScanLocalBookOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */; }; - 97D681261D6F70AC00E5FA99 /* SearchOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D6811F1D6F70AC00E5FA99 /* SearchOperation.swift */; }; 97D681271D6F70AC00E5FA99 /* UIOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D681201D6F70AC00E5FA99 /* UIOperations.swift */; }; 97D681281D6F70AC00E5FA99 /* UpdateWidgetDataSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97D681211D6F70AC00E5FA99 /* UpdateWidgetDataSourceOperation.swift */; }; 97D6812E1D6F70DE00E5FA99 /* Kiwix.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 97D6812A1D6F70DE00E5FA99 /* Kiwix.xcdatamodeld */; }; @@ -315,7 +315,7 @@ 97A127C71D777CF100FB204D /* SearchController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchController.swift; sourceTree = ""; }; 97A127C81D777CF100FB204D /* SearchResultTBVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResultTBVC.swift; sourceTree = ""; }; 97A1FD121D6F71CE00A80EE2 /* DirectoryMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryMonitor.swift; sourceTree = ""; }; - 97A1FD131D6F71CE00A80EE2 /* ExtensionAndTypealias.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionAndTypealias.swift; sourceTree = ""; }; + 97A1FD131D6F71CE00A80EE2 /* Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; }; 97A1FD141D6F71CE00A80EE2 /* SearchResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResult.swift; sourceTree = ""; }; 97A1FD151D6F71CE00A80EE2 /* ZimMultiReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZimMultiReader.swift; sourceTree = ""; }; 97A1FD1A1D6F71D800A80EE2 /* KiwixURLProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KiwixURLProtocol.swift; sourceTree = ""; }; @@ -349,12 +349,13 @@ 97C601DB1D7F15C400362D4F /* Bookmark.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Bookmark.storyboard; sourceTree = ""; }; 97C601DD1D7F342100362D4F /* HTMLHeading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLHeading.swift; sourceTree = ""; }; 97D452BD1D1723FF0033666F /* CollectionViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCells.swift; sourceTree = ""; }; + 97D4D64E1D874E6E00C1B065 /* SearchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchOperation.swift; sourceTree = ""; }; 97D55EF51D2075180081B523 /* TableOfContentsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TableOfContentsController.swift; path = "Kiwix-iOS/Controller/TableOfContentsController.swift"; sourceTree = SOURCE_ROOT; }; 97D6811A1D6E2A7100E5FA99 /* DownloadTasksController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadTasksController.swift; sourceTree = ""; }; 97D6811C1D6F70AC00E5FA99 /* GlobalQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalQueue.swift; sourceTree = ""; }; 97D6811D1D6F70AC00E5FA99 /* RefreshLibraryOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshLibraryOperation.swift; sourceTree = ""; }; 97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanLocalBookOperation.swift; sourceTree = ""; }; - 97D6811F1D6F70AC00E5FA99 /* SearchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchOperation.swift; sourceTree = ""; }; + 97D6811F1D6F70AC00E5FA99 /* SearchOperation-old.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SearchOperation-old.swift"; sourceTree = ""; }; 97D681201D6F70AC00E5FA99 /* UIOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIOperations.swift; sourceTree = ""; }; 97D681211D6F70AC00E5FA99 /* UpdateWidgetDataSourceOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateWidgetDataSourceOperation.swift; sourceTree = ""; }; 97D681221D6F70AC00E5FA99 /* URLSessionDownloadTaskOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionDownloadTaskOperation.swift; sourceTree = ""; }; @@ -626,10 +627,10 @@ 97254FDD1C26442F0056950B /* ZimMultiReader */ = { isa = PBXGroup; children = ( - 97A1FD121D6F71CE00A80EE2 /* DirectoryMonitor.swift */, - 97A1FD131D6F71CE00A80EE2 /* ExtensionAndTypealias.swift */, - 97A1FD141D6F71CE00A80EE2 /* SearchResult.swift */, 97A1FD151D6F71CE00A80EE2 /* ZimMultiReader.swift */, + 97A1FD121D6F71CE00A80EE2 /* DirectoryMonitor.swift */, + 97A1FD131D6F71CE00A80EE2 /* Extension.swift */, + 97A1FD141D6F71CE00A80EE2 /* SearchResult.swift */, ); path = ZimMultiReader; sourceTree = ""; @@ -988,7 +989,8 @@ 97D6811C1D6F70AC00E5FA99 /* GlobalQueue.swift */, 97D6811D1D6F70AC00E5FA99 /* RefreshLibraryOperation.swift */, 97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */, - 97D6811F1D6F70AC00E5FA99 /* SearchOperation.swift */, + 97D4D64E1D874E6E00C1B065 /* SearchOperation.swift */, + 97D6811F1D6F70AC00E5FA99 /* SearchOperation-old.swift */, 97D681201D6F70AC00E5FA99 /* UIOperations.swift */, 97D681211D6F70AC00E5FA99 /* UpdateWidgetDataSourceOperation.swift */, 97D681221D6F70AC00E5FA99 /* URLSessionDownloadTaskOperation.swift */, @@ -1545,7 +1547,6 @@ 97A127C41D774C9900FB204D /* ControllerRetainer.swift in Sources */, 97D681321D6F70EC00E5FA99 /* MigrationPolicy.swift in Sources */, 97D6811B1D6E2A7100E5FA99 /* DownloadTasksController.swift in Sources */, - 97D681261D6F70AC00E5FA99 /* SearchOperation.swift in Sources */, 9764CBD11D806AD800072D6A /* RefreshLibControl.swift in Sources */, 971A10381D022C15007FC62C /* WebViewController.swift in Sources */, 97C601DE1D7F342100362D4F /* HTMLHeading.swift in Sources */, @@ -1555,6 +1556,7 @@ 9764F5991D833F2B00E0B1C4 /* KiwixURL.swift in Sources */, 97A127CC1D777CF100FB204D /* SearchResultTBVC.swift in Sources */, 97A8AD841D6C951A00584ED1 /* LocalBooksController.swift in Sources */, + 97D4D64F1D874E6E00C1B065 /* SearchOperation.swift in Sources */, 97D452BE1D1723FF0033666F /* CollectionViewCells.swift in Sources */, 971A102F1D022AD5007FC62C /* Logo.swift in Sources */, 97D55EF61D2075180081B523 /* TableOfContentsController.swift in Sources */, @@ -1565,7 +1567,7 @@ 97A1FD261D6F71E200A80EE2 /* ZimReader.mm in Sources */, 97A1FD1C1D6F71D800A80EE2 /* KiwixURLProtocol.swift in Sources */, 97D6813F1D6F712800E5FA99 /* Article+CoreDataProperties.swift in Sources */, - 97A1FD171D6F71CE00A80EE2 /* ExtensionAndTypealias.swift in Sources */, + 97A1FD171D6F71CE00A80EE2 /* Extension.swift in Sources */, 971A103C1D022C2C007FC62C /* FontSizeTBVC.swift in Sources */, 971A103B1D022C2C007FC62C /* AdjustLayoutTBVC.swift in Sources */, 97219DBD1D383A00009FDFF1 /* BookmarkController.swift in Sources */, diff --git a/Kiwix/Operations/GlobalQueue.swift b/Kiwix/Operations/GlobalQueue.swift index c00d201a..2711c520 100644 --- a/Kiwix/Operations/GlobalQueue.swift +++ b/Kiwix/Operations/GlobalQueue.swift @@ -10,6 +10,31 @@ import Operations class GlobalQueue: OperationQueue { static let shared = GlobalQueue() + + private weak var scanOperation: ScanLocalBookOperation? + private weak var searchOperation: SearchOperation? + + func add(scan operation: ScanLocalBookOperation) { + addOperation(operation) + scanOperation = operation + } + + func add(search operation: SearchOperation) { + if let _ = searchOperation { + print("search is not released") + } + + if let scanOperation = scanOperation { + operation.addDependency(scanOperation) + print("scan not finished") + } + + if let searchOperation = self.searchOperation { + searchOperation.cancel() + } + addOperation(operation) + searchOperation = operation + } } public enum OperationErrorCode: Int { diff --git a/Kiwix/Operations/ScanLocalBookOperation.swift b/Kiwix/Operations/ScanLocalBookOperation.swift index 7d717faa..10efd3f5 100644 --- a/Kiwix/Operations/ScanLocalBookOperation.swift +++ b/Kiwix/Operations/ScanLocalBookOperation.swift @@ -11,17 +11,14 @@ import Operations class ScanLocalBookOperation: Operation { private let context: NSManagedObjectContext - private var firstBookAdded = false + private(set) var firstBookAdded = false private var lastZimFileURLSnapshot: Set - private var currentZimFileURLSnapshot = Set() + private(set) var currentZimFileURLSnapshot = Set() private let lastIndexFolderURLSnapshot: Set - private var currentIndexFolderURLSnapshot = Set() + private(set) 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)) { + init(lastZimFileURLSnapshot: Set, lastIndexFolderURLSnapshot: Set) { self.context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType) context.parentContext = NSManagedObjectContext.mainQueueContext context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy @@ -29,7 +26,6 @@ class ScanLocalBookOperation: Operation { self.lastZimFileURLSnapshot = lastZimFileURLSnapshot self.lastIndexFolderURLSnapshot = lastIndexFolderURLSnapshot - self.completionHandler = completionHandler super.init() addCondition(MutuallyExclusive()) name = String(self) @@ -54,10 +50,6 @@ class ScanLocalBookOperation: Operation { override func operationDidFinish(errors: [ErrorType]) { 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() { diff --git a/Kiwix/Operations/SearchOperation-old.swift b/Kiwix/Operations/SearchOperation-old.swift new file mode 100644 index 00000000..cb12e6db --- /dev/null +++ b/Kiwix/Operations/SearchOperation-old.swift @@ -0,0 +1,110 @@ +// +// SearchOperation.swift +// Kiwix +// +// Created by Chris Li on 4/9/16. +// Copyright © 2016 Chris. All rights reserved. +// + +import UIKit +import Operations + +class SearchOperation: GroupOperation { + let completionHandler: ([SearchResult]?) -> Void + private(set) var results = [SearchResult]() + //private let startTime = NSDate() + + init(searchTerm: String, completionHandler: ([SearchResult]?) -> Void) { + self.completionHandler = completionHandler + super.init(operations: [NSOperation]()) + + let sortOperation = SortSearchResultsOperation { (results) in + self.results = results + } + + for (id, zimReader) in ZimMultiReader.sharedInstance.readers { + let managedObjectContext = UIApplication.appDelegate.managedObjectContext + guard let book = Book.fetch(id, context: managedObjectContext) else {continue} + guard book.includeInSearch else {continue} + let operation = SingleBookSearchOperation(zimReader: zimReader, + lowerCaseSearchTerm: searchTerm.lowercaseString, + completionHandler: { [unowned sortOperation] (results) in + sortOperation.results += results + }) + + addOperation(operation) + sortOperation.addDependency(operation) + } + + addOperation(sortOperation) + + addCondition(MutuallyExclusive()) + } + + override func operationDidFinish(errors: [ErrorType]) { + NSOperationQueue.mainQueue().addOperationWithBlock { + self.completionHandler(self.cancelled ? nil : self.results) + } + } +} + +private class SingleBookSearchOperation: Operation { + let zimReader: ZimReader + let lowerCaseSearchTerm: String + let completionHandler: ([SearchResult]) -> Void + + init(zimReader: ZimReader, lowerCaseSearchTerm: String, completionHandler: ([SearchResult]) -> Void) { + self.zimReader = zimReader + self.lowerCaseSearchTerm = lowerCaseSearchTerm + self.completionHandler = completionHandler + super.init() + } + + override private func execute() { + var results = [String: SearchResult]() + let indexedDics = zimReader.searchUsingIndex(lowerCaseSearchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]() + let titleDics = zimReader.searchSuggestionsSmart(lowerCaseSearchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]() + let mixedDics = titleDics + indexedDics // It is important we process the title search result first, so that we always keep the indexed search result + for dic in mixedDics { + guard let result = SearchResult (rawResult: dic, lowerCaseSearchTerm: lowerCaseSearchTerm) else {continue} + results[result.path] = result + } + completionHandler(Array(results.values)) + finish() + } +} + +private class SortSearchResultsOperation: Operation { + let completionHandler: ([SearchResult]) -> Void + var results = [SearchResult]() + + init(completionHandler: ([SearchResult]) -> Void) { + self.completionHandler = completionHandler + super.init() + } + + override private func execute() { + sort() + completionHandler(results) + finish() + } + + private func sort() { + results.sortInPlace { (result0, result1) -> Bool in + if result0.score != result1.score { + return result0.score < result1.score + } else { + if result0.snippet != nil {return true} + if result1.snippet != nil {return false} + return titleCaseInsensitiveCompare(result0, result1: result1) + } + } + } + + // MARK: - Utilities + + private func titleCaseInsensitiveCompare(result0: SearchResult, result1: SearchResult) -> Bool { + return result0.title.caseInsensitiveCompare(result1.title) == NSComparisonResult.OrderedAscending + } +} + diff --git a/Kiwix/Operations/SearchOperation.swift b/Kiwix/Operations/SearchOperation.swift index cb12e6db..9c91c0f0 100644 --- a/Kiwix/Operations/SearchOperation.swift +++ b/Kiwix/Operations/SearchOperation.swift @@ -2,109 +2,14 @@ // SearchOperation.swift // Kiwix // -// Created by Chris Li on 4/9/16. +// Created by Chris Li on 9/12/16. // Copyright © 2016 Chris. All rights reserved. // -import UIKit import Operations class SearchOperation: GroupOperation { - let completionHandler: ([SearchResult]?) -> Void - private(set) var results = [SearchResult]() - //private let startTime = NSDate() - - init(searchTerm: String, completionHandler: ([SearchResult]?) -> Void) { - self.completionHandler = completionHandler - super.init(operations: [NSOperation]()) - - let sortOperation = SortSearchResultsOperation { (results) in - self.results = results - } - - for (id, zimReader) in ZimMultiReader.sharedInstance.readers { - let managedObjectContext = UIApplication.appDelegate.managedObjectContext - guard let book = Book.fetch(id, context: managedObjectContext) else {continue} - guard book.includeInSearch else {continue} - let operation = SingleBookSearchOperation(zimReader: zimReader, - lowerCaseSearchTerm: searchTerm.lowercaseString, - completionHandler: { [unowned sortOperation] (results) in - sortOperation.results += results - }) - - addOperation(operation) - sortOperation.addDependency(operation) - } - - addOperation(sortOperation) - - addCondition(MutuallyExclusive()) - } - - override func operationDidFinish(errors: [ErrorType]) { - NSOperationQueue.mainQueue().addOperationWithBlock { - self.completionHandler(self.cancelled ? nil : self.results) - } + init(searchTerm: String) { + super.init(operations: []) } } - -private class SingleBookSearchOperation: Operation { - let zimReader: ZimReader - let lowerCaseSearchTerm: String - let completionHandler: ([SearchResult]) -> Void - - init(zimReader: ZimReader, lowerCaseSearchTerm: String, completionHandler: ([SearchResult]) -> Void) { - self.zimReader = zimReader - self.lowerCaseSearchTerm = lowerCaseSearchTerm - self.completionHandler = completionHandler - super.init() - } - - override private func execute() { - var results = [String: SearchResult]() - let indexedDics = zimReader.searchUsingIndex(lowerCaseSearchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]() - let titleDics = zimReader.searchSuggestionsSmart(lowerCaseSearchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]() - let mixedDics = titleDics + indexedDics // It is important we process the title search result first, so that we always keep the indexed search result - for dic in mixedDics { - guard let result = SearchResult (rawResult: dic, lowerCaseSearchTerm: lowerCaseSearchTerm) else {continue} - results[result.path] = result - } - completionHandler(Array(results.values)) - finish() - } -} - -private class SortSearchResultsOperation: Operation { - let completionHandler: ([SearchResult]) -> Void - var results = [SearchResult]() - - init(completionHandler: ([SearchResult]) -> Void) { - self.completionHandler = completionHandler - super.init() - } - - override private func execute() { - sort() - completionHandler(results) - finish() - } - - private func sort() { - results.sortInPlace { (result0, result1) -> Bool in - if result0.score != result1.score { - return result0.score < result1.score - } else { - if result0.snippet != nil {return true} - if result1.snippet != nil {return false} - return titleCaseInsensitiveCompare(result0, result1: result1) - } - } - } - - // MARK: - Utilities - - private func titleCaseInsensitiveCompare(result0: SearchResult, result1: SearchResult) -> Bool { - return result0.title.caseInsensitiveCompare(result1.title) == NSComparisonResult.OrderedAscending - } -} - diff --git a/Kiwix/ZimMultiReader/Extension.swift b/Kiwix/ZimMultiReader/Extension.swift new file mode 100644 index 00000000..fbcc8947 --- /dev/null +++ b/Kiwix/ZimMultiReader/Extension.swift @@ -0,0 +1,32 @@ +// +// ExtensionAndTypealias.swift +// Kiwix +// +// Created by Chris Li on 7/11/16. +// Copyright © 2016 Chris. All rights reserved. +// + +import UIKit + +typealias ZimID = String +typealias ArticlePath = String + +extension ZimReader { + var metaData: [String: AnyObject] { + var metadata = [String: AnyObject]() + + if let id = getID() {metadata["id"] = id} + if let title = getTitle() {metadata["title"] = title} + if let description = getDesc() {metadata["description"] = description} + if let creator = getCreator() {metadata["creator"] = creator} + if let publisher = getPublisher() {metadata["publisher"] = publisher} + if let favicon = getFavicon() {metadata["favicon"] = favicon} + if let date = getDate() {metadata["date"] = date} + if let articleCount = getArticleCount() {metadata["articleCount"] = articleCount} + if let mediaCount = getMediaCount() {metadata["mediaCount"] = mediaCount} + if let fileSize = getFileSize() {metadata["size"] = fileSize} + if let langCode = getLanguage() {metadata["language"] = langCode} + + return metadata + } +} diff --git a/Kiwix/ZimMultiReader/ExtensionAndTypealias.swift b/Kiwix/ZimMultiReader/ExtensionAndTypealias.swift deleted file mode 100644 index 76980da5..00000000 --- a/Kiwix/ZimMultiReader/ExtensionAndTypealias.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// ExtensionAndTypealias.swift -// Kiwix -// -// Created by Chris Li on 7/11/16. -// Copyright © 2016 Chris. All rights reserved. -// - -import UIKit - -typealias ZimID = String -typealias ArticlePath = String - -extension ZimReader { - var metaData: [String: AnyObject] { - var metadata = [String: AnyObject]() - - if let id = getID() {metadata["id"] = id} - if let title = getTitle() {metadata["title"] = title} - if let description = getDesc() {metadata["description"] = description} - if let creator = getCreator() {metadata["creator"] = creator} - if let publisher = getPublisher() {metadata["publisher"] = publisher} - if let favicon = getFavicon() {metadata["favicon"] = favicon} - if let date = getDate() {metadata["date"] = date} - if let articleCount = getArticleCount() {metadata["articleCount"] = articleCount} - if let mediaCount = getMediaCount() {metadata["mediaCount"] = mediaCount} - if let fileSize = getFileSize() {metadata["size"] = fileSize} - if let langCode = getLanguage() {metadata["language"] = langCode} - - return metadata - } -} - -// https://gist.github.com/adamyanalunas/69f6601fad6040686d300a1cdc20f500 -private extension String { - subscript(index: Int) -> Character { - return self[startIndex.advancedBy(index)] - } - - subscript(range: Range) -> String { - let start = startIndex.advancedBy(range.startIndex) - let end = startIndex.advancedBy(range.endIndex) - return self[start.. Int { - let (length, cmpLength) = (characters.count, cmpString.characters.count) - var matrix = Array( - count: cmpLength + 1, - repeatedValue: Array( - count: length + 1, - repeatedValue: 0 - ) - ) - - for m in 1..() private var lastIndexFolderURLSnapshot = Set() @@ -35,15 +32,20 @@ class ZimMultiReader: NSObject, DirectoryMonitorDelegate { } func startScan() { - let scanOperation = ScanLocalBookOperation(lastZimFileURLSnapshot: lastZimFileURLSnapshot, lastIndexFolderURLSnapshot: lastIndexFolderURLSnapshot) { (currentZimFileURLSnapshot, currentIndexFolderURLSnapshot, firstBookAdded) in - self.lastZimFileURLSnapshot = currentZimFileURLSnapshot - self.lastIndexFolderURLSnapshot = currentIndexFolderURLSnapshot - if firstBookAdded { + let operation = ScanLocalBookOperation(lastZimFileURLSnapshot: lastZimFileURLSnapshot, lastIndexFolderURLSnapshot: lastIndexFolderURLSnapshot) + operation.addObserver(DidFinishObserver { (operation, errors) in + guard let operation = operation as? ScanLocalBookOperation else {return} + NSOperationQueue.mainQueue().addOperationWithBlock({ + self.lastZimFileURLSnapshot = operation.currentZimFileURLSnapshot + self.lastIndexFolderURLSnapshot = operation.currentIndexFolderURLSnapshot + + guard operation.firstBookAdded else {return} self.delegate?.firstBookAdded() - } - } - GlobalQueue.shared.addOperation(scanOperation) - self.scanOperation = scanOperation + }) + }) + operation.queuePriority = .VeryHigh + if readers.count == 0 { operation.qualityOfService = .UserInitiated } + GlobalQueue.shared.add(scan: operation) } // MARK: - Reader Addition / Deletion @@ -69,20 +71,6 @@ class ZimMultiReader: NSObject, DirectoryMonitorDelegate { startScan() } - // MARK: - Search - - func startSearch(searchOperation: SearchOperation) { - if let scanOperation = scanOperation { - searchOperation.addDependency(scanOperation) - } - - if let searchOperation = self.searchOperation { - searchOperation.cancel() - } - searchQueue.addOperation(searchOperation) - self.searchOperation = searchOperation - } - // MARK: - Loading System func data(id: String, contentURLString: String) -> [String: AnyObject]? {