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