Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / FileAgent.cs @ 1cc1e8c5

History | View | Annotate | Download (26.9 kB)

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(Dictionary<string, FileSystemEventArgs[]> fileEvents)
85
        {
86
            PollAgent.SynchNow();
87
        }
88

    
89
/*
90
        private void ProcessBatchedEvents(Dictionary<string, FileSystemEventArgs[]> fileEvents)
91
        {
92
            StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,String.Format("Uploading {0} files",fileEvents.Count));
93
            //Start with events that do not originate in one of the ignored folders
94
            var initialEvents = from evt in fileEvents
95
                              where !IgnorePaths(evt.Key)
96
                              select evt;
97

    
98
            IEnumerable<KeyValuePair<string, FileSystemEventArgs[]>> cleanEvents;
99

    
100
            
101
            var selectiveEnabled = Selectives.IsSelectiveEnabled(AccountInfo.AccountKey);
102
            //When selective sync is enabled,
103
            if (selectiveEnabled)
104
            {
105
                //Include all selected items
106
                var selectedEvents = from evt in initialEvents
107
                                     where Selectives.IsSelected(AccountInfo, evt.Key)
108
                                     select evt;                
109
                //And all folder creations in the unselected folders
110
                var folderCreations = from evt in initialEvents
111
                                      let folderPath=evt.Key
112
                                      //The original folder may not exist due to renames. Just make sure that the path is not a file
113
                                      where !File.Exists(folderPath)
114
                                            //We only want unselected items
115
                                            && !Selectives.IsSelected(AccountInfo, folderPath)
116
                                            //Is there any creation event related to the folder?
117
                                            && evt.Value.Any(arg => arg.ChangeType == WatcherChangeTypes.Created)
118
                                      select evt;
119
                cleanEvents = selectedEvents.Union(folderCreations).ToList();
120
            }
121
            //If selective is disabled, only exclude the shared folders 
122
            else
123
            {
124
                cleanEvents = (from evt in initialEvents
125
                              where !evt.Key.IsSharedTo(AccountInfo)
126
                              select evt).ToList();
127
            }
128

    
129

    
130
            foreach (var fileEvent in cleanEvents)
131
            {
132
                //var filePath = fileEvent.Key;
133
                var changes = fileEvent.Value;
134

    
135
                var isNotFile = !File.Exists(fileEvent.Key);
136
                foreach (var change in changes)
137
                {
138
                    if (change.ChangeType == WatcherChangeTypes.Renamed)
139
                    {
140
                        var rename = (MovedEventArgs) change;
141
                        _agent.Post(new WorkflowState(change)
142
                                        {
143
                                            AccountInfo = AccountInfo,
144
                                            OldPath = rename.OldFullPath,
145
                                            OldFileName = Path.GetFileName(rename.OldName),
146
                                            Path = rename.FullPath,
147
                                            FileName = Path.GetFileName(rename.Name),
148
                                            TriggeringChange = rename.ChangeType
149
                                        });
150
                    }
151
                    else
152
                    {
153
                        var isCreation = selectiveEnabled && isNotFile && change.ChangeType == WatcherChangeTypes.Created;
154
                        _agent.Post(new WorkflowState(change)
155
                                        {
156
                                            AccountInfo = AccountInfo,
157
                                            Path = change.FullPath,
158
                                            FileName = Path.GetFileName(change.Name),
159
                                            TriggeringChange = change.ChangeType,
160
                                            IsCreation=isCreation
161
                                        });
162
                    }
163
                }
164
            }
165
            StatusNotification.SetPithosStatus(PithosStatus.LocalComplete);
166
        }
167
*/
168

    
169
        public void Start(AccountInfo accountInfo,string rootPath)
