/SparkleShare/Mac/SparkleMacWatcher.cs
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 27 28using System; 29using System.Collections.Generic; 30using System.Diagnostics; 31using System.Runtime.InteropServices; 32using System.IO; 33using System.Threading; 34using System.Timers; 35 36using MonoMac.AppKit; 37using MonoMac.Foundation; 38 39namespace SparkleShare { 40 41 public sealed class SparkleMacWatcher : IDisposable { 42 43 public delegate void ChangedEventHandler (List<string> paths); 44 public event ChangedEventHandler Changed; 45 46 public string BasePath { get; private set; } 47 48 49 [Flags] 50 [Serializable] 51 private enum FSEventStreamCreateFlags : uint 52 { 53 kFSEventStreamCreateFlagNone = 0x00000000, 54 kFSEventStreamCreateFlagUseCFTypes = 0x00000001, 55 kFSEventStreamCreateFlagNoDefer = 0x00000002, 56 kFSEventStreamCreateFlagWatchRoot = 0x00000004, 57 } 58 59 private DateTime last_found_timestamp; 60 private IntPtr m_stream; 61 private FSEventStreamCallback m_callback; // need to keep a reference around so that it isn't GC'ed 62 private static readonly IntPtr kCFRunLoopDefaultMode = new NSString ("kCFRunLoopDefaultMode").Handle; 63 private ulong kFSEventStreamEventIdSinceNow = 0xFFFFFFFFFFFFFFFFUL; 64 65 private delegate void FSEventStreamCallback (IntPtr streamRef, IntPtr clientCallBackInfo, 66 int numEvents, IntPtr eventPaths, IntPtr eventFlags, IntPtr eventIds); 67 68 69 ~SparkleMacWatcher () 70 { 71 Dispose (false); 72 } 73 74 75 public SparkleMacWatcher (string path) 76 { 77 BasePath = path; 78 m_callback = DoCallback; 79 80 NSString [] s = new NSString [1]; 81 s [0] = new NSString (path); 82 NSArray path_p = NSArray.FromNSObjects (s); 83 84 m_stream = FSEventStreamCreate ( // note that the stream will always be valid 85 IntPtr.Zero, // allocator 86 m_callback, // callback 87 IntPtr.Zero, // context 88 path_p.Handle, // pathsToWatch 89 kFSEventStreamEventIdSinceNow, // sinceWhen 90 2, // latency (in seconds) 91 FSEventStreamCreateFlags.kFSEventStreamCreateFlagNone); // flags 92 93 FSEventStreamScheduleWithRunLoop ( 94 m_stream, // streamRef 95 CFRunLoopGetMain(), // runLoop 96 kCFRunLoopDefaultMode); // runLoopMode 97 98 bool started = FSEventStreamStart (m_stream); 99 if (!started) { 100 GC.SuppressFinalize (this); 101 throw new InvalidOperationException ("Failed to start FSEvent stream for " + path); 102 } 103 } 104 105 106 public void Dispose () 107 { 108 Dispose (true); 109 GC.SuppressFinalize (this); 110 } 111 112 113 private void Dispose (bool disposing) 114 { 115 if (m_stream != IntPtr.Zero) { 116 FSEventStreamStop (m_stream); 117 FSEventStreamInvalidate (m_stream); 118 FSEventStreamRelease (m_stream); 119 120 m_stream = IntPtr.Zero; 121 } 122 } 123 124 125 private void checkDirectory (string dir) 126 { 127 if (dir == null) 128 return; 129 130 DirectoryInfo parent = new DirectoryInfo (dir); 131 132 if (!parent.FullName.Contains ("/.") && 133 DateTime.Compare (parent.LastWriteTime, this.last_found_timestamp) > 0) { 134 135 last_found_timestamp = parent.LastWriteTime; 136 } 137 } 138 139 140 private void DoCallback (IntPtr streamRef, IntPtr clientCallBackInfo, 141 int numEvents, IntPtr eventPaths, IntPtr eventFlags, IntPtr eventIds) 142 { 143 int bytes = Marshal.SizeOf (typeof (IntPtr)); 144 string [] paths = new string [numEvents]; 145 146 for (int i = 0; i < numEvents; ++i) { 147 IntPtr p = Marshal.ReadIntPtr (eventPaths, i * bytes); 148 paths [i] = Marshal.PtrToStringAnsi (p); 149 checkDirectory (paths [i]); 150 } 151 152 var handler = Changed; 153 if (handler != null) { 154 List<string> filtered_paths = new List<string> (); 155 foreach (var path in paths) { 156 if (path.Length > BasePath.Length) { 157 var t = path.Substring (BasePath.Length); 158 t = t.Trim ("/".ToCharArray ()); 159 160 if (!string.IsNullOrWhiteSpace (t)) 161 filtered_paths.Add(t); 162 } 163 } 164 165 if(filtered_paths.Count > 0) 166 handler (filtered_paths); 167 } 168 169 GC.KeepAlive (this); 170 } 171 172 173 [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")] 174 private extern static IntPtr CFRunLoopGetMain (); 175 176 [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")] 177 private extern static IntPtr FSEventStreamCreate (IntPtr allocator, FSEventStreamCallback callback, 178 IntPtr context, IntPtr pathsToWatch, ulong sinceWhen, double latency, FSEventStreamCreateFlags flags); 179 180 [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")] 181 private extern static void FSEventStreamScheduleWithRunLoop (IntPtr streamRef, IntPtr runLoop, IntPtr runLoopMode); 182 183 [return: MarshalAs (UnmanagedType.U1)] 184 [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")] 185 private extern static bool FSEventStreamStart (IntPtr streamRef); 186 187 [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")] 188 private extern static void FSEventStreamStop (IntPtr streamRef); 189 190 [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")] 191 private extern static void FSEventStreamInvalidate (IntPtr streamRef); 192 193 [DllImport("/System/Library/Frameworks/CoreServices.framework/CoreServices")] 194 private extern static void FSEventStreamRelease (IntPtr streamRef); 195 } 196}