Added isDirectory parameter to DeleteObject
[pithos-ms-client] / trunk / Pithos.Core / Agents / FileAgent.cs
1 #region
2 /* -----------------------------------------------------------------------
3  * <copyright file="FileAgent.cs" company="GRNet">
4  * 
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or
8  * without modification, are permitted provided that the following
9  * conditions are met:
10  *
11  *   1. Redistributions of source code must retain the above
12  *      copyright notice, this list of conditions and the following
13  *      disclaimer.
14  *
15  *   2. Redistributions in binary form must reproduce the above
16  *      copyright notice, this list of conditions and the following
17  *      disclaimer in the documentation and/or other materials
18  *      provided with the distribution.
19  *
20  *
21  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * The views and conclusions contained in the software and
35  * documentation are those of the authors and should not be
36  * interpreted as representing official policies, either expressed
37  * or implied, of GRNET S.A.
38  * </copyright>
39  * -----------------------------------------------------------------------
40  */
41 #endregion
42 using System;
43 using System.Collections.Generic;
44 using System.Diagnostics.Contracts;
45 using System.IO;
46 using System.Linq;
47 using System.Reflection;
48 using System.Threading.Tasks;
49 using Pithos.Interfaces;
50 using Pithos.Network;
51 using log4net;
52
53 namespace Pithos.Core.Agents
54 {
55 //    [Export]
56     public class FileAgent
57     {
58         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
59
60         /*
61                 Agent<WorkflowState> _agent;
62         */
63         private FileSystemWatcher _watcher;
64         private FileSystemWatcherAdapter _adapter;
65         private FileEventIdleBatch _eventIdleBatch;
66
67         //[Import]
68         public IStatusKeeper StatusKeeper { get; set; }
69
70         public IStatusNotification StatusNotification { get; set; }
71         //[Import]
72         public IPithosWorkflow Workflow { get; set; }
73         //[Import]
74         //public WorkflowAgent WorkflowAgent { get; set; }
75
76         private AccountInfo AccountInfo { get; set; }
77
78         internal string RootPath { get;  set; }
79         
80         public TimeSpan IdleTimeout { get; set; }
81
82         public PollAgent PollAgent { get; set; }
83
84         private void ProcessBatchedEvents(FileSystemEventArgs[] fileEvents)
85         {
86             var paths = new HashSet<string>();
87
88             foreach (var evt in fileEvents)
89             {
90                 paths.Add(evt.FullPath);
91                 if (evt is MovedEventArgs)
92                 {
93                     paths.Add((evt as MovedEventArgs).OldFullPath);
94                 }
95                 else if (evt is RenamedEventArgs)
96                 {
97                     paths.Add((evt as RenamedEventArgs).OldFullPath);
98                 }
99             }
100                   
101             PollAgent.SynchNow(paths);
102         }
103
104 /*
105         private void ProcessBatchedEvents(Dictionary<string, FileSystemEventArgs[]> fileEvents)
106         {
107             StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,String.Format("Uploading {0} files",fileEvents.Count));
108             //Start with events that do not originate in one of the ignored folders
109             var initialEvents = from evt in fileEvents
110                               where !IgnorePaths(evt.Key)
111                               select evt;
112
113             IEnumerable<KeyValuePair<string, FileSystemEventArgs[]>> cleanEvents;
114
115             
116             var selectiveEnabled = Selectives.IsSelectiveEnabled(AccountInfo.AccountKey);
117             //When selective sync is enabled,
118             if (selectiveEnabled)
119             {
120                 //Include all selected items
121                 var selectedEvents = from evt in initialEvents
122                                      where Selectives.IsSelected(AccountInfo, evt.Key)
123                                      select evt;                
124                 //And all folder creations in the unselected folders
125                 var folderCreations = from evt in initialEvents
126                                       let folderPath=evt.Key
127                                       //The original folder may not exist due to renames. Just make sure that the path is not a file
128                                       where !File.Exists(folderPath)
129                                             //We only want unselected items
130                                             && !Selectives.IsSelected(AccountInfo, folderPath)
131                                             //Is there any creation event related to the folder?
132                                             && evt.Value.Any(arg => arg.ChangeType == WatcherChangeTypes.Created)
133                                       select evt;
134                 cleanEvents = selectedEvents.Union(folderCreations).ToList();
135             }
136             //If selective is disabled, only exclude the shared folders 
137             else
138             {
139                 cleanEvents = (from evt in initialEvents
140                               where !evt.Key.IsSharedTo(AccountInfo)
141                               select evt).ToList();
142             }
143
144
145             foreach (var fileEvent in cleanEvents)
146             {
147                 //var filePath = fileEvent.Key;
148                 var changes = fileEvent.Value;
149
150                 var isNotFile = !File.Exists(fileEvent.Key);
151                 foreach (var change in changes)
152                 {
153                     if (change.ChangeType == WatcherChangeTypes.Renamed)
154                     {
155                         var rename = (MovedEventArgs) change;
156                         _agent.Post(new WorkflowState(change)
157                                         {
158                                             AccountInfo = AccountInfo,
159                                             OldPath = rename.OldFullPath,
160                                             OldFileName = Path.GetFileName(rename.OldName),
161                                             Path = rename.FullPath,
162                                             FileName = Path.GetFileName(rename.Name),
163                                             TriggeringChange = rename.ChangeType
164                                         });
165                     }
166                     else
167                     {
168                         var isCreation = selectiveEnabled && isNotFile && change.ChangeType == WatcherChangeTypes.Created;
169                         _agent.Post(new WorkflowState(change)
170                                         {
171                                             AccountInfo = AccountInfo,
172                                             Path = change.FullPath,
173                                             FileName = Path.GetFileName(change.Name),
174                                             TriggeringChange = change.ChangeType,
175                                             IsCreation=isCreation
176                                         });
177                     }
178                 }
179             }
180             StatusNotification.SetPithosStatus(PithosStatus.LocalComplete);
181         }
182 */
183
184         public void Start(AccountInfo accountInfo,string rootPath)
185         {
186             if (accountInfo==null)
187                 throw new ArgumentNullException("accountInfo");
188             if (String.IsNullOrWhiteSpace(rootPath))
189                 throw new ArgumentNullException("rootPath");
190             if (!Path.IsPathRooted(rootPath))
191                 throw new ArgumentException("rootPath must be an absolute path","rootPath");
192             if (IdleTimeout == null)
193                 throw new InvalidOperationException("IdleTimeout must have a valid value");
194                 Contract.EndContractBlock();
195
196             AccountInfo = accountInfo;
197             RootPath = rootPath;
198             
199             _eventIdleBatch = new FileEventIdleBatch(PollAgent,(int)IdleTimeout.TotalMilliseconds, ProcessBatchedEvents);
200
201             _watcher = new FileSystemWatcher(rootPath) { IncludeSubdirectories = true, InternalBufferSize = 8 * 4096 };
202             _adapter = new FileSystemWatcherAdapter(_watcher,this);
203
204             _adapter.Changed += OnFileEvent;
205             _adapter.Created += OnFileEvent;
206             _adapter.Deleted += OnFileEvent;
207             //_adapter.Renamed += OnRenameEvent;
208             _adapter.Moved += OnMoveEvent;
209             _watcher.EnableRaisingEvents = true;
210
211 /*            
212
213
214
215             _agent = Agent<WorkflowState>.Start(inbox =>
216             {
217                 Action loop = null;
218                 loop = () =>
219                 {
220                     var message = inbox.Receive();
221                     var process=message.Then(Process,inbox.CancellationToken);                    
222                     inbox.LoopAsync(process,loop,ex=>
223                         Log.ErrorFormat("[ERROR] File Event Processing:\r{0}", ex));
224                 };
225                 loop();
226             });*/
227         }
228
229 /*
230         private Task<object> Process(WorkflowState state)
231         {
232             if (state==null)
233                 throw new ArgumentNullException("state");
234             Contract.EndContractBlock();
235
236             if (Ignore(state.Path))
237                 return CompletedTask<object>.Default;
238
239             var networkState = NetworkGate.GetNetworkState(state.Path);
240             //Skip if the file is already being downloaded or uploaded and 
241             //the change is create or modify
242             if (networkState != NetworkOperation.None &&
243                 (
244                     state.TriggeringChange == WatcherChangeTypes.Created ||
245                     state.TriggeringChange == WatcherChangeTypes.Changed
246                 ))
247                 return CompletedTask<object>.Default;
248
249             try
250             {
251                 //StatusKeeper.EnsureFileState(state.Path);
252                 
253                 UpdateFileStatus(state);
254                 UpdateOverlayStatus(state);
255                 UpdateLastMD5(state);
256                 WorkflowAgent.Post(state);
257             }
258             catch (IOException exc)
259             {
260                 if (File.Exists(state.Path))
261                 {
262                     Log.WarnFormat("File access error occured, retrying {0}\n{1}", state.Path, exc);
263                     _agent.Post(state);
264                 }
265                 else
266                 {
267                     Log.WarnFormat("File {0} does not exist. Will be ignored\n{1}", state.Path, exc);
268                 }
269             }
270             catch (Exception exc)
271             {
272                 Log.WarnFormat("Error occured while indexing{0}. The file will be skipped\n{1}",
273                                state.Path, exc);
274             }
275             return CompletedTask<object>.Default;
276         }
277
278         public bool Pause
279         {
280             get { return _watcher == null || !_watcher.EnableRaisingEvents; }
281             set
282             {
283                 if (_watcher != null)
284                     _watcher.EnableRaisingEvents = !value;                
285             }
286         }
287 */
288
289         public string CachePath { get; set; }
290
291         /*private List<string> _selectivePaths = new List<string>();
292         public List<string> SelectivePaths
293         {
294             get { return _selectivePaths; }
295             set { _selectivePaths = value; }
296         }
297 */
298         public Selectives Selectives { get; set; }
299
300
301 /*
302         public void Post(WorkflowState workflowState)
303         {
304             if (workflowState == null)
305                 throw new ArgumentNullException("workflowState");
306             Contract.EndContractBlock();
307
308             _agent.Post(workflowState);
309         }
310
311         public void Stop()
312         {
313             if (_watcher != null)
314             {
315                 _watcher.Dispose();
316             }
317             _watcher = null;
318
319             if (_agent!=null)
320                 _agent.Stop();
321         }
322
323 */
324         // Enumerate all files in the Pithos directory except those in the Fragment folder
325         // and files with a .ignore extension
326         public IEnumerable<string> EnumerateFiles(string searchPattern="*")
327         {
328             var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
329                                  where !Ignore(filePath)
330                                  orderby filePath ascending 
331                                  select filePath;
332             return monitoredFiles;
333         }
334
335         public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
336         {
337             var rootDir = new DirectoryInfo(RootPath);
338             var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
339                                  where !Ignore(file.FullName)
340                                  orderby file.FullName ascending 
341                                  select file;
342             return monitoredFiles;
343         }                
344
345         public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern="*")
346         {
347             var rootDir = new DirectoryInfo(RootPath);
348             //Ensure folders appear first, to allow folder processing as soon as possilbe
349             var folders = (from file in rootDir.EnumerateDirectories(searchPattern, SearchOption.AllDirectories)
350                                      where !Ignore(file.FullName)
351                                      orderby file.FullName ascending
352                                      select file).ToList();
353             var files = (from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
354                                   where !Ignore(file.FullName)
355                                   orderby file.Length ascending
356                                   select file as FileSystemInfo).ToList();
357             var monitoredFiles = folders
358                                  //Process small files first, leaving expensive large files for last
359                                  .Concat(files);
360             return monitoredFiles;
361         }                
362
363         public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
364         {
365             var rootDir = new DirectoryInfo(RootPath);
366             var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
367                                  where !Ignore(file.FullName)
368                                  orderby file.FullName ascending 
369                                  select file.AsRelativeUrlTo(RootPath);
370             return monitoredFiles;
371         }                
372
373         public IEnumerable<string> EnumerateFilesSystemInfosAsRelativeUrls(string searchPattern="*")
374         {
375             var rootDir = new DirectoryInfo(RootPath);
376             var monitoredFiles = from file in rootDir.EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories)
377                                  where !Ignore(file.FullName)
378                                  orderby file.FullName ascending 
379                                  select file.AsRelativeUrlTo(RootPath);
380             return monitoredFiles;
381         }                
382
383
384         
385
386         public bool Ignore(string filePath)
387         {
388             if (IgnorePaths(filePath)) return true;
389
390
391             //If selective sync is enabled, 
392             if (IsUnselectedRootFolder(filePath))
393                     return false;
394             //Ignore if selective synchronization is defined, 
395             //And the target file is not below any of the selective paths
396             var ignore = !Selectives.IsSelected(AccountInfo, filePath);
397             return ignore;
398         }
399
400         public bool IsUnselectedRootFolder(string filePath)
401         {
402             return Selectives.IsSelectiveEnabled(AccountInfo.AccountKey) //propagate folder events 
403                    && Directory.Exists(filePath) //from the container root folder only. Note, in the first level below the account root path are the containers
404                    && FoundBelowRoot(filePath, RootPath, 2);
405         }
406
407         public bool IgnorePaths(string filePath)
408         {
409 //Ignore all first-level directories and files (ie at the container folders level)
410             if (FoundBelowRoot(filePath, RootPath, 1))
411                 return true;
412
413             //Ignore first-level items under the "others" folder (ie at the accounts folders level).
414             var othersPath = Path.Combine(RootPath, FolderConstants.OthersFolder);
415             if (FoundBelowRoot(filePath, othersPath, 1))
416                 return true;
417
418             //Ignore second-level (container) folders under the "others" folder (ie at the container folders level). 
419             if (FoundBelowRoot(filePath, othersPath, 2))
420                 return true;
421
422
423             //Ignore anything happening in the cache path
424             if (filePath.StartsWith(CachePath))
425                 return true;
426             
427             //Finally, ignore events about one of the ignored files
428             return _ignoreFiles.ContainsKey(filePath.ToLower());
429         }
430
431 /*        private static bool FoundInRoot(string filePath, string rootPath)
432         {
433             //var rootDirectory = new DirectoryInfo(rootPath);
434
435             //If the paths are equal, return true
436             if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))
437                 return true;
438
439             //If the filepath is below the root path
440             if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))
441             {
442                 //Get the relative path
443                 var relativePath = filePath.Substring(rootPath.Length + 1);
444                 //If the relativePath does NOT contains a path separator, we found a match
445                 return (!relativePath.Contains(@"\"));
446             }
447
448             //If the filepath is not under the root path, return false
449             return false;            
450         }*/
451
452
453         private static bool FoundBelowRoot(string filePath, string rootPath,int level)
454         {
455             //var rootDirectory = new DirectoryInfo(rootPath);
456
457             //If the paths are equal, return true
458             if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))
459                 return true;
460
461             //If the filepath is below the root path
462             if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))
463             {
464                 //Get the relative path
465                 var relativePath = filePath.Substring(rootPath.Length + 1);
466                 //If the relativePath does NOT contains a path separator, we found a match
467                 var levels=relativePath.ToCharArray().Count(c=>c=='\\')+1;                
468                 return levels==level;
469             }
470
471             //If the filepath is not under the root path, return false
472             return false;            
473         }
474
475         /*
476         //Post a Change message for renames containing the old and new names
477         void OnRenameEvent(object sender, RenamedEventArgs e)
478         {
479             var oldFullPath = e.OldFullPath;
480             var fullPath = e.FullPath;
481             if (Ignore(oldFullPath) || Ignore(fullPath))
482                 return;
483
484             _agent.Post(new WorkflowState
485             {
486                 AccountInfo=AccountInfo,
487                 OldPath = oldFullPath,
488                 OldFileName = e.OldName,
489                 Path = fullPath,
490                 FileName = e.Name,
491                 TriggeringChange = e.ChangeType
492             });
493         }
494         */
495
496         //Post a Change message for all events except rename
497         void OnFileEvent(object sender, FileSystemEventArgs e)
498         {
499             //Ignore events that affect the cache folder
500             var filePath = e.FullPath;
501             if (Ignore(filePath))
502                 return;
503             _eventIdleBatch.Post(e);
504         }
505
506         //Post a Change message for moves containing the old and new names
507         void OnMoveEvent(object sender, MovedEventArgs e)
508         {
509             var oldFullPath = e.OldFullPath;
510             var fullPath = e.FullPath;
511             
512
513             //If the source path is one of the ignored folders, ignore
514             if (IgnorePaths(oldFullPath)) 
515                 return;
516
517             //TODO: Must prevent move propagation if the source folder is blocked by selective sync
518             //Ignore takes into account Selective Sync
519             if (Ignore(fullPath))
520                 return;
521             PollAgent.PostMove(e);
522             _eventIdleBatch.Post(e);
523         }
524
525
526
527         private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
528                                                                              {
529             {WatcherChangeTypes.Created,FileStatus.Created},
530             {WatcherChangeTypes.Changed,FileStatus.Modified},
531             {WatcherChangeTypes.Deleted,FileStatus.Deleted},
532             {WatcherChangeTypes.Renamed,FileStatus.Renamed}
533         };
534
535         private Dictionary<string, string> _ignoreFiles=new Dictionary<string, string>();
536
537         private WorkflowState UpdateFileStatus(WorkflowState state)
538         {
539             if (state==null)
540                 throw new ArgumentNullException("state");
541             if (String.IsNullOrWhiteSpace(state.Path))
542                 throw new ArgumentException("The state's Path can't be empty","state");
543             Contract.EndContractBlock();
544
545             var path = state.Path;
546             var status = _statusDict[state.TriggeringChange];
547             var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
548             if (status == oldStatus)
549             {
550                 state.Status = status;
551                 state.Skip = true;
552                 return state;
553             }
554             if (state.Status == FileStatus.Renamed)
555                 Workflow.ClearFileStatus(path);
556
557             state.Status = Workflow.SetFileStatus(path, status);
558             return state;
559         }
560
561         private WorkflowState UpdateOverlayStatus(WorkflowState state)
562         {
563             if (state==null)
564                 throw new ArgumentNullException("state");
565             Contract.EndContractBlock();
566
567             if (state.Skip)
568                 return state;
569
570             switch (state.Status)
571             {
572                 case FileStatus.Created:
573                     this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ETag).Wait();
574                     break;
575                 case FileStatus.Modified:
576                     this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();
577                     break;
578                 case FileStatus.Deleted:
579                     //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
580                     break;
581                 case FileStatus.Renamed:
582                     this.StatusKeeper.ClearFileStatus(state.OldPath);
583                     this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();
584                     break;
585                 case FileStatus.Unchanged:
586                     this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal, state.ETag).Wait();
587                     break;
588             }
589
590             if (state.Status == FileStatus.Deleted)
591                 NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
592             else
593                 NativeMethods.RaiseChangeNotification(state.Path);
594             return state;
595         }
596
597
598         private WorkflowState UpdateFileChecksum(WorkflowState state)
599         {
600             if (state.Skip)
601                 return state;
602
603             if (state.Status == FileStatus.Deleted)
604                 return state;
605
606             var path = state.Path;
607             //Skip calculation for folders
608             if (Directory.Exists(path))
609                 return state;
610
611             var info = new FileInfo(path);
612
613             using (StatusNotification.GetNotifier("Hashing {0}", "Finished Hashing {0}", info.Name))
614             {
615
616                 var etag = info.ComputeShortHash(StatusNotification);
617
618                 var progress = new Progress<double>(d =>
619                     StatusNotification.Notify(new StatusNotification(String.Format("Hashing {0} of {1}", d, info.Name))));
620
621                 string merkleHash = info.CalculateHash(StatusKeeper.BlockSize, StatusKeeper.BlockHash,progress);
622                 StatusKeeper.UpdateFileChecksum(path, etag, merkleHash);
623
624                 state.Hash = merkleHash;
625                 return state;
626             }
627         }
628
629         //Does the file exist in the container's local folder?
630         public bool Exists(string relativePath)
631         {
632             if (String.IsNullOrWhiteSpace(relativePath))
633                 throw new ArgumentNullException("relativePath");
634             //A RootPath must be set before calling this method
635             if (String.IsNullOrWhiteSpace(RootPath))
636                 throw new InvalidOperationException("RootPath was not set");
637             Contract.EndContractBlock();
638             //Create the absolute path by combining the RootPath with the relativePath
639             var absolutePath=Path.Combine(RootPath, relativePath);
640             //Is this a valid file?
641             if (File.Exists(absolutePath))
642                 return true;
643             //Or a directory?
644             if (Directory.Exists(absolutePath))
645                 return true;
646             //Fail if it is neither
647             return false;
648         }
649
650         public static FileAgent GetFileAgent(AccountInfo accountInfo)
651         {
652             return GetFileAgent(accountInfo.AccountPath);
653         }
654
655         public static FileAgent GetFileAgent(string rootPath)
656         {
657             return AgentLocator<FileAgent>.Get(rootPath.ToLower());
658         }
659
660
661         public FileSystemInfo GetFileSystemInfo(string relativePath)
662         {
663             if (String.IsNullOrWhiteSpace(relativePath))
664                 throw new ArgumentNullException("relativePath");
665             //A RootPath must be set before calling this method
666             if (String.IsNullOrWhiteSpace(RootPath))
667                 throw new InvalidOperationException("RootPath was not set");            
668             Contract.EndContractBlock();            
669
670             var absolutePath = Path.Combine(RootPath, relativePath);
671
672             if (Directory.Exists(absolutePath))
673                 return new DirectoryInfo(absolutePath).WithProperCapitalization();
674             else
675                 return new FileInfo(absolutePath).WithProperCapitalization();
676             
677         }
678
679         public void Delete(string relativePath)
680         {
681             var absolutePath = Path.Combine(RootPath, relativePath).ToLower();
682             if (Log.IsDebugEnabled)
683                 Log.DebugFormat("Deleting {0}", absolutePath);
684             if (File.Exists(absolutePath))
685             {    
686                 try
687                 {
688                     File.Delete(absolutePath);
689                 }
690                 //The file may have been deleted by another thread. Just ignore the relevant exception
691                 catch (FileNotFoundException) { }
692             }
693             else if (Directory.Exists(absolutePath))
694             {
695                 try
696                 {
697                     
698                     var dirinfo = new DirectoryInfo(absolutePath);
699                     dirinfo.Attributes &= ~FileAttributes.ReadOnly;
700                     foreach (var info in dirinfo.EnumerateFileSystemInfos("*",SearchOption.AllDirectories))
701                     {
702                         info.Attributes &= ~FileAttributes.ReadOnly;
703                     }
704                                         
705                     dirinfo.Delete(true);
706                 }
707                 //The directory may have been deleted by another thread. Just ignore the relevant exception
708                 catch (DirectoryNotFoundException){}                
709             }
710         
711             //_ignoreFiles[absolutePath] = absolutePath;                
712             StatusKeeper.ClearFileStatus(absolutePath);
713         }
714     }
715 }