1/* mdextractor.m
2 *
3 * Copyright (C) 2006-2013 Free Software Foundation, Inc.
4 *
5 * Author: Enrico Sersale <enrico@dtedu.net>
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#include <sys/types.h>
26#include <sys/stat.h>
27#include <limits.h>
28#include <float.h>
29
30#import <Foundation/Foundation.h>
31#import <AppKit/AppKit.h>
32
33#import "mdextractor.h"
34#import "dbschema.h"
35#include "config.h"
36
37#define DLENGTH 256
38#define MAX_RETRY 1000
39#define UPDATE_COUNT 100
40
41#define GWDebugLog(format, args...) \
42  do { if (GW_DEBUG_LOG) \
43    NSLog(format , ## args); } while (0)
44
45#define EXECUTE_QUERY(q, r) \
46do { \
47  if ([sqlite executeQuery: q] == NO) { \
48    NSLog(@"error at: %@", q); \
49    return r; \
50  } \
51} while (0)
52
53#define STATEMENT_EXECUTE_QUERY(s, r) \
54do { \
55  if ([sqlite executeQueryWithStatement: s] == NO) { \
56    NSLog(@"error at: %@", [s query]); \
57    return r; \
58  } \
59} while (0)
60
61
62static BOOL updating = NO;
63
64static void check_updating(sqlite3_context *context, int argc, sqlite3_value **argv)
65{
66  sqlite3_result_int(context, (int)updating);
67}
68
69static void path_exists(sqlite3_context *context, int argc, sqlite3_value **argv)
70{
71  const unsigned char *path = sqlite3_value_text(argv[0]);
72  int exists = 0;
73
74  if (path) {
75    struct stat statbuf;
76    exists = (stat((const char *)path, &statbuf) == 0);
77  }
78
79  sqlite3_result_int(context, exists);
80}
81
82static void path_moved(sqlite3_context *context, int argc, sqlite3_value **argv)
83{
84  const unsigned char *oldbase = sqlite3_value_text(argv[0]);
85  int oldblen = strlen((const char *)oldbase);
86  const unsigned char *newbase = sqlite3_value_text(argv[1]);
87  int newblen = strlen((const char *)newbase);
88  const unsigned char *oldpath = sqlite3_value_text(argv[2]);
89  int oldplen = strlen((const char *)oldpath);
90  char newpath[PATH_MAX] = "";
91  int i = newblen;
92  int j;
93
94  strncpy(newpath, (const char *)newbase, newblen);
95
96  for (j = oldblen; j < oldplen; j++) {
97    newpath[i] = oldpath[j];
98    i++;
99  }
100
101  newpath[i] = '\0';
102
103  sqlite3_result_text(context, newpath, strlen(newpath), SQLITE_TRANSIENT);
104}
105
106static void time_stamp(sqlite3_context *context, int argc, sqlite3_value **argv)
107{
108  NSTimeInterval interval = [[NSDate date] timeIntervalSinceReferenceDate];
109
110  sqlite3_result_double(context, interval);
111}
112
113
114@implementation	GMDSExtractor
115
116- (void)dealloc
117{
118  if (statusTimer && [statusTimer isValid]) {
119    [statusTimer invalidate];
120  }
121  TEST_RELEASE (statusTimer);
122
123  [dnc removeObserver: self];
124  [nc removeObserver: self];
125
126  RELEASE (indexablePaths);
127  freeTree(includePathsTree);
128  freeTree(excludedPathsTree);
129  RELEASE (excludedSuffixes);
130  RELEASE (dbpath);
131  RELEASE (sqlite);
132  RELEASE (indexedStatusPath);
133  RELEASE (indexedStatusLock);
134  TEST_RELEASE (errHandle);
135  RELEASE (extractors);
136  RELEASE (textExtractor);
137
138  //
139  // fswatcher_update
140  //
141  if (fswatcher && [[(NSDistantObject *)fswatcher connectionForProxy] isValid]) {
142    [fswatcher unregisterClient: (id <FSWClientProtocol>)self];
143    DESTROY (fswatcher);
144  }
145
146  if (fswupdateTimer && [fswupdateTimer isValid]) {
147    [fswupdateTimer invalidate];
148  }
149  TEST_RELEASE (fswupdateTimer);
150
151  RELEASE (fswupdatePaths);
152  RELEASE (fswupdateSkipBuff);
153  RELEASE (lostPaths);
154
155  if (lostPathsTimer && [lostPathsTimer isValid]) {
156    [lostPathsTimer invalidate];
157  }
158  TEST_RELEASE (lostPathsTimer);
159
160  //
161  // ddbd_update
162  //
163  DESTROY (ddbd);
164
165  //
166  // scheduled_update
167  //
168  if (schedupdateTimer && [schedupdateTimer isValid]) {
169    [schedupdateTimer invalidate];
170  }
171  TEST_RELEASE (schedupdateTimer);
172  RELEASE (directories);
173
174  //
175  // update_notifications
176  //
177  if (notificationsTimer && [notificationsTimer isValid]) {
178    [notificationsTimer invalidate];
179  }
180  TEST_RELEASE (notificationsTimer);
181  RELEASE (notifDate);
182
183  [super dealloc];
184}
185
186- (id)init
187{
188  self = [super init];
189
190  if (self) {
191    NSUserDefaults *defaults;
192    id entry;
193    NSString *lockpath;
194    NSString *errpath;
195    unsigned i;
196
197    fm = [NSFileManager defaultManager];
198
199    dbdir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
200    dbdir = [dbdir stringByAppendingPathComponent: @"gmds"];
201    dbdir = [dbdir stringByAppendingPathComponent: @".db"];
202
203    ASSIGN (indexedStatusPath, [dbdir stringByAppendingPathComponent: @"status.plist"]);
204    lockpath = [dbdir stringByAppendingPathComponent: @"extractors.lock"];
205
206    errpath = [dbdir stringByAppendingPathComponent: @"error.log"];
207
208    dbdir = [dbdir stringByAppendingPathComponent: db_version];
209    RETAIN (dbdir);
210    ASSIGN (dbpath, [dbdir stringByAppendingPathComponent: @"contents.db"]);
211
212    sqlite = [SQLite new];
213
214    if ([self opendb] == NO) {
215      DESTROY (self);
216      return self;
217    }
218
219    indexedStatusLock = [[NSDistributedLock alloc] initWithPath: lockpath];
220
221    if (indexedStatusLock == nil) {
222      DESTROY (self);
223      return self;
224    }
225
226    if ([fm fileExistsAtPath: errpath] == NO) {
227      [fm createFileAtPath: errpath contents: nil attributes: nil];
228    }
229    errHandle = [NSFileHandle fileHandleForWritingAtPath: errpath];
230    RETAIN (errHandle);
231
232
233    conn = [NSConnection defaultConnection];
234    [conn setRootObject: self];
235    [conn setDelegate: self];
236
237    if ([conn registerName: @"mdextractor"] == NO) {
238	    NSLog(@"unable to register with name server - quiting.");
239	    DESTROY (self);
240	    return self;
241	  }
242
243    nc = [NSNotificationCenter defaultCenter];
244
245    [nc addObserver: self
246           selector: @selector(connectionDidDie:)
247	             name: NSConnectionDidDieNotification
248	           object: conn];
249
250    textExtractor = nil;
251    [self loadExtractors];
252
253    dnc = [NSDistributedNotificationCenter defaultCenter];
254
255    [dnc addObserver: self
256            selector: @selector(indexedDirectoriesChanged:)
257	              name: @"GSMetadataIndexedDirectoriesChanged"
258	            object: nil];
259
260    ws = [NSWorkspace sharedWorkspace];
261
262    defaults = [NSUserDefaults standardUserDefaults];
263    [defaults synchronize];
264
265    indexablePaths = [NSMutableArray new];
266
267    includePathsTree = newTreeWithIdentifier(@"included");
268    excludedPathsTree = newTreeWithIdentifier(@"excluded");
269    excludedSuffixes = [[NSMutableSet alloc] initWithCapacity: 1];
270
271    entry = [defaults arrayForKey: @"GSMetadataIndexablePaths"];
272
273    if (entry) {
274      for (i = 0; i < [entry count]; i++) {
275        NSString *path = [entry objectAtIndex: i];
276        GMDSIndexablePath *indpath = [[GMDSIndexablePath alloc] initWithPath: path
277                                                                    ancestor: nil];
278        [indexablePaths addObject: indpath];
279        RELEASE (indpath);
280
281        insertComponentsOfPath(path, includePathsTree);
282      }
283    }
284
285    entry = [defaults arrayForKey: @"GSMetadataExcludedPaths"];
286    if (entry) {
287      for (i = 0; i < [entry count]; i++) {
288        insertComponentsOfPath([entry objectAtIndex: i], excludedPathsTree);
289      }
290    }
291
292    entry = [defaults arrayForKey: @"GSMetadataExcludedSuffixes"];
293    if (entry == nil) {
294      entry = [NSArray arrayWithObjects: @"a", @"d", @"dylib", @"er1",
295                                         @"err", @"extinfo", @"frag", @"la",
296                                         @"log", @"o", @"out", @"part",
297                                         @"sed", @"so", @"status", @"temp",
298                                         @"tmp",
299                                         nil];
300    }
301
302    [excludedSuffixes addObjectsFromArray: entry];
303
304    indexingEnabled = [defaults boolForKey: @"GSMetadataIndexingEnabled"];
305
306    extracting = NO;
307    subpathsChanged = NO;
308    statusTimer = nil;
309
310    [self setupUpdaters];
311
312    if ([self synchronizePathsStatus: YES] && indexingEnabled) {
313      [self startExtracting];
314    }
315  }
316
317  return self;
318}
319
320- (void)indexedDirectoriesChanged:(NSNotification *)notification
321{
322  CREATE_AUTORELEASE_POOL(arp);
323  NSDictionary *info = [notification userInfo];
324  NSArray *indexable = [info objectForKey: @"GSMetadataIndexablePaths"];
325  NSArray *excluded = [info objectForKey: @"GSMetadataExcludedPaths"];
326  NSArray *suffixes = [info objectForKey: @"GSMetadataExcludedSuffixes"];
327  NSArray *excludedPaths = pathsOfTreeWithBase(excludedPathsTree);
328  BOOL shouldExtract;
329  unsigned count;
330  unsigned i;
331
332  emptyTreeWithBase(includePathsTree);
333
334  for (i = 0; i < [indexable count]; i++) {
335    NSString *path = [indexable objectAtIndex: i];
336    GMDSIndexablePath *indpath = [self indexablePathWithPath: path];
337
338    if (indpath == nil) {
339      indpath = [[GMDSIndexablePath alloc] initWithPath: path ancestor: nil];
340      [indexablePaths addObject: indpath];
341      RELEASE (indpath);
342    }
343
344    insertComponentsOfPath(path, includePathsTree);
345  }
346
347  count = [indexablePaths count];
348
349  for (i = 0; i < count; i++) {
350    GMDSIndexablePath *indpath = [indexablePaths objectAtIndex: i];
351
352    if ([indexable containsObject: [indpath path]] == NO) {
353      [indexablePaths removeObject: indpath];
354      count--;
355      i--;
356
357      /* FIXME
358      - remove the path from the db?
359      - stop indexing if the current indexed path == indpath?
360      */
361    }
362  }
363
364  emptyTreeWithBase(excludedPathsTree);
365
366  for (i = 0; i < [excluded count]; i++) {
367    NSString *path = [excluded objectAtIndex: i];
368
369    insertComponentsOfPath(path, excludedPathsTree);
370
371    if ([excludedPaths containsObject: path] == NO) {
372      GMDSIndexablePath *ancestor = [self ancestorOfAddedPath: path];
373
374      if (ancestor) {
375        [ancestor removeSubpath: path];
376
377        /* FIXME
378        - remove the path from the db?
379        - stop indexing if the current indexed path == path?
380        */
381      }
382    }
383  }
384
385  for (i = 0; i < [excludedPaths count]; i++) {
386    NSString *path = [excludedPaths objectAtIndex: i];
387
388    if ([excluded containsObject: path] == NO) {
389      GMDSIndexablePath *indpath = [self ancestorForAddingPath: path];
390
391      if (indpath) {
392        [indpath addSubpath: path];
393        subpathsChanged = YES;
394      }
395    }
396  }
397
398  [excludedSuffixes removeAllObjects];
399  [excludedSuffixes addObjectsFromArray: suffixes];
400
401  indexingEnabled = [[info objectForKey: @"GSMetadataIndexingEnabled"] boolValue];
402
403  shouldExtract = [self synchronizePathsStatus: NO];
404
405  if (indexingEnabled) {
406    if (shouldExtract && (extracting == NO)) {
407      subpathsChanged = NO;
408      [self startExtracting];
409    }
410
411  } else if (extracting) {
412    [self stopExtracting];
413  }
414
415  RELEASE (arp);
416}
417
418- (BOOL)synchronizePathsStatus:(BOOL)onstart
419{
420  BOOL shouldExtract = NO;
421  unsigned i;
422
423  if (onstart) {
424    NSArray *savedPaths = [self readPathsStatus];
425
426    for (i = 0; i < [indexablePaths count]; i++) {
427      GMDSIndexablePath *indPath = [indexablePaths objectAtIndex: i];
428      NSDictionary *savedInfo = [self infoOfPath: [indPath path] inSavedStatus: savedPaths];
429      id entry;
430
431      if (savedInfo) {
432        entry = [savedInfo objectForKey: @"subpaths"];
433
434        if (entry) {
435          unsigned j;
436
437          for (j = 0; j < [entry count]; j++) {
438            NSDictionary *subSaved = [entry objectAtIndex: j];
439            id subentry = [subSaved objectForKey: @"path"];
440            GMDSIndexablePath *subpath = [indPath addSubpath: subentry];
441
442            subentry = [subSaved objectForKey: @"indexed"];
443            [subpath setIndexed: [subentry boolValue]];
444
445            if ([subpath indexed] == NO) {
446              shouldExtract = YES;
447            }
448          }
449        }
450
451        entry = [savedInfo objectForKey: @"count"];
452
453        if (entry) {
454          [indPath setFilesCount: [entry unsignedLongValue]];
455        }
456
457        entry = [savedInfo objectForKey: @"indexed"];
458
459        if (entry) {
460          [indPath setIndexed: [entry boolValue]];
461
462          if ([indPath indexed] == NO) {
463            shouldExtract = YES;
464          }
465        }
466
467      } else {
468        shouldExtract = YES;
469      }
470    }
471
472  } else {
473    for (i = 0; i < [indexablePaths count]; i++) {
474      GMDSIndexablePath *indPath = [indexablePaths objectAtIndex: i];
475
476      if ([indPath indexed] == NO) {
477        shouldExtract = YES;
478      }
479
480      if (shouldExtract == NO) {
481        NSArray *subpaths = [indPath subpaths];
482        unsigned j;
483
484        for (j = 0; j < [subpaths count]; j++) {
485          GMDSIndexablePath *subpath = [subpaths objectAtIndex: j];
486
487          if ([subpath indexed] == NO) {
488            shouldExtract = YES;
489            break;
490          }
491        }
492      }
493
494      if (shouldExtract == YES) {
495        break;
496      }
497    }
498
499    [self writePathsStatus: nil];
500  }
501
502  return shouldExtract;
503}
504
505- (NSArray *)readPathsStatus
506{
507  NSArray *status = nil;
508
509  if (indexedStatusPath && [fm isReadableFileAtPath: indexedStatusPath]) {
510    if ([indexedStatusLock tryLock] == NO) {
511      unsigned sleeps = 0;
512
513      if ([[indexedStatusLock lockDate] timeIntervalSinceNow] < -20.0) {
514	      NS_DURING
515	        {
516	      [indexedStatusLock breakLock];
517	        }
518	      NS_HANDLER
519	        {
520        NSLog(@"Unable to break lock %@ ... %@", indexedStatusLock, localException);
521	        }
522	      NS_ENDHANDLER
523      }
524
525      for (sleeps = 0; sleeps < 10; sleeps++) {
526	      if ([indexedStatusLock tryLock]) {
527	        break;
528	      }
529
530        sleeps++;
531	      [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
532	    }
533
534      if (sleeps >= 10) {
535        NSLog(@"Unable to obtain lock %@", indexedStatusLock);
536        return [NSDictionary dictionary];
537	    }
538    }
539
540    status = [NSArray arrayWithContentsOfFile: indexedStatusPath];
541    [indexedStatusLock unlock];
542  }
543
544  if (status != nil) {
545    return status;
546  }
547
548  return [NSArray array];
549}
550
551- (void)writePathsStatus:(id)sender
552{
553  if (indexedStatusPath) {
554    CREATE_AUTORELEASE_POOL(arp);
555    NSMutableArray *status = [NSMutableArray array];
556    unsigned i;
557
558    if ([indexedStatusLock tryLock] == NO) {
559      unsigned sleeps = 0;
560
561      if ([[indexedStatusLock lockDate] timeIntervalSinceNow] < -20.0) {
562	      NS_DURING
563	        {
564	      [indexedStatusLock breakLock];
565	        }
566	      NS_HANDLER
567	        {
568        NSLog(@"Unable to break lock %@ ... %@", indexedStatusLock, localException);
569	        }
570	      NS_ENDHANDLER
571      }
572
573      for (sleeps = 0; sleeps < 10; sleeps++) {
574	      if ([indexedStatusLock tryLock]) {
575	        break;
576	      }
577
578        sleeps++;
579	      [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
580	    }
581
582      if (sleeps >= 10) {
583        NSLog(@"Unable to obtain lock %@", indexedStatusLock);
584        RELEASE (arp);
585        return;
586	    }
587    }
588
589    for (i = 0; i < [indexablePaths count]; i++) {
590      [status addObject: [[indexablePaths objectAtIndex: i] info]];
591    }
592
593    [status writeToFile: indexedStatusPath atomically: YES];
594    [indexedStatusLock unlock];
595
596    GWDebugLog(@"paths status updated");
597
598    RELEASE (arp);
599  }
600}
601
602- (NSDictionary *)infoOfPath:(NSString *)path
603               inSavedStatus:(NSArray *)status
604{
605  unsigned i;
606
607  for (i = 0; i < [status count]; i++) {
608    NSDictionary *info = [status objectAtIndex: i];
609
610    if ([[info objectForKey: @"path"] isEqual: path]) {
611      return info;
612    }
613  }
614
615  return nil;
616}
617
618- (void)updateStatusOfPath:(GMDSIndexablePath *)indpath
619                 startTime:(NSDate *)stime
620                   endTime:(NSDate *)etime
621                filesCount:(unsigned long)count
622               indexedDone:(BOOL)indexed
623{
624  if ([indexablePaths containsObject: indpath]) {
625    if (stime) {
626      [indpath setStartTime: stime];
627    }
628    if (etime) {
629      [indpath setEndTime: etime];
630    }
631    [indpath setFilesCount: count];
632    [indpath setIndexed: indexed];
633
634  } else {
635    GMDSIndexablePath *ancestor = [indpath ancestor];
636
637    if (ancestor) {
638      if (stime) {
639        [indpath setStartTime: stime];
640      }
641      if (etime) {
642        [indpath setEndTime: etime];
643      }
644      [indpath setFilesCount: count];
645      [indpath setIndexed: indexed];
646
647      if (indexed) {
648        [ancestor checkIndexingDone];
649      }
650    }
651  }
652}
653
654- (GMDSIndexablePath *)indexablePathWithPath:(NSString *)path
655{
656  unsigned i;
657
658  for (i = 0; i < [indexablePaths count]; i++) {
659    GMDSIndexablePath *indpath = [indexablePaths objectAtIndex: i];
660
661    if ([[indpath path] isEqual: path]) {
662      return indpath;
663    }
664  }
665
666  return nil;
667}
668
669- (GMDSIndexablePath *)ancestorForAddingPath:(NSString *)path
670{
671  unsigned i;
672
673  for (i = 0; i < [indexablePaths count]; i++) {
674    GMDSIndexablePath *indpath = [indexablePaths objectAtIndex: i];
675
676    if ([indpath acceptsSubpath: path]) {
677      return indpath;
678    }
679  }
680
681  return nil;
682}
683
684- (GMDSIndexablePath *)ancestorOfAddedPath:(NSString *)path
685{
686  unsigned i;
687
688  for (i = 0; i < [indexablePaths count]; i++) {
689    GMDSIndexablePath *indpath = [indexablePaths objectAtIndex: i];
690
691    if ([indpath subpathWithPath: path] != nil) {
692      return indpath;
693    }
694  }
695
696  return nil;
697}
698
699- (void)startExtracting
700{
701  unsigned index = 0;
702
703  GWDebugLog(@"start extracting");
704  extracting = YES;
705
706  if (statusTimer && [statusTimer isValid]) {
707    [statusTimer invalidate];
708  }
709  DESTROY (statusTimer);
710
711  statusTimer = [NSTimer scheduledTimerWithTimeInterval: 5.0
712						                         target: self
713                                   selector: @selector(writePathsStatus:)
714																   userInfo: nil
715                                    repeats: YES];
716  RETAIN (statusTimer);
717
718  while (1) {
719    if (index < [indexablePaths count]) {
720      GMDSIndexablePath *indpath = [indexablePaths objectAtIndex: index];
721      NSArray *subpaths = [indpath subpaths];
722      BOOL indexed = [indpath indexed];
723
724      RETAIN (indpath);
725
726      if (indexed == NO) {
727        if ([self extractFromPath: indpath] == NO) {
728          NSLog(@"An error occurred while processing %@", [indpath path]);
729          RELEASE (indpath);
730          break;
731        }
732      }
733
734      if (subpaths) {
735        unsigned i;
736
737        for (i = 0; i < [subpaths count]; i++) {
738          GMDSIndexablePath *subpath = [subpaths objectAtIndex: i];
739
740          RETAIN (subpath);
741
742          if ([subpath indexed] == NO) {
743            if ([self extractFromPath: subpath] == NO) {
744              NSLog(@"An error occurred while processing %@", [subpath path]);
745              RELEASE (subpath);
746              break;
747            }
748          }
749
750          TEST_RELEASE (subpath);
751        }
752      }
753
754      TEST_RELEASE (indpath);
755
756    } else {
757      break;
758    }
759
760    if (extracting == NO) {
761      break;
762    }
763
764    index++;
765  }
766
767  if (statusTimer && [statusTimer isValid]) {
768    [statusTimer invalidate];
769  }
770  DESTROY (statusTimer);
771
772  [self writePathsStatus: nil];
773  extracting = NO;
774
775  GWDebugLog(@"extracting done!");
776
777  if (subpathsChanged) {
778    subpathsChanged = NO;
779    [self startExtracting];
780  }
781}
782
783- (void)stopExtracting
784{
785  extracting = NO;
786}
787
788- (BOOL)extractFromPath:(GMDSIndexablePath *)indpath
789{
790  NSString *path = [NSString stringWithString: [indpath path]];
791  NSDictionary *attributes = [fm fileAttributesAtPath: path traverseLink: NO];
792
793  if (attributes) {
794    NSString *app = nil;
795    NSString *type = nil;
796    NSDirectoryEnumerator *enumerator;
797    id extractor = nil;
798    unsigned long fcount = 0;
799    int path_id;
800
801    [self updateStatusOfPath: indpath
802                   startTime: [NSDate date]
803                     endTime: nil
804                  filesCount: fcount
805                 indexedDone: NO];
806
807    EXECUTE_QUERY (@"BEGIN", NO);
808
809    [ws getInfoForFile: path application: &app type: &type];
810
811    path_id = [self insertOrUpdatePath: path
812                                ofType: type
813                        withAttributes: attributes];
814
815    if (path_id == -1) {
816      [sqlite executeQuery: @"ROLLBACK"];
817      return NO;
818    }
819
820    extractor = [self extractorForPath: path
821                                ofType: type
822                        withAttributes: attributes];
823
824    if (extractor) {
825      if ([extractor extractMetadataAtPath: path
826                                    withID: path_id
827                                attributes: attributes] == NO) {
828        [sqlite executeQuery: @"ROLLBACK"];
829        return NO;
830      }
831    }
832
833    [sqlite executeQuery: @"COMMIT"];
834
835    GWDebugLog(@"%@", path);
836
837    fcount++;
838
839    enumerator = [fm enumeratorAtPath: path];
840
841    while (1) {
842      CREATE_AUTORELEASE_POOL(arp);
843      NSString *entry = [enumerator nextObject];
844      NSDate *date = [NSDate dateWithTimeIntervalSinceNow: 0.001];
845      BOOL skip = NO;
846
847      [[NSRunLoop currentRunLoop] runUntilDate: date];
848
849      if (entry) {
850        NSString *subpath = [path stringByAppendingPathComponent: entry];
851        NSString *ext = [[subpath pathExtension] lowercaseString];
852
853        skip = ([excludedSuffixes containsObject: ext]
854                    || isDotFile(subpath)
855                    || inTreeFirstPartOfPath(subpath, excludedPathsTree));
856
857        attributes = [fm fileAttributesAtPath: subpath traverseLink: NO];
858
859        if (attributes) {
860          BOOL failed = NO;
861          BOOL hasextractor = NO;
862
863          if (skip == NO) {
864            NSString *app = nil;
865            NSString *type = nil;
866
867            [sqlite executeQuery: @"BEGIN"];
868
869            [ws getInfoForFile: subpath application: &app type: &type];
870
871            path_id = [self insertOrUpdatePath: subpath
872                                        ofType: type
873                                withAttributes: attributes];
874
875            if (path_id != -1) {
876              extractor = [self extractorForPath: subpath
877                                          ofType: type
878                                  withAttributes: attributes];
879
880              if (extractor) {
881                hasextractor = YES;
882
883                if ([extractor extractMetadataAtPath: subpath
884                                              withID: path_id
885                                          attributes: attributes] == NO) {
886                  failed = YES;
887                }
888              }
889
890            } else {
891              failed = YES;
892            }
893
894            [sqlite executeQuery: (failed ? @"ROLLBACK" : @"COMMIT")];
895
896            if ((failed == NO) && (skip == NO)) {
897              fcount++;
898            }
899
900            if ((fcount % UPDATE_COUNT) == 0) {
901              [self updateStatusOfPath: indpath
902                             startTime: nil
903                               endTime: nil
904                            filesCount: fcount
905                           indexedDone: NO];
906
907              GWDebugLog(@"updating %lu", fcount);
908            }
909          }
910
911          if (skip) {
912            GWDebugLog(@"skipping %@", subpath);
913
914            if ([attributes fileType] == NSFileTypeDirectory) {
915              [enumerator skipDescendents];
916            }
917
918          } else {
919            if (failed) {
920              [self logError: [NSString stringWithFormat: @"EXTRACT %@", subpath]];
921              GWDebugLog(@"error extracting at: %@", subpath);
922            } else if (hasextractor == NO) {
923              GWDebugLog(@"no extractor for: %@", subpath);
924            } else {
925              GWDebugLog(@"extracted: %@", subpath);
926            }
927          }
928        }
929
930      } else {
931        RELEASE (arp);
932        break;
933      }
934
935      if (extracting == NO) {
936        GWDebugLog(@"stopped");
937        RELEASE (arp);
938        break;
939      }
940
941      TEST_RELEASE (arp);
942    }
943
944    [self updateStatusOfPath: indpath
945                   startTime: nil
946                     endTime: [NSDate date]
947                  filesCount: fcount
948                 indexedDone: extracting];
949
950    [self writePathsStatus: nil];
951
952    GWDebugLog(@"done %@", path);
953  }
954
955  return YES;
956}
957
958- (int)insertOrUpdatePath:(NSString *)path
959                   ofType:(NSString *)type
960           withAttributes:(NSDictionary *)attributes
961{
962  NSTimeInterval interval = [[attributes fileModificationDate] timeIntervalSinceReferenceDate];
963  NSMutableArray *mdattributes = [NSMutableArray array];
964  NSString *qpath = stringForQuery(path);
965  NSString *qname = stringForQuery([path lastPathComponent]);
966  NSString *qext = stringForQuery([[path pathExtension] lowercaseString]);
967  SQLitePreparedStatement *statement;
968  NSString *query;
969  int path_id;
970  BOOL didexist;
971  unsigned i;
972
973#define KEY_AND_ATTRIBUTE(k, a) \
974do { \
975  if (a) { \
976    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: \
977                                        k, @"key", a, @"attribute", nil]; \
978    [mdattributes addObject: dict]; \
979  } \
980} while (0)
981
982  query = @"SELECT id FROM paths WHERE path = :path";
983
984  statement = [sqlite statementForQuery: query
985                         withIdentifier: @"insert_or_update_1"
986                               bindings: SQLITE_TEXT, @":path", qpath, 0];
987
988  path_id = [sqlite getIntEntryWithStatement: statement];
989
990  didexist = (path_id != INT_MAX);
991
992  if (didexist == NO) {
993    BOOL isdir = ([attributes fileType] == NSFileTypeDirectory);
994
995    if (isdir && ([directories containsObject: path] == NO)) {
996      [directories addObject: path];
997    }
998
999    query = @"INSERT INTO paths "
1000            @"(path, words_count, moddate, is_directory) "
1001            @"VALUES(:path, 0, :moddate, :isdir)";
1002
1003    statement = [sqlite statementForQuery: query
1004                           withIdentifier: @"insert_or_update_2"
1005                                 bindings: SQLITE_TEXT, @":path", qpath,
1006                                           SQLITE_FLOAT, @":moddate", interval,
1007                                           SQLITE_INTEGER, @":isdir", isdir, 0];
1008
1009    STATEMENT_EXECUTE_QUERY (statement, -1);
1010
1011    path_id = [sqlite lastInsertRowId];
1012
1013  } else {
1014    query = @"UPDATE paths "
1015            @"SET words_count = 0, moddate = :moddate "
1016            @"WHERE id = :pathid";
1017
1018    statement = [sqlite statementForQuery: query
1019                           withIdentifier: @"insert_or_update_3"
1020                                 bindings: SQLITE_FLOAT, @":moddate", interval,
1021                                           SQLITE_INTEGER, @":pathid", path_id, 0];
1022
1023    STATEMENT_EXECUTE_QUERY (statement, -1);
1024
1025    query = @"DELETE FROM attributes WHERE path_id = :pathid";
1026
1027    statement = [sqlite statementForQuery: query
1028                           withIdentifier: @"insert_or_update_4"
1029                                 bindings: SQLITE_INTEGER, @":pathid", path_id, 0];
1030
1031    STATEMENT_EXECUTE_QUERY (statement, -1);
1032
1033    query = @"DELETE FROM postings WHERE path_id = :pathid";
1034
1035    statement = [sqlite statementForQuery: query
1036                           withIdentifier: @"insert_or_update_5"
1037                                 bindings: SQLITE_INTEGER, @":pathid", path_id, 0];
1038
1039    STATEMENT_EXECUTE_QUERY (statement, -1);
1040  }
1041
1042  KEY_AND_ATTRIBUTE (@"GSMDItemFSName", qname);
1043  KEY_AND_ATTRIBUTE (@"GSMDItemFSExtension", qext);
1044  KEY_AND_ATTRIBUTE (@"GSMDItemFSType", type);
1045
1046  if (ddbd) {
1047    NSArray *usermdata = [ddbd userMetadataForPath: path];
1048
1049    if (usermdata) {
1050      [mdattributes addObjectsFromArray: usermdata];
1051    }
1052  }
1053
1054  for (i = 0; i < [mdattributes count]; i++) {
1055    NSDictionary *dict = [mdattributes objectAtIndex: i];
1056    NSString *key = [dict objectForKey: @"key"];
1057    NSString *attribute = [dict objectForKey: @"attribute"];
1058
1059    query = @"INSERT INTO attributes (path_id, key, attribute) "
1060            @"VALUES(:pathid, :key, :attribute)";
1061
1062    statement = [sqlite statementForQuery: query
1063                           withIdentifier: @"insert_or_update_6"
1064                                 bindings: SQLITE_INTEGER, @":pathid", path_id,
1065                                           SQLITE_TEXT, @":key", key,
1066                                       SQLITE_TEXT, @":attribute", attribute, 0];
1067
1068    STATEMENT_EXECUTE_QUERY (statement, -1);
1069  }
1070
1071  return path_id;
1072}
1073
1074- (BOOL)setMetadata:(NSDictionary *)mddict
1075            forPath:(NSString *)path
1076             withID:(int)path_id
1077{
1078  NSDictionary *wordsdict;
1079  NSDictionary *attrsdict;
1080  SQLitePreparedStatement *statement;
1081  NSString *query;
1082
1083  wordsdict = [mddict objectForKey: @"words"];
1084
1085  if (wordsdict) {
1086    NSCountedSet *wordset = [wordsdict objectForKey: @"wset"];
1087    NSEnumerator *enumerator = [wordset objectEnumerator];
1088    unsigned wcount = [[wordsdict objectForKey: @"wcount"] unsignedLongValue];
1089    NSString *word;
1090
1091    query = @"UPDATE paths "
1092            @"SET words_count = :wcount "
1093            @"WHERE id = :pathid";
1094
1095    statement = [sqlite statementForQuery: query
1096                           withIdentifier: @"set_metadata_1"
1097                                 bindings: SQLITE_INTEGER, @":wcount", wcount,
1098                                           SQLITE_INTEGER, @":pathid", path_id, 0];
1099
1100    STATEMENT_EXECUTE_QUERY (statement, NO);
1101
1102    while ((word = [enumerator nextObject])) {
1103      NSString *qword = stringForQuery(word);
1104      unsigned word_count = [wordset countForObject: word];
1105      int word_id;
1106
1107      query = @"SELECT id FROM words WHERE word = :word";
1108
1109      statement = [sqlite statementForQuery: query
1110                             withIdentifier: @"set_metadata_2"
1111                                   bindings: SQLITE_TEXT, @":word", qword, 0];
1112
1113      word_id = [sqlite getIntEntryWithStatement: statement];
1114
1115      if (word_id == INT_MAX) {
1116        query = @"INSERT INTO words (word) VALUES(:word)";
1117
1118        statement = [sqlite statementForQuery: query
1119                               withIdentifier: @"set_metadata_3"
1120                                     bindings: SQLITE_TEXT, @":word", qword, 0];
1121
1122        STATEMENT_EXECUTE_QUERY (statement, NO);
1123
1124        word_id = [sqlite lastInsertRowId];
1125      }
1126
1127      query = @"INSERT INTO postings (word_id, path_id, word_count) "
1128              @"VALUES(:wordid, :pathid, :wordcount)";
1129
1130      statement = [sqlite statementForQuery: query
1131                             withIdentifier: @"set_metadata_4"
1132                                   bindings: SQLITE_INTEGER, @":wordid", word_id,
1133                                             SQLITE_INTEGER, @":pathid", path_id,
1134                                             SQLITE_INTEGER, @":wordcount", word_count, 0];
1135
1136      STATEMENT_EXECUTE_QUERY (statement, NO);
1137    }
1138  }
1139
1140  attrsdict = [mddict objectForKey: @"attributes"];
1141
1142  if (attrsdict) {
1143    NSArray *keys = [attrsdict allKeys];
1144    unsigned i;
1145
1146    for (i = 0; i < [keys count]; i++) {
1147      NSString *key = [keys objectAtIndex: i];
1148      id mdvalue = [attrsdict objectForKey: key];
1149
1150      query = @"INSERT INTO attributes "
1151              @"(path_id, key, attribute) "
1152              @"VALUES(:pathid, :key, :mdvalue)";
1153
1154      if ([mdvalue isKindOfClass: [NSString class]]) {
1155        statement = [sqlite statementForQuery: query
1156                               withIdentifier: @"set_metadata_5"
1157                                     bindings: SQLITE_INTEGER, @":pathid", path_id,
1158                                               SQLITE_TEXT, @":key", key,
1159                                               SQLITE_TEXT, @":mdvalue", mdvalue, 0];
1160
1161      } else if ([mdvalue isKindOfClass: [NSArray class]]) {
1162        statement = [sqlite statementForQuery: query
1163                               withIdentifier: @"set_metadata_5"
1164                                     bindings: SQLITE_INTEGER, @":pathid", path_id,
1165                                               SQLITE_TEXT, @":key", key,
1166                                               SQLITE_TEXT, @":mdvalue", [mdvalue description], 0];
1167
1168      } else if ([mdvalue isKindOfClass: [NSNumber class]]) {
1169        statement = [sqlite statementForQuery: query
1170                               withIdentifier: @"set_metadata_5"
1171                                     bindings: SQLITE_INTEGER, @":pathid", path_id,
1172                                               SQLITE_TEXT, @":key", key,
1173                                               SQLITE_TEXT, @":mdvalue", [mdvalue description], 0];
1174
1175      } else if ([mdvalue isKindOfClass: [NSData class]]) {
1176        statement = [sqlite statementForQuery: query
1177                               withIdentifier: @"set_metadata_5"
1178                                     bindings: SQLITE_INTEGER, @":pathid", path_id,
1179                                               SQLITE_TEXT, @":key", key,
1180                                               SQLITE_BLOB, @":mdvalue", mdvalue, 0];
1181      } else {
1182        return NO;
1183      }
1184
1185      STATEMENT_EXECUTE_QUERY (statement, NO);
1186    }
1187  }
1188
1189  return YES;
1190}
1191
1192- (id)extractorForPath:(NSString *)path
1193                ofType:(NSString *)type
1194        withAttributes:(NSDictionary *)attributes
1195{
1196  NSString *ext = [[path pathExtension] lowercaseString];
1197  NSData *data = nil;
1198  id extractor = nil;
1199
1200  if ([attributes fileType] == NSFileTypeRegular) {
1201    NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath: path];
1202
1203    if (handle) {
1204      NS_DURING
1205        {
1206          data = [handle readDataOfLength: DLENGTH];
1207        }
1208      NS_HANDLER
1209        {
1210          data = nil;
1211        }
1212      NS_ENDHANDLER
1213
1214      [handle closeFile];
1215    }
1216  }
1217
1218  extractor = [extractors objectForKey: ext];
1219
1220  if (extractor) {
1221    if ([extractor canExtractFromFileType: type
1222                            withExtension: ext
1223                               attributes: attributes
1224                                 testData: data]) {
1225      return extractor;
1226    }
1227  }
1228
1229  if ([textExtractor canExtractFromFileType: type
1230                              withExtension: ext
1231                                 attributes: attributes
1232                                   testData: data]) {
1233    return textExtractor;
1234  }
1235
1236  return nil;
1237}
1238
1239- (void)loadExtractors
1240{
1241  NSString *bundlesDir;
1242  NSMutableArray *bundlesPaths;
1243  NSEnumerator *e1;
1244  NSEnumerator *enumerator;
1245  NSString *dir;
1246  int i;
1247
1248  bundlesPaths = [NSMutableArray array];
1249  e1 = [NSSearchPathForDirectoriesInDomains
1250    (NSLibraryDirectory, NSAllDomainsMask, YES) objectEnumerator];
1251  while ((bundlesDir = [e1 nextObject]) != nil)
1252    {
1253      bundlesDir = [bundlesDir stringByAppendingPathComponent: @"Bundles"];
1254      enumerator = [[fm directoryContentsAtPath: bundlesDir] objectEnumerator];
1255
1256      while ((dir = [enumerator nextObject])) {
1257	if ([[dir pathExtension] isEqual: @"extr"]) {
1258	  [bundlesPaths addObject:
1259	    [bundlesDir stringByAppendingPathComponent: dir]];
1260	}
1261      }
1262    }
1263
1264  extractors = [NSMutableDictionary new];
1265
1266  for (i = 0; i < [bundlesPaths count]; i++) {
1267    NSString *bpath = [bundlesPaths objectAtIndex: i];
1268    NSBundle *bundle = [NSBundle bundleWithPath: bpath];
1269
1270    if (bundle) {
1271			Class principalClass = [bundle principalClass];
1272
1273			if ([principalClass conformsToProtocol: @protocol(ExtractorsProtocol)]) {
1274        id extractor = [[principalClass alloc] initForExtractor: self];
1275
1276        if (extractor) {
1277          NSArray *extensions = [extractor pathExtensions];
1278
1279          if ([extensions containsObject: @"txt"]) {
1280            ASSIGN (textExtractor, extractor);
1281
1282          } else {
1283            unsigned j;
1284
1285            for (j = 0; j < [extensions count]; j++) {
1286              [extractors setObject: extractor
1287                             forKey: [[extensions objectAtIndex: j] lowercaseString]];
1288            }
1289
1290            RELEASE ((id)extractor);
1291          }
1292        }
1293      }
1294    }
1295  }
1296}
1297
1298- (BOOL)opendb
1299{
1300  BOOL newdb;
1301
1302  if ([sqlite opendbAtPath: dbpath isNew: &newdb]) {
1303    if (newdb) {
1304      if ([sqlite executeSimpleQuery: db_schema] == NO) {
1305        NSLog(@"unable to create the database at %@", dbpath);
1306        return NO;
1307      } else {
1308        GWDebugLog(@"contents database created");
1309      }
1310    }
1311  } else {
1312    NSLog(@"unable to open the database at %@", dbpath);
1313    return NO;
1314  }
1315
1316  [sqlite createFunctionWithName: @"checkUpdating"
1317                  argumentsCount: 0
1318                    userFunction: check_updating];
1319
1320  [sqlite createFunctionWithName: @"pathExists"
1321                  argumentsCount: 1
1322                    userFunction: path_exists];
1323
1324  [sqlite createFunctionWithName: @"pathMoved"
1325                  argumentsCount: 3
1326                    userFunction: path_moved];
1327
1328  [sqlite createFunctionWithName: @"timeStamp"
1329                  argumentsCount: 0
1330                    userFunction: time_stamp];
1331
1332  [sqlite executeQuery: @"PRAGMA cache_size = 20000"];
1333  [sqlite executeQuery: @"PRAGMA count_changes = 0"];
1334  [sqlite executeQuery: @"PRAGMA synchronous = OFF"];
1335  [sqlite executeQuery: @"PRAGMA temp_store = MEMORY"];
1336
1337  if ([sqlite executeSimpleQuery: db_schema_tmp] == NO) {
1338    NSLog(@"unable to create temp tables");
1339    [sqlite closeDb];
1340    return NO;
1341  }
1342
1343  /* only to avoid a compiler warning */
1344  if (0) {
1345    NSLog(@"%@", user_db_schema);
1346    NSLog(@"%@", user_db_schema_tmp);
1347  }
1348
1349  return YES;
1350}
1351
1352- (void)logError:(NSString *)err
1353{
1354  NSString *errbuf = [NSString stringWithFormat: @"%@\n", err];
1355  NSData *data = [errbuf dataUsingEncoding: [NSString defaultCStringEncoding]];
1356
1357  if (data == nil) {
1358    data = [errbuf dataUsingEncoding: NSUnicodeStringEncoding];
1359  }
1360
1361  [errHandle seekToEndOfFile];
1362  [errHandle writeData: data];
1363}
1364
1365- (BOOL)connection:(NSConnection *)ancestor
1366            shouldMakeNewConnection:(NSConnection *)newConn;
1367{
1368  [nc addObserver: self
1369         selector: @selector(connectionDidDie:)
1370	           name: NSConnectionDidDieNotification
1371	         object: newConn];
1372
1373  [newConn setDelegate: self];
1374
1375  GWDebugLog(@"new connection");
1376
1377  return YES;
1378}
1379
1380- (void)connectionDidDie:(NSNotification *)notification
1381{
1382  id connection = [notification object];
1383
1384  [nc removeObserver: self
1385	              name: NSConnectionDidDieNotification
1386	            object: connection];
1387
1388  if (connection == conn) {
1389    NSLog(@"mdextractor connection has been destroyed. Exiting.");
1390    [sqlite closeDb];
1391    exit(EXIT_FAILURE);
1392  } else {
1393    GWDebugLog(@"connection closed");
1394  }
1395}
1396
1397@end
1398
1399
1400@implementation	GMDSIndexablePath
1401
1402- (void)dealloc
1403{
1404  RELEASE (path);
1405  TEST_RELEASE (startTime);
1406  TEST_RELEASE (endTime);
1407  RELEASE (subpaths);
1408  TEST_RELEASE (ancestor);
1409
1410  [super dealloc];
1411}
1412
1413- (id)initWithPath:(NSString *)apath
1414          ancestor:(GMDSIndexablePath *)prepath
1415{
1416  self = [super init];
1417
1418  if (self) {
1419    ASSIGN (path, apath);
1420    subpaths = [NSMutableArray new];
1421    ancestor = nil;
1422    if (prepath) {
1423      ASSIGN (ancestor, prepath);
1424    }
1425    startTime = nil;
1426    endTime = nil;
1427    filescount = 0L;
1428    indexed = NO;
1429  }
1430
1431  return self;
1432}
1433
1434- (NSString *)path
1435{
1436  return path;
1437}
1438
1439- (NSArray *)subpaths
1440{
1441  return subpaths;
1442}
1443
1444- (GMDSIndexablePath *)subpathWithPath:(NSString *)apath
1445{
1446  unsigned i;
1447
1448  for (i = 0; i < [subpaths count]; i++) {
1449    GMDSIndexablePath *subpath = [subpaths objectAtIndex: i];
1450
1451    if ([[subpath path] isEqual: apath]) {
1452      return subpath;
1453    }
1454  }
1455
1456  return nil;
1457}
1458
1459- (BOOL)acceptsSubpath:(NSString *)subpath
1460{
1461  if (subPathOfPath(path, subpath)) {
1462    return ([self subpathWithPath: subpath] == nil);
1463  }
1464
1465  return NO;
1466}
1467
1468- (GMDSIndexablePath *)addSubpath:(NSString *)apath
1469{
1470  if ([self acceptsSubpath: apath]) {
1471    GMDSIndexablePath *subpath = [[GMDSIndexablePath alloc] initWithPath: apath ancestor: self];
1472
1473    [subpaths addObject: subpath];
1474    RELEASE (subpath);
1475
1476    return subpath;
1477  }
1478
1479  return nil;
1480}
1481
1482- (void)removeSubpath:(NSString *)apath
1483{
1484  GMDSIndexablePath *subpath = [self subpathWithPath: apath];
1485
1486  if (subpath) {
1487    [subpaths removeObject: subpath];
1488  }
1489}
1490
1491- (BOOL)isSubpath
1492{
1493  return (ancestor != nil);
1494}
1495
1496- (GMDSIndexablePath *)ancestor
1497{
1498  return ancestor;
1499}
1500
1501- (unsigned long)filescount
1502{
1503  return filescount;
1504}
1505
1506- (void)setFilesCount:(unsigned long)count
1507{
1508  filescount = count;
1509}
1510
1511- (NSDate *)startTime
1512{
1513  return startTime;
1514}
1515
1516- (void)setStartTime:(NSDate *)date
1517{
1518  ASSIGN (startTime, date);
1519}
1520
1521- (NSDate *)endTime
1522{
1523  return endTime;
1524}
1525
1526- (void)setEndTime:(NSDate *)date
1527{
1528  ASSIGN (endTime, date);
1529}
1530
1531- (BOOL)indexed
1532{
1533  return indexed;
1534}
1535
1536- (void)setIndexed:(BOOL)value
1537{
1538  indexed = value;
1539}
1540
1541- (void)checkIndexingDone
1542{
1543  unsigned count = [subpaths count];
1544  unsigned i;
1545
1546  for (i = 0; i < count; i++) {
1547    GMDSIndexablePath *subpath = [subpaths objectAtIndex: i];
1548
1549    [self setFilesCount: (filescount + [subpath filescount])];
1550
1551    if ([subpath indexed]) {
1552      [self setEndTime: [subpath endTime]];
1553      [subpaths removeObject: subpath];
1554      count--;
1555      i--;
1556    }
1557  }
1558}
1559
1560- (NSDictionary *)info
1561{
1562  NSMutableDictionary *info = [NSMutableDictionary dictionary];
1563  NSMutableArray *subinfo = [NSMutableArray array];
1564  unsigned i;
1565
1566  [info setObject: path forKey: @"path"];
1567
1568  if (startTime) {
1569    [info setObject: startTime forKey: @"start_time"];
1570  }
1571
1572  if (endTime) {
1573    [info setObject: endTime forKey: @"end_time"];
1574  }
1575
1576  [info setObject: [NSNumber numberWithBool: indexed] forKey: @"indexed"];
1577
1578  [info setObject: [NSNumber numberWithUnsignedLong: filescount] forKey: @"count"];
1579
1580  for (i = 0; i < [subpaths count]; i++) {
1581    [subinfo addObject: [[subpaths objectAtIndex: i] info]];
1582  }
1583  [info setObject: [subinfo makeImmutableCopyOnFail: NO]
1584           forKey: @"subpaths"];
1585
1586  return [info makeImmutableCopyOnFail: NO];
1587}
1588
1589@end
1590
1591
1592int main(int argc, char** argv)
1593{
1594  CREATE_AUTORELEASE_POOL(pool);
1595  NSProcessInfo *info = [NSProcessInfo processInfo];
1596  NSMutableArray *args = AUTORELEASE ([[info arguments] mutableCopy]);
1597  static BOOL	is_daemon = NO;
1598  BOOL subtask = YES;
1599
1600  if ([[info arguments] containsObject: @"--daemon"]) {
1601    subtask = NO;
1602    is_daemon = YES;
1603  }
1604
1605  if (subtask) {
1606    NSTask *task = [NSTask new];
1607
1608    NS_DURING
1609	    {
1610	      [args removeObjectAtIndex: 0];
1611	      [args addObject: @"--daemon"];
1612	      [task setLaunchPath: [[NSBundle mainBundle] executablePath]];
1613	      [task setArguments: args];
1614	      [task setEnvironment: [info environment]];
1615	      [task launch];
1616	      DESTROY (task);
1617	    }
1618    NS_HANDLER
1619	    {
1620	      fprintf (stderr, "unable to launch the mdextractor task. exiting.\n");
1621	      DESTROY (task);
1622	    }
1623    NS_ENDHANDLER
1624
1625    exit(EXIT_FAILURE);
1626  }
1627
1628  RELEASE(pool);
1629
1630  {
1631    CREATE_AUTORELEASE_POOL (pool);
1632	  GMDSExtractor *extractor;
1633
1634    [NSApplication sharedApplication];
1635    extractor = [GMDSExtractor new];
1636    RELEASE (pool);
1637
1638    if (extractor != nil) {
1639	    CREATE_AUTORELEASE_POOL (pool);
1640      [[NSRunLoop currentRunLoop] run];
1641  	  RELEASE (pool);
1642    }
1643  }
1644
1645  exit(EXIT_SUCCESS);
1646}
1647
1648
1649void setUpdating(BOOL value)
1650{
1651  updating = value;
1652}
1653
1654BOOL isDotFile(NSString *path)
1655{
1656  NSArray *components;
1657  NSEnumerator *e;
1658  NSString *c;
1659  BOOL found;
1660
1661  if (path == nil)
1662    return NO;
1663
1664  found = NO;
1665  components = [path pathComponents];
1666  e = [components objectEnumerator];
1667  while ((c = [e nextObject]) && !found)
1668    {
1669      if (([c length] > 0) && ([c characterAtIndex:0] == '.'))
1670	found = YES;
1671    }
1672
1673  return found;
1674}
1675
1676
1677BOOL subPathOfPath(NSString *p1, NSString *p2)
1678{
1679  int l1 = [p1 length];
1680  int l2 = [p2 length];
1681
1682  if ((l1 > l2) || ([p1 isEqual: p2])) {
1683    return NO;
1684  } else if ([[p2 substringToIndex: l1] isEqual: p1]) {
1685    if ([[p2 pathComponents] containsObject: [p1 lastPathComponent]]) {
1686      return YES;
1687    }
1688  }
1689
1690  return NO;
1691}
1692
1693NSString *path_separator(void)
1694{
1695  static NSString *separator = nil;
1696
1697  if (separator == nil) {
1698    #if defined(__MINGW32__)
1699      separator = @"\\";
1700    #else
1701      separator = @"/";
1702    #endif
1703
1704    RETAIN (separator);
1705  }
1706
1707  return separator;
1708}
1709