|
|
|
@ -14,20 +14,35 @@ public class SearchIndexer<T> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func index(_ item: T) -> String {
|
|
|
|
|
return indexBlock(item)
|
|
|
|
|
return normalize(indexingText: indexBlock(item))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func normalize(indexingText: String) -> String {
|
|
|
|
|
var normalized: String = indexingText.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
|
|
|
|
|
// Remove any punctuation from the search index
|
|
|
|
|
let nonformattingScalars = normalized.unicodeScalars.lazy.filter {
|
|
|
|
|
!CharacterSet.punctuationCharacters.contains($0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
normalized = String(String.UnicodeScalarView(nonformattingScalars))
|
|
|
|
|
|
|
|
|
|
return normalized
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc
|
|
|
|
|
public class FullTextSearchFinder: NSObject {
|
|
|
|
|
|
|
|
|
|
// Mark: Querying
|
|
|
|
|
|
|
|
|
|
public func enumerateObjects(searchText: String, transaction: YapDatabaseReadTransaction, block: @escaping (Any, String) -> Void) {
|
|
|
|
|
guard let ext: YapDatabaseFullTextSearchTransaction = ext(transaction: transaction) else {
|
|
|
|
|
assertionFailure("ext was unexpectedly nil")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let normalized = FullTextSearchFinder.normalize(queryText: searchText)
|
|
|
|
|
let normalized = normalize(queryText: searchText)
|
|
|
|
|
|
|
|
|
|
// We want to match by prefix for "search as you type" functionality.
|
|
|
|
|
// SQLite does not support suffix or contains matches.
|
|
|
|
@ -47,33 +62,10 @@ public class FullTextSearchFinder: NSObject {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func ext(transaction: YapDatabaseReadTransaction) -> YapDatabaseFullTextSearchTransaction? {
|
|
|
|
|
return transaction.ext(FullTextSearchFinder.dbExtensionName) as? YapDatabaseFullTextSearchTransaction
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mark: Index Building
|
|
|
|
|
|
|
|
|
|
private class var contactsManager: ContactsManagerProtocol {
|
|
|
|
|
return TextSecureKitEnv.shared().contactsManager
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class func normalize(indexingText: String) -> String {
|
|
|
|
|
var normalized: String = indexingText.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
|
|
|
|
|
// Remove any formatting from the search terms
|
|
|
|
|
let nonformattingScalars = normalized.unicodeScalars.lazy.filter {
|
|
|
|
|
!CharacterSet.punctuationCharacters.contains($0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
normalized = String(String.UnicodeScalarView(nonformattingScalars))
|
|
|
|
|
|
|
|
|
|
return normalized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class func normalize(queryText: String) -> String {
|
|
|
|
|
private func normalize(queryText: String) -> String {
|
|
|
|
|
var normalized: String = queryText.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
|
|
|
|
|
// Remove any formatting from the search terms
|
|
|
|
|
// Remove any punctuation from the search terms
|
|
|
|
|
let nonformattingScalars = normalized.unicodeScalars.lazy.filter {
|
|
|
|
|
!CharacterSet.punctuationCharacters.contains($0)
|
|
|
|
|
}
|
|
|
|
@ -91,6 +83,12 @@ public class FullTextSearchFinder: NSObject {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mark: Index Building
|
|
|
|
|
|
|
|
|
|
private class var contactsManager: ContactsManagerProtocol {
|
|
|
|
|
return TextSecureKitEnv.shared().contactsManager
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static let groupThreadIndexer: SearchIndexer<TSGroupThread> = SearchIndexer { (groupThread: TSGroupThread) in
|
|
|
|
|
let groupName = groupThread.groupModel.groupName ?? ""
|
|
|
|
|
|
|
|
|
@ -98,16 +96,12 @@ public class FullTextSearchFinder: NSObject {
|
|
|
|
|
recipientIndexer.index(recipientId)
|
|
|
|
|
}.joined(separator: " ")
|
|
|
|
|
|
|
|
|
|
let searchableContent = "\(groupName) \(memberStrings)"
|
|
|
|
|
|
|
|
|
|
return normalize(indexingText: searchableContent)
|
|
|
|
|
return "\(groupName) \(memberStrings)"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static let contactThreadIndexer: SearchIndexer<TSContactThread> = SearchIndexer { (contactThread: TSContactThread) in
|
|
|
|
|
let recipientId = contactThread.contactIdentifier()
|
|
|
|
|
let searchableContent = recipientIndexer.index(recipientId)
|
|
|
|
|
|
|
|
|
|
return normalize(indexingText: searchableContent)
|
|
|
|
|
return recipientIndexer.index(recipientId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static let recipientIndexer: SearchIndexer<String> = SearchIndexer { (recipientId: String) in
|
|
|
|
@ -128,15 +122,11 @@ public class FullTextSearchFinder: NSObject {
|
|
|
|
|
return String(String.UnicodeScalarView(digitScalars))
|
|
|
|
|
}(recipientId)
|
|
|
|
|
|
|
|
|
|
let searchableContent = "\(recipientId) \(nationalNumber) \(displayName)"
|
|
|
|
|
|
|
|
|
|
return normalize(indexingText: searchableContent)
|
|
|
|
|
return "\(recipientId) \(nationalNumber) \(displayName)"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static let messageIndexer: SearchIndexer<TSMessage> = SearchIndexer { (message: TSMessage) in
|
|
|
|
|
let searchableContent = message.body ?? ""
|
|
|
|
|
|
|
|
|
|
return normalize(indexingText: searchableContent)
|
|
|
|
|
return message.body ?? ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class func indexContent(object: Any) -> String? {
|
|
|
|
@ -161,8 +151,11 @@ public class FullTextSearchFinder: NSObject {
|
|
|
|
|
|
|
|
|
|
// MARK: - Extension Registration
|
|
|
|
|
|
|
|
|
|
// MJK - FIXME - while developing it's helpful to rebuild the index every launch. But we need to remove this before releasing.
|
|
|
|
|
private static let dbExtensionName: String = "FullTextSearchFinderExtension\(Date())"
|
|
|
|
|
private static let dbExtensionName: String = "FullTextSearchFinderExtension)"
|
|
|
|
|
|
|
|
|
|
private func ext(transaction: YapDatabaseReadTransaction) -> YapDatabaseFullTextSearchTransaction? {
|
|
|
|
|
return transaction.ext(FullTextSearchFinder.dbExtensionName) as? YapDatabaseFullTextSearchTransaction
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc
|
|
|
|
|
public class func asyncRegisterDatabaseExtension(storage: OWSStorage) {
|
|
|
|
@ -175,9 +168,6 @@ public class FullTextSearchFinder: NSObject {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class var dbExtensionConfig: YapDatabaseFullTextSearch {
|
|
|
|
|
// TODO is it worth doing faceted search, i.e. Author / Name / Content?
|
|
|
|
|
// seems unlikely that mobile users would use the "author: Alice" search syntax.
|
|
|
|
|
// so for now, everything searchable is jammed into a single column
|
|
|
|
|
let contentColumnName = "content"
|
|
|
|
|
|
|
|
|
|
let handler = YapDatabaseFullTextSearchHandler.withObjectBlock { (dict: NSMutableDictionary, _: String, _: String, object: Any) in
|
|
|
|
|