Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / FileSystemWatcherAdapter.cs @ 89472316

History | View | Annotate | Download (11.4 kB)

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