MacFileWatcher.m 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Copyright (c) 2008-2023 the Urho3D project
  2. // License: MIT
  3. #import <Foundation/Foundation.h>
  4. #import <CoreServices/CoreServices.h>
  5. #include "../IO/MacFileWatcher.h"
  6. static void CallbackFunction(ConstFSEventStreamRef streamRef, void* clientCallBackInfo, size_t numEvents, void* eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);
  7. @interface MacFileWatcher : NSObject
  8. {
  9. NSString* pathName_;
  10. bool watchSubDirs_;
  11. FSEventStreamRef streamRef_;
  12. NSMutableString* changes_;
  13. }
  14. @property (nonatomic, retain) NSString* pathName;
  15. @property (nonatomic, assign) bool watchSubDirs;
  16. @property (nonatomic, retain) NSMutableString* changes;
  17. - (MacFileWatcher*)initWithPathName:(const char*)pathName recursive:(bool)watchSubDirs;
  18. - (const char*)readChanges;
  19. - (void)addChange:(NSString*)fileName;
  20. @end
  21. @implementation MacFileWatcher
  22. @synthesize pathName=pathName_;
  23. @synthesize watchSubDirs=watchSubDirs_;
  24. @synthesize changes=changes_;
  25. - (MacFileWatcher*)initWithPathName:(const char*)pathName recursive:(bool)watchSubDirs
  26. {
  27. self = [super init];
  28. if (self)
  29. {
  30. self.pathName = [[[NSString stringWithUTF8String:pathName] stringByResolvingSymlinksInPath] stringByAppendingString:@"/"];
  31. self.watchSubDirs = watchSubDirs;
  32. self.changes = [NSMutableString stringWithString:@""];
  33. CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&pathName_, 1, NULL);
  34. FSEventStreamContext context = {0, self, NULL, NULL, NULL};
  35. CFAbsoluteTime latency = 0.1; // in seconds
  36. streamRef_ = FSEventStreamCreate(NULL, &CallbackFunction, &context, pathsToWatch, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents);
  37. CFRelease(pathsToWatch);
  38. if (streamRef_)
  39. {
  40. FSEventStreamScheduleWithRunLoop(streamRef_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
  41. if (!FSEventStreamStart(streamRef_))
  42. {
  43. [self release];
  44. return nil;
  45. }
  46. }
  47. }
  48. return self;
  49. }
  50. - (void)dealloc
  51. {
  52. FSEventStreamStop(streamRef_);
  53. FSEventStreamInvalidate(streamRef_);
  54. FSEventStreamRelease(streamRef_);
  55. streamRef_ = NULL;
  56. self.changes = nil;
  57. self.pathName = nil;
  58. [super dealloc];
  59. }
  60. - (const char*)readChanges
  61. {
  62. @synchronized(changes_)
  63. {
  64. const char* changes = [changes_ UTF8String];
  65. [changes_ setString:@""];
  66. return changes;
  67. }
  68. }
  69. - (void)addChange:(NSString*)fileName
  70. {
  71. @synchronized(changes_)
  72. {
  73. // Use character with ASCII code 1 as separator character
  74. [changes_ appendFormat:@"%@%c", fileName, 1];
  75. }
  76. }
  77. @end
  78. static void CallbackFunction(ConstFSEventStreamRef streamRef, void* clientCallBackInfo, size_t numEvents, void* eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[])
  79. {
  80. MacFileWatcher* watcher = (MacFileWatcher*)clientCallBackInfo;
  81. NSArray* paths = eventPaths;
  82. int index = -1;
  83. for (NSString* fileName in paths)
  84. {
  85. ++index;
  86. if (![fileName length])
  87. continue;
  88. if ([[fileName lastPathComponent] isEqualToString:@".DS_Store"])
  89. continue;
  90. if ([fileName hasPrefix:watcher.pathName])
  91. fileName = [fileName substringFromIndex:[watcher.pathName length]];
  92. // Skip if event path is a sub dir and watch sub dirs is not requested
  93. if (!watcher.watchSubDirs && [fileName rangeOfString:@"/"].location != NSNotFound)
  94. continue;
  95. FSEventStreamEventFlags flags = eventFlags[index];
  96. if (flags & kFSEventStreamEventFlagItemIsFile && (
  97. flags & kFSEventStreamEventFlagItemCreated || flags & kFSEventStreamEventFlagItemModified ||
  98. flags & kFSEventStreamEventFlagItemRenamed || flags & kFSEventStreamEventFlagItemRemoved))
  99. {
  100. [watcher addChange:fileName];
  101. }
  102. }
  103. }
  104. bool CheckMinimalVersion(int major, int minor)
  105. {
  106. NSString* plistPath = @"/System/Library/CoreServices/SystemVersion.plist";
  107. NSDictionary* pListDict = [[[NSDictionary alloc] initWithContentsOfFile:plistPath] autorelease];
  108. NSString* productVersion = [pListDict valueForKey:@"ProductVersion"];
  109. NSArray* parts = [productVersion componentsSeparatedByString: @"."];
  110. if ([[parts objectAtIndex:0] integerValue] < major)
  111. return false;
  112. if ([[parts objectAtIndex:1] integerValue] < minor)
  113. return false;
  114. return true;
  115. }
  116. bool IsFileWatcherSupported()
  117. {
  118. // The FS Event API only supports individual file watching in 10.7 (Lion) or later
  119. return CheckMinimalVersion(10, 7);
  120. }
  121. void* CreateFileWatcher(const char* pathName, bool watchSubDirs)
  122. {
  123. // Do not autorelease as the caller has no way to retain it
  124. return [[MacFileWatcher alloc] initWithPathName:pathName recursive:watchSubDirs];
  125. }
  126. void CloseFileWatcher(void* watcher)
  127. {
  128. // Release the object now
  129. [(MacFileWatcher*)watcher release];
  130. }
  131. const char* ReadFileWatcher(void* watcher)
  132. {
  133. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  134. const char* result = [(MacFileWatcher*)watcher readChanges];
  135. [pool release];
  136. pool = nil;
  137. return result;
  138. }