170
        {
171
            if (accountInfo==null)
172
                throw new ArgumentNullException("accountInfo");
173
            if (String.IsNullOrWhiteSpace(rootPath))
174
                throw new ArgumentNullException("rootPath");
175
            if (!Path.IsPathRooted(rootPath))
176
                throw new ArgumentException("rootPath must be an absolute path","rootPath");
177
            if (IdleTimeout == null)
178
                throw new InvalidOperationException("IdleTimeout must have a valid value");
179
                Contract.EndContractBlock();
180

    
181
            AccountInfo = accountInfo;
182
            RootPath = rootPath;
183
            
184
            _eventIdleBatch = new FileEventIdleBatch((int)IdleTimeout.TotalMilliseconds, ProcessBatchedEvents);
185

    
186
            _watcher = new FileSystemWatcher(rootPath) { IncludeSubdirectories = true, InternalBufferSize = 8 * 4096 };
187
            _adapter = new FileSystemWatcherAdapter(_watcher);
188

    
189
            _adapter.Changed += OnFileEvent;
190
            _adapter.Created += OnFileEvent;
191
            _adapter.Deleted += OnFileEvent;
192
            //_adapter.Renamed += OnRenameEvent;
193
            _adapter.Moved += OnMoveEvent;
194
            _watcher.EnableRaisingEvents = true;
195

    
196
/*            
197

    
198

    
199

    
200
            _agent = Agent<WorkflowState>.Start(inbox =>
201
            {
202
                Action loop = null;
203
                loop = () =>
204
                {
205
                    var message = inbox.Receive();
206
                    var process=message.Then(Process,inbox.CancellationToken);                    
207
                    inbox.LoopAsync(process,loop,ex=>
208
                        Log.ErrorFormat("[ERROR] File Event Processing:\r{0}", ex));
209
                };
210
                loop();
211
            });*/
212
        }
213

    
214
/*
215
        private Task<object> Process(WorkflowState state)
216
        {
217
            if (state==null)
218
                throw new ArgumentNullException("state");
219
            Contract.EndContractBlock();
220

    
221
            if (Ignore(state.Path))
222
                return CompletedTask<object>.Default;
223

    
224
            var networkState = NetworkGate.GetNetworkState(state.Path);
225
            //Skip if the file is already being downloaded or uploaded and 
226
            //the change is create or modify
227
            if (networkState != NetworkOperation.None &&
228
                (
229
                    state.TriggeringChange == WatcherChangeTypes.Created ||
230
                    state.TriggeringChange == WatcherChangeTypes.Changed
231
                ))
232
                return CompletedTask<object>.Default;
233

    
234
            try
235
            {
236
                //StatusKeeper.EnsureFileState(state.Path);
237
                
238
                UpdateFileStatus(state);
239
                UpdateOverlayStatus(state);
240
                UpdateFileChecksum(state);
241
                WorkflowAgent.Post(state);
242
            }
243
            catch (IOException exc)
244
            {
245
                if (File.Exists(state.Path))
246
                {
247
                    Log.WarnFormat("File access error occured, retrying {0}\n{1}", state.Path, exc);
248
                    _agent.Post(state);
249
                }
250
                else
251
                {
252
                    Log.WarnFormat("File {0} does not exist. Will be ignored\n{1}", state.Path, exc);
253
                }
254
            }
255
            catch (Exception exc)
256
            {
257
                Log.WarnFormat("Error occured while indexing{0}. The file will be skipped\n{1}",
258
                               state.Path, exc);
259
            }
260
            return CompletedTask<object>.Default;
261
        }
262

    
263
        public bool Pause
264
        {
265
            get { return _watcher == null || !_watcher.EnableRaisingEvents; }
266
            set
267
            {
268
                if (_watcher != null)
269
                    _watcher.EnableRaisingEvents = !value;                
270
            }
271
        }
272
*/
273

    
274
        public string CachePath { get; set; }
275

    
276
        /*private List<string> _selectivePaths = new List<string>();
277
        public List<string> SelectivePaths
278
        {
279
            get { return _selectivePaths; }
280
            set { _selectivePaths = value; }
281
        }
282
*/
283
        public Selectives Selectives { get; set; }
284

    
285

    
286
/*
287
        public void Post(WorkflowState workflowState)
288
        {
289
            if (workflowState == null)
290
                throw new ArgumentNullException("workflowState");
291
            Contract.EndContractBlock();
292

    
293
            _agent.Post(workflowState);
294
        }
295

    
296
        public void Stop()
297
        {
298
            if (_watcher != null)
299
            {
300
                _watcher.Dispose();
301
            }
302
            _watcher = null;
303

    
304
            if (_agent!=null)
305
                _agent.Stop();
306
        }
307

    
308
*/
309
        // Enumerate all files in the Pithos directory except those in the Fragment folder
310
        // and files with a .ignore extension
311
        public IEnumerable<string> EnumerateFiles(string searchPattern="*")
312
        {
313
            var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
314
                                 where !Ignore(filePath)
315
                                 orderby filePath ascending 
316
                                 select filePath;
317
            return monitoredFiles;
318
        }
