GVFileNotificationCenter.m 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /*************************************************************************
  2. * Copyright (c) 2011 AT&T Intellectual Property
  3. * All rights reserved. This program and the accompanying materials
  4. * are made available under the terms of the Eclipse Public License v1.0
  5. * which accompanies this distribution, and is available at
  6. * https://www.eclipse.org/legal/epl-v10.html
  7. *
  8. * Contributors: Details at http://www.graphviz.org/
  9. *************************************************************************/
  10. #include <fcntl.h>
  11. #include <sys/event.h>
  12. #import "GVFileNotificationCenter.h"
  13. static GVFileNotificationCenter *_defaultCenter = nil;
  14. @interface GVFileNotificationRecord : NSObject
  15. {
  16. id _observer;
  17. NSString *_path;
  18. SEL _selector;
  19. int _fileDescriptor;
  20. }
  21. @property(readonly) id observer;
  22. @property(readonly) NSString *path;
  23. @property SEL selector;
  24. @property int fileDescriptor;
  25. @end
  26. @implementation GVFileNotificationRecord
  27. @synthesize observer = _observer;
  28. @synthesize path = _path;
  29. @synthesize selector = _selector;
  30. @synthesize fileDescriptor = _fileDescriptor;
  31. - (id)initWithObserver:(id)observer path:(NSString *)path
  32. {
  33. if (self = [super init]) {
  34. _observer = observer;
  35. _path = [path retain];
  36. }
  37. return self;
  38. }
  39. - (BOOL)isEqual:(id)anObject
  40. {
  41. /* if the other object is one of us, compare observers + paths only */
  42. return [anObject isKindOfClass:[GVFileNotificationRecord class]] ? [self observer] == [anObject observer] && [[self path] isEqualToString:[anObject path]] : NO;
  43. }
  44. - (NSUInteger)hash
  45. {
  46. /* hash based on observers + paths only */
  47. return (NSUInteger)_observer ^ [_path hash];
  48. }
  49. - (void)dealloc
  50. {
  51. [_path release];
  52. [super dealloc];
  53. }
  54. @end
  55. static void noteFileChanged(CFFileDescriptorRef queue, CFOptionFlags callBackTypes, void *info)
  56. {
  57. int queueDescriptor = CFFileDescriptorGetNativeDescriptor(queue);
  58. /* grab the next event from the kernel queue */
  59. struct kevent event;
  60. kevent(queueDescriptor, NULL, 0, &event, 1, NULL);
  61. GVFileNotificationRecord *record = (GVFileNotificationRecord *)event.udata;
  62. if (record) {
  63. /* report the file change to the observer */
  64. [record.observer performSelector:record.selector withObject:record.path];
  65. /* if watched file is deleted, try to reopen the file and watch it again */
  66. if (event.fflags & NOTE_DELETE) {
  67. close(record.fileDescriptor);
  68. int fileDescriptor = open([record.path UTF8String], O_EVTONLY);
  69. if (fileDescriptor != -1) {
  70. struct kevent change;
  71. EV_SET (&change, fileDescriptor, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND, 0, record);
  72. if (kevent (queueDescriptor, &change, 1, NULL, 0, NULL) != -1)
  73. record.fileDescriptor = fileDescriptor;
  74. }
  75. }
  76. }
  77. /* reenable the callbacks (they were automatically disabled when handling the CFFileDescriptor) */
  78. CFFileDescriptorEnableCallBacks(queue, kCFFileDescriptorReadCallBack);
  79. }
  80. @implementation GVFileNotificationCenter
  81. + (void)initialize
  82. {
  83. if (!_defaultCenter)
  84. _defaultCenter = [[GVFileNotificationCenter alloc] init];
  85. }
  86. + (id)defaultCenter
  87. {
  88. return _defaultCenter;
  89. }
  90. - (id)init
  91. {
  92. if (self = [super init]) {
  93. /* create kernel queue, wrap a CFFileDescriptor around it and schedule it on the Cocoa run loop */
  94. _queue = CFFileDescriptorCreate(kCFAllocatorDefault, kqueue(), true, noteFileChanged, NULL);
  95. CFFileDescriptorEnableCallBacks(_queue, kCFFileDescriptorReadCallBack);
  96. CFRunLoopAddSource(
  97. [[NSRunLoop currentRunLoop] getCFRunLoop],
  98. CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, _queue, 0),
  99. kCFRunLoopDefaultMode);
  100. /* need to keep track of observers */
  101. _records = [[NSMutableSet alloc] init];
  102. }
  103. return self;
  104. }
  105. - (void)addObserver:(id)observer selector:(SEL)selector path:(NSString *)path
  106. {
  107. GVFileNotificationRecord *record = [[[GVFileNotificationRecord alloc] initWithObserver:observer path:path] autorelease];
  108. GVFileNotificationRecord *oldRecord = [_records member:record];
  109. if (oldRecord)
  110. /* record already exists, just update the selector */
  111. oldRecord.selector = selector;
  112. else {
  113. /* new record, start monitoring the path */
  114. int fileDescriptor = open([path UTF8String], O_EVTONLY);
  115. if (fileDescriptor != -1) {
  116. struct kevent change;
  117. EV_SET (&change, fileDescriptor, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND, 0, record);
  118. if (kevent (CFFileDescriptorGetNativeDescriptor(_queue), &change, 1, NULL, 0, NULL) != -1) {
  119. record.selector = selector;
  120. record.fileDescriptor = fileDescriptor;
  121. [_records addObject:record];
  122. }
  123. }
  124. }
  125. }
  126. - (void)removeObserver:(id)observer path:(NSString *)path
  127. {
  128. GVFileNotificationRecord *record = [_records member:[[[GVFileNotificationRecord alloc] initWithObserver:observer path:path] autorelease]];
  129. if (record) {
  130. close(record.fileDescriptor); /* closing the file descriptor also removes it from the kqueue */
  131. [_records removeObject:record];
  132. }
  133. }
  134. @end