1/* MDIndexing.m
2 *
3 * Copyright (C) 2006-2013 Free Software Foundation, Inc.
4 *
5 * Author: Enrico Sersale <enrico@imago.ro>
6 * Date: February 2006
7 *
8 * This file is part of the GNUstep GWorkspace application
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA.
23 */
24
25#import <AppKit/AppKit.h>
26#import "MDIndexing.h"
27#import "CategoriesEditor.h"
28#import "StartAppWin.h"
29
30BOOL subPathOfPath(NSString *p1, NSString *p2);
31
32BOOL isDotFile(NSString *path);
33
34
35@implementation MDIndexing
36
37- (void)dealloc
38{
39  if (statusTimer && [statusTimer isValid]) {
40    [statusTimer invalidate];
41  }
42  DESTROY (statusTimer);
43
44  TEST_RELEASE (indexedPaths);
45  TEST_RELEASE (excludedPaths);
46  TEST_RELEASE (excludedSuffixes);
47  TEST_RELEASE (startAppWin);
48  TEST_RELEASE (indexedStatusPath);
49  TEST_RELEASE (indexedStatusLock);
50  TEST_RELEASE (statusWindow);
51  TEST_RELEASE (errorLogPath);
52  TEST_RELEASE (errorWindow);
53
54  [super dealloc];
55}
56
57- (void)mainViewDidLoad
58{
59  if (loaded == NO) {
60    id cell;
61    CGFloat fonth;
62    int index;
63    NSString *str;
64    NSUInteger i;
65
66    fm = [NSFileManager defaultManager];
67    nc = [NSNotificationCenter defaultCenter];
68    dnc = [NSDistributedNotificationCenter defaultCenter];
69
70    indexedPaths = [NSMutableArray new];
71    excludedPaths = [NSMutableArray new];
72    excludedSuffixes = [NSMutableArray new];
73
74    [self readDefaults];
75
76    index = [tabView indexOfTabViewItemWithIdentifier: @"paths"];
77    [[tabView tabViewItemAtIndex: index] setLabel: NSLocalizedString(@"Paths", @"")];
78
79    index = [tabView indexOfTabViewItemWithIdentifier: @"results"];
80    [[tabView tabViewItemAtIndex: index] setLabel: NSLocalizedString(@"Search Results", @"")];
81
82    [indexedScroll setBorderType: NSBezelBorder];
83    [indexedScroll setHasHorizontalScroller: YES];
84    [indexedScroll setHasVerticalScroller: YES];
85
86    cell = [NSBrowserCell new];
87    fonth = [[cell font] defaultLineHeightForFont];
88
89    indexedMatrix = [[NSMatrix alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)
90                                               mode: NSRadioModeMatrix
91                                          prototype: cell
92                                       numberOfRows: 0
93                                    numberOfColumns: 0];
94    RELEASE (cell);
95    [indexedMatrix setIntercellSpacing: NSZeroSize];
96    [indexedMatrix setCellSize: NSMakeSize([indexedScroll contentSize].width, fonth)];
97    [indexedMatrix setAutoscroll: YES];
98    [indexedMatrix setAllowsEmptySelection: YES];
99    [indexedScroll setDocumentView: indexedMatrix];
100    RELEASE (indexedMatrix);
101
102    for (i = 0; i < [indexedPaths count]; i++) {
103      NSString *name = [indexedPaths objectAtIndex: i];
104      NSUInteger count = [[indexedMatrix cells] count];
105
106      [indexedMatrix insertRow: count];
107      cell = [indexedMatrix cellAtRow: count column: 0];
108      [cell setStringValue: name];
109      [cell setLeaf: YES];
110    }
111
112    [self adjustMatrix: indexedMatrix];
113    [indexedMatrix sizeToCells];
114    [indexedMatrix setTarget: self];
115    [indexedMatrix setAction: @selector(indexedMatrixAction:)];
116
117    [indexedRemove setEnabled: ([[excludedMatrix cells] count] > 0)];
118
119    [excludedScroll setBorderType: NSBezelBorder];
120    [excludedScroll setHasHorizontalScroller: YES];
121    [excludedScroll setHasVerticalScroller: YES];
122
123    excludedMatrix = [[NSMatrix alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)
124                                                mode: NSRadioModeMatrix
125                                           prototype: [[NSBrowserCell new] autorelease]
126                                        numberOfRows: 0
127                                     numberOfColumns: 0];
128    [excludedMatrix setIntercellSpacing: NSZeroSize];
129    [excludedMatrix setCellSize: NSMakeSize([excludedScroll contentSize].width, fonth)];
130    [excludedMatrix setAutoscroll: YES];
131	  [excludedMatrix setAllowsEmptySelection: YES];
132	  [excludedScroll setDocumentView: excludedMatrix];
133    RELEASE (excludedMatrix);
134
135    for (i = 0; i < [excludedPaths count]; i++) {
136      NSString *path = [excludedPaths objectAtIndex: i];
137      NSUInteger count = [[excludedMatrix cells] count];
138
139      [excludedMatrix insertRow: count];
140      cell = [excludedMatrix cellAtRow: count column: 0];
141      [cell setStringValue: path];
142      [cell setLeaf: YES];
143    }
144
145    [self adjustMatrix: excludedMatrix];
146    [excludedMatrix sizeToCells];
147    [excludedMatrix setTarget: self];
148    [excludedMatrix setAction: @selector(excludedMatrixAction:)];
149
150    [excludedRemove setEnabled: ([[excludedMatrix cells] count] > 0)];
151
152    [suffixScroll setBorderType: NSBezelBorder];
153    [suffixScroll setHasHorizontalScroller: YES];
154    [suffixScroll setHasVerticalScroller: YES];
155
156    suffixMatrix = [[NSMatrix alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)
157                                              mode: NSRadioModeMatrix
158                                         prototype: [[NSBrowserCell new] autorelease]
159                                      numberOfRows: 0
160                                   numberOfColumns: 0];
161    [suffixMatrix setIntercellSpacing: NSZeroSize];
162    [suffixMatrix setCellSize: NSMakeSize([suffixScroll contentSize].width, fonth)];
163    [suffixMatrix setAutoscroll: YES];
164    [suffixMatrix setAllowsEmptySelection: YES];
165    [suffixScroll setDocumentView: suffixMatrix];
166    RELEASE (suffixMatrix);
167
168    for (i = 0; i < [excludedSuffixes count]; i++) {
169      NSString *path = [excludedSuffixes objectAtIndex: i];
170      NSUInteger count = [[suffixMatrix cells] count];
171
172      [suffixMatrix insertRow: count];
173      cell = [suffixMatrix cellAtRow: count column: 0];
174      [cell setStringValue: path];
175      [cell setLeaf: YES];
176    }
177
178    [self adjustMatrix: suffixMatrix];
179    [suffixMatrix sizeToCells];
180    [suffixMatrix setTarget: self];
181    [suffixMatrix setAction: @selector(suffixMatrixAction:)];
182
183    [suffixField setStringValue: @""];
184
185    [suffixRemove setEnabled: ([[suffixMatrix cells] count] > 0)];
186
187    pathsUnselReply = NSUnselectNow;
188    searchResultsReply = NSUnselectNow;
189    loaded = YES;
190
191    [revertButton setEnabled: NO];
192    [applyButton setEnabled: NO];
193
194    startAppWin = [[StartAppWin alloc] init];
195
196    [statusWindow setTitle: NSLocalizedString(@"Status", @"")];
197    [statusWindow setFrameUsingName: @"mdindexing_status_win"];
198    [statusWindow setDelegate: self];
199
200    [statusScroll setBorderType: NSBezelBorder];
201    [statusScroll setHasHorizontalScroller: NO];
202    [statusScroll setHasVerticalScroller: YES];
203    statusView = [[NSTextView alloc] initWithFrame: [[statusScroll contentView] bounds]];
204    [statusView setEditable: NO];
205    [statusView setSelectable: NO];
206    [statusView setVerticallyResizable: YES];
207    [statusView setHorizontallyResizable: NO];
208    [statusView setFont: [NSFont userFixedPitchFontOfSize: 0]];
209    [statusScroll setDocumentView: statusView];
210    RELEASE (statusView);
211
212    [errorWindow setTitle: NSLocalizedString(@"Error log", @"")];
213    [errorWindow setFrameUsingName: @"mdindexing_error_win"];
214    [errorWindow setDelegate: self];
215    [errorScroll setBorderType: NSBezelBorder];
216    [errorScroll setHasHorizontalScroller: NO];
217    [errorScroll setHasVerticalScroller: YES];
218
219    errorView = [[NSTextView alloc] initWithFrame: [[errorScroll contentView] bounds]];
220    [errorView setEditable: NO];
221    [errorView setSelectable: NO];
222    [errorView setVerticallyResizable: YES];
223    [errorView setHorizontallyResizable: YES];
224    [errorView setFont: [NSFont userFixedPitchFontOfSize: 0]];
225    [errorScroll setDocumentView: errorView];
226    RELEASE (errorView);
227
228    //
229    // Search Results
230    //
231    str = @"Drag categories to change the order in which results appear.";
232    [searchResTitle setStringValue: NSLocalizedString(str, @"")];
233
234    str = @"Only selected categories will appear in search results.";
235    [searchResSubtitle setStringValue: NSLocalizedString(str, @"")];
236
237    [searchResEditor setMdindexing: self];
238
239    [searchResApply setTitle: NSLocalizedString(@"Apply", @"")];
240
241    indexedStatusPath = nil;
242    errorLogPath = nil;
243    statusTimer = nil;
244    [self setupDbPaths];
245
246    mdextractor = nil;
247    [self connectMDExtractor];
248  }
249}
250
251- (NSPreferencePaneUnselectReply)shouldUnselect
252{
253  if ((pathsUnselReply == NSUnselectNow)
254                && (searchResultsReply == NSUnselectNow)) {
255    return NSUnselectNow;
256  }
257
258  return NSUnselectCancel;
259}
260
261- (void)didSelect
262{
263  if (mdextractor == nil) {
264    if (NSRunAlertPanel(nil,
265                      NSLocalizedString(@"The mdextractor connection died.\nDo you want to restart it?", @""),
266                      NSLocalizedString(@"Yes", @""),
267                      NSLocalizedString(@"No", @""),
268                      nil)) {
269      [self connectMDExtractor];
270    }
271  }
272}
273
274- (void)willUnselect
275{
276  if ([statusWindow isVisible]) {
277    [statusWindow close];
278  }
279  if ([errorWindow isVisible]) {
280    [errorWindow close];
281  }
282}
283
284- (void)indexedMatrixAction:(id)sender
285{
286  [indexedRemove setEnabled: ([[indexedMatrix cells] count] > 0)];
287}
288
289- (IBAction)indexedButtAction:(id)sender
290{
291  NSPreferencePaneUnselectReply oldReply = pathsUnselReply;
292  NSArray *cells = [indexedMatrix cells];
293  NSUInteger count = [cells count];
294  id cell;
295  NSUInteger i;
296
297#define IND_ERR_RETURN(x) \
298do { \
299NSRunAlertPanel(nil, \
300NSLocalizedString(x, @""), \
301NSLocalizedString(@"Ok", @""), \
302nil, \
303nil); \
304pathsUnselReply = oldReply; \
305return; \
306} while (0)
307
308  if (sender == indexedAdd) {
309    NSString *path;
310
311    pathsUnselReply = NSUnselectCancel;
312    path = [self chooseNewPath];
313
314    if (path) {
315      if (isDotFile(path)) {
316        IND_ERR_RETURN (@"Paths containing \'.\' are not indexable!");
317      }
318
319      if ([indexedPaths containsObject: path]) {
320        IND_ERR_RETURN (@"The path is already present!");
321      }
322
323      for (i = 0; i < [indexedPaths count]; i++) {
324        if (subPathOfPath([indexedPaths objectAtIndex: i], path)) {
325          IND_ERR_RETURN (@"This path is a subpath of an already indexable path!");
326        }
327      }
328
329      for (i = 0; i < [excludedPaths count]; i++) {
330        NSString *exclpath = [excludedPaths objectAtIndex: i];
331
332        if ([path isEqual: exclpath] || subPathOfPath(exclpath, path)) {
333          IND_ERR_RETURN (@"This path is excluded from the indexable paths!");
334        }
335      }
336
337      [indexedPaths addObject: path];
338
339      [indexedMatrix insertRow: count];
340      cell = [indexedMatrix cellAtRow: count column: 0];
341      [cell setStringValue: path];
342      [cell setLeaf: YES];
343      [self adjustMatrix: indexedMatrix];
344      [indexedMatrix sizeToCells];
345      [indexedMatrix selectCellAtRow: count column: 0];
346
347      [indexedMatrix sendAction];
348
349    } else {
350      pathsUnselReply = oldReply;
351    }
352
353  } else if (sender == indexedRemove) {
354    cell = [indexedMatrix selectedCell];
355
356    if (cell) {
357      NSInteger row, col;
358
359      [indexedPaths removeObject: [cell stringValue]];
360
361      [indexedMatrix getRow: &row column: &col ofCell: cell];
362      [indexedMatrix removeRow: row];
363      [self adjustMatrix: indexedMatrix];
364      [indexedMatrix sizeToCells];
365
366      [indexedMatrix sendAction];
367
368      pathsUnselReply = NSUnselectCancel;
369
370    } else {
371      pathsUnselReply = oldReply;
372    }
373  }
374
375  [revertButton setEnabled: (pathsUnselReply != NSUnselectNow)];
376  [applyButton setEnabled: (pathsUnselReply != NSUnselectNow)];
377}
378
379- (void)excludedMatrixAction:(id)sender
380{
381  [excludedRemove setEnabled: ([[excludedMatrix cells] count] > 0)];
382}
383
384- (IBAction)excludedButtAction:(id)sender
385{
386  NSPreferencePaneUnselectReply oldReply = pathsUnselReply;
387  NSArray *cells = [excludedMatrix cells];
388  NSUInteger count = [cells count];
389  id cell;
390  NSUInteger i;
391
392#define EXCL_ERR_RETURN(x) \
393do { \
394NSRunAlertPanel(nil, \
395NSLocalizedString(x, @""), \
396NSLocalizedString(@"Ok", @""), \
397nil, \
398nil); \
399pathsUnselReply = oldReply; \
400return; \
401} while (0)
402
403  if (sender == excludedAdd) {
404    NSString *path;
405
406    pathsUnselReply = NSUnselectCancel;
407    path = [self chooseNewPath];
408
409    if (path) {
410      BOOL valid = NO;
411
412      if (isDotFile(path)) {
413        IND_ERR_RETURN (@"Paths containing \'.\' are not indexable by default!");
414      }
415
416      for (i = 0; i < [indexedPaths count]; i++) {
417        if (subPathOfPath([indexedPaths objectAtIndex: i], path)) {
418          valid = YES;
419          break;
420        }
421      }
422
423      if (valid == NO) {
424        EXCL_ERR_RETURN (@"An excluded path must be a subpath of an indexable path!");
425      }
426
427      if ([excludedPaths containsObject: path]) {
428        EXCL_ERR_RETURN (@"The path is already present!");
429      }
430
431      for (i = 0; i < [excludedPaths count]; i++) {
432        if (subPathOfPath([excludedPaths objectAtIndex: i], path)) {
433          EXCL_ERR_RETURN (@"This path is a subpath of an already excluded path!");
434        }
435      }
436
437      for (i = 0; i < [indexedPaths count]; i++) {
438        NSString *idxpath = [indexedPaths objectAtIndex: i];
439
440        if ([path isEqual: idxpath] || subPathOfPath(path, idxpath)) {
441          EXCL_ERR_RETURN (@"This path would exclude a path defined as indexable!");
442        }
443      }
444
445      [excludedPaths addObject: path];
446
447      [excludedMatrix insertRow: count];
448      cell = [excludedMatrix cellAtRow: count column: 0];
449      [cell setStringValue: path];
450      [cell setLeaf: YES];
451      [self adjustMatrix: excludedMatrix];
452      [excludedMatrix sizeToCells];
453      [excludedMatrix selectCellAtRow: count column: 0];
454
455      [excludedMatrix sendAction];
456
457    } else {
458      pathsUnselReply = oldReply;
459    }
460
461  } else if (sender == excludedRemove) {
462    cell = [excludedMatrix selectedCell];
463
464    if (cell) {
465      NSInteger row, col;
466
467      [excludedPaths removeObject: [cell stringValue]];
468
469      [excludedMatrix getRow: &row column: &col ofCell: cell];
470      [excludedMatrix removeRow: row];
471      [self adjustMatrix: excludedMatrix];
472      [excludedMatrix sizeToCells];
473
474      [excludedMatrix sendAction];
475
476      pathsUnselReply = NSUnselectCancel;
477
478    } else {
479      pathsUnselReply = oldReply;
480    }
481  }
482
483  [revertButton setEnabled: (pathsUnselReply != NSUnselectNow)];
484  [applyButton setEnabled: (pathsUnselReply != NSUnselectNow)];
485}
486
487- (void)suffixMatrixAction:(id)sender
488{
489  [suffixRemove setEnabled: ([[suffixMatrix cells] count] > 0)];
490}
491
492- (IBAction)suffixButtAction:(id)sender
493{
494  NSPreferencePaneUnselectReply oldReply = pathsUnselReply;
495  NSArray *cells = [suffixMatrix cells];
496  NSUInteger count = [cells count];
497  id cell;
498
499#define SUFF_ERR_RETURN(x) \
500do { \
501NSRunAlertPanel(nil, \
502NSLocalizedString(x, @""), \
503NSLocalizedString(@"Ok", @""), \
504nil, \
505nil); \
506pathsUnselReply = oldReply; \
507[suffixField setStringValue: @""]; \
508return; \
509} while (0)
510
511  if (sender == suffixAdd) {
512    NSString *suff = [suffixField stringValue];
513
514    pathsUnselReply = NSUnselectCancel;
515
516    if ([suff length]) {
517      NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString: @". "];
518
519      if ([suff rangeOfCharacterFromSet: set].location != NSNotFound) {
520        SUFF_ERR_RETURN (@"Invalid character in suffix!");
521      }
522
523      if ([excludedSuffixes containsObject: suff]) {
524        SUFF_ERR_RETURN (@"The suffix is already present!");
525      }
526
527      [excludedSuffixes addObject: suff];
528
529      [suffixMatrix insertRow: count];
530      cell = [suffixMatrix cellAtRow: count column: 0];
531      [cell setStringValue: suff];
532      [cell setLeaf: YES];
533      [self adjustMatrix: suffixMatrix];
534      [suffixMatrix sizeToCells];
535      [suffixMatrix selectCellAtRow: count column: 0];
536
537      [suffixMatrix sendAction];
538
539    } else {
540      pathsUnselReply = oldReply;
541    }
542
543  } else if (sender == suffixRemove) {
544    cell = [suffixMatrix selectedCell];
545
546    if (cell) {
547      NSInteger row, col;
548
549      [excludedSuffixes removeObject: [cell stringValue]];
550
551      [suffixMatrix getRow: &row column: &col ofCell: cell];
552      [suffixMatrix removeRow: row];
553      [self adjustMatrix: suffixMatrix];
554      [suffixMatrix sizeToCells];
555
556      [suffixMatrix sendAction];
557
558      pathsUnselReply = NSUnselectCancel;
559
560    } else {
561      pathsUnselReply = oldReply;
562    }
563  }
564
565  [suffixField setStringValue: @""];
566
567  [revertButton setEnabled: (pathsUnselReply != NSUnselectNow)];
568  [applyButton setEnabled: (pathsUnselReply != NSUnselectNow)];
569}
570
571- (IBAction)enableSwitchAction:(id)sender
572{
573  BOOL oldEnabled = indexingEnabled;
574
575  indexingEnabled = ([enableSwitch state] == NSOnState);
576
577  [revertButton setEnabled: (oldEnabled != indexingEnabled)];
578  [applyButton setEnabled: (oldEnabled != indexingEnabled)];
579}
580
581- (IBAction)revertButtAction:(id)sender
582{
583  id cell;
584  NSUInteger i;
585
586  DESTROY (indexedPaths);
587  DESTROY (excludedPaths);
588  DESTROY (excludedSuffixes);
589
590  indexedPaths = [NSMutableArray new];
591  excludedPaths = [NSMutableArray new];
592  excludedSuffixes = [NSMutableArray new];
593
594  [self readDefaults];
595
596  if ([indexedMatrix numberOfColumns] > 0) {
597    [indexedMatrix removeColumn: 0];
598  }
599
600  for (i = 0; i < [indexedPaths count]; i++) {
601    NSString *name = [indexedPaths objectAtIndex: i];
602    NSUInteger count = [[indexedMatrix cells] count];
603
604    [indexedMatrix insertRow: count];
605    cell = [indexedMatrix cellAtRow: count column: 0];
606    [cell setStringValue: name];
607    [cell setLeaf: YES];
608  }
609
610  [self adjustMatrix: indexedMatrix];
611  [indexedMatrix sizeToCells];
612
613  [indexedRemove setEnabled: ([[indexedMatrix cells] count] > 0)];
614
615  if ([excludedMatrix numberOfColumns] > 0) {
616    [excludedMatrix removeColumn: 0];
617  }
618
619  for (i = 0; i < [excludedPaths count]; i++) {
620    NSString *path = [excludedPaths objectAtIndex: i];
621    NSUInteger count = [[excludedMatrix cells] count];
622
623    [excludedMatrix insertRow: count];
624    cell = [excludedMatrix cellAtRow: count column: 0];
625    [cell setStringValue: path];
626    [cell setLeaf: YES];
627  }
628
629  [self adjustMatrix: excludedMatrix];
630  [excludedMatrix sizeToCells];
631
632  [excludedRemove setEnabled: ([[excludedMatrix cells] count] > 0)];
633
634  if ([suffixMatrix numberOfColumns] > 0) {
635    [suffixMatrix removeColumn: 0];
636  }
637
638  for (i = 0; i < [excludedSuffixes count]; i++) {
639    NSString *suff = [excludedSuffixes objectAtIndex: i];
640    NSUInteger count = [[suffixMatrix cells] count];
641
642    [suffixMatrix insertRow: count];
643    cell = [suffixMatrix cellAtRow: count column: 0];
644    [cell setStringValue: suff];
645    [cell setLeaf: YES];
646  }
647
648  [self adjustMatrix: suffixMatrix];
649  [suffixMatrix sizeToCells];
650
651  [suffixRemove setEnabled: ([[suffixMatrix cells] count] > 0)];
652
653  pathsUnselReply = NSUnselectNow;
654  [revertButton setEnabled: NO];
655  [applyButton setEnabled: NO];
656}
657
658- (IBAction)applyButtAction:(id)sender
659{
660  [self applyChanges];
661  pathsUnselReply = NSUnselectNow;
662  [revertButton setEnabled: NO];
663  [applyButton setEnabled: NO];
664}
665
666- (NSString *)chooseNewPath
667{
668  NSOpenPanel *openPanel = [NSOpenPanel openPanel];
669  int result;
670
671  [openPanel setTitle: NSLocalizedString(@"Choose directory", @"")];
672  [openPanel setAllowsMultipleSelection: NO];
673  [openPanel setCanChooseFiles: NO];
674  [openPanel setCanChooseDirectories: YES];
675
676  result = [openPanel runModalForDirectory: nil file: nil types: nil];
677
678  if (result == NSOKButton) {
679    return [openPanel filename];
680  }
681
682  return nil;
683}
684
685- (void)adjustMatrix:(NSMatrix *)matrix
686{
687  NSArray *cells = [matrix cells];
688
689  if (cells && [cells count])
690    {
691      NSSize cellsize = [matrix cellSize];
692      CGFloat margin = 10.0;
693      CGFloat maxw = margin;
694      NSDictionary *fontAttr;
695      NSUInteger i;
696
697      fontAttr = [NSDictionary dictionaryWithObject: [[cells objectAtIndex: 0] font]
698                                             forKey: NSFontAttributeName];
699
700      for (i = 0; i < [cells count]; i++) {
701        NSString *str = [[cells objectAtIndex: i] stringValue];
702        CGFloat strw = [str sizeWithAttributes: fontAttr].width + margin;
703
704        maxw = (strw > maxw) ? strw : maxw;
705      }
706
707      if (maxw > cellsize.width) {
708        [matrix setCellSize: NSMakeSize(maxw, cellsize.height)];
709      }
710    }
711}
712
713- (void)setupDbPaths
714{
715  NSString *dbdir;
716  NSString *lockpath;
717  BOOL isdir;
718
719  dbdir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
720  dbdir = [dbdir stringByAppendingPathComponent: @"gmds"];
721
722  if (([fm fileExistsAtPath: dbdir isDirectory: &isdir] &isdir) == NO) {
723    if ([fm createDirectoryAtPath: dbdir attributes: nil] == NO) {
724      NSRunAlertPanel(nil,
725                      NSLocalizedString(@"unable to create the db directory.", @""),
726                      NSLocalizedString(@"Ok", @""),
727                      nil,
728                      nil);
729      return;
730    }
731  }
732
733  dbdir = [dbdir stringByAppendingPathComponent: @".db"];
734
735  if (([fm fileExistsAtPath: dbdir isDirectory: &isdir] &isdir) == NO) {
736    if ([fm createDirectoryAtPath: dbdir attributes: nil] == NO) {
737      NSRunAlertPanel(nil,
738                      NSLocalizedString(@"unable to create the db directory.", @""),
739                      NSLocalizedString(@"Ok", @""),
740                      nil,
741                      nil);
742      return;
743    }
744  }
745
746  ASSIGN (indexedStatusPath, [dbdir stringByAppendingPathComponent: @"status.plist"]);
747
748  ASSIGN (errorLogPath, [dbdir stringByAppendingPathComponent: @"error.log"]);
749
750  lockpath = [dbdir stringByAppendingPathComponent: @"extractors.lock"];
751  indexedStatusLock = [[NSDistributedLock alloc] initWithPath: lockpath];
752}
753
754- (void)connectMDExtractor
755{
756  if (mdextractor == nil) {
757    mdextractor = [NSConnection rootProxyForConnectionWithRegisteredName: @"mdextractor"
758                                                                    host: @""];
759
760    if (mdextractor == nil) {
761	    NSString *cmd;
762      int i;
763
764      cmd = [NSTask launchPathForTool: @"mdextractor"];
765
766      [startAppWin showWindowWithTitle: @"MDIndexing"
767                               appName: @"mdextractor"
768                             operation: NSLocalizedString(@"starting:", @"")
769                          maxProgValue: 80.0];
770
771      [NSTask launchedTaskWithLaunchPath: cmd arguments: nil];
772
773      for (i = 1; i <= 80; i++) {
774        [startAppWin updateProgressBy: 1.0];
775	      [[NSRunLoop currentRunLoop] runUntilDate:
776		                     [NSDate dateWithTimeIntervalSinceNow: 0.1]];
777
778        mdextractor = [NSConnection rootProxyForConnectionWithRegisteredName: @"mdextractor"
779                                                                        host: @""];
780        if (mdextractor) {
781          [startAppWin updateProgressBy: 80.0 - i];
782          break;
783        }
784      }
785
786      [[startAppWin win] close];
787    }
788
789    if (mdextractor) {
790      [mdextractor setProtocolForProxy: @protocol(MDExtractorProtocol)];
791      RETAIN (mdextractor);
792
793	    [[NSNotificationCenter defaultCenter] addObserver: self
794	                   selector: @selector(mdextractorConnectionDidDie:)
795		                     name: NSConnectionDidDieNotification
796		                   object: [mdextractor connectionForProxy]];
797    } else {
798      NSRunAlertPanel(nil,
799              NSLocalizedString(@"unable to contact mdextractor!", @""),
800              NSLocalizedString(@"Ok", @""),
801              nil,
802              nil);
803    }
804  }
805}
806
807- (void)mdextractorConnectionDidDie:(NSNotification *)notif
808{
809  id connection = [notif object];
810
811  [nc removeObserver: self
812	              name: NSConnectionDidDieNotification
813	            object: connection];
814
815  NSAssert(connection == [mdextractor connectionForProxy],
816		                                  NSInternalInconsistencyException);
817  RELEASE (mdextractor);
818  mdextractor = nil;
819
820  if ([self isSelected]) {
821    if (NSRunAlertPanel(nil,
822                      NSLocalizedString(@"The mdextractor connection died.\nDo you want to restart it?", @""),
823                      NSLocalizedString(@"Yes", @""),
824                      NSLocalizedString(@"No", @""),
825                      nil)) {
826      [self connectMDExtractor];
827    }
828  }
829}
830
831- (IBAction)statusButtAction:(id)sender
832{
833  if ([statusWindow isVisible] == NO) {
834    [statusWindow makeKeyAndOrderFront: nil];
835
836    [self readIndexedPathsStatus: nil];
837
838    if (statusTimer && [statusTimer isValid]) {
839      [statusTimer invalidate];
840    }
841    DESTROY (statusTimer);
842
843    statusTimer = [NSTimer scheduledTimerWithTimeInterval: 5.0
844						                           target: self
845                                     selector: @selector(readIndexedPathsStatus:)
846																     userInfo: nil
847                                      repeats: YES];
848    RETAIN (statusTimer);
849  }
850}
851
852- (IBAction)errorButtAction:(id)sender
853{
854  NSString *errstr = @"";
855
856  if ([fm fileExistsAtPath: errorLogPath]) {
857	  NS_DURING
858	    {
859	      errstr = [NSString stringWithContentsOfFile: errorLogPath];
860	    }
861	  NS_HANDLER
862	    {
863        errstr = @"";
864	    }
865	  NS_ENDHANDLER
866  }
867
868  [errorView setString: errstr];
869//  [errorView sizeToFit];
870
871  if ([errorWindow isVisible] == NO) {
872    [errorWindow makeKeyAndOrderFront: nil];
873  }
874}
875
876- (void)readIndexedPathsStatus:(id)sender
877{
878  CREATE_AUTORELEASE_POOL(arp);
879
880  if (indexedStatusPath && [fm isReadableFileAtPath: indexedStatusPath]) {
881    NSArray *status = nil;
882
883    if ([indexedStatusLock tryLock] == NO) {
884      unsigned sleeps = 0;
885
886      if ([[indexedStatusLock lockDate] timeIntervalSinceNow] < -20.0) {
887	      NS_DURING
888	        {
889	      [indexedStatusLock breakLock];
890	        }
891	      NS_HANDLER
892	        {
893        NSLog(@"Unable to break lock %@ ... %@", indexedStatusLock, localException);
894	        }
895	      NS_ENDHANDLER
896      }
897
898      for (sleeps = 0; sleeps < 10; sleeps++) {
899	      if ([indexedStatusLock tryLock]) {
900	        break;
901	      }
902
903        sleeps++;
904	      [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
905	    }
906
907      if (sleeps >= 10) {
908        NSLog(@"Unable to obtain lock %@", indexedStatusLock);
909        RELEASE (arp);
910        return;
911	    }
912    }
913
914    status = [NSArray arrayWithContentsOfFile: indexedStatusPath];
915    [indexedStatusLock unlock];
916
917    if (status) {
918      NSMutableString *str = [NSMutableString string];
919      NSUInteger i;
920
921      for (i = 0; i < [status count]; i++) {
922        NSDictionary *info = [status objectAtIndex: i];
923        NSString *path = [info objectForKey: @"path"];
924        BOOL indexed = [[info objectForKey: @"indexed"] boolValue];
925        NSNumber *fcount = [info objectForKey: @"count"];
926        NSDate *startTime = [info objectForKey: @"start_time"];
927        NSDate *endTime = [info objectForKey: @"end_time"];
928        NSArray *subPaths = [info objectForKey: @"subpaths"];
929
930        [str appendFormat: @"%@\n", path];
931        [str appendFormat: @"  indexed: %@\n", (indexed ? @"YES" : @"NO")];
932
933        if (startTime) {
934          [str appendFormat: @"  start:   %@\n", [startTime description]];
935        }
936        if (endTime) {
937          [str appendFormat: @"  end:     %@\n", [endTime description]];
938        }
939        if (fcount) {
940          [str appendFormat: @"  files:    %lu\n", [fcount unsignedLongValue]];
941        }
942
943        if (subPaths && [subPaths count]) {
944          unsigned j;
945
946          [str appendString: @"  subpaths:\n"];
947
948          for (j = 0; j < [subPaths count]; j++) {
949            info = [subPaths objectAtIndex: j];
950            path = [info objectForKey: @"path"];
951            indexed = [[info objectForKey: @"indexed"] boolValue];
952            fcount = [info objectForKey: @"count"];
953            startTime = [info objectForKey: @"start_time"];
954            endTime = [info objectForKey: @"end_time"];
955
956            [str appendFormat: @"    %@\n", path];
957            [str appendFormat: @"      indexed: %@\n", (indexed ? @"YES" : @"NO")];
958
959            if (startTime) {
960              [str appendFormat: @"      start:   %@\n", [startTime description]];
961            }
962            if (endTime) {
963              [str appendFormat: @"      end:     %@\n", [endTime description]];
964            }
965            if (fcount) {
966              [str appendFormat: @"      files:    %lu\n", [fcount unsignedLongValue]];
967            }
968          }
969        }
970
971        [str appendString: @"\n"];
972      }
973
974      [statusView setString: str];
975      [statusView sizeToFit];
976    }
977  }
978
979  RELEASE (arp);
980}
981
982- (void)windowWillClose:(NSNotification *)aNotification
983{
984  id win = [aNotification object];
985
986  if (win == statusWindow) {
987    if (statusTimer && [statusTimer isValid]) {
988      [statusTimer invalidate];
989    }
990    DESTROY (statusTimer);
991
992    [statusWindow saveFrameUsingName: @"mdindexing_status_win"];
993
994  } else if (win == errorWindow) {
995    [errorWindow saveFrameUsingName: @"mdindexing_error_win"];
996  }
997}
998
999- (void)readDefaults
1000{
1001  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
1002  id entry;
1003
1004  [defaults synchronize];
1005
1006  entry = [defaults arrayForKey: @"GSMetadataIndexablePaths"];
1007  if (entry) {
1008    [indexedPaths addObjectsFromArray: entry];
1009
1010  } else {
1011    NSArray *dirs;
1012    NSUInteger i;
1013
1014    [indexedPaths addObject: NSHomeDirectory()];
1015
1016    dirs = NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory,
1017                                                      NSAllDomainsMask, YES);
1018    [indexedPaths addObjectsFromArray: dirs];
1019
1020    dirs = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
1021                                                      NSAllDomainsMask, YES);
1022    for (i = 0; i < [dirs count]; i++) {
1023      NSString *dir = [dirs objectAtIndex: i];
1024      NSString *path = [dir stringByAppendingPathComponent: @"Headers"];
1025
1026      if ([fm fileExistsAtPath: path]) {
1027        [indexedPaths addObject: path];
1028      }
1029
1030      path = [dir stringByAppendingPathComponent: @"Documentation"];
1031
1032      if ([fm fileExistsAtPath: path]) {
1033        [indexedPaths addObject: path];
1034      }
1035    }
1036  }
1037
1038  entry = [defaults arrayForKey: @"GSMetadataExcludedPaths"];
1039  if (entry) {
1040    [excludedPaths addObjectsFromArray: entry];
1041  }
1042
1043  entry = [defaults arrayForKey: @"GSMetadataExcludedSuffixes"];
1044  if (entry == nil) {
1045    entry = [NSArray arrayWithObjects: @"a", @"d", @"dylib", @"er1",
1046                                       @"err", @"extinfo", @"frag", @"la",
1047                                       @"log", @"o", @"out", @"part",
1048                                       @"sed", @"so", @"status", @"temp",
1049                                       @"tmp",
1050                                       nil];
1051  }
1052
1053  [excludedSuffixes addObjectsFromArray: entry];
1054
1055  indexingEnabled = [defaults boolForKey: @"GSMetadataIndexingEnabled"];
1056  [enableSwitch setState: (indexingEnabled ? NSOnState : NSOffState)];
1057}
1058
1059- (void)applyChanges
1060{
1061  CREATE_AUTORELEASE_POOL(arp);
1062  NSUserDefaults *defaults;
1063  NSMutableDictionary *domain;
1064  NSMutableDictionary *info;
1065
1066  defaults = [NSUserDefaults standardUserDefaults];
1067  [defaults synchronize];
1068  domain = [[defaults persistentDomainForName: NSGlobalDomain] mutableCopy];
1069
1070  [domain setObject: indexedPaths forKey: @"GSMetadataIndexablePaths"];
1071  [domain setObject: excludedPaths forKey: @"GSMetadataExcludedPaths"];
1072  [domain setObject: excludedSuffixes forKey: @"GSMetadataExcludedSuffixes"];
1073  [domain setObject: [NSNumber numberWithBool: indexingEnabled]
1074             forKey: @"GSMetadataIndexingEnabled"];
1075
1076  [defaults setPersistentDomain: domain forName: NSGlobalDomain];
1077  [defaults synchronize];
1078  RELEASE (domain);
1079
1080  info = [NSMutableDictionary dictionary];
1081
1082  [info setObject: indexedPaths forKey: @"GSMetadataIndexablePaths"];
1083  [info setObject: excludedPaths forKey: @"GSMetadataExcludedPaths"];
1084  [info setObject: excludedSuffixes forKey: @"GSMetadataExcludedSuffixes"];
1085  [info setObject: [NSNumber numberWithBool: indexingEnabled]
1086           forKey: @"GSMetadataIndexingEnabled"];
1087
1088  [dnc postNotificationName: @"GSMetadataIndexedDirectoriesChanged"
1089	 								   object: nil
1090                   userInfo: info];
1091
1092  RELEASE (arp);
1093}
1094
1095//
1096// Search Results
1097//
1098- (IBAction)searchResButtAction:(id)sender
1099{
1100  if (sender == searchResApply) {
1101    [searchResEditor applyChanges];
1102  } else {
1103    [searchResEditor revertChanges];
1104  }
1105}
1106
1107- (void)searchResultDidStartEditing
1108{
1109  [searchResRevert setEnabled: YES];
1110  [searchResApply setEnabled: YES];
1111  searchResultsReply = NSUnselectCancel;
1112}
1113
1114- (void)searchResultDidEndEditing
1115{
1116  [searchResRevert setEnabled: NO];
1117  [searchResApply setEnabled: NO];
1118  searchResultsReply = NSUnselectNow;
1119}
1120
1121@end
1122
1123
1124BOOL subPathOfPath(NSString *p1, NSString *p2)
1125{
1126  int l1 = [p1 length];
1127  int l2 = [p2 length];
1128
1129  if ((l1 > l2) || ([p1 isEqual: p2])) {
1130    return NO;
1131  } else if ([[p2 substringToIndex: l1] isEqual: p1]) {
1132    if ([[p2 pathComponents] containsObject: [p1 lastPathComponent]]) {
1133      return YES;
1134    }
1135  }
1136
1137  return NO;
1138}
1139
1140BOOL isDotFile(NSString *path)
1141{
1142  NSArray *components;
1143  NSEnumerator *e;
1144  NSString *c;
1145  BOOL found;
1146
1147  if (path == nil)
1148    return NO;
1149
1150  found = NO;
1151  components = [path pathComponents];
1152  e = [components objectEnumerator];
1153  while ((c = [e nextObject]) && !found)
1154    {
1155      if (([c length] > 0) && ([c characterAtIndex:0] == '.'))
1156	found = YES;
1157    }
1158
1159  return found;
1160}
1161
1162