MacFileWatcher.m 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. //
  2. // Copyright (c) 2008-2020 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. NSArray* paths = eventPaths;
  101. int index = -1;
  102. for (NSString* fileName in paths)
  103. {
  104. ++index;
  105. if (![fileName length])
  106. continue;
  107. if ([[fileName lastPathComponent] isEqualToString:@".DS_Store"])
  108. continue;
  109. if ([fileName hasPrefix:watcher.pathName])
  110. fileName = [fileName substringFromIndex:[watcher.pathName length]];
  111. // Skip if event path is a sub dir and watch sub dirs is not requested
  112. if (!watcher.watchSubDirs && [fileName rangeOfString:@"/"].location != NSNotFound)
  113. continue;
  114. FSEventStreamEventFlags flags = eventFlags[index];
  115. if (flags & kFSEventStreamEventFlagItemIsFile && (
  116. flags & kFSEventStreamEventFlagItemCreated || flags & kFSEventStreamEventFlagItemModified ||
  117. flags & kFSEventStreamEventFlagItemRenamed || flags & kFSEventStreamEventFlagItemRemoved))
  118. {
  119. [watcher addChange:fileName];
  120. }
  121. }
  122. }
  123. bool CheckMinimalVersion(int major, int minor)
  124. {
  125. NSString* plistPath = @"/System/Library/CoreServices/SystemVersion.plist";
  126. NSDictionary* pListDict = [[[NSDictionary alloc] initWithContentsOfFile:plistPath] autorelease];
  127. NSString* productVersion = [pListDict valueForKey:@"ProductVersion"];
  128. NSArray* parts = [productVersion componentsSeparatedByString: @"."];
  129. if ([[parts objectAtIndex:0] integerValue] < major)
  130. return false;
  131. if ([[parts objectAtIndex:1] integerValue] < minor)
  132. return false;
  133. return true;
  134. }
  135. bool IsFileWatcherSupported()
  136. {
  137. // The FS Event API only supports individual file watching in 10.7 (Lion) or later
  138. return CheckMinimalVersion(10, 7);
  139. }
  140. void* CreateFileWatcher(const char* pathName, bool watchSubDirs)
  141. {
  142. // Do not autorelease as the caller has no way to retain it
  143. return [[MacFileWatcher alloc] initWithPathName:pathName recursive:watchSubDirs];
  144. }
  145. void CloseFileWatcher(void* watcher)
  146. {
  147. // Release the object now
  148. [(MacFileWatcher*)watcher release];
  149. }
  150. const char* ReadFileWatcher(void* watcher)
  151. {
  152. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  153. const char* result = [(MacFileWatcher*)watcher readChanges];
  154. [pool release];
  155. pool = nil;
  156. return result;
  157. }