MacFileWatcher.mm 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. //
  2. // Copyright (c) 2008-2017 the Urho3D project.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #import <Foundation/Foundation.h>
  23. #import <CoreServices/CoreServices.h>
  24. #include "../IO/MacFileWatcher.h"
  25. static void CallbackFunction(ConstFSEventStreamRef streamRef, void* clientCallBackInfo, size_t numEvents, void* eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);
  26. @interface MacFileWatcher : NSObject
  27. {
  28. NSString* pathName_;
  29. bool watchSubDirs_;
  30. FSEventStreamRef streamRef_;
  31. NSMutableString* changes_;
  32. }
  33. @property (nonatomic, retain) NSString* pathName;
  34. @property (nonatomic, assign) bool watchSubDirs;
  35. @property (nonatomic, retain) NSMutableString* changes;
  36. - (MacFileWatcher*)initWithPathName:(const char*)pathName recursive:(bool)watchSubDirs;
  37. - (const char*)readChanges;
  38. - (void)addChange:(NSString*)fileName;
  39. @end
  40. @implementation MacFileWatcher
  41. @synthesize pathName=pathName_;
  42. @synthesize watchSubDirs=watchSubDirs_;
  43. @synthesize changes=changes_;
  44. - (MacFileWatcher*)initWithPathName:(const char*)pathName recursive:(bool)watchSubDirs
  45. {
  46. self = [super init];
  47. if (self)
  48. {
  49. self.pathName = [[[NSString stringWithUTF8String:pathName] stringByResolvingSymlinksInPath] stringByAppendingString:@"/"];
  50. self.watchSubDirs = watchSubDirs;
  51. self.changes = [NSMutableString stringWithString:@""];
  52. CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&pathName_, 1, NULL);
  53. FSEventStreamContext context = {0, self, NULL, NULL, NULL};
  54. CFAbsoluteTime latency = 0.1; // in seconds
  55. streamRef_ = FSEventStreamCreate(NULL, &CallbackFunction, &context, pathsToWatch, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents);
  56. CFRelease(pathsToWatch);
  57. if (streamRef_)
  58. {
  59. FSEventStreamScheduleWithRunLoop(streamRef_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
  60. if (!FSEventStreamStart(streamRef_))
  61. {
  62. [self release];
  63. return nil;
  64. }
  65. }
  66. }
  67. return self;
  68. }
  69. - (void)dealloc
  70. {
  71. FSEventStreamStop(streamRef_);
  72. FSEventStreamInvalidate(streamRef_);
  73. FSEventStreamRelease(streamRef_);
  74. streamRef_ = NULL;
  75. self.changes = nil;
  76. self.pathName = nil;
  77. [super dealloc];
  78. }
  79. - (const char*)readChanges
  80. {
  81. @synchronized(changes_)
  82. {
  83. const char* changes = [changes_ UTF8String];
  84. [changes_ setString:@""];
  85. return changes;
  86. }
  87. }
  88. - (void)addChange:(NSString*)fileName
  89. {
  90. @synchronized(changes_)
  91. {
  92. // Use character with ASCII code 1 as separator character
  93. [changes_ appendFormat:@"%@%c", fileName, 1];
  94. }
  95. }
  96. @end
  97. static void CallbackFunction(ConstFSEventStreamRef streamRef, void* clientCallBackInfo, size_t numEvents, void* eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[])
  98. {
  99. MacFileWatcher* watcher = (MacFileWatcher*)clientCallBackInfo;
  100. // ATOMIC BEGIN
  101. NSArray* paths = (NSArray*)eventPaths;
  102. // ATOMIC END
  103. int index = -1;
  104. for (NSString* fileName in paths)
  105. {
  106. ++index;
  107. if (![fileName length])
  108. continue;
  109. if ([[fileName lastPathComponent] isEqualToString:@".DS_Store"])
  110. continue;
  111. if ([fileName hasPrefix:watcher.pathName])
  112. fileName = [fileName substringFromIndex:[watcher.pathName length]];
  113. // Skip if event path is a sub dir and watch sub dirs is not requested
  114. if (!watcher.watchSubDirs && [fileName rangeOfString:@"/"].location != NSNotFound)
  115. continue;
  116. FSEventStreamEventFlags flags = eventFlags[index];
  117. if (flags & kFSEventStreamEventFlagItemIsFile && (
  118. flags & kFSEventStreamEventFlagItemCreated || flags & kFSEventStreamEventFlagItemModified ||
  119. flags & kFSEventStreamEventFlagItemRenamed || flags & kFSEventStreamEventFlagItemRemoved))
  120. {
  121. [watcher addChange:fileName];
  122. }
  123. }
  124. }
  125. bool CheckMinimalVersion(int major, int minor)
  126. {
  127. NSString* plistPath = @"/System/Library/CoreServices/SystemVersion.plist";
  128. NSDictionary* pListDict = [[[NSDictionary alloc] initWithContentsOfFile:plistPath] autorelease];
  129. NSString* productVersion = [pListDict valueForKey:@"ProductVersion"];
  130. NSArray* parts = [productVersion componentsSeparatedByString: @"."];
  131. if ([[parts objectAtIndex:0] integerValue] < major)
  132. return false;
  133. if ([[parts objectAtIndex:1] integerValue] < minor)
  134. return false;
  135. return true;
  136. }
  137. bool IsFileWatcherSupported()
  138. {
  139. // The FS Event API only supports individual file watching in 10.7 (Lion) or later
  140. return CheckMinimalVersion(10, 7);
  141. }
  142. void* CreateFileWatcher(const char* pathName, bool watchSubDirs)
  143. {
  144. // Do not autorelease as the caller has no way to retain it
  145. return [[MacFileWatcher alloc] initWithPathName:pathName recursive:watchSubDirs];
  146. }
  147. void CloseFileWatcher(void* watcher)
  148. {
  149. // Release the object now
  150. [(MacFileWatcher*)watcher release];
  151. }
  152. const char* ReadFileWatcher(void* watcher)
  153. {
  154. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  155. const char* result = [(MacFileWatcher*)watcher readChanges];
  156. [pool release];
  157. pool = nil;
  158. return result;
  159. }