1/* fswatcher.m 2 * 3 * Copyright (C) 2004-2013 Free Software Foundation, Inc. 4 * 5 * Author: Enrico Sersale <enrico@imago.ro> 6 * Date: February 2004 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 "fswatcher.h" 26#include "config.h" 27 28#define GWDebugLog(format, args...) \ 29 do { if (GW_DEBUG_LOG) \ 30 NSLog(format , ## args); } while (0) 31 32static BOOL is_daemon = NO; /* Currently running as daemon. */ 33static BOOL auto_stop = NO; /* Should we shut down when unused? */ 34 35@implementation FSWClientInfo 36 37- (void)dealloc 38{ 39 RELEASE (conn); 40 RELEASE (client); 41 RELEASE (wpaths); 42 [super dealloc]; 43} 44 45- (id)init 46{ 47 self = [super init]; 48 49 if (self) 50 { 51 client = nil; 52 conn = nil; 53 wpaths = [[NSCountedSet alloc] initWithCapacity: 1]; 54 global = NO; 55 } 56 57 return self; 58} 59 60- (void)setConnection:(NSConnection *)connection 61{ 62 ASSIGN (conn, connection); 63} 64 65- (NSConnection *)connection 66{ 67 return conn; 68} 69 70- (void)setClient:(id <FSWClientProtocol>)clnt 71{ 72 ASSIGN (client, clnt); 73} 74 75- (id <FSWClientProtocol>)client 76{ 77 return client; 78} 79 80- (void)addWatchedPath:(NSString *)path 81{ 82 [wpaths addObject: path]; 83} 84 85- (void)removeWatchedPath:(NSString *)path 86{ 87 [wpaths removeObject: path]; 88} 89 90- (BOOL)isWatchingPath:(NSString *)path 91{ 92 return [wpaths containsObject: path]; 93} 94 95- (NSSet *)watchedPaths 96{ 97 return wpaths; 98} 99 100- (void)setGlobal:(BOOL)value 101{ 102 global = value; 103} 104 105- (BOOL)isGlobal 106{ 107 return global; 108} 109 110@end 111 112 113@implementation FSWatcher 114 115- (void)dealloc 116{ 117 NSUInteger i; 118 119 for (i = 0; i < [clientsInfo count]; i++) 120 { 121 NSConnection *connection = [[clientsInfo objectAtIndex: i] connection]; 122 123 if (connection) 124 { 125 [nc removeObserver: self 126 name: NSConnectionDidDieNotification 127 object: connection]; 128 } 129 } 130 131 if (conn) { 132 [nc removeObserver: self 133 name: NSConnectionDidDieNotification 134 object: conn]; 135 DESTROY (conn); 136 } 137 138 [dnc removeObserver: self]; 139 140 RELEASE (clientsInfo); 141 NSZoneFree (NSDefaultMallocZone(), (void *)watchers); 142 freeTree(includePathsTree); 143 freeTree(excludePathsTree); 144 RELEASE (excludedSuffixes); 145 146 [super dealloc]; 147} 148 149- (id)init 150{ 151 self = [super init]; 152 153 if (self) 154 { 155 fm = [NSFileManager defaultManager]; 156 nc = [NSNotificationCenter defaultCenter]; 157 dnc = [NSDistributedNotificationCenter defaultCenter]; 158 159 conn = [NSConnection defaultConnection]; 160 [conn setRootObject: self]; 161 [conn setDelegate: self]; 162 163 if ([conn registerName: @"fswatcher"] == NO) 164 { 165 NSLog(@"unable to register with name server - quiting."); 166 DESTROY (self); 167 return self; 168 } 169 170 clientsInfo = [NSMutableArray new]; 171 172 watchers = NSCreateMapTable(NSObjectMapKeyCallBacks, 173 NSObjectMapValueCallBacks, 0); 174 175 includePathsTree = newTreeWithIdentifier(@"incl_paths"); 176 excludePathsTree = newTreeWithIdentifier(@"excl_paths"); 177 excludedSuffixes = [[NSMutableSet alloc] initWithCapacity: 1]; 178 [self setDefaultGlobalPaths]; 179 180 [nc addObserver: self 181 selector: @selector(connectionBecameInvalid:) 182 name: NSConnectionDidDieNotification 183 object: conn]; 184 [dnc addObserver: self 185 selector: @selector(globalPathsChanged:) 186 name: @"GSMetadataIndexedDirectoriesChanged" 187 object: nil]; 188 } 189 return self; 190} 191 192- (BOOL)connection:(NSConnection *)ancestor 193 shouldMakeNewConnection:(NSConnection *)newConn; 194{ 195 FSWClientInfo *info = [FSWClientInfo new]; 196 197 [info setConnection: newConn]; 198 [clientsInfo addObject: info]; 199 RELEASE (info); 200 201 [nc addObserver: self 202 selector: @selector(connectionBecameInvalid:) 203 name: NSConnectionDidDieNotification 204 object: newConn]; 205 206 [newConn setDelegate: self]; 207 208 return YES; 209} 210 211- (void)connectionBecameInvalid:(NSNotification *)notification 212{ 213 id connection = [notification object]; 214 215 [nc removeObserver: self 216 name: NSConnectionDidDieNotification 217 object: connection]; 218 219 NSLog(@"Connection became invalid"); 220 if (connection == conn) 221 { 222 NSLog(@"argh - fswatcher server root connection has been destroyed."); 223 exit(EXIT_FAILURE); 224 225 } else 226 { 227 FSWClientInfo *info = [self clientInfoWithConnection: connection]; 228 229 if (info) 230 { 231 NSSet *wpaths = [info watchedPaths]; 232 NSEnumerator *enumerator = [wpaths objectEnumerator]; 233 NSString *wpath; 234 235 while ((wpath = [enumerator nextObject])) 236 { 237 Watcher *watcher = [self watcherForPath: wpath]; 238 239 if (watcher) 240 { 241 [watcher removeListener]; 242 } 243 } 244 245 [clientsInfo removeObject: info]; 246 } 247 248 if (auto_stop == YES && [clientsInfo count] <= 1) 249 { 250 /* If there is nothing else using this process, and this is not 251 * a daemon, then we can quietly terminate. 252 */ 253 NSLog(@"No more clients, shutting down."); 254 exit(EXIT_SUCCESS); 255 } 256 } 257} 258 259- (void)setDefaultGlobalPaths 260{ 261 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 262 id entry; 263 NSUInteger i; 264 265 [defaults synchronize]; 266 267 entry = [defaults arrayForKey: @"GSMetadataIndexablePaths"]; 268 269 if (entry) { 270 for (i = 0; i < [entry count]; i++) { 271 insertComponentsOfPath([entry objectAtIndex: i], includePathsTree); 272 } 273 274 } else { 275 insertComponentsOfPath(NSHomeDirectory(), includePathsTree); 276 277 entry = NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, 278 NSAllDomainsMask, YES); 279 for (i = 0; i < [entry count]; i++) { 280 insertComponentsOfPath([entry objectAtIndex: i], includePathsTree); 281 } 282 283 entry = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, 284 NSAllDomainsMask, YES); 285 for (i = 0; i < [entry count]; i++) { 286 NSString *dir = [entry objectAtIndex: i]; 287 NSString *path = [dir stringByAppendingPathComponent: @"Headers"]; 288 289 if ([fm fileExistsAtPath: path]) { 290 insertComponentsOfPath(path, includePathsTree); 291 } 292 293 path = [dir stringByAppendingPathComponent: @"Documentation"]; 294 295 if ([fm fileExistsAtPath: path]) { 296 insertComponentsOfPath(path, includePathsTree); 297 } 298 } 299 } 300 301 entry = [defaults arrayForKey: @"GSMetadataExcludedPaths"]; 302 303 if (entry) { 304 for (i = 0; i < [entry count]; i++) { 305 insertComponentsOfPath([entry objectAtIndex: i], excludePathsTree); 306 } 307 } 308 309 entry = [defaults arrayForKey: @"GSMetadataExcludedSuffixes"]; 310 311 if (entry == nil) { 312 entry = [NSArray arrayWithObjects: @"a", @"d", @"dylib", @"er1", 313 @"err", @"extinfo", @"frag", @"la", 314 @"log", @"o", @"out", @"part", 315 @"sed", @"so", @"status", @"temp", 316 @"tmp", 317 nil]; 318 } 319 320 [excludedSuffixes addObjectsFromArray: entry]; 321} 322 323- (void)globalPathsChanged:(NSNotification *)notification 324{ 325 NSDictionary *info = [notification userInfo]; 326 NSArray *indexable = [info objectForKey: @"GSMetadataIndexablePaths"]; 327 NSArray *excluded = [info objectForKey: @"GSMetadataExcludedPaths"]; 328 NSArray *suffixes = [info objectForKey: @"GSMetadataExcludedSuffixes"]; 329 330 NSUInteger i; 331 332 emptyTreeWithBase(includePathsTree); 333 334 for (i = 0; i < [indexable count]; i++) { 335 insertComponentsOfPath([indexable objectAtIndex: i], includePathsTree); 336 } 337 338 emptyTreeWithBase(excludePathsTree); 339 340 for (i = 0; i < [excluded count]; i++) { 341 insertComponentsOfPath([excluded objectAtIndex: i], excludePathsTree); 342 } 343 344 [excludedSuffixes removeAllObjects]; 345 [excludedSuffixes addObjectsFromArray: suffixes]; 346} 347 348- (oneway void)registerClient:(id <FSWClientProtocol>)client 349 isGlobalWatcher:(BOOL)global 350{ 351 NSConnection *connection = [(NSDistantObject *)client connectionForProxy]; 352 FSWClientInfo *info = [self clientInfoWithConnection: connection]; 353 354 if (info == nil) 355 { 356 [NSException raise: NSInternalInconsistencyException 357 format: @"registration with unknown connection"]; 358 } 359 360 if ([info client] != nil) 361 { 362 [NSException raise: NSInternalInconsistencyException 363 format: @"registration with registered client"]; 364 } 365 366 if ([(id)client isProxy] == YES) 367 { 368 [(id)client setProtocolForProxy: @protocol(FSWClientProtocol)]; 369 [info setClient: client]; 370 [info setGlobal: global]; 371 } 372 NSLog(@"register client %lu", (unsigned long)[clientsInfo count]); 373} 374 375- (oneway void)unregisterClient:(id <FSWClientProtocol>)client 376{ 377 NSConnection *connection = [(NSDistantObject *)client connectionForProxy]; 378 FSWClientInfo *info = [self clientInfoWithConnection: connection]; 379 NSSet *wpaths; 380 NSEnumerator *enumerator; 381 NSString *wpath; 382 383 if (info == nil) { 384 [NSException raise: NSInternalInconsistencyException 385 format: @"unregistration with unknown connection"]; 386 } 387 388 if ([info client] == nil) { 389 [NSException raise: NSInternalInconsistencyException 390 format: @"unregistration with unregistered client"]; 391 } 392 393 wpaths = [info watchedPaths]; 394 enumerator = [wpaths objectEnumerator]; 395 396 while ((wpath = [enumerator nextObject])) { 397 Watcher *watcher = [self watcherForPath: wpath]; 398 399 if (watcher) { 400 [watcher removeListener]; 401 } 402 } 403 404 [nc removeObserver: self 405 name: NSConnectionDidDieNotification 406 object: connection]; 407 408 [clientsInfo removeObject: info]; 409 410 if (auto_stop == YES && [clientsInfo count] <= 1) 411 { 412 /* If there is nothing else using this process, and this is not 413 * a daemon, then we can quietly terminate. 414 */ 415 exit(EXIT_SUCCESS); 416 } 417} 418 419- (FSWClientInfo *)clientInfoWithConnection:(NSConnection *)connection 420{ 421 NSUInteger i; 422 423 for (i = 0; i < [clientsInfo count]; i++) 424 { 425 FSWClientInfo *info = [clientsInfo objectAtIndex: i]; 426 427 if ([info connection] == connection) 428 return info; 429 430 } 431 432 return nil; 433} 434 435- (FSWClientInfo *)clientInfoWithRemote:(id)remote 436{ 437 NSUInteger i; 438 439 for (i = 0; i < [clientsInfo count]; i++) 440 { 441 FSWClientInfo *info = [clientsInfo objectAtIndex: i]; 442 443 if ([info client] == remote) 444 return info; 445 } 446 447 return nil; 448} 449 450- (oneway void)client:(id <FSWClientProtocol>)client 451 addWatcherForPath:(NSString *)path 452{ 453 NSConnection *connection = [(NSDistantObject *)client connectionForProxy]; 454 FSWClientInfo *info = [self clientInfoWithConnection: connection]; 455 Watcher *watcher = [self watcherForPath: path]; 456 457 if (info == nil) { 458 [NSException raise: NSInternalInconsistencyException 459 format: @"adding watcher from unknown connection"]; 460 } 461 462 if ([info client] == nil) { 463 [NSException raise: NSInternalInconsistencyException 464 format: @"adding watcher for unregistered client"]; 465 } 466 467 if (watcher) { 468 GWDebugLog(@"watcher found; adding listener for: %@", path); 469 [info addWatchedPath: path]; 470 [watcher addListener]; 471 472 } else { 473 if ([fm fileExistsAtPath: path]) { 474 GWDebugLog(@"add watcher for: %@", path); 475 [info addWatchedPath: path]; 476 watcher = [[Watcher alloc] initWithWatchedPath: path fswatcher: self]; 477 NSMapInsert (watchers, path, watcher); 478 RELEASE (watcher); 479 } 480 } 481} 482 483- (oneway void)client:(id <FSWClientProtocol>)client 484 removeWatcherForPath:(NSString *)path 485{ 486 NSConnection *connection = [(NSDistantObject *)client connectionForProxy]; 487 FSWClientInfo *info = [self clientInfoWithConnection: connection]; 488 Watcher *watcher = [self watcherForPath: path]; 489 490 if (info == nil) { 491 [NSException raise: NSInternalInconsistencyException 492 format: @"removing watcher from unknown connection"]; 493 } 494 495 if ([info client] == nil) { 496 [NSException raise: NSInternalInconsistencyException 497 format: @"removing watcher for unregistered client"]; 498 } 499 500 if (watcher && ([watcher isOld] == NO)) { 501 GWDebugLog(@"remove listener for: %@", path); 502 [info removeWatchedPath: path]; 503 [watcher removeListener]; 504 } 505} 506 507- (Watcher *)watcherForPath:(NSString *)path 508{ 509 return (Watcher *)NSMapGet(watchers, path); 510} 511 512- (void)watcherTimeOut:(NSTimer *)sender 513{ 514 Watcher *watcher = (Watcher *)[sender userInfo]; 515 516 if ([watcher isOld]) { 517 [self removeWatcher: watcher]; 518 } else { 519 [watcher watchFile]; 520 } 521} 522 523- (void)removeWatcher:(Watcher *)watcher 524{ 525 NSString *path = [watcher watchedPath]; 526 NSTimer *timer = [watcher timer]; 527 528 if (timer && [timer isValid]) { 529 [timer invalidate]; 530 } 531 532 GWDebugLog(@"removed watcher for: %@", path); 533 534 RETAIN (path); 535 NSMapRemove(watchers, path); 536 RELEASE (path); 537} 538 539- (pcomp *)includePathsTree 540{ 541 return includePathsTree; 542} 543 544- (pcomp *)excludePathsTree 545{ 546 return excludePathsTree; 547} 548 549- (NSSet *)excludedSuffixes 550{ 551 return excludedSuffixes; 552} 553 554static inline BOOL isDotFile(NSString *path) 555{ 556 NSArray *components; 557 NSEnumerator *e; 558 NSString *c; 559 BOOL found; 560 561 if (path == nil) 562 return NO; 563 564 found = NO; 565 components = [path pathComponents]; 566 e = [components objectEnumerator]; 567 while ((c = [e nextObject]) && !found) 568 { 569 if (([c length] > 0) && ([c characterAtIndex:0] == '.')) 570 found = YES; 571 } 572 573 return found; 574} 575 576- (BOOL)isGlobalValidPath:(NSString *)path 577{ 578 NSString *ext = [[path pathExtension] lowercaseString]; 579 580 return (([excludedSuffixes containsObject: ext] == NO) 581 && (isDotFile(path) == NO) 582 && inTreeFirstPartOfPath(path, includePathsTree) 583 && (inTreeFirstPartOfPath(path, excludePathsTree) == NO)); 584} 585 586- (void)notifyClients:(NSDictionary *)info 587{ 588 CREATE_AUTORELEASE_POOL(pool); 589 NSString *path = [info objectForKey: @"path"]; 590 NSString *event = [info objectForKey: @"event"]; 591 NSData *data = [NSArchiver archivedDataWithRootObject: info]; 592 NSUInteger i; 593 594 for (i = 0; i < [clientsInfo count]; i++) 595 { 596 FSWClientInfo *clinfo = [clientsInfo objectAtIndex: i]; 597 598 if ([clinfo isWatchingPath: path]) 599 { 600 [[clinfo client] watchedPathDidChange: data]; 601 } 602 } 603 604 if ([event isEqual: @"GWWatchedPathDeleted"] 605 && [self isGlobalValidPath: path]) 606 { 607 GWDebugLog(@"DELETE %@", path); 608 [self notifyGlobalWatchingClients: info]; 609 610 } 611 else if ([event isEqual: @"GWWatchedFileModified"] 612 && [self isGlobalValidPath: path]) 613 { 614 GWDebugLog(@"MODIFIED %@", path); 615 [self notifyGlobalWatchingClients: info]; 616 617 } 618 else if ([event isEqual: @"GWFileDeletedInWatchedDirectory"]) 619 { 620 NSArray *files = [info objectForKey: @"files"]; 621 622 for (i = 0; i < [files count]; i++) 623 { 624 NSString *fname = [files objectAtIndex: i]; 625 NSString *fullpath = [path stringByAppendingPathComponent: fname]; 626 627 if ([self isGlobalValidPath: fullpath]) 628 { 629 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 630 631 [dict setObject: fullpath forKey: @"path"]; 632 [dict setObject: @"GWWatchedPathDeleted" forKey: @"event"]; 633 634 [self notifyGlobalWatchingClients: dict]; 635 } 636 } 637 638 } 639 else if ([event isEqual: @"GWFileCreatedInWatchedDirectory"]) 640 { 641 NSArray *files = [info objectForKey: @"files"]; 642 643 for (i = 0; i < [files count]; i++) 644 { 645 NSString *fname = [files objectAtIndex: i]; 646 NSString *fullpath = [path stringByAppendingPathComponent: fname]; 647 648 if ([self isGlobalValidPath: fullpath]) { 649 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 650 651 [dict setObject: fullpath forKey: @"path"]; 652 [dict setObject: @"GWFileCreatedInWatchedDirectory" forKey: @"event"]; 653 654 [self notifyGlobalWatchingClients: dict]; 655 } 656 } 657 } 658 659 RELEASE (pool); 660} 661 662- (void)notifyGlobalWatchingClients:(NSDictionary *)info 663{ 664 NSUInteger i; 665 666 for (i = 0; i < [clientsInfo count]; i++) 667 { 668 FSWClientInfo *clinfo = [clientsInfo objectAtIndex: i]; 669 670 if ([clinfo isGlobal]) 671 [[clinfo client] globalWatchedPathDidChange: info]; 672 } 673} 674 675@end 676 677 678@implementation Watcher 679 680- (void)dealloc 681{ 682 if (timer && [timer isValid]) 683 [timer invalidate]; 684 685 RELEASE (watchedPath); 686 RELEASE (pathContents); 687 RELEASE (date); 688 [super dealloc]; 689} 690 691- (id)initWithWatchedPath:(NSString *)path 692 fswatcher:(id)fsw 693{ 694 self = [super init]; 695 696 if (self) 697 { 698 NSDictionary *attributes; 699 NSString *type; 700 701 ASSIGN (watchedPath, path); 702 fm = [NSFileManager defaultManager]; 703 attributes = [fm fileAttributesAtPath: path traverseLink: YES]; 704 type = [attributes fileType]; 705 ASSIGN (date, [attributes fileModificationDate]); 706 707 if (type == NSFileTypeDirectory) { 708 ASSIGN (pathContents, ([fm directoryContentsAtPath: watchedPath])); 709 isdir = YES; 710 } else { 711 isdir = NO; 712 } 713 714 fswatcher = fsw; 715 listeners = 1; 716 isOld = NO; 717 718 timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 719 target: fswatcher 720 selector: @selector(watcherTimeOut:) 721 userInfo: self 722 repeats: YES]; 723 } 724 725 return self; 726} 727 728- (void)watchFile 729{ 730 CREATE_AUTORELEASE_POOL(pool); 731 NSDictionary *attributes; 732 NSDate *moddate; 733 NSMutableDictionary *notifdict; 734 735 if (isOld) 736 { 737 RELEASE (pool); 738 return; 739 } 740 741 attributes = [fm fileAttributesAtPath: watchedPath traverseLink: YES]; 742 743 if (attributes == nil) { 744 notifdict = [NSMutableDictionary dictionary]; 745 [notifdict setObject: watchedPath forKey: @"path"]; 746 [notifdict setObject: @"GWWatchedPathDeleted" forKey: @"event"]; 747 [fswatcher notifyClients: notifdict]; 748 isOld = YES; 749 RELEASE (pool); 750 return; 751 } 752 753 moddate = [attributes fileModificationDate]; 754 755 if ([date isEqualToDate: moddate] == NO) { 756 if (isdir) { 757 NSArray *oldconts = [pathContents copy]; 758 NSArray *newconts = [fm directoryContentsAtPath: watchedPath]; 759 NSMutableArray *diffFiles = [NSMutableArray array]; 760 BOOL contentsChanged = NO; 761 int i; 762 763 ASSIGN (date, moddate); 764 ASSIGN (pathContents, newconts); 765 766 notifdict = [NSMutableDictionary dictionary]; 767 [notifdict setObject: watchedPath forKey: @"path"]; 768 769 /* if there is an error in fileAttributesAtPath */ 770 /* or watchedPath doesn't exist anymore */ 771 if (newconts == nil) { 772 [notifdict setObject: @"GWWatchedPathDeleted" forKey: @"event"]; 773 [fswatcher notifyClients: notifdict]; 774 RELEASE (oldconts); 775 isOld = YES; 776 RELEASE (pool); 777 return; 778 } 779 780 for (i = 0; i < [oldconts count]; i++) { 781 NSString *fname = [oldconts objectAtIndex: i]; 782 if ([newconts containsObject: fname] == NO) { 783 [diffFiles addObject: fname]; 784 } 785 } 786 787 if ([diffFiles count] > 0) { 788 contentsChanged = YES; 789 [notifdict setObject: @"GWFileDeletedInWatchedDirectory" forKey: @"event"]; 790 [notifdict setObject: diffFiles forKey: @"files"]; 791 [fswatcher notifyClients: notifdict]; 792 } 793 794 [diffFiles removeAllObjects]; 795 796 for (i = 0; i < [newconts count]; i++) { 797 NSString *fname = [newconts objectAtIndex: i]; 798 if ([oldconts containsObject: fname] == NO) { 799 [diffFiles addObject: fname]; 800 } 801 } 802 803 if ([diffFiles count] > 0) { 804 contentsChanged = YES; 805 [notifdict setObject: watchedPath forKey: @"path"]; 806 [notifdict setObject: @"GWFileCreatedInWatchedDirectory" forKey: @"event"]; 807 [notifdict setObject: diffFiles forKey: @"files"]; 808 [fswatcher notifyClients: notifdict]; 809 } 810 811 RELEASE (oldconts); 812 813 if (contentsChanged == NO) { 814 [notifdict setObject: @"GWWatchedFileModified" forKey: @"event"]; 815 [fswatcher notifyClients: notifdict]; 816 } 817 818 } else { // isdir == NO 819 ASSIGN (date, moddate); 820 821 notifdict = [NSMutableDictionary dictionary]; 822 823 [notifdict setObject: watchedPath forKey: @"path"]; 824 [notifdict setObject: @"GWWatchedFileModified" forKey: @"event"]; 825 826 [fswatcher notifyClients: notifdict]; 827 } 828 } 829 830 RELEASE (pool); 831} 832 833- (void)addListener 834{ 835 listeners++; 836} 837 838- (void)removeListener 839{ 840 listeners--; 841 if (listeners <= 0) { 842 isOld = YES; 843 } 844} 845 846- (BOOL)isWatchingPath:(NSString *)apath 847{ 848 return ([apath isEqualToString: watchedPath]); 849} 850 851- (NSString *)watchedPath 852{ 853 return watchedPath; 854} 855 856- (BOOL)isOld 857{ 858 return isOld; 859} 860 861- (NSTimer *)timer 862{ 863 return timer; 864} 865 866@end 867 868 869int main(int argc, char** argv) 870{ 871 CREATE_AUTORELEASE_POOL(pool); 872 NSProcessInfo *info = [NSProcessInfo processInfo]; 873 NSMutableArray *args = AUTORELEASE ([[info arguments] mutableCopy]); 874 BOOL subtask = YES; 875 876 if ([[info arguments] containsObject: @"--auto"] == YES) 877 { 878 auto_stop = YES; 879 } 880 881 if ([[info arguments] containsObject: @"--daemon"]) 882 { 883 subtask = NO; 884 is_daemon = YES; 885 } 886 887 if (subtask) 888 { 889 NSTask *task; 890 891 892 task = [NSTask new]; 893 894 NS_DURING 895 { 896 [args removeObjectAtIndex: 0]; 897 [args addObject: @"--daemon"]; 898 [task setLaunchPath: [[NSBundle mainBundle] executablePath]]; 899 [task setArguments: args]; 900 [task setEnvironment: [info environment]]; 901 [task launch]; 902 DESTROY (task); 903 } 904 NS_HANDLER 905 { 906 fprintf (stderr, "unable to launch the fswatcher task. exiting.\n"); 907 DESTROY (task); 908 } 909 NS_ENDHANDLER 910 911 exit(EXIT_FAILURE); 912 } 913 914 RELEASE(pool); 915 916 { 917 CREATE_AUTORELEASE_POOL (pool); 918 FSWatcher *fsw = [[FSWatcher alloc] init]; 919 RELEASE (pool); 920 921 if (fsw != nil) 922 { 923 CREATE_AUTORELEASE_POOL (pool); 924 [[NSRunLoop currentRunLoop] run]; 925 RELEASE (pool); 926 } 927 } 928 929 exit(EXIT_SUCCESS); 930} 931 932