Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
DirectoryWatcher.Windows.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP
4 using System;
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using SiliconStudio.Core.Diagnostics;
11 
12 namespace SiliconStudio.Core.IO
13 {
14  public partial class DirectoryWatcher
15  {
16  private readonly Dictionary<string, DirectoryWatcherItem> watchers = new Dictionary<string, DirectoryWatcherItem>(StringComparer.InvariantCultureIgnoreCase);
17 
18  private void InitializeInternal()
19  {
20  watcherCheckThread = new Thread(SafeAction.Wrap(RunCheckWatcher)) { IsBackground = true, Name = "RunCheckWatcher thread" };
21  watcherCheckThread.Start();
22  }
23 
24  private void DisposeInternal()
25  {
26  foreach (var watcher in watchers.Values)
27  {
28  if (watcher.Watcher != null)
29  {
30  DisposeNativeWatcher(watcher.Watcher);
31  }
32  watcher.Watcher = null;
33  }
34  watchers.Clear();
35  }
36 
37  private List<string> GetTrackedDirectoriesInternal()
38  {
39  List<string> directories;
40  lock (watchers)
41  {
42  directories = ListTrackedDirectories().Select(pair => pair.Key).ToList();
43  }
44  directories.Sort();
45  return directories;
46  }
47 
48  private void TrackInternal(string path)
49  {
50  var info = GetDirectoryInfoFromPath(path);
51  if (info == null)
52  {
53  return;
54  }
55 
56  lock (watchers)
57  {
58  Track(info, true);
59  }
60  }
61 
62  private void UnTrackInternal(string path)
63  {
64  var info = GetDirectoryInfoFromPath(path);
65  if (info == null)
66  {
67  return;
68  }
69 
70  lock (watchers)
71  {
72  DirectoryWatcherItem watcher;
73  if (!watchers.TryGetValue(info.FullName, out watcher))
74  {
75  return;
76  }
77 
78  UnTrack(watcher, true);
79  }
80  }
81 
82 
83  private void RunCheckWatcher()
84  {
85  try
86  {
87  while (!exitThread)
88  {
89  // TODO should use a wait on an event in order to cancel it more quickly instead of a blocking Thread.Sleep
90  Thread.Sleep(SleepBetweenWatcherCheck);
91 
92  lock (watchers)
93  {
94  var list = ListTrackedDirectories().ToList();
95  foreach (var watcherKeyPath in list)
96  {
97  if (!watcherKeyPath.Value.IsPathExist())
98  {
99  UnTrack(watcherKeyPath.Value, true);
100  OnModified(this, new FileEvent(FileEventChangeType.Deleted, Path.GetFileName(watcherKeyPath.Value.Path), watcherKeyPath.Value.Path));
101  }
102  }
103 
104  // If no more directories are tracked, clear completely the watchers tree
105  if (!ListTrackedDirectories().Any())
106  {
107  watchers.Clear();
108  }
109  }
110  }
111  }
112  catch (Exception ex)
113  {
114  Trace.WriteLine(string.Format("Unexpected end of thread {0}", ex));
115  //Console.WriteLine("Unexpected exception {0}", ex);
116  }
117  }
118 
119  private IEnumerable<KeyValuePair<string, DirectoryWatcherItem>> ListTrackedDirectories()
120  {
121  return watchers.Where(pair => pair.Value.Watcher != null);
122  }
123 
124  private DirectoryInfo GetDirectoryInfoFromPath(string path)
125  {
126  if (path == null) throw new ArgumentNullException("path");
127 
128  path = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, path));
129 
130  // 1) Extract directory information from path
131  DirectoryInfo info;
132  if (File.Exists(path))
133  {
134  path = Path.GetDirectoryName(path);
135  }
136 
137  if (path != null && Directory.Exists(path))
138  {
139  info = new DirectoryInfo(path.ToLower());
140  }
141  else
142  {
143  return null;
144  }
145 
146  return info;
147  }
148 
149  private IEnumerable<DirectoryWatcherItem> ListTracked(IEnumerable<DirectoryInfo> directories)
150  {
151  foreach (var directoryInfo in directories)
152  {
153  DirectoryWatcherItem watcher;
154  if (watchers.TryGetValue(directoryInfo.FullName, out watcher))
155  {
156  yield return watcher;
157  }
158  }
159  }
160 
161  private IEnumerable<DirectoryWatcherItem> ListTrackedChildren(DirectoryWatcherItem watcher)
162  {
163  return ListTracked(watcher.ListChildrenDirectories());
164  }
165 
166  private int CountTracked(IEnumerable<DirectoryInfo> directories)
167  {
168  return ListTracked(directories).Count(watcher => watcher.Watcher != null);
169  }
170 
171  private DirectoryWatcherItem Track(DirectoryInfo info, bool watcherNode)
172  {
173  DirectoryWatcherItem watcher;
174  if (watchers.TryGetValue(info.FullName, out watcher))
175  {
176  watcher.TrackCount++;
177  return watcher;
178  }
179 
180  var parent = info.Parent != null ? Track(info.Parent, false) : null;
181 
182  if (parent != null && watcherNode)
183  {
184  if (parent.Watcher != null)
185  {
186  return parent;
187  }
188 
189  var childrenDirectoryList = parent.ListChildrenDirectories().ToList();
190  var countTracked = CountTracked(childrenDirectoryList);
191 
192  var newCount = (countTracked + 1);
193  if (newCount == childrenDirectoryList.Count && newCount > 1)
194  {
195  UnTrack(parent, false);
196  parent.Watcher = CreateFileSystemWatcher(parent.Path);
197  return parent;
198  }
199  }
200 
201  watcher = new DirectoryWatcherItem(info) { Parent = parent };
202  if (watcherNode)
203  {
204  watcher.Watcher = CreateFileSystemWatcher(watcher.Path);
205  }
206  watchers.Add(watcher.Path, watcher);
207 
208  watcher.TrackCount++;
209  return watcher;
210  }
211 
212  private void UnTrack(DirectoryWatcherItem watcher, bool removeWatcherFromGlobals)
213  {
214  foreach (var child in ListTrackedChildren(watcher))
215  {
216  UnTrack(child, true);
217  }
218 
219  watcher.TrackCount--;
220 
221  if (watcher.TrackCount == 0)
222  {
223  if (watcher.Watcher != null)
224  {
225  DisposeNativeWatcher(watcher.Watcher);
226  watcher.Watcher = null;
227  }
228 
229  watcher.Parent = null;
230 
231  if (removeWatcherFromGlobals)
232  {
233  watchers.Remove(watcher.Path);
234  }
235  }
236  }
237 
238  private void DisposeNativeWatcher(FileSystemWatcher watcher)
239  {
240  //Console.WriteLine("Watcher disposing {0}", watcher.Path);
241  watcher.EnableRaisingEvents = false;
242  watcher.Changed -= OnModified;
243  watcher.Created -= OnModified;
244  watcher.Deleted -= OnModified;
245  watcher.Renamed -= OnModified;
246  watcher.Error -= WatcherOnError;
247  watcher.Dispose();
248  //Console.WriteLine("Watcher disposed {0}", watcher.Path);
249  }
250 
251  protected FileSystemWatcher CreateFileSystemWatcher(string directory)
252  {
253  //Console.WriteLine("Watcher creating {0}", directory);
254  var watcher = new FileSystemWatcher()
255  {
256  Path = directory,
257  NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName,
258  Filter = FileFilter,
259  IncludeSubdirectories = true
260  };
261 
262  watcher.BeginInit();
263 
264  watcher.Changed += OnModified;
265  watcher.Created += OnModified;
266  watcher.Deleted += OnModified;
267  watcher.Renamed += OnModified;
268  watcher.Error += WatcherOnError;
269 
270  watcher.EndInit();
271  watcher.EnableRaisingEvents = true;
272 
273  //Console.WriteLine("Watcher created {0}", directory);
274  return watcher;
275  }
276 
277  private void WatcherOnError(object sender, ErrorEventArgs errorEventArgs)
278  {
279  try
280  {
281  //Console.WriteLine("DirectoryWatcher faile Watcher exception: {0}", errorEventArgs.GetException());
282  lock (watchers)
283  {
284  var watcher = watchers.Values.FirstOrDefault(item => item.Watcher == sender);
285  if (watcher != null)
286  {
287  // Remove a specific watcher if there was any error with it
288  UnTrack(watcher, true);
289  }
290  }
291  }
292  catch (Exception ex)
293  {
294  Trace.WriteLine(string.Format("Unexpected exception in WatcherOnError: {0}", ex));
295  }
296  }
297 
298  private void OnModified(object sender, FileSystemEventArgs e)
299  {
300  lock (watchers)
301  {
302  DirectoryWatcherItem watcher;
303  if (e.ChangeType == WatcherChangeTypes.Deleted && watchers.TryGetValue(e.FullPath, out watcher))
304  {
305  UnTrack(watcher, true);
306  }
307  }
308 
309  var handler = Modified;
310  if (handler != null)
311  {
312  OnModified(this, new FileEvent((FileEventChangeType)e.ChangeType, e.Name, e.FullPath));
313  }
314  }
315 
316  [DebuggerDisplay("Active: {IsActive}, Path: {Path}")]
317  private sealed class DirectoryWatcherItem
318  {
319  public DirectoryWatcherItem(DirectoryInfo path)
320  {
321  Path = path.FullName.ToLower();
322  }
323 
324  public DirectoryWatcherItem Parent;
325 
326  public string Path { get; private set; }
327 
328  public bool IsPathExist()
329  {
330  return Directory.Exists(Path);
331  }
332 
333  public int TrackCount { get; set; }
334 
335  public FileSystemWatcher Watcher { get; set; }
336 
337  public IEnumerable<DirectoryInfo> ListChildrenDirectories()
338  {
339  var info = new DirectoryInfo(Path);
340  try
341  {
342  if (info.Exists)
343  {
344  return info.EnumerateDirectories();
345  }
346  }
347  catch (Exception)
348  {
349  // An exception can occur if the file is being removed
350  }
351  return Enumerable.Empty<DirectoryInfo>();
352  }
353 
354  private bool IsActive
355  {
356  get
357  {
358  return Watcher != null;
359  }
360 
361  }
362  }
363  }
364 }
365 #endif
Let the emitter choose the style.
System.IO.File File
Data reference has been set to a new value by the user. It will be changed to Loaded as soon as it ha...
FileEventChangeType
Change type of file used by FileEvent and DirectoryWatcher.