Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / FileAgent.cs @ b1303755

History | View | Annotate | Download (29 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(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,PollAgent.CancellationToken,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
                DeleteWithRetry(absolutePath, 3);
696
            }
697

    
698
            //_ignoreFiles[absolutePath] = absolutePath;                
699
            StatusKeeper.ClearFileStatus(absolutePath);
700
        }
701

    
702
        private static void DeleteWithRetry(string absolutePath, int retries)
703
        {
704
            try
705
            {
706
                var dirinfo = new DirectoryInfo(absolutePath);
707
                dirinfo.Attributes &= ~FileAttributes.ReadOnly;
708
                foreach (var info in dirinfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
709
                {
710
                    info.Attributes &= ~FileAttributes.ReadOnly;
711
                }
712
                dirinfo.Refresh();
713
                dirinfo.Delete(true);
714
            }
715
            //The directory may have been deleted by another thread. Just ignore the relevant exception
716
            catch (DirectoryNotFoundException) { }
717
            catch (IOException)
718
            {
719
                if (retries>0)
720
                    DeleteWithRetry(absolutePath,retries-1);
721
                else
722
                {
723
                    throw;
724
                }
725
            }
726
        }
727
    }
728
}