319

    
320
        public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
321
        {
322
            var rootDir = new DirectoryInfo(RootPath);
323
            var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
324
                                 where !Ignore(file.FullName)
325
                                 orderby file.FullName ascending 
326
                                 select file;
327
            return monitoredFiles;
328
        }                
329

    
330
        public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern="*")
331
        {
332
            var rootDir = new DirectoryInfo(RootPath);
333
            //Ensure folders appear first, to allow folder processing as soon as possilbe
334
            var folders = (from file in rootDir.EnumerateDirectories(searchPattern, SearchOption.AllDirectories)
335
                                     where !Ignore(file.FullName)
336
                                     orderby file.FullName ascending
337
                                     select file).ToList();
338
            var files = (from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
339
                                  where !Ignore(file.FullName)
340
                                  orderby file.Length ascending
341
                                  select file as FileSystemInfo).ToList();
342
            var monitoredFiles = folders
343
                                 //Process small files first, leaving expensive large files for last
344
                                 .Concat(files);
345
            return monitoredFiles;
346
        }                
347

    
348
        public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
349
        {
350
            var rootDir = new DirectoryInfo(RootPath);
351
            var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
352
                                 where !Ignore(file.FullName)
353
                                 orderby file.FullName ascending 
354
                                 select file.AsRelativeUrlTo(RootPath);
355
            return monitoredFiles;
356
        }                
357

    
358
        public IEnumerable<string> EnumerateFilesSystemInfosAsRelativeUrls(string searchPattern="*")
359
        {
360
            var rootDir = new DirectoryInfo(RootPath);
361
            var monitoredFiles = from file in rootDir.EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories)
362
                                 where !Ignore(file.FullName)
363
                                 orderby file.FullName ascending 
364
                                 select file.AsRelativeUrlTo(RootPath);
365
            return monitoredFiles;
366
        }                
367

    
368

    
369
        
370

    
371
        public bool Ignore(string filePath)
372
        {
373
            if (IgnorePaths(filePath)) return true;
374

    
375

    
376
            //If selective sync is enabled, 
377
            if (IsUnselectedRootFolder(filePath))
378
                    return false;
379
            //Ignore if selective synchronization is defined, 
380
            //And the target file is not below any of the selective paths
381
            var ignore = !Selectives.IsSelected(AccountInfo, filePath);
382
            return ignore;
383
        }
384

    
385
        public bool IsUnselectedRootFolder(string filePath)
386
        {
387
            return Selectives.IsSelectiveEnabled(AccountInfo.AccountKey) //propagate folder events 
388
                   && Directory.Exists(filePath) //from the container root folder only. Note, in the first level below the account root path are the containers
389
                   && FoundBelowRoot(filePath, RootPath, 2);
390
        }
391

    
392
        public bool IgnorePaths(string filePath)
393
        {
394
//Ignore all first-level directories and files (ie at the container folders level)
395
            if (FoundBelowRoot(filePath, RootPath, 1))
396
                return true;
397

    
398
            //Ignore first-level items under the "others" folder (ie at the accounts folders level).
399
            var othersPath = Path.Combine(RootPath, FolderConstants.OthersFolder);
400
            if (FoundBelowRoot(filePath, othersPath, 1))
401
                return true;
402

    
403
            //Ignore second-level (container) folders under the "others" folder (ie at the container folders level). 
404
            if (FoundBelowRoot(filePath, othersPath, 2))
405
                return true;
406

    
407

    
408
            //Ignore anything happening in the cache path
409
            if (filePath.StartsWith(CachePath))
410
                return true;
411
            
412
            //Finally, ignore events about one of the ignored files
413
            return _ignoreFiles.ContainsKey(filePath.ToLower());
414
        }
415

    
416
/*        private static bool FoundInRoot(string filePath, string rootPath)
417
        {
418
            //var rootDirectory = new DirectoryInfo(rootPath);
419

    
420
            //If the paths are equal, return true
421
            if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))
422
                return true;
423

    
424
            //If the filepath is below the root path
425
            if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))
426
            {
427
                //Get the relative path
428
                var relativePath = filePath.Substring(rootPath.Length + 1);
429
                //If the relativePath does NOT contains a path separator, we found a match
430
                return (!relativePath.Contains(@"\"));
431
            }
432

    
433
            //If the filepath is not under the root path, return false
434
            return false;            
435
        }*/
