1from Cocoa import * 2from SearchKit import * 3 4import os 5 6class AppController (NSObject): 7 8 myWindow = objc.IBOutlet() 9 10 selectDirectoryButton = objc.IBOutlet() 11 directoryTextField = objc.IBOutlet() 12 13 numberOfDocumentsTextField = objc.IBOutlet() 14 numberOfTermsTextField = objc.IBOutlet() 15 16 buildIndexButton = objc.IBOutlet() 17 18 searchField = objc.IBOutlet() 19 searchResultsTextView = objc.IBOutlet() 20 21 directoryToIndex = objc.ivar() 22 myIndex = objc.ivar() 23 24 25 def awakeFromNib(self): 26 # set our default directory to index to be our home directory 27 self.directoryToIndex = NSHomeDirectory() 28 # go ahead and put that value into the UI right on wake-up 29 self.directoryTextField.setStringValue_(self.directoryToIndex) 30 31 # tell the search field cell that we only want to send 32 # search requests (the associated action) on pressing return 33 searchCell = self.searchField.cell() 34 searchCell.setSendsWholeSearchString_(True) 35 # the alternative (False) would be to get a search initiated 36 # with every keystroke - which may be what is desired in a 37 # prefix style search (like iTunes). 38 39 40 @objc.IBAction 41 def chooseDirectory_(self, sender): 42 # we are setting up an NSOpenPanel to select only a directory and then 43 # we will use that directory to choose where to place our index file and 44 # which files we'll read in to make searchable. 45 op = NSOpenPanel.openPanel() 46 op.setCanChooseDirectories_(True) 47 op.setCanChooseFiles_(False) 48 op.setResolvesAliases_(True) 49 op.setAllowsMultipleSelection_(False) 50 result = op.runModalForDirectory_file_types_(None, None, None) 51 if result == NSOKButton: 52 self.directoryToIndex = op.filename() 53 self.directoryTextField.setStringValue_(self.directoryToIndex) 54 55 @objc.IBAction 56 def buildIndex_(self, sender): 57 # we will need an NSFileManager object to do various checking of files. 58 fm = NSFileManager.defaultManager() 59 60 # you don't have to provide the index with a name, but we will... 61 indexName = "My Arbitrary Index Name" 62 # when you build an index on disk, you need to give it a file:# URL 63 # So we pick the directory that's been chosen and append "/myindex" 64 # onto it 65 indexFile = os.path.join(self.directoryToIndex, "myindex") 66 # and build an NSURL from that 67 fileUrlToIndex = NSURL.fileURLWithPath_(indexFile) 68 69 # When indexing documents, there's a number of index specific 70 # considerations that can be applied with search kit. To set any of 71 # them manually, we first need to create a Dictionary in which to hold 72 # them. 73 analysisDict = {} 74 # Then we can set things like the language to expect 75 # note that the constants associated with these attributes are 76 # CFStringRef's so we cast them to NSString to make it easy to insert 77 # into the dictionary. 78 # We could also do this all in procedural C with the native 79 # CoreFoundation components, but I find this is significantly easier 80 # - both to write and to understand. 81 analysisDict[kSKLanguageTypes] = u"en" 82 # another example of setting an attribute, in this case the minimum term 83 # length 84 analysisDict[kSKMinTermLength] = 2 85 # when we hand this dictionary into the function to create the index, we 86 # simply cast it back to a CFDictionaryRef and everything just nicely 87 # moves with it. This toll-free bridging concept is so handy! 88 89 if fm.fileExistsAtPath_(indexFile): 90 # our index file already exists... if we try to create one anyone, 91 # the function will silently fail. So let's just be sure and 92 # delete it. in a proper function, we would recognize it already 93 # exists and attmept to open it for editing rather than recreate 94 # the whole thing. 95 fm.removeFileAtPath_handler_(indexFile, None) 96 97 # the function to create the on-disk index 98 self.myIndex = SKIndexCreateWithURL( 99 # the file:# URL of where to place the 100 # index file 101 fileUrlToIndex, 102 # a name for the index (this may be None) 103 indexName, 104 # the type of index 105 kSKIndexInverted, 106 # and our index attributes dictionary 107 analysisDict) 108 # note that the above function call will silently fail if you give it a 109 # directory and not a file... or if the index file already exists 110 111 if self.myIndex is None: 112 # we shouldn't get here... 113 NSLog("TROUBLE: index is None") 114 return 115 116 # display information about our newly created and completely blank index 117 self.displayIndexInformation() 118 119 # before we get into the meat of reading in these files, we need to tell 120 # the Search Kit to load the default extractor plugins so that the 121 # SearchKit can find the bits it needs to from the files. In 122 # MacOS 10.3, the only plugins supported are the default plugins, 123 # which include processing for plaintext, PDF, HTML, RTF, and 124 # Microsoft Word documents. 125 SKLoadDefaultExtractorPlugIns() 126 127 # we're just going to get a "short list". You could alternately use the 128 # NSDirectoryEnumerator class to recursively descend through all the 129 # files under a directory, and you would probably want to do it in 130 # some other thread, updating a progress bar or such to indicate 131 # something was happening. It can take a while (for example) to 132 # process every file in your home directory and beneath. 133 134 listOfFiles = fm.directoryContentsAtPath_(self.directoryToIndex) 135 for aFile in listOfFiles: 136 # the particular function we'll use like to get an NSURL 137 # object, so we'll create one and pass it over. 138 139 # create the URL with the filename 140 fileURL = NSURL.fileURLWithPath_( 141 os.path.join(self.directoryToIndex, aFile)) 142 143 # invoke a helper method to add this file into our index 144 self.addDocumentToIndex_(fileURL) 145 146 # once all the files have been added to the index, it's very important 147 # to flush the data down to disk. Without this step, the data resides 148 # in memory but isn't useful for searching and won't respond with the 149 # correct information about the index (number of documents, number of 150 # terms, etc). 151 if not SKIndexFlush(self.myIndex): 152 NSLog("A problem was encountered flushing the index to disk.") 153 154 NSLog("flushed index to disk") 155 156 # update the index information 157 self.displayIndexInformation() 158 159 @objc.IBAction 160 def search_(self, sender): 161 # do some sanity checking to make sure we're not going to run into some 162 # memory exception because we never initialized our SearchKit index 163 # properly 164 if self.myIndex is None: 165 msg = "No index has been created to against which to search." 166 NSLog(msg) 167 self.searchResultsTextView.setString_(msg) 168 return 169 170 # Since this is just an example, I am going to be lazy and just report 171 # the results of our search into a text field. I'm creating an 172 # NSMutableString to build up what will appear there. 173 textOfResults = '' 174 175 searchQuery = sender.stringValue() 176 textOfResults += "Searching for:" 177 textOfResults += searchQuery 178 textOfResults += "\n" 179 180 # to do a search, we need a SearchGroup 181 # so we start by creating an array of 1 item (our search index) 182 searchArray = [self.myIndex] 183 184 # and then we immediately turn around and use that to create 185 # a Search Kit Search Group reference. 186 searchgroup = SKSearchGroupCreate(searchArray) 187 188 # now that we have a searchgroup, we can request a result set 189 # from it with our search terms. 190 searchResults = SKSearchCreate(self.myIndex, searchQuery, kSKSearchRanked) 191 192 if searchResults is None: 193 msg = "Search function failed" 194 NSLog(msg) 195 self.searchResultsTextView.setString_(msg) 196 return 197 198 # now to go through the results, we can create an array for each 199 # SearchKit document and another for the scores, and then populate 200 # them from the SearchResults 201 busy, outDocumentIDsArray, scoresArray, resultCount= SKSearchFindMatches(searchResults, # the search result set 202 10, 203 None, # an array of SKDocumentID 204 None, # an array of scores 205 1.0, # max 1 sec 206 None # outFoundcount 207 ) 208 if busy: 209 textOfResults += "%d Results Found (still busy)\n"%(resultCount,) 210 else: 211 textOfResults += "%d Results Found\n"%(resultCount,) 212 213 assert resultCount == len(scoresArray) 214 215 # iterate over the results and tell the NSTextView what we found 216 for score, hitID in zip(scoresArray, outDocumentIDsArray): 217 hit = SKIndexCopyDocumentForDocumentID(self.myIndex, hitID) 218 if hit is None: 219 continue 220 221 documentName = SKDocumentGetName(hit) 222 223 textOfResults += "Score: %f ==> %s\n"%(score, documentName) 224 225 self.searchResultsTextView.setString_(textOfResults) 226 227 def addDocumentToIndex_(self, fileURL): 228 # do some sanity checking to make sure we're not going to run into some 229 # memory exception because we never initialized our SearchKit index 230 # properly 231 if self.myIndex is None: 232 NSLog("myIndex is None - not processing %@", 233 fileURL) 234 return 235 236 NSLog("Processing %@", fileURL.absoluteString()) 237 # create the Search Kit document object 238 # note that this method only accepts file:# style URL's 239 aDocument = SKDocumentCreateWithURL(fileURL) 240 241 # if you wanted to watch them process in, just uncomment the following 242 # 2 lines. 243 #NSLog("Name: %@", SKDocumentGetName(aDocument)) 244 #NSLog("Scheme: %@", SKDocumentGetSchemeName(aDocument)) 245 246 # add the document to the index 247 if not SKIndexAddDocument(self.myIndex, # a reference ot the index added to 248 aDocument, # the document we want to add 249 None, # this could be a mime type hint in the form 250 # of a CFStringRef 251 1): # a boolean value indicating the document 252 # can be overwritten 253 NSLog("There was a problem adding %@", fileURL) 254 255 256 def displayIndexInformation(self): 257 if self.myIndex is not None: 258 # get the number of documents in the index 259 numberOfDocuments = SKIndexGetDocumentCount(self.myIndex) 260 # place it into the text field 261 self.numberOfDocumentsTextField.setIntValue_(numberOfDocuments) 262 263 # get the number of terms in the index 264 maxTerm = SKIndexGetMaximumTermID(self.myIndex) 265 # place it into the text field 266 self.numberOfTermsTextField.setIntValue_(maxTerm) 267 268 else: 269 self.numberOfDocumentsTextField.setStringValue_('N/A') 270 self.numberOfTermsTextField.setStringValue_('N/A') 271