FileSystemWatcher.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. //
  2. // System.IO.FileSystemWatcher.cs
  3. //
  4. // Authors:
  5. // Tim Coleman ([email protected])
  6. // Gonzalo Paniagua Javier ([email protected])
  7. //
  8. // Copyright (C) Tim Coleman, 2002
  9. // (c) 2003 Ximian, Inc. (http://www.ximian.com)
  10. // (c) 2004 Novell, Inc. (http://www.novell.com)
  11. //
  12. using System;
  13. using System.ComponentModel;
  14. using System.Runtime.CompilerServices;
  15. using System.Runtime.InteropServices;
  16. using System.Threading;
  17. namespace System.IO {
  18. [DefaultEvent("Changed")]
  19. public class FileSystemWatcher : Component, ISupportInitialize {
  20. #region Fields
  21. bool enableRaisingEvents;
  22. string filter;
  23. bool includeSubdirectories;
  24. int internalBufferSize;
  25. NotifyFilters notifyFilter;
  26. string path;
  27. string fullpath;
  28. ISynchronizeInvoke synchronizingObject;
  29. bool disposed;
  30. WaitForChangedResult lastData;
  31. bool waiting;
  32. SearchPattern2 pattern;
  33. static IFileWatcher watcher;
  34. #endregion // Fields
  35. #region Constructors
  36. public FileSystemWatcher ()
  37. {
  38. this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
  39. this.enableRaisingEvents = false;
  40. this.filter = "*.*";
  41. this.includeSubdirectories = false;
  42. this.internalBufferSize = 8192;
  43. this.path = "";
  44. InitWatcher ();
  45. }
  46. public FileSystemWatcher (string path)
  47. : this (path, "*.*")
  48. {
  49. }
  50. public FileSystemWatcher (string path, string filter)
  51. {
  52. if (path == null)
  53. throw new ArgumentNullException ("path");
  54. if (filter == null)
  55. throw new ArgumentNullException ("filter");
  56. if (path == String.Empty)
  57. throw new ArgumentException ("Empty path", "path");
  58. if (!Directory.Exists (path))
  59. throw new ArgumentException ("Directory does not exists", "path");
  60. this.enableRaisingEvents = false;
  61. this.filter = filter;
  62. this.includeSubdirectories = false;
  63. this.internalBufferSize = 8192;
  64. this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
  65. this.path = path;
  66. this.synchronizingObject = null;
  67. InitWatcher ();
  68. }
  69. void InitWatcher ()
  70. {
  71. lock (typeof (FileSystemWatcher)) {
  72. if (watcher != null)
  73. return;
  74. string managed = Environment.GetEnvironmentVariable ("MONO_MANAGED_WATCHER");
  75. int mode = 0;
  76. if (managed == null)
  77. mode = InternalSupportsFSW ();
  78. bool ok = false;
  79. if (mode == 2)
  80. ok = FAMWatcher.GetInstance (out watcher);
  81. else if (mode == 1)
  82. ok = DefaultWatcher.GetInstance (out watcher);
  83. //ok = WindowsWatcher.GetInstance (out watcher);
  84. if (mode == 0 || !ok)
  85. DefaultWatcher.GetInstance (out watcher);
  86. }
  87. }
  88. #endregion // Constructors
  89. #region Properties
  90. /* If this is enabled, we Pulse this instance */
  91. internal bool Waiting {
  92. get { return waiting; }
  93. set { waiting = value; }
  94. }
  95. internal SearchPattern2 Pattern {
  96. get {
  97. if (pattern == null) {
  98. string f = Filter;
  99. if (f == "*.*" && !(watcher.GetType () == typeof (WindowsWatcher)))
  100. f = "*";
  101. pattern = new SearchPattern2 (f);
  102. }
  103. return pattern;
  104. }
  105. }
  106. internal string FullPath {
  107. get {
  108. if (fullpath == null) {
  109. if (path == null || path == "")
  110. fullpath = Environment.CurrentDirectory;
  111. else
  112. fullpath = System.IO.Path.GetFullPath (path);
  113. }
  114. return fullpath;
  115. }
  116. }
  117. [DefaultValue(false)]
  118. [IODescription("Flag to indicate if this instance is active")]
  119. public bool EnableRaisingEvents {
  120. get { return enableRaisingEvents; }
  121. set {
  122. if (value == enableRaisingEvents)
  123. return; // Do nothing
  124. enableRaisingEvents = value;
  125. if (value) {
  126. Start ();
  127. } else {
  128. Stop ();
  129. }
  130. }
  131. }
  132. [DefaultValue("*.*")]
  133. [IODescription("File name filter pattern")]
  134. [RecommendedAsConfigurable(true)]
  135. [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
  136. public string Filter {
  137. get { return filter; }
  138. set {
  139. if (value == null || value == "")
  140. value = "*.*";
  141. if (filter != value) {
  142. filter = value;
  143. pattern = null;
  144. }
  145. }
  146. }
  147. [DefaultValue(false)]
  148. [IODescription("Flag to indicate we want to watch subdirectories")]
  149. public bool IncludeSubdirectories {
  150. get { return includeSubdirectories; }
  151. set {
  152. if (includeSubdirectories == value)
  153. return;
  154. includeSubdirectories = value;
  155. if (value && enableRaisingEvents) {
  156. Stop ();
  157. Start ();
  158. }
  159. }
  160. }
  161. [Browsable(false)]
  162. [DefaultValue(8192)]
  163. public int InternalBufferSize {
  164. get { return internalBufferSize; }
  165. set {
  166. if (internalBufferSize == value)
  167. return;
  168. if (value < 4196)
  169. value = 4196;
  170. internalBufferSize = value;
  171. if (enableRaisingEvents) {
  172. Stop ();
  173. Start ();
  174. }
  175. }
  176. }
  177. [DefaultValue(NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite)]
  178. [IODescription("Flag to indicate which change event we want to monitor")]
  179. public NotifyFilters NotifyFilter {
  180. get { return notifyFilter; }
  181. set {
  182. if (notifyFilter == value)
  183. return;
  184. notifyFilter = value;
  185. if (enableRaisingEvents) {
  186. Stop ();
  187. Start ();
  188. }
  189. }
  190. }
  191. [DefaultValue("")]
  192. [IODescription("The directory to monitor")]
  193. [RecommendedAsConfigurable(true)]
  194. [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
  195. [Editor ("System.Diagnostics.Design.FSWPathEditor, " + Consts.AssemblySystem_Design, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
  196. public string Path {
  197. get { return path; }
  198. set {
  199. if (path == value)
  200. return;
  201. bool exists = false;
  202. Exception exc = null;
  203. try {
  204. exists = Directory.Exists (value);
  205. } catch (Exception e) {
  206. exc = e;
  207. }
  208. if (exc != null)
  209. throw new ArgumentException ("Invalid directory name", "value", exc);
  210. if (!exists)
  211. throw new ArgumentException ("Directory does not exists", "value");
  212. path = value;
  213. fullpath = null;
  214. if (enableRaisingEvents) {
  215. Stop ();
  216. Start ();
  217. }
  218. }
  219. }
  220. [Browsable(false)]
  221. public override ISite Site {
  222. get { return base.Site; }
  223. set { base.Site = value; }
  224. }
  225. [DefaultValue(null)]
  226. [IODescription("The object used to marshal the event handler calls resulting from a directory change")]
  227. public ISynchronizeInvoke SynchronizingObject {
  228. get { return synchronizingObject; }
  229. set { synchronizingObject = value; }
  230. }
  231. #endregion // Properties
  232. #region Methods
  233. [MonoTODO]
  234. public void BeginInit ()
  235. {
  236. throw new NotImplementedException ();
  237. }
  238. protected override void Dispose (bool disposing)
  239. {
  240. if (disposing) {
  241. Stop ();
  242. }
  243. disposed = true;
  244. base.Dispose (disposing);
  245. }
  246. [MonoTODO]
  247. public void EndInit ()
  248. {
  249. throw new NotImplementedException ();
  250. }
  251. private void RaiseEvent (Delegate ev, EventArgs arg)
  252. {
  253. if (ev == null)
  254. return;
  255. object [] args = new object [] {this, arg};
  256. if (synchronizingObject == null) {
  257. ev.DynamicInvoke (args);
  258. return;
  259. }
  260. synchronizingObject.BeginInvoke (ev, args);
  261. }
  262. protected void OnChanged (FileSystemEventArgs e)
  263. {
  264. RaiseEvent (Changed, e);
  265. }
  266. protected void OnCreated (FileSystemEventArgs e)
  267. {
  268. RaiseEvent (Created, e);
  269. }
  270. protected void OnDeleted (FileSystemEventArgs e)
  271. {
  272. RaiseEvent (Deleted, e);
  273. }
  274. protected void OnError (ErrorEventArgs e)
  275. {
  276. RaiseEvent (Error, e);
  277. }
  278. protected void OnRenamed (RenamedEventArgs e)
  279. {
  280. RaiseEvent (Renamed, e);
  281. }
  282. public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType)
  283. {
  284. return WaitForChanged (changeType, Timeout.Infinite);
  285. }
  286. public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType, int timeout)
  287. {
  288. WaitForChangedResult result = new WaitForChangedResult ();
  289. bool prevEnabled = EnableRaisingEvents;
  290. if (!prevEnabled)
  291. EnableRaisingEvents = true;
  292. bool gotData;
  293. lock (this) {
  294. waiting = true;
  295. gotData = Monitor.Wait (this, timeout);
  296. if (gotData)
  297. result = this.lastData;
  298. }
  299. EnableRaisingEvents = prevEnabled;
  300. if (!gotData)
  301. result.TimedOut = true;
  302. return result;
  303. }
  304. internal void DispatchEvents (FileAction act, string filename, ref RenamedEventArgs renamed)
  305. {
  306. if (waiting) {
  307. lastData = new WaitForChangedResult ();
  308. }
  309. switch (act) {
  310. case FileAction.Added:
  311. lastData.Name = filename;
  312. lastData.ChangeType = WatcherChangeTypes.Created;
  313. OnCreated (new FileSystemEventArgs (WatcherChangeTypes.Created, path, filename));
  314. break;
  315. case FileAction.Removed:
  316. lastData.Name = filename;
  317. lastData.ChangeType = WatcherChangeTypes.Deleted;
  318. OnDeleted (new FileSystemEventArgs (WatcherChangeTypes.Deleted, path, filename));
  319. break;
  320. case FileAction.Modified:
  321. lastData.Name = filename;
  322. lastData.ChangeType = WatcherChangeTypes.Changed;
  323. OnChanged (new FileSystemEventArgs (WatcherChangeTypes.Changed, path, filename));
  324. break;
  325. case FileAction.RenamedOldName:
  326. if (renamed != null) {
  327. OnRenamed (renamed);
  328. }
  329. lastData.OldName = filename;
  330. lastData.ChangeType = WatcherChangeTypes.Renamed;
  331. renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, path, null, filename);
  332. break;
  333. case FileAction.RenamedNewName:
  334. lastData.Name = filename;
  335. lastData.ChangeType = WatcherChangeTypes.Renamed;
  336. if (renamed != null) {
  337. renamed.SetName (filename);
  338. } else {
  339. renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, path, filename, null);
  340. }
  341. OnRenamed (renamed);
  342. renamed = null;
  343. break;
  344. default:
  345. break;
  346. }
  347. }
  348. void Start ()
  349. {
  350. watcher.StartDispatching (this);
  351. }
  352. void Stop ()
  353. {
  354. watcher.StopDispatching (this);
  355. }
  356. #endregion // Methods
  357. #region Events and Delegates
  358. [IODescription("Occurs when a file/directory change matches the filter")]
  359. public event FileSystemEventHandler Changed;
  360. [IODescription("Occurs when a file/directory creation matches the filter")]
  361. public event FileSystemEventHandler Created;
  362. [IODescription("Occurs when a file/directory deletion matches the filter")]
  363. public event FileSystemEventHandler Deleted;
  364. [Browsable(false)]
  365. public event ErrorEventHandler Error;
  366. [IODescription("Occurs when a file/directory rename matches the filter")]
  367. public event RenamedEventHandler Renamed;
  368. #endregion // Events and Delegates
  369. /* 0 -> not supported */
  370. /* 1 -> windows */
  371. /* 2 -> FAM */
  372. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  373. static extern int InternalSupportsFSW ();
  374. /*[MethodImplAttribute(MethodImplOptions.InternalCall)]
  375. static extern IntPtr InternalOpenDirectory (string path, IntPtr reserved);
  376. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  377. static extern IntPtr InternalCloseDirectory (IntPtr handle);
  378. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  379. static extern bool InternalReadDirectoryChanges (IntPtr handle,
  380. byte [] buffer,
  381. bool includeSubdirectories,
  382. NotifyFilters notifyFilter,
  383. out NativeOverlapped overlap,
  384. OverlappedHandler overlappedCallback);
  385. */
  386. }
  387. }