436

    
437

    
438
        private static bool FoundBelowRoot(string filePath, string rootPath,int level)
439
        {
440
            //var rootDirectory = new DirectoryInfo(rootPath);
441

    
442
            //If the paths are equal, return true
443
            if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))
444
                return true;
445

    
446
            //If the filepath is below the root path
447
            if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))
448
            {
449
                //Get the relative path
450
                var relativePath = filePath.Substring(rootPath.Length + 1);
451
                //If the relativePath does NOT contains a path separator, we found a match
452
                var levels=relativePath.ToCharArray().Count(c=>c=='\\')+1;                
453
                return levels==level;
454
            }
455

    
456
            //If the filepath is not under the root path, return false
457
            return false;            
458
        }
459

    
460
        /*
461
        //Post a Change message for renames containing the old and new names
462
        void OnRenameEvent(object sender, RenamedEventArgs e)
463
        {
464
            var oldFullPath = e.OldFullPath;
465
            var fullPath = e.FullPath;
466
            if (Ignore(oldFullPath) || Ignore(fullPath))
467
                return;
468

    
469
            _agent.Post(new WorkflowState
470
            {
471
                AccountInfo=AccountInfo,
472
                OldPath = oldFullPath,
473
                OldFileName = e.OldName,
474
                Path = fullPath,
475
                FileName = e.Name,
476
                TriggeringChange = e.ChangeType
477
            });
478
        }
479
        */
480

    
481
        //Post a Change message for all events except rename
482
        void OnFileEvent(object sender, FileSystemEventArgs e)
483
        {
484
            //Ignore events that affect the cache folder
485
            var filePath = e.FullPath;
486
            if (Ignore(filePath))
487
                return;
488
            _eventIdleBatch.Post(e);
489
        }
490

    
491
        //Post a Change message for moves containing the old and new names
492
        void OnMoveEvent(object sender, MovedEventArgs e)
493
        {
494
            var oldFullPath = e.OldFullPath;
495
            var fullPath = e.FullPath;
496
            
497

    
498
            //If the source path is one of the ignored folders, ignore
499
            if (IgnorePaths(oldFullPath)) 
500
                return;
501

    
502
            //TODO: Must prevent move propagation if the source folder is blocked by selective sync
503
            //Ignore takes into account Selective Sync
504
            if (Ignore(fullPath))
505
                return;
506

    
507
            _eventIdleBatch.Post(e);
508
        }
509

    
510

    
511

    
512
        private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
513
                                                                             {
514
            {WatcherChangeTypes.Created,FileStatus.Created},
515
            {WatcherChangeTypes.Changed,FileStatus.Modified},
516
            {WatcherChangeTypes.Deleted,FileStatus.Deleted},
517
            {WatcherChangeTypes.Renamed,FileStatus.Renamed}
518
        };
519

    
520
        private Dictionary<string, string> _ignoreFiles=new Dictionary<string, string>();
521

    
522
        private WorkflowState UpdateFileStatus(WorkflowState state)
523
        {
524
            if (state==null)
525
                throw new ArgumentNullException("state");
526
            if (String.IsNullOrWhiteSpace(state.Path))
527
                throw new ArgumentException("The state's Path can't be empty","state");
528
            Contract.EndContractBlock();
529

    
530
            var path = state.Path;
531
            var status = _statusDict[state.TriggeringChange];
532
            var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
533
            if (status == oldStatus)
534
            {
535
                state.Status = status;
536
                state.Skip = true;
537
                return state;
538
            }
539
            if (state.Status == FileStatus.Renamed)
540
                Workflow.ClearFileStatus(path);
541

    
542
            state.Status = Workflow.SetFileStatus(path, status);
543
            return state;
544
        }
545

    
546
        private WorkflowState UpdateOverlayStatus(WorkflowState state)
547
        {
548
            if (state==null)
549
                throw new ArgumentNullException("state");
550
            Contract.EndContractBlock();
551

    
552
            if (state.Skip)
553
                return state;
554

    
555
            switch (state.Status)
556
            {
557
                case FileStatus.Created:
558
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ShortHash).Wait();
559
                    break;
560
                case FileStatus.Modified:
561
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ShortHash).Wait();
562
                    break;
563
                case FileStatus.Deleted:
564
                    //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
565
                    break;
566
                case FileStatus.Renamed:
