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