@ -1,19 +1,67 @@
//
// C o p y r i g h t ( c ) 2 0 1 7 O p e n W h i s p e r S y s t e m s . A l l r i g h t s r e s e r v e d .
// C o p y r i g h t ( c ) 2 0 1 8 O p e n W h i s p e r S y s t e m s . A l l r i g h t s r e s e r v e d .
//
import Foundation
import SignalServiceKit
@objc
public class ConversationSearchItem : NSObject {
@objc
public let thread : ThreadViewModel
init ( thread : ThreadViewModel ) {
self . thread = thread
}
}
@objc
public class ConversationSearchResults : NSObject {
let conversations : [ ConversationSearchItem ]
let contacts : [ ConversationSearchItem ]
let messages : [ ConversationSearchItem ]
public init ( conversations : [ ConversationSearchItem ] , contacts : [ ConversationSearchItem ] , messages : [ ConversationSearchItem ] ) {
self . conversations = conversations
self . contacts = contacts
self . messages = messages
}
}
@objc
public class ConversationSearcher : NSObject {
private let finder : ConversationFullTextSearchFinder
@objc
public static let shared : ConversationSearcher = ConversationSearcher ( )
override private init ( ) {
finder = ConversationFullTextSearchFinder ( )
super . init ( )
}
@objc
public func results ( searchText : String , transaction : YapDatabaseReadTransaction ) -> ConversationSearchResults {
// T O D O l i m i t r e s u l t s , p r i o r i t i z e c o n v e r s a t i o n s , t h e n c o n t a c t s , t h e n m e s s a g e s .
var conversations : [ ConversationSearchItem ] = [ ]
var contacts : [ ConversationSearchItem ] = [ ]
var messages : [ ConversationSearchItem ] = [ ]
self . finder . enumerateObjects ( searchText : searchText , transaction : transaction ) { ( match : Any ) in
if let thread = match as ? TSThread {
let threadViewModel = ThreadViewModel ( thread : thread , transaction : transaction )
let searchItem = ConversationSearchItem ( thread : threadViewModel )
conversations . append ( searchItem )
} else {
Logger . debug ( " \( self . logTag ) in \( #function ) unhandled item: \( match ) " )
}
}
return ConversationSearchResults ( conversations : conversations , contacts : contacts , messages : messages )
}
@objc ( filterThreads : withSearchText : )
public func filterThreads ( _ threads : [ TSThread ] , searchText : String ) -> [ TSThread ] {
guard searchText . trimmingCharacters ( in : . whitespacesAndNewlines ) . count > 0 else {
@ -58,6 +106,7 @@ public class ConversationSearcher: NSObject {
// MARK: - H e l p e r s
// MARK: S e a r c h e r s
private lazy var groupThreadSearcher : Searcher < TSGroupThread > = Searcher { ( groupThread : TSGroupThread ) in
let groupName = groupThread . groupModel . groupName
let memberStrings = groupThread . groupModel . groupMemberIds . map { recipientId in
@ -88,3 +137,50 @@ public class ConversationSearcher: NSObject {
return " \( recipientId ) \( contactName ) \( profileName ? ? " " ) "
}
}
public class ConversationFullTextSearchFinder {
public func enumerateObjects ( searchText : String , transaction : YapDatabaseReadTransaction , block : @ escaping ( Any ) -> Void ) {
guard let ext = ext ( transaction : transaction ) else {
owsFail ( " ext was unexpectedly nil " )
return
}
ext . enumerateKeysAndObjects ( matching : searchText ) { ( _ , _ , object , _ ) in
block ( object )
}
}
private func ext ( transaction : YapDatabaseReadTransaction ) -> YapDatabaseFullTextSearchTransaction ? {
return transaction . ext ( ConversationFullTextSearchFinder . dbExtensionName ) as ? YapDatabaseFullTextSearchTransaction
}
// MARK: - E x t e n s i o n R e g i s t r a t i o n
static let dbExtensionName : String = " ConversationFullTextSearchFinderExtension1 "
public class func asyncRegisterDatabaseExtension ( storage : OWSStorage ) {
storage . asyncRegister ( dbExtensionConfig , withName : dbExtensionName )
}
// O n l y f o r t e s t i n g .
public class func syncRegisterDatabaseExtension ( storage : OWSStorage ) {
storage . register ( dbExtensionConfig , withName : dbExtensionName )
}
private class var dbExtensionConfig : YapDatabaseFullTextSearch {
let contentColumnName = " content "
let handler = YapDatabaseFullTextSearchHandler . withObjectBlock { ( dict : NSMutableDictionary , _ : String , _ : String , object : Any ) in
if let groupThread = object as ? TSGroupThread {
dict [ contentColumnName ] = groupThread . groupModel . groupName
}
}
// u p d a t e s e a r c h i n d e x o n c o n t a c t n a m e c h a n g e s ?
// u p d a t e s e a r c h i n d e x o n m e s s a g e i n s e r t i o n ?
// T O D O i s i t w o r t h d o i n g f a c e t e d s e a r c h , i . e . A u t h o r / N a m e / C o n t e n t ?
// s e e m s u n l i k e l y t h a t m o b i l e u s e r s w o u l d u s e t h e " a u t h o r : A l i c e " s e a r c h s y n t a x .
return YapDatabaseFullTextSearch ( columnNames : [ " content " ] , handler : handler )
}
}