PageRenderTime 23ms CodeModel.GetById 15ms app.highlight 5ms RepoModel.GetById 1ms app.codeStats 0ms

/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
 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}