1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Runtime.Caching.Hosting;
7 using System.Runtime.Caching.Resources;
8 using System.Collections;
9 using System.IO;
10 using System.Security;
11 using System.Security.Permissions;
12 
13 namespace System.Runtime.Caching
14 {
15     internal sealed class FileChangeNotificationSystem : IFileChangeNotificationSystem
16     {
17         private Hashtable _dirMonitors;
18         private object _lock;
19 
20         internal class DirectoryMonitor
21         {
22             internal FileSystemWatcher Fsw;
23         }
24 
25         internal class FileChangeEventTarget
26         {
27             private string _fileName;
28             private OnChangedCallback _onChangedCallback;
29             private FileSystemEventHandler _changedHandler;
30             private ErrorEventHandler _errorHandler;
31             private RenamedEventHandler _renamedHandler;
32 
EqualsIgnoreCase(string s1, string s2)33             private static bool EqualsIgnoreCase(string s1, string s2)
34             {
35                 if (String.IsNullOrEmpty(s1) && String.IsNullOrEmpty(s2))
36                 {
37                     return true;
38                 }
39                 if (String.IsNullOrEmpty(s1) || String.IsNullOrEmpty(s2))
40                 {
41                     return false;
42                 }
43                 if (s2.Length != s1.Length)
44                 {
45                     return false;
46                 }
47                 return 0 == string.Compare(s1, 0, s2, 0, s2.Length, StringComparison.OrdinalIgnoreCase);
48             }
49 
OnChanged(Object sender, FileSystemEventArgs e)50             private void OnChanged(Object sender, FileSystemEventArgs e)
51             {
52                 if (EqualsIgnoreCase(_fileName, e.Name))
53                 {
54                     _onChangedCallback(null);
55                 }
56             }
57 
OnError(Object sender, ErrorEventArgs e)58             private void OnError(Object sender, ErrorEventArgs e)
59             {
60                 _onChangedCallback(null);
61             }
62 
OnRenamed(Object sender, RenamedEventArgs e)63             private void OnRenamed(Object sender, RenamedEventArgs e)
64             {
65                 if (EqualsIgnoreCase(_fileName, e.Name) || EqualsIgnoreCase(_fileName, e.OldName))
66                 {
67                     _onChangedCallback(null);
68                 }
69             }
70 
71             internal FileSystemEventHandler ChangedHandler { get { return _changedHandler; } }
72             internal ErrorEventHandler ErrorHandler { get { return _errorHandler; } }
73             internal RenamedEventHandler RenamedHandler { get { return _renamedHandler; } }
74 
FileChangeEventTarget(string fileName, OnChangedCallback onChangedCallback)75             internal FileChangeEventTarget(string fileName, OnChangedCallback onChangedCallback)
76             {
77                 _fileName = fileName;
78                 _onChangedCallback = onChangedCallback;
79                 _changedHandler = new FileSystemEventHandler(this.OnChanged);
80                 _errorHandler = new ErrorEventHandler(this.OnError);
81                 _renamedHandler = new RenamedEventHandler(this.OnRenamed);
82             }
83         }
84 
FileChangeNotificationSystem()85         internal FileChangeNotificationSystem()
86         {
87             _dirMonitors = Hashtable.Synchronized(new Hashtable(StringComparer.OrdinalIgnoreCase));
88             _lock = new object();
89         }
90 
IFileChangeNotificationSystem.StartMonitoring(string filePath, OnChangedCallback onChangedCallback, out Object state, out DateTimeOffset lastWriteTime, out long fileSize)91         void IFileChangeNotificationSystem.StartMonitoring(string filePath, OnChangedCallback onChangedCallback, out Object state, out DateTimeOffset lastWriteTime, out long fileSize)
92         {
93             if (filePath == null)
94             {
95                 throw new ArgumentNullException("filePath");
96             }
97             if (onChangedCallback == null)
98             {
99                 throw new ArgumentNullException("onChangedCallback");
100             }
101             FileInfo fileInfo = new FileInfo(filePath);
102             string dir = Path.GetDirectoryName(filePath);
103             DirectoryMonitor dirMon = _dirMonitors[dir] as DirectoryMonitor;
104             if (dirMon == null)
105             {
106                 lock (_lock)
107                 {
108                     dirMon = _dirMonitors[dir] as DirectoryMonitor;
109                     if (dirMon == null)
110                     {
111                         dirMon = new DirectoryMonitor();
112                         dirMon.Fsw = new FileSystemWatcher(dir);
113                         dirMon.Fsw.NotifyFilter = NotifyFilters.FileName
114                                                   | NotifyFilters.DirectoryName
115                                                   | NotifyFilters.CreationTime
116                                                   | NotifyFilters.Size
117                                                   | NotifyFilters.LastWrite
118                                                   | NotifyFilters.Security;
119                         dirMon.Fsw.EnableRaisingEvents = true;
120                     }
121                     _dirMonitors[dir] = dirMon;
122                 }
123             }
124 
125             FileChangeEventTarget target = new FileChangeEventTarget(fileInfo.Name, onChangedCallback);
126 
127             lock (dirMon)
128             {
129                 dirMon.Fsw.Changed += target.ChangedHandler;
130                 dirMon.Fsw.Created += target.ChangedHandler;
131                 dirMon.Fsw.Deleted += target.ChangedHandler;
132                 dirMon.Fsw.Error += target.ErrorHandler;
133                 dirMon.Fsw.Renamed += target.RenamedHandler;
134             }
135 
136             state = target;
137             lastWriteTime = File.GetLastWriteTime(filePath);
138             fileSize = (fileInfo.Exists) ? fileInfo.Length : -1;
139         }
140 
IFileChangeNotificationSystem.StopMonitoring(string filePath, Object state)141         void IFileChangeNotificationSystem.StopMonitoring(string filePath, Object state)
142         {
143             if (filePath == null)
144             {
145                 throw new ArgumentNullException("filePath");
146             }
147             if (state == null)
148             {
149                 throw new ArgumentNullException("state");
150             }
151             FileChangeEventTarget target = state as FileChangeEventTarget;
152             if (target == null)
153             {
154                 throw new ArgumentException(SR.Invalid_state, "state");
155             }
156             string dir = Path.GetDirectoryName(filePath);
157             DirectoryMonitor dirMon = _dirMonitors[dir] as DirectoryMonitor;
158             if (dirMon != null)
159             {
160                 lock (dirMon)
161                 {
162                     dirMon.Fsw.Changed -= target.ChangedHandler;
163                     dirMon.Fsw.Created -= target.ChangedHandler;
164                     dirMon.Fsw.Deleted -= target.ChangedHandler;
165                     dirMon.Fsw.Error -= target.ErrorHandler;
166                     dirMon.Fsw.Renamed -= target.RenamedHandler;
167                 }
168             }
169         }
170     }
171 }
172