/SparkleShare/Mac/SparkleMacWatcher.cs

http://github.com/hbons/SparkleShare · C# · 196 lines · 127 code · 44 blank · 25 comment · 12 complexity · 207dc0fcb14d40eea99704462d5a4bcf MD5 · raw file

  1. // Originally taken from:
  2. // https://github.com/jesse99/Continuum/blob/master/source/shared/DirectoryWatcher.cs
  3. // Modified to use MonoMac and integrate into SparkleShare
  4. //
  5. // Copyright (C) 2008 Jesse Jones
  6. // Copyright (C) 2012 Hylke Bons
  7. //
  8. // Permission is hereby granted, free of charge, to any person obtaining
  9. // a copy of this software and associated documentation files (the
  10. // "Software"), to deal in the Software without restriction, including
  11. // without limitation the rights to use, copy, modify, merge, publish,
  12. // distribute, sublicense, and/or sell copies of the Software, and to
  13. // permit persons to whom the Software is furnished to do so, subject to
  14. // the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be
  17. // included in all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  20. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  21. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  22. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  23. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  24. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  25. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  26. using System;
  27. using System.Collections.Generic;
  28. using System.Diagnostics;
  29. using System.Runtime.InteropServices;
  30. using System.IO;
  31. using System.Threading;
  32. using System.Timers;
  33. using MonoMac.AppKit;
  34. using MonoMac.Foundation;
  35. namespace SparkleShare {
  36. public sealed class SparkleMacWatcher : IDisposable {
  37. public delegate void ChangedEventHandler (List<string> paths);
  38. public event ChangedEventHandler Changed;
  39. public string BasePath { get; private set; }
  40. [Flags]
  41. [Serializable]
  42. private enum FSEventStreamCreateFlags : uint
  43. {
  44. kFSEventStreamCreateFlagNone = 0x00000000,
  45. kFSEventStreamCreateFlagUseCFTypes = 0x00000001,
  46. kFSEventStreamCreateFlagNoDefer = 0x00000002,
  47. kFSEventStreamCreateFlagWatchRoot = 0x00000004,
  48. }
  49. private DateTime last_found_timestamp;
  50. private IntPtr m_stream;
  51. private FSEventStreamCallback m_callback; // need to keep a reference around so that it isn't GC'ed
  52. private static readonly IntPtr kCFRunLoopDefaultMode = new NSString ("kCFRunLoopDefaultMode").Handle;
  53. private ulong kFSEventStreamEventIdSinceNow = 0xFFFFFFFFFFFFFFFFUL;
  54. private delegate void FSEventStreamCallback (IntPtr streamRef, IntPtr clientCallBackInfo,
  55. int numEvents, IntPtr eventPaths, IntPtr eventFlags, IntPtr eventIds);
  56. ~SparkleMacWatcher ()
  57. {
  58. Dispose (false);
  59. }
  60. public SparkleMacWatcher (string path)
  61. {
  62. BasePath = path;
  63. m_callback = DoCallback;
  64. NSString [] s = new NSString [1];
  65. s [0] = new NSString (path);
  66. NSArray path_p = NSArray.FromNSObjects (s);
  67. m_stream = FSEventStreamCreate ( // note that the stream will always be valid
  68. IntPtr.Zero, // allocator
  69. m_callback, // callback
  70. IntPtr.Zero, // context
  71. path_p.Handle, // pathsToWatch
  72. kFSEventStreamEventIdSinceNow, // sinceWhen
  73. 2, // latency (in seconds)
  74. FSEventStreamCreateFlags.kFSEventStreamCreateFlagNone); // flags
  75. FSEventStreamScheduleWithRunLoop (
  76. m_stream, // streamRef
  77. CFRunLoopGetMain(), // runLoop
  78. kCFRunLoopDefaultMode); // runLoopMode
  79. bool started = FSEventStreamStart (m_stream);
  80. if (!started) {
  81. GC.SuppressFinalize (this);
  82. throw new InvalidOperationException ("Failed to start FSEvent stream for " + path);
  83. }
  84. }
  85. public void Dispose ()
  86. {
  87. Dispose (true);
  88. GC.SuppressFinalize (this);
  89. }
  90. private void Dispose (bool disposing)
  91. {
  92. if (m_stream != IntPtr.Zero) {
  93. FSEventStreamStop (m_stream);
  94. FSEventStreamInvalidate (m_stream);
  95. FSEventStreamRelease (m_stream);
  96. m_stream = IntPtr.Zero;
  97. }
  98. }
  99. private void checkDirectory (string dir)
  100. {
  101. if (dir == null)
  102. return;
  103. DirectoryInfo parent = new DirectoryInfo (dir);
  104. if (!parent.FullName.Contains ("/.") &&
  105. DateTime.Compare (parent.LastWriteTime, this.last_found_timestamp) > 0) {
  106. last_found_timestamp = parent.LastWriteTime;
  107. }
  108. }
  109. private void DoCallback (IntPtr streamRef, IntPtr clientCallBackInfo,
  110. int numEvents, IntPtr eventPaths, IntPtr eventFlags, IntPtr eventIds)
  111. {
  112. int bytes = Marshal.SizeOf (typeof (IntPtr));
  113. string [] paths = new string [numEvents];
  114. for (int i = 0; i < numEvents; ++i) {
  115. IntPtr p = Marshal.ReadIntPtr (eventPaths, i * bytes);
  116. paths [i] = Marshal.PtrToStringAnsi (p);
  117. checkDirectory (paths [i]);
  118. }
  119. var handler = Changed;
  120. if (handler != null) {
  121. List<string> filtered_paths = new List<string> ();
  122. foreach (var path in paths) {
  123. if (path.Length > BasePath.Length) {
  124. var t = path.Substring (BasePath.Length);
  125. t = t.Trim ("/".ToCharArray ());
  126. if (!string.IsNullOrWhiteSpace (t))
  127. filtered_paths.Add(t);
  128. }
  129. }
  130. if(filtered_paths.Count > 0)
  131. handler (filtered_paths);
  132. }
  133. GC.KeepAlive (this);
  134. }
  135. [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
  136. private extern static IntPtr CFRunLoopGetMain ();
  137. [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
  138. private extern static IntPtr FSEventStreamCreate (IntPtr allocator, FSEventStreamCallback callback,
  139. IntPtr context, IntPtr pathsToWatch, ulong sinceWhen, double latency, FSEventStreamCreateFlags flags);
  140. [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
  141. private extern static void FSEventStreamScheduleWithRunLoop (IntPtr streamRef, IntPtr runLoop, IntPtr runLoopMode);
  142. [return: MarshalAs (UnmanagedType.U1)]
  143. [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
  144. private extern static bool FSEventStreamStart (IntPtr streamRef);
  145. [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
  146. private extern static void FSEventStreamStop (IntPtr streamRef);
  147. [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
  148. private extern static void FSEventStreamInvalidate (IntPtr streamRef);
  149. [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")]
  150. private extern static void FSEventStreamRelease (IntPtr streamRef);
  151. }
  152. }