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
19 - (id)initWithWindowNibName:(NSString *)windowNibName
21 if ((self = [super initWithWindowNibName:windowNibName])) {
22 currentSearchLock = [[NSLock alloc] init];
29 [super windowDidLoad];
31 [tableView_results setAutosaveName:@"LogViewerResults"];
32 [tableView_results setAutosaveTableColumns:YES];
36 * @brief Perform a content search of the indexed logs
38 * This uses the 10.4+ asynchronous search functions.
39 * Google-like search syntax (phrase, prefix/suffix, boolean, etc. searching) is automatically supported.
41 - (void)_logContentFilter:(NSString *)searchString searchID:(int)searchID onSearchIndex:(SKIndexRef)logSearchIndex
43 float largestRankingValue = 0;
44 SKSearchRef thisSearch;
46 UInt32 totalCount = 0;
48 [currentSearchLock lock];
50 SKSearchCancel(currentSearch);
51 CFRelease(currentSearch); currentSearch = NULL;
54 thisSearch = SKSearchCreate(logSearchIndex,
55 (CFStringRef)searchString,
56 kSKSearchOptionDefault);
57 currentSearch = (thisSearch ? (SKSearchRef)CFRetain(thisSearch) : NULL);
58 [currentSearchLock unlock];
60 //Retrieve matches as long as more are pending
61 while (more && currentSearch) {
62 #define BATCH_NUMBER 100
63 SKDocumentID foundDocIDs[BATCH_NUMBER];
64 float foundScores[BATCH_NUMBER];
65 SKDocumentRef foundDocRefs[BATCH_NUMBER];
67 CFIndex foundCount = 0;
70 more = SKSearchFindMatches (
75 0.5, // maximum time before func returns, in seconds
79 totalCount += foundCount;
81 SKIndexCopyDocumentRefsForDocumentIDs (
87 for (i = 0; ((i < foundCount) && (searchID == activeSearchID)) ; i++) {
88 SKDocumentRef document = foundDocRefs[i];
89 CFURLRef url = SKDocumentCopyURL(document);
91 * Nasty implementation note: As of 10.4.7 and all previous versions, a path longer than 1024 bytes (PATH_MAX)
92 * will cause CFURLCopyFileSystemPath() to crash [ultimately in CFGetAllocator()]. This is the case for all
93 * Cocoa applications...
95 CFStringRef logPath = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
96 NSArray *pathComponents = [(NSString *)logPath pathComponents];
98 //Don't test for the date now; we'll test once we've found the AIChatLog if we make it that far
99 if ([self searchShouldDisplayDocument:document pathComponents:pathComponents testDate:NO]) {
100 unsigned int numPathComponents = [pathComponents count];
101 NSString *toPath = [NSString stringWithFormat:@"%@/%@",
102 [pathComponents objectAtIndex:numPathComponents-3],
103 [pathComponents objectAtIndex:numPathComponents-2]];
104 NSString *path = [NSString stringWithFormat:@"%@/%@",toPath,[pathComponents objectAtIndex:numPathComponents-1]];
107 /* Add the log - if our index is currently out of date (for example, a log was just deleted)
108 * we may get a null log, so be careful.
110 theLog = [[logToGroupDict objectForKey:toPath] logAtPath:path];
112 AILog(@"_logContentFilter: %x's key %@ yields %@; logAtPath:%@ gives %@",logToGroupDict,toPath,[logToGroupDict objectForKey:toPath],path,theLog);
115 if ((theLog != nil) &&
116 (![currentSearchResults containsObjectIdenticalTo:theLog]) &&
117 [self chatLogMatchesDateFilter:theLog] &&
118 (searchID == activeSearchID)) {
119 [theLog setRankingValueOnArbitraryScale:foundScores[i]];
121 //SearchKit does not normalize ranking scores, so we track the largest we've found and use it as 1.0
122 if (foundScores[i] > largestRankingValue) largestRankingValue = foundScores[i];
124 [currentSearchResults addObject:theLog];
126 //Didn't get a valid log, so decrement our totalCount which is tracking how many logs we found
129 [resultsLock unlock];
132 //Didn't add this log, so decrement our totalCount which is tracking how many logs we found
136 if (logPath) CFRelease(logPath);
137 if (url) CFRelease(url);
138 if (document) CFRelease(document);
141 //Scale all logs' ranking values to the largest ranking value we've seen thus far
143 for (i = 0; ((i < totalCount) && (searchID == activeSearchID)); i++) {
144 AIChatLog *theLog = [currentSearchResults objectAtIndex:i];
145 [theLog setRankingPercentage:([theLog rankingValueOnArbitraryScale] / largestRankingValue)];
147 [resultsLock unlock];
149 [self performSelectorOnMainThread:@selector(updateProgressDisplay)
153 if (searchID != activeSearchID) {
158 //Ensure current search isn't released in two places simultaneously
159 [currentSearchLock lock];
161 CFRelease(currentSearch);
162 currentSearch = NULL;
164 [currentSearchLock unlock];
166 if (thisSearch) CFRelease(thisSearch);
169 - (void)stopSearching
171 [currentSearchLock lock];
173 SKSearchCancel(currentSearch);
174 CFRelease(currentSearch); currentSearch = nil;
176 [currentSearchLock unlock];
178 [super stopSearching];
181 #pragma mark Date type menu
183 - (void)configureDateFilter
185 [super configureDateFilter];
187 [datePicker setDateValue:[NSDate date]];
190 - (IBAction)selectDate:(id)sender
192 [filterDate release];
193 filterDate = [[[datePicker dateValue] dateWithCalendarFormat:nil timeZone:nil] retain];
195 [self startSearchingClearingCurrentResults:YES];
198 - (NSMenu *)dateTypeMenu
200 NSMenu *dateTypeMenu = [super dateTypeMenu];
202 NSDictionary *dateTypeTitleDict = [NSDictionary dictionaryWithObjectsAndKeys:
203 AILocalizedString(@"Exactly", nil), [NSNumber numberWithInt:AIDateTypeExactly],
204 AILocalizedString(@"Before", nil), [NSNumber numberWithInt:AIDateTypeBefore],
205 AILocalizedString(@"After", nil), [NSNumber numberWithInt:AIDateTypeAfter],
208 [dateTypeMenu addItem:[NSMenuItem separatorItem]];
210 for (dateType = AIDateTypeExactly; dateType <= AIDateTypeAfter; dateType++) {
211 [dateTypeMenu addItem:[self _menuItemForDateType:dateType dict:dateTypeTitleDict]];
218 * @brief A new date type was selected
220 * The date picker will be hidden/revealed as appropriate.
221 * This does not start a search
223 - (void)selectedDateType:(AIDateType)dateType
225 BOOL showDatePicker = NO;
227 [super selectedDateType:dateType];
230 case AIDateTypeExactly:
231 filterDateType = AIDateTypeExactly;
232 filterDate = [[[datePicker dateValue] dateWithCalendarFormat:nil timeZone:nil] retain];
233 showDatePicker = YES;
236 case AIDateTypeBefore:
237 filterDateType = AIDateTypeBefore;
238 filterDate = [[[datePicker dateValue] dateWithCalendarFormat:nil timeZone:nil] retain];
239 showDatePicker = YES;
242 case AIDateTypeAfter:
243 filterDateType = AIDateTypeAfter;
244 filterDate = [[[datePicker dateValue] dateWithCalendarFormat:nil timeZone:nil] retain];
245 showDatePicker = YES;
253 BOOL updateSize = NO;
254 if (showDatePicker && [datePicker isHidden]) {
255 [datePicker setHidden:NO];
258 } else if (!showDatePicker && ![datePicker isHidden]) {
259 [datePicker setHidden:YES];
264 NSEnumerator *enumerator = [[[[self window] toolbar] items] objectEnumerator];
265 NSToolbarItem *toolbarItem;
266 while ((toolbarItem = [enumerator nextObject])) {
267 if ([[toolbarItem itemIdentifier] isEqualToString:DATE_ITEM_IDENTIFIER]) {
268 NSSize newSize = NSMakeSize(([datePicker isHidden] ? 180 : 290), NSHeight([view_DatePicker frame]));
269 [toolbarItem setMinSize:newSize];
270 [toolbarItem setMaxSize:newSize];
277 - (NSString *)dateItemNibName
279 return @"LogViewerDateFilter";
284 [filterDate release]; filterDate = nil;
285 [currentSearchLock release]; currentSearchLock = nil;