Added header to all files. Closes #2064
[pithos-ms-client] / trunk / Pithos.Core / Agents / FileSystemWatcherAdapter.cs
1 #region\r
2 /* -----------------------------------------------------------------------\r
3  * <copyright file="FileSystemWatcherAdapter.cs" company="GRNet">\r
4  * \r
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
6  *\r
7  * Redistribution and use in source and binary forms, with or\r
8  * without modification, are permitted provided that the following\r
9  * conditions are met:\r
10  *\r
11  *   1. Redistributions of source code must retain the above\r
12  *      copyright notice, this list of conditions and the following\r
13  *      disclaimer.\r
14  *\r
15  *   2. Redistributions in binary form must reproduce the above\r
16  *      copyright notice, this list of conditions and the following\r
17  *      disclaimer in the documentation and/or other materials\r
18  *      provided with the distribution.\r
19  *\r
20  *\r
21  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
22  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
32  * POSSIBILITY OF SUCH DAMAGE.\r
33  *\r
34  * The views and conclusions contained in the software and\r
35  * documentation are those of the authors and should not be\r
36  * interpreted as representing official policies, either expressed\r
37  * or implied, of GRNET S.A.\r
38  * </copyright>\r
39  * -----------------------------------------------------------------------\r
40  */\r
41 #endregion\r
42 using System.Diagnostics.Contracts;\r
43 using System.IO;\r
44 using System.Threading.Tasks;\r
45 \r
46 namespace Pithos.Core.Agents\r
47 {\r
48     using System;\r
49     using System.Collections.Generic;\r
50     using System.Linq;\r
51     using System.Text;\r
52 \r
53     /// <summary>\r
54     /// TODO: Update summary.\r
55     /// </summary>\r
56     public class FileSystemWatcherAdapter\r
57     {\r
58          public event FileSystemEventHandler Changed;\r
59         public event FileSystemEventHandler Created;\r
60         public event FileSystemEventHandler Deleted;\r
61         public event RenamedEventHandler Renamed;\r
62         public event MovedEventHandler Moved;\r
63 \r
64         public FileSystemWatcherAdapter(FileSystemWatcher watcher)\r
65         {\r
66             if (watcher==null)\r
67                 throw new ArgumentNullException("watcher");\r
68             Contract.EndContractBlock();\r
69 \r
70             watcher.Changed += OnChangeOrCreate;\r
71             watcher.Created += OnChangeOrCreate;\r
72             watcher.Deleted += OnDeleted;\r
73             watcher.Renamed += OnRename;            \r
74             \r
75         }\r
76 \r
77         private string _cachedDeletedFullPath;\r
78         private const int PropagateDelay = 10;        \r
79 \r
80         private static void OnTimeout(object state)\r
81         {\r
82             throw new NotImplementedException();\r
83         }\r
84 \r
85         private void OnDeleted(object sender, FileSystemEventArgs e)\r
86         {\r
87             if (sender == null)\r
88                 throw new ArgumentNullException("sender");\r
89             if (e.ChangeType != WatcherChangeTypes.Deleted)\r
90                 throw new ArgumentException("e");\r
91             if (string.IsNullOrWhiteSpace(e.FullPath))\r
92                 throw new ArgumentException("e");\r
93             Contract.Ensures(!String.IsNullOrWhiteSpace(_cachedDeletedFullPath));\r
94             Contract.EndContractBlock();\r
95 \r
96             //Handle any previously deleted event\r
97             PropagateCachedDeleted(sender);\r
98 \r
99             //A delete event may be an actual delete event or the first event in a move action.\r
100             //To decide which action occured, we need to wait for the next action, so\r
101             //we  store the file path and return .\r
102             //A delete action will not be followed by any other event, so we need to add a watchdog \r
103             //that will declare a Delete action after a short amount of time\r
104 \r
105             //TODO: Moving a folder to the recycle bin results in a single delete event for the entire folder and its contents\r
106             //      as this is actually a MOVE operation\r
107             //Deleting by Shift+Delete results in a delete event for each file followed by the delete of the folder itself\r
108             _cachedDeletedFullPath = e.FullPath;\r
109 \r
110             //TODO: This requires synchronization of the _cachedDeletedFullPath field\r
111             //TODO: This creates a new task for each file even though we can cancel any existing tasks if a new event arrives\r
112             //Maybe, use a timer instead of a task\r
113             \r
114             TaskEx.Delay(PropagateDelay).ContinueWith(t =>\r
115                                                            {\r
116                                                                var myPath = e.FullPath;\r
117                                                                if (this._cachedDeletedFullPath==myPath)\r
118                                                                     PropagateCachedDeleted(sender);\r
119                                                            });\r
120         }\r
121 \r
122         private void OnRename(object sender, RenamedEventArgs e)\r
123         {\r
124             if (sender == null)\r
125                 throw new ArgumentNullException("sender");\r
126             Contract.Ensures(_cachedDeletedFullPath == null);\r
127             Contract.EndContractBlock();\r
128 \r
129             try\r
130             {\r
131                 //Propagate any previous cached delete event\r
132                 PropagateCachedDeleted(sender);\r
133                 \r
134                 if (Renamed!=null)\r
135                     Renamed(sender, e);\r
136                 \r
137             }\r
138             finally\r
139             {\r
140                 _cachedDeletedFullPath = null;    \r
141             }\r
142         }\r
143 \r
144         private void OnChangeOrCreate(object sender, FileSystemEventArgs e)\r
145         {\r
146             if (sender == null)\r
147                 throw new ArgumentNullException("sender");\r
148             if (!(e.ChangeType == WatcherChangeTypes.Created || e.ChangeType == WatcherChangeTypes.Changed))\r
149                 throw new ArgumentException("e");\r
150             Contract.Ensures(_cachedDeletedFullPath == null);\r
151             Contract.EndContractBlock();\r
152 \r
153             try\r
154             {\r
155                 //A Move action results in a sequence of a Delete and a Create or Change event\r
156                 //If the actual action is a Move, raise a Move event instead of the actual event\r
157                 if (HandleMoved(sender, e))\r
158                     return;\r
159 \r
160                 //Otherwise, propagate the Delete event if it exists \r
161                 PropagateCachedDeleted(sender);\r
162                 //and propagate the actual event\r
163                 var actualEvent = e.ChangeType == WatcherChangeTypes.Created ? Created : Changed;\r
164                 if (actualEvent != null)\r
165                     actualEvent(sender, e);\r
166             }\r
167             finally\r
168             {\r
169                 //Finally, make sure the cached path is cleared\r
170                 _cachedDeletedFullPath = null;\r
171             }\r
172 \r
173         }\r
174 \r
175         private bool HandleMoved(object sender, FileSystemEventArgs e)\r
176         {\r
177             if (sender == null)\r
178                 throw new ArgumentNullException("sender");\r
179             if (!(e.ChangeType == WatcherChangeTypes.Created ||\r
180                                                   e.ChangeType == WatcherChangeTypes.Changed))\r
181                 throw new ArgumentException("e");\r
182             Contract.EndContractBlock();\r
183 \r
184             //TODO: If a file is deleted and another file with the same name is created, it will be detected as a MOVE\r
185             //instead of a sequence of independent actions\r
186             //One way to detect this would be to request that the full paths are NOT the same\r
187 \r
188             var oldName = Path.GetFileName(_cachedDeletedFullPath);\r
189             //NOTE: e.Name is a path relative to the watched path. We MUST call Path.GetFileName to get the actual path\r
190             var newName = Path.GetFileName(e.Name);\r
191             //If the last deleted filename is equal to the current and the action is create, we have a MOVE operation\r
192             var hasMoved = (_cachedDeletedFullPath != e.FullPath && oldName == newName);\r
193 \r
194             if (!hasMoved)\r
195                 return false;\r
196 \r
197             try\r
198             {\r
199                 //If the actual action is a Move, raise a Move event instead of the actual event\r
200                 var newDirectory = Path.GetDirectoryName(e.FullPath);\r
201                 var oldDirectory = Path.GetDirectoryName(_cachedDeletedFullPath);\r
202                 if (Moved != null)\r
203                     Moved(sender, new MovedEventArgs(newDirectory, newName, oldDirectory, oldName));\r
204             }\r
205             finally\r
206             {\r
207                 _cachedDeletedFullPath = null;\r
208             }\r
209             return true;\r
210         }\r
211 \r
212         private void PropagateCachedDeleted(object sender)\r
213         {\r
214             if (sender == null)\r
215                 throw new ArgumentNullException("sender");\r
216             Contract.Ensures(_cachedDeletedFullPath == null);\r
217             Contract.EndContractBlock();\r
218 \r
219             //Nothing to handle if there is no cached deleted file\r
220             if (String.IsNullOrWhiteSpace(_cachedDeletedFullPath))\r
221                 return;\r
222             \r
223             var deletedFileName = Path.GetFileName(_cachedDeletedFullPath);\r
224             var deletedFileDirectory = Path.GetDirectoryName(_cachedDeletedFullPath);\r
225 \r
226             if (Deleted!=null)\r
227                 Deleted(sender, new FileSystemEventArgs(WatcherChangeTypes.Deleted, deletedFileDirectory, deletedFileName));\r
228 \r
229             _cachedDeletedFullPath = null;\r
230         }\r
231     }\r
232 }\r