2 // AIMDLogViewerWindowController.m
5 // Created by Evan Schoenberg on 3/1/06.
8 #import "AIMDLogViewerWindowController.h"
9 #import <AIUtilities/AIArrayAdditions.h>
10 #import <AIUtilities/AIStringAdditions.h>
11 #import "AILoggerPlugin.h"
12 #import "AILogToGroup.h"
13 #import "AILogFromGroup.h"
15 #import <Adium/AIContactControllerProtocol.h>
17 @implementation AIMDLogViewerWindowController
21 [super windowDidLoad];
23 [tableView_results setAutosaveName:@"LogViewerResults"];
24 [tableView_results setAutosaveTableColumns:YES];
28 * @brief Perform a content search of the indexed logs
30 * This uses the 10.4+ asynchronous search functions.
31 * Google-like search syntax (phrase, prefix/suffix, boolean, etc. searching) is automatically supported.
33 - (void)_logContentFilter:(NSString *)searchString searchID:(int)searchID onSearchIndex:(SKIndexRef)logSearchIndex
35 float largestRankingValue = 0;
36 SKSearchRef thisSearch;
38 UInt32 totalCount = 0;
41 SKSearchCancel(currentSearch);
42 CFRelease(currentSearch); currentSearch = NULL;
45 thisSearch = SKSearchCreate(logSearchIndex,
46 (CFStringRef)searchString,
47 kSKSearchOptionDefault);
48 currentSearch = (thisSearch ? (SKSearchRef)CFRetain(thisSearch) : NULL);
50 //Retrieve matches as long as more are pending
51 while (more && currentSearch) {
52 #define BATCH_NUMBER 100
53 SKDocumentID foundDocIDs[BATCH_NUMBER];
54 float foundScores[BATCH_NUMBER];
55 SKDocumentRef foundDocRefs[BATCH_NUMBER];
57 CFIndex foundCount = 0;
60 more = SKSearchFindMatches (
65 0.5, // maximum time before func returns, in seconds
69 totalCount += foundCount;
71 SKIndexCopyDocumentRefsForDocumentIDs (
77 for (i = 0; ((i < foundCount) && (searchID == activeSearchID)) ; i++) {
78 SKDocumentRef document = foundDocRefs[i];
79 CFURLRef url = SKDocumentCopyURL(document);
81 * Nasty implementation note: As of 10.4.7 and all previous versions, a path longer than 1024 bytes (PATH_MAX)
82 * will cause CFURLCopyFileSystemPath() to crash [ultimately in CFGetAllocator()]. This is the case for all
83 * Cocoa applications...
85 CFStringRef logPath = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
86 NSArray *pathComponents = [(NSString *)logPath pathComponents];
88 //Don't test for the date now; we'll test once we've found the AIChatLog if we make it that far
89 if ([self searchShouldDisplayDocument:document pathComponents:pathComponents testDate:NO]) {
90 unsigned int numPathComponents = [pathComponents count];
91 NSString *toPath = [NSString stringWithFormat:@"%@/%@",
92 [pathComponents objectAtIndex:numPathComponents-3],
93 [pathComponents objectAtIndex:numPathComponents-2]];
94 NSString *path = [NSString stringWithFormat:@"%@/%@",toPath,[pathComponents objectAtIndex:numPathComponents-1]];
97 /* Add the log - if our index is currently out of date (for example, a log was just deleted)
98 * we may get a null log, so be careful.
100 theLog = [[logToGroupDict objectForKey:toPath] logAtPath:path];
102 AILog(@"_logContentFilter: %x's key %@ yields %@; logAtPath:%@ gives %@",logToGroupDict,toPath,[logToGroupDict objectForKey:toPath],path,theLog);
105 if ((theLog != nil) &&
106 (![currentSearchResults containsObjectIdenticalTo:theLog]) &&
107 [self chatLogMatchesDateFilter:theLog] &&
108 (searchID == activeSearchID)) {
109 [theLog setRankingValueOnArbitraryScale:foundScores[i]];
111 //SearchKit does not normalize ranking scores, so we track the largest we've found and use it as 1.0
112 if (foundScores[i] > largestRankingValue) largestRankingValue = foundScores[i];
114 [currentSearchResults addObject:theLog];
116 //Didn't get a valid log, so decrement our totalCount which is tracking how many logs we found
119 [resultsLock unlock];
122 //Didn't add this log, so decrement our totalCount which is tracking how many logs we found
126 if (logPath) CFRelease(logPath);
127 if (url) CFRelease(url);
128 if (document) CFRelease(document);
131 //Scale all logs' ranking values to the largest ranking value we've seen thus far
133 for (i = 0; ((i < totalCount) && (searchID == activeSearchID)); i++) {
134 AIChatLog *theLog = [currentSearchResults objectAtIndex:i];
135 [theLog setRankingPercentage:([theLog rankingValueOnArbitraryScale] / largestRankingValue)];
137 [resultsLock unlock];
139 [self performSelectorOnMainThread:@selector(updateProgressDisplay)
143 if (searchID != activeSearchID) {
148 //Ensure current search isn't released in two places simultaneously
150 CFRelease(currentSearch);
151 currentSearch = NULL;
154 if (thisSearch) CFRelease(thisSearch);
157 - (void)stopSearching
160 SKSearchCancel(currentSearch);
161 CFRelease(currentSearch); currentSearch = nil;
164 [super stopSearching];
167 #pragma mark Date type menu
169 - (void)configureDateFilter
171 [super configureDateFilter];
173 [datePicker setDateValue:[NSDate date]];
176 - (IBAction)selectDate:(id)sender
178 [filterDate release];
179 filterDate = [[[datePicker dateValue] dateWithCalendarFormat:nil timeZone:nil] retain];
181 [self startSearchingClearingCurrentResults:YES];
184 - (NSMenu *)dateTypeMenu
186 NSMenu *dateTypeMenu = [super dateTypeMenu];
188 NSDictionary *dateTypeTitleDict = [NSDictionary dictionaryWithObjectsAndKeys:
189 AILocalizedString(@"Exactly", nil), [NSNumber numberWithInt:AIDateTypeExactly],
190 AILocalizedString(@"Before", nil), [NSNumber numberWithInt:AIDateTypeBefore],
191 AILocalizedString(@"After", nil), [NSNumber numberWithInt:AIDateTypeAfter],
194 [dateTypeMenu addItem:[NSMenuItem separatorItem]];
196 for (dateType = AIDateTypeExactly; dateType <= AIDateTypeAfter; dateType++) {
197 [dateTypeMenu addItem:[self _menuItemForDateType:dateType dict:dateTypeTitleDict]];
204 * @brief A new date type was selected
206 * The date picker will be hidden/revealed as appropriate.
207 * This does not start a search
209 - (void)selectedDateType:(AIDateType)dateType
211 BOOL showDatePicker = NO;
213 [super selectedDateType:dateType];
216 case AIDateTypeExactly:
217 filterDateType = AIDateTypeExactly;
218 filterDate = [[[datePicker dateValue] dateWithCalendarFormat:nil timeZone:nil] retain];
219 showDatePicker = YES;
222 case AIDateTypeBefore:
223 filterDateType = AIDateTypeBefore;
224 filterDate = [[[datePicker dateValue] dateWithCalendarFormat:nil timeZone:nil] retain];
225 showDatePicker = YES;
228 case AIDateTypeAfter:
229 filterDateType = AIDateTypeAfter;
230 filterDate = [[[datePicker dateValue] dateWithCalendarFormat:nil timeZone:nil] retain];
231 showDatePicker = YES;
239 BOOL updateSize = NO;
240 if (showDatePicker && [datePicker isHidden]) {
241 [datePicker setHidden:NO];
244 } else if (!showDatePicker && ![datePicker isHidden]) {
245 [datePicker setHidden:YES];
250 NSEnumerator *enumerator = [[[[self window] toolbar] items] objectEnumerator];
251 NSToolbarItem *toolbarItem;
252 while ((toolbarItem = [enumerator nextObject])) {
253 if ([[toolbarItem itemIdentifier] isEqualToString:DATE_ITEM_IDENTIFIER]) {
254 NSSize newSize = NSMakeSize(([datePicker isHidden] ? 180 : 290), NSHeight([view_DatePicker frame]));
255 [toolbarItem setMinSize:newSize];
256 [toolbarItem setMaxSize:newSize];
263 - (NSString *)dateItemNibName
265 return @"LogViewerDateFilter";
270 [filterDate release]; filterDate = nil;