FileSystemWatcher.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  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. //
  13. // Permission is hereby granted, free of charge, to any person obtaining
  14. // a copy of this software and associated documentation files (the
  15. // "Software"), to deal in the Software without restriction, including
  16. // without limitation the rights to use, copy, modify, merge, publish,
  17. // distribute, sublicense, and/or sell copies of the Software, and to
  18. // permit persons to whom the Software is furnished to do so, subject to
  19. // the following conditions:
  20. //
  21. // The above copyright notice and this permission notice shall be
  22. // included in all copies or substantial portions of the Software.
  23. //
  24. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  25. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  26. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  27. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  28. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  29. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  30. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  31. //
  32. using System;
  33. using System.ComponentModel;
  34. using System.Runtime.CompilerServices;
  35. using System.Runtime.InteropServices;
  36. using System.Threading;
  37. namespace System.IO {
  38. [DefaultEvent("Changed")]
  39. public class FileSystemWatcher : Component, ISupportInitialize {
  40. #region Fields
  41. bool enableRaisingEvents;
  42. string filter;
  43. bool includeSubdirectories;
  44. int internalBufferSize;
  45. NotifyFilters notifyFilter;
  46. string path;
  47. string fullpath;
  48. ISynchronizeInvoke synchronizingObject;
  49. WaitForChangedResult lastData;
  50. bool waiting;
  51. SearchPattern2 pattern;
  52. bool disposed;
  53. string mangledFilter;
  54. static IFileWatcher watcher;
  55. static object lockobj = new object ();
  56. #endregion // Fields
  57. #region Constructors
  58. public FileSystemWatcher ()
  59. {
  60. this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
  61. this.enableRaisingEvents = false;
  62. this.filter = "*.*";
  63. this.includeSubdirectories = false;
  64. this.internalBufferSize = 8192;
  65. this.path = "";
  66. InitWatcher ();
  67. }
  68. public FileSystemWatcher (string path)
  69. : this (path, "*.*")
  70. {
  71. }
  72. public FileSystemWatcher (string path, string filter)
  73. {
  74. if (path == null)
  75. throw new ArgumentNullException ("path");
  76. if (filter == null)
  77. throw new ArgumentNullException ("filter");
  78. if (path == String.Empty)
  79. throw new ArgumentException ("Empty path", "path");
  80. if (!Directory.Exists (path))
  81. throw new ArgumentException ("Directory does not exists", "path");
  82. this.enableRaisingEvents = false;
  83. this.filter = filter;
  84. this.includeSubdirectories = false;
  85. this.internalBufferSize = 8192;
  86. this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
  87. this.path = path;
  88. this.synchronizingObject = null;
  89. InitWatcher ();
  90. }
  91. void InitWatcher ()
  92. {
  93. lock (lockobj) {
  94. if (watcher != null)
  95. return;
  96. string managed = Environment.GetEnvironmentVariable ("MONO_MANAGED_WATCHER");
  97. int mode = 0;
  98. if (managed == null)
  99. mode = InternalSupportsFSW ();
  100. bool ok = false;
  101. if (mode == 3)
  102. ok = KeventWatcher.GetInstance (out watcher);
  103. else if (mode == 2)
  104. ok = FAMWatcher.GetInstance (out watcher);
  105. else if (mode == 1)
  106. ok = DefaultWatcher.GetInstance (out watcher);
  107. //ok = WindowsWatcher.GetInstance (out watcher);
  108. if (mode == 0 || !ok)
  109. DefaultWatcher.GetInstance (out watcher);
  110. }
  111. }
  112. #endregion // Constructors
  113. #region Properties
  114. /* If this is enabled, we Pulse this instance */
  115. internal bool Waiting {
  116. get { return waiting; }
  117. set { waiting = value; }
  118. }
  119. internal string MangledFilter {
  120. get {
  121. if (filter != "*.*")
  122. return filter;
  123. if (mangledFilter != null)
  124. return mangledFilter;
  125. string filterLocal = "*.*";
  126. if (!(watcher.GetType () == typeof (WindowsWatcher)))
  127. filterLocal = "*";
  128. return filterLocal;
  129. }
  130. }
  131. internal SearchPattern2 Pattern {
  132. get {
  133. if (pattern == null) {
  134. pattern = new SearchPattern2 (MangledFilter);
  135. }
  136. return pattern;
  137. }
  138. }
  139. internal string FullPath {
  140. get {
  141. if (fullpath == null) {
  142. if (path == null || path == "")
  143. fullpath = Environment.CurrentDirectory;
  144. else
  145. fullpath = System.IO.Path.GetFullPath (path);
  146. }
  147. return fullpath;
  148. }
  149. }
  150. [DefaultValue(false)]
  151. [IODescription("Flag to indicate if this instance is active")]
  152. public bool EnableRaisingEvents {
  153. get { return enableRaisingEvents; }
  154. set {
  155. if (value == enableRaisingEvents)
  156. return; // Do nothing
  157. enableRaisingEvents = value;
  158. if (value) {
  159. Start ();
  160. } else {
  161. Stop ();
  162. }
  163. }
  164. }
  165. [DefaultValue("*.*")]
  166. [IODescription("File name filter pattern")]
  167. [RecommendedAsConfigurable(true)]
  168. [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
  169. public string Filter {
  170. get { return filter; }
  171. set {
  172. if (value == null || value == "")
  173. value = "*.*";
  174. if (filter != value) {
  175. filter = value;
  176. pattern = null;
  177. mangledFilter = null;
  178. }
  179. }
  180. }
  181. [DefaultValue(false)]
  182. [IODescription("Flag to indicate we want to watch subdirectories")]
  183. public bool IncludeSubdirectories {
  184. get { return includeSubdirectories; }
  185. set {
  186. if (includeSubdirectories == value)
  187. return;
  188. includeSubdirectories = value;
  189. if (value && enableRaisingEvents) {
  190. Stop ();
  191. Start ();
  192. }
  193. }
  194. }
  195. [Browsable(false)]
  196. [DefaultValue(8192)]
  197. public int InternalBufferSize {
  198. get { return internalBufferSize; }
  199. set {
  200. if (internalBufferSize == value)
  201. return;
  202. if (value < 4196)
  203. value = 4196;
  204. internalBufferSize = value;
  205. if (enableRaisingEvents) {
  206. Stop ();
  207. Start ();
  208. }
  209. }
  210. }
  211. [DefaultValue(NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite)]
  212. [IODescription("Flag to indicate which change event we want to monitor")]
  213. public NotifyFilters NotifyFilter {
  214. get { return notifyFilter; }
  215. set {
  216. if (notifyFilter == value)
  217. return;
  218. notifyFilter = value;
  219. if (enableRaisingEvents) {
  220. Stop ();
  221. Start ();
  222. }
  223. }
  224. }
  225. [DefaultValue("")]
  226. [IODescription("The directory to monitor")]
  227. [RecommendedAsConfigurable(true)]
  228. [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
  229. [Editor ("System.Diagnostics.Design.FSWPathEditor, " + Consts.AssemblySystem_Design, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
  230. public string Path {
  231. get { return path; }
  232. set {
  233. if (path == value)
  234. return;
  235. bool exists = false;
  236. Exception exc = null;
  237. try {
  238. exists = Directory.Exists (value);
  239. } catch (Exception e) {
  240. exc = e;
  241. }
  242. if (exc != null)
  243. throw new ArgumentException ("Invalid directory name", "value", exc);
  244. if (!exists)
  245. throw new ArgumentException ("Directory does not exists", "value");
  246. path = value;
  247. fullpath = null;
  248. if (enableRaisingEvents) {
  249. Stop ();
  250. Start ();
  251. }
  252. }
  253. }
  254. [Browsable(false)]
  255. public override ISite Site {
  256. get { return base.Site; }
  257. set { base.Site = value; }
  258. }
  259. [DefaultValue(null)]
  260. [IODescription("The object used to marshal the event handler calls resulting from a directory change")]
  261. public ISynchronizeInvoke SynchronizingObject {
  262. get { return synchronizingObject; }
  263. set { synchronizingObject = value; }
  264. }
  265. #endregion // Properties
  266. #region Methods
  267. [MonoTODO]
  268. public void BeginInit ()
  269. {
  270. throw new NotImplementedException ();
  271. }
  272. protected override void Dispose (bool disposing)
  273. {
  274. if (!disposed) {
  275. disposed = true;
  276. Stop ();
  277. }
  278. base.Dispose (disposing);
  279. }
  280. ~FileSystemWatcher ()
  281. {
  282. disposed = true;
  283. Stop ();
  284. }
  285. [MonoTODO]
  286. public void EndInit ()
  287. {
  288. throw new NotImplementedException ();
  289. }
  290. private void RaiseEvent (Delegate ev, EventArgs arg)
  291. {
  292. if (ev == null)
  293. return;
  294. object [] args = new object [] {this, arg};
  295. if (synchronizingObject == null) {
  296. ev.DynamicInvoke (args);
  297. return;
  298. }
  299. synchronizingObject.BeginInvoke (ev, args);
  300. }
  301. protected void OnChanged (FileSystemEventArgs e)
  302. {
  303. RaiseEvent (Changed, e);
  304. }
  305. protected void OnCreated (FileSystemEventArgs e)
  306. {
  307. RaiseEvent (Created, e);
  308. }
  309. protected void OnDeleted (FileSystemEventArgs e)
  310. {
  311. RaiseEvent (Deleted, e);
  312. }
  313. protected void OnError (ErrorEventArgs e)
  314. {
  315. RaiseEvent (Error, e);
  316. }
  317. protected void OnRenamed (RenamedEventArgs e)
  318. {
  319. RaiseEvent (Renamed, e);
  320. }
  321. public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType)
  322. {
  323. return WaitForChanged (changeType, Timeout.Infinite);
  324. }
  325. public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType, int timeout)
  326. {
  327. WaitForChangedResult result = new WaitForChangedResult ();
  328. bool prevEnabled = EnableRaisingEvents;
  329. if (!prevEnabled)
  330. EnableRaisingEvents = true;
  331. bool gotData;
  332. lock (this) {
  333. waiting = true;
  334. gotData = Monitor.Wait (this, timeout);
  335. if (gotData)
  336. result = this.lastData;
  337. }
  338. EnableRaisingEvents = prevEnabled;
  339. if (!gotData)
  340. result.TimedOut = true;
  341. return result;
  342. }
  343. internal void DispatchEvents (FileAction act, string filename, ref RenamedEventArgs renamed)
  344. {
  345. if (waiting) {
  346. lastData = new WaitForChangedResult ();
  347. }
  348. switch (act) {
  349. case FileAction.Added:
  350. lastData.Name = filename;
  351. lastData.ChangeType = WatcherChangeTypes.Created;
  352. OnCreated (new FileSystemEventArgs (WatcherChangeTypes.Created, path, filename));
  353. break;
  354. case FileAction.Removed:
  355. lastData.Name = filename;
  356. lastData.ChangeType = WatcherChangeTypes.Deleted;
  357. OnDeleted (new FileSystemEventArgs (WatcherChangeTypes.Deleted, path, filename));
  358. break;
  359. case FileAction.Modified:
  360. lastData.Name = filename;
  361. lastData.ChangeType = WatcherChangeTypes.Changed;
  362. OnChanged (new FileSystemEventArgs (WatcherChangeTypes.Changed, path, filename));
  363. break;
  364. case FileAction.RenamedOldName:
  365. if (renamed != null) {
  366. OnRenamed (renamed);
  367. }
  368. lastData.OldName = filename;
  369. lastData.ChangeType = WatcherChangeTypes.Renamed;
  370. renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, path, null, filename);
  371. break;
  372. case FileAction.RenamedNewName:
  373. lastData.Name = filename;
  374. lastData.ChangeType = WatcherChangeTypes.Renamed;
  375. if (renamed != null) {
  376. renamed.SetName (filename);
  377. } else {
  378. renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, path, filename, null);
  379. }
  380. OnRenamed (renamed);
  381. renamed = null;
  382. break;
  383. default:
  384. break;
  385. }
  386. }
  387. void Start ()
  388. {
  389. watcher.StartDispatching (this);
  390. }
  391. void Stop ()
  392. {
  393. watcher.StopDispatching (this);
  394. }
  395. #endregion // Methods
  396. #region Events and Delegates
  397. [IODescription("Occurs when a file/directory change matches the filter")]
  398. public event FileSystemEventHandler Changed;
  399. [IODescription("Occurs when a file/directory creation matches the filter")]
  400. public event FileSystemEventHandler Created;
  401. [IODescription("Occurs when a file/directory deletion matches the filter")]
  402. public event FileSystemEventHandler Deleted;
  403. [Browsable(false)]
  404. public event ErrorEventHandler Error;
  405. [IODescription("Occurs when a file/directory rename matches the filter")]
  406. public event RenamedEventHandler Renamed;
  407. #endregion // Events and Delegates
  408. /* 0 -> not supported */
  409. /* 1 -> windows */
  410. /* 2 -> FAM */
  411. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  412. static extern int InternalSupportsFSW ();
  413. /*[MethodImplAttribute(MethodImplOptions.InternalCall)]
  414. static extern IntPtr InternalOpenDirectory (string path, IntPtr reserved);
  415. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  416. static extern IntPtr InternalCloseDirectory (IntPtr handle);
  417. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  418. static extern bool InternalReadDirectoryChanges (IntPtr handle,
  419. byte [] buffer,
  420. bool includeSubdirectories,
  421. NotifyFilters notifyFilter,
  422. out NativeOverlapped overlap,
  423. OverlappedHandler overlappedCallback);
  424. */
  425. }
  426. }