567
                    this.StatusKeeper.ClearFileStatus(state.OldPath);
568
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ShortHash).Wait();
569
                    break;
570
                case FileStatus.Unchanged:
571
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal, state.ShortHash).Wait();
572
                    break;
573
            }
574

    
575
            if (state.Status == FileStatus.Deleted)
576
                NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
577
            else
578
                NativeMethods.RaiseChangeNotification(state.Path);
579
            return state;
580
        }
581

    
582

    
583
        private WorkflowState UpdateFileChecksum(WorkflowState state)
584
        {
585
            if (state.Skip)
586
                return state;
587

    
588
            if (state.Status == FileStatus.Deleted)
589
                return state;
590

    
591
            var path = state.Path;
592
            //Skip calculation for folders
593
            if (Directory.Exists(path))
594
                return state;
595

    
596
            var info = new FileInfo(path);
597

    
598
            using (StatusNotification.GetNotifier("Hashing {0}", "Finished Hashing {0}", info.Name))
599
            {
600

    
601
                var shortHash = info.ComputeShortHash();
602

    
603
                string merkleHash = info.CalculateHash(StatusKeeper.BlockSize, StatusKeeper.BlockHash);
604
                StatusKeeper.UpdateFileChecksum(path, shortHash, merkleHash);
605

    
606
                state.Hash = merkleHash;
607
                return state;
608
            }
609
        }
610

    
611
        //Does the file exist in the container's local folder?
612
        public bool Exists(string relativePath)
613
        {
614
            if (String.IsNullOrWhiteSpace(relativePath))
615
                throw new ArgumentNullException("relativePath");
616
            //A RootPath must be set before calling this method
617
            if (String.IsNullOrWhiteSpace(RootPath))
618
                throw new InvalidOperationException("RootPath was not set");
619
            Contract.EndContractBlock();
620
            //Create the absolute path by combining the RootPath with the relativePath
621
            var absolutePath=Path.Combine(RootPath, relativePath);
622
            //Is this a valid file?
623
            if (File.Exists(absolutePath))
624
                return true;
625
            //Or a directory?
626
            if (Directory.Exists(absolutePath))
627
                return true;
628
            //Fail if it is neither
629
            return false;
630
        }
631

    
632
        public static FileAgent GetFileAgent(AccountInfo accountInfo)
633
        {
634
            return GetFileAgent(accountInfo.AccountPath);
635
        }
636

    
637
        public static FileAgent GetFileAgent(string rootPath)
638
        {
639
            return AgentLocator<FileAgent>.Get(rootPath.ToLower());
640
        }
641

    
642

    
643
        public FileSystemInfo GetFileSystemInfo(string relativePath)
644
        {
645
            if (String.IsNullOrWhiteSpace(relativePath))
646
                throw new ArgumentNullException("relativePath");
647
            //A RootPath must be set before calling this method
648
            if (String.IsNullOrWhiteSpace(RootPath))
649
                throw new InvalidOperationException("RootPath was not set");            
650
            Contract.EndContractBlock();            
651

    
652
            var absolutePath = Path.Combine(RootPath, relativePath);
653

    
654
            if (Directory.Exists(absolutePath))
655
                return new DirectoryInfo(absolutePath).WithProperCapitalization();
656
            else
657
                return new FileInfo(absolutePath).WithProperCapitalization();
658
            
659
        }
660

    
661
        public void Delete(string relativePath)
662
        {
663
            var absolutePath = Path.Combine(RootPath, relativePath).ToLower();
664
            if (Log.IsDebugEnabled)
665
                Log.DebugFormat("Deleting {0}", absolutePath);
666
            if (File.Exists(absolutePath))
667
            {    
668
                try
669
                {
670
                    File.Delete(absolutePath);
671
                }
672
                //The file may have been deleted by another thread. Just ignore the relevant exception
673
                catch (FileNotFoundException) { }
674
            }
675
            else if (Directory.Exists(absolutePath))
676
            {
677
                try
678
                {
679
                    Directory.Delete(absolutePath, true);
680
                }
681
                //The directory may have been deleted by another thread. Just ignore the relevant exception
682
                catch (DirectoryNotFoundException){}                
683
            }
684
        
685
            //_ignoreFiles[absolutePath] = absolutePath;                
686
            StatusKeeper.ClearFileStatus(absolutePath);
687
        }
688
    }
689
}