Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / Agents / FileAgent.cs @ 496595a5

History | View | Annotate | Download (25.2 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
        Agent<WorkflowState> _agent;
61
        private FileSystemWatcher _watcher;
62
        private FileSystemWatcherAdapter _adapter;
63

    
64
        //[Import]
65
        public IStatusKeeper StatusKeeper { get; set; }
66

    
67
        public IStatusNotification StatusNotification { get; set; }
68
        //[Import]
69
        public IPithosWorkflow Workflow { get; set; }
70
        //[Import]
71
        public WorkflowAgent WorkflowAgent { get; set; }
72

    
73
        private AccountInfo AccountInfo { get; set; }
74

    
75
        internal string RootPath { get;  set; }
76
        
77
        private FileEventIdleBatch _eventIdleBatch;
78

    
79
        public TimeSpan IdleTimeout { get; set; }
80

    
81

    
82
        private void ProcessBatchedEvents(Dictionary<string, FileSystemEventArgs[]> fileEvents)
83
        {
84
            StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,String.Format("Uploading {0} files",fileEvents.Count));
85
            //Start with events that do not originate in one of the ignored folders
86
            var initialEvents = from evt in fileEvents
87
                              where !IgnorePaths(evt.Key)
88
                              select evt;
89

    
90
            IEnumerable<KeyValuePair<string, FileSystemEventArgs[]>> cleanEvents;
91

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

    
121

    
122
            foreach (var fileEvent in cleanEvents)
123
            {
124
                //var filePath = fileEvent.Key;
125
                var changes = fileEvent.Value;
126

    
127
                var isNotFile = !File.Exists(fileEvent.Key);
128
                foreach (var change in changes)
129
                {
130
                    if (change.ChangeType == WatcherChangeTypes.Renamed)
131
                    {
132
                        var rename = (MovedEventArgs) change;
133
                        _agent.Post(new WorkflowState(change)
134
                                        {
135
                                            AccountInfo = AccountInfo,
136
                                            OldPath = rename.OldFullPath,
137
                                            OldFileName = Path.GetFileName(rename.OldName),
138
                                            Path = rename.FullPath,
139
                                            FileName = Path.GetFileName(rename.Name),
140
                                            TriggeringChange = rename.ChangeType
141
                                        });
142
                    }
143
                    else
144
                    {
145
                        var isCreation = selectiveEnabled && isNotFile && change.ChangeType == WatcherChangeTypes.Created;
146
                        _agent.Post(new WorkflowState(change)
147
                                        {
148
                                            AccountInfo = AccountInfo,
149
                                            Path = change.FullPath,
150
                                            FileName = Path.GetFileName(change.Name),
151
                                            TriggeringChange = change.ChangeType,
152
                                            IsCreation=isCreation
153
                                        });
154
                    }
155
                }
156
            }
157
            StatusNotification.SetPithosStatus(PithosStatus.LocalComplete);
158
        }
159

    
160
        public void Start(AccountInfo accountInfo,string rootPath)
161
        {
162
            if (accountInfo==null)
163
                throw new ArgumentNullException("accountInfo");
164
            if (String.IsNullOrWhiteSpace(rootPath))
165
                throw new ArgumentNullException("rootPath");
166
            if (!Path.IsPathRooted(rootPath))
167
                throw new ArgumentException("rootPath must be an absolute path","rootPath");
168
            if (IdleTimeout == null)
169
                throw new InvalidOperationException("IdleTimeout must have a valid value");
170
            Contract.EndContractBlock();
171

    
172
            AccountInfo = accountInfo;
173
            RootPath = rootPath;
174

    
175
            _eventIdleBatch = new FileEventIdleBatch((int)IdleTimeout.TotalMilliseconds, ProcessBatchedEvents);
176

    
177
            _watcher = new FileSystemWatcher(rootPath) {IncludeSubdirectories = true,InternalBufferSize=8*4096};
178
            _adapter = new FileSystemWatcherAdapter(_watcher);
179

    
180
            _adapter.Changed += OnFileEvent;
181
            _adapter.Created += OnFileEvent;
182
            _adapter.Deleted += OnFileEvent;
183
            //_adapter.Renamed += OnRenameEvent;
184
            _adapter.Moved += OnMoveEvent;
185
            _watcher.EnableRaisingEvents = true;
186

    
187

    
188
            _agent = Agent<WorkflowState>.Start(inbox =>
189
            {
190
                Action loop = null;
191
                loop = () =>
192
                {
193
                    var message = inbox.Receive();
194
                    var process=message.Then(Process,inbox.CancellationToken);                    
195
                    inbox.LoopAsync(process,loop,ex=>
196
                        Log.ErrorFormat("[ERROR] File Event Processing:\r{0}", ex));
197
                };
198
                loop();
199
            });
200
        }
201

    
202
        private Task<object> Process(WorkflowState state)
203
        {
204
            if (state==null)
205
                throw new ArgumentNullException("state");
206
            Contract.EndContractBlock();
207

    
208
            if (Ignore(state.Path))
209
                return CompletedTask<object>.Default;
210

    
211
            var networkState = NetworkGate.GetNetworkState(state.Path);
212
            //Skip if the file is already being downloaded or uploaded and 
213
            //the change is create or modify
214
            if (networkState != NetworkOperation.None &&
215
                (
216
                    state.TriggeringChange == WatcherChangeTypes.Created ||
217
                    state.TriggeringChange == WatcherChangeTypes.Changed
218
                ))
219
                return CompletedTask<object>.Default;
220

    
221
            try
222
            {
223
                //StatusKeeper.EnsureFileState(state.Path);
224
                
225
                UpdateFileStatus(state);
226
                UpdateOverlayStatus(state);
227
                UpdateFileChecksum(state);
228
                WorkflowAgent.Post(state);
229
            }
230
            catch (IOException exc)
231
            {
232
                if (File.Exists(state.Path))
233
                {
234
                    Log.WarnFormat("File access error occured, retrying {0}\n{1}", state.Path, exc);
235
                    _agent.Post(state);
236
                }
237
                else
238
                {
239
                    Log.WarnFormat("File {0} does not exist. Will be ignored\n{1}", state.Path, exc);
240
                }
241
            }
242
            catch (Exception exc)
243
            {
244
                Log.WarnFormat("Error occured while indexing{0}. The file will be skipped\n{1}",
245
                               state.Path, exc);
246
            }
247
            return CompletedTask<object>.Default;
248
        }
249

    
250
        public bool Pause
251
        {
252
            get { return _watcher == null || !_watcher.EnableRaisingEvents; }
253
            set
254
            {
255
                if (_watcher != null)
256
                    _watcher.EnableRaisingEvents = !value;                
257
            }
258
        }
259

    
260
        public string CachePath { get; set; }
261

    
262
        /*private List<string> _selectivePaths = new List<string>();
263
        public List<string> SelectivePaths
264
        {
265
            get { return _selectivePaths; }
266
            set { _selectivePaths = value; }
267
        }
268
*/
269
        public Selectives Selectives { get; set; }
270

    
271

    
272
        public void Post(WorkflowState workflowState)
273
        {
274
            if (workflowState == null)
275
                throw new ArgumentNullException("workflowState");
276
            Contract.EndContractBlock();
277

    
278
            _agent.Post(workflowState);
279
        }
280

    
281
        public void Stop()
282
        {
283
            if (_watcher != null)
284
            {
285
                _watcher.Dispose();
286
            }
287
            _watcher = null;
288

    
289
            if (_agent!=null)
290
                _agent.Stop();
291
        }
292

    
293
        // Enumerate all files in the Pithos directory except those in the Fragment folder
294
        // and files with a .ignore extension
295
        public IEnumerable<string> EnumerateFiles(string searchPattern="*")
296
        {
297
            var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
298
                                 where !Ignore(filePath)
299
                                 select filePath;
300
            return monitoredFiles;
301
        }
302

    
303
        public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
304
        {
305
            var rootDir = new DirectoryInfo(RootPath);
306
            var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
307
                                 where !Ignore(file.FullName)
308
                                 select file;
309
            return monitoredFiles;
310
        }                
311

    
312
        public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
313
        {
314
            var rootDir = new DirectoryInfo(RootPath);
315
            var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
316
                                 where !Ignore(file.FullName)
317
                                 select file.AsRelativeUrlTo(RootPath);
318
            return monitoredFiles;
319
        }                
320

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

    
330

    
331
        
332

    
333
        private bool Ignore(string filePath)
334
        {
335
            if (IgnorePaths(filePath)) return true;
336

    
337

    
338
            //If selective sync is enabled, 
339
            if (Selectives.IsSelectiveEnabled(AccountInfo.AccountKey) 
340
                //propagate folder events 
341
                && Directory.Exists(filePath)
342
                //from the container root folder only. Note, in the first level below the account root path are the containers
343
                && FoundBelowRoot(filePath, RootPath, 2))
344
                    return false;
345
            //Ignore if selective synchronization is defined, 
346
            //And the target file is not below any of the selective paths
347
            return !Selectives.IsSelected(AccountInfo, filePath);
348
        }
349

    
350
        private bool IgnorePaths(string filePath)
351
        {
352
//Ignore all first-level directories and files (ie at the container folders level)
353
            if (FoundBelowRoot(filePath, RootPath, 1))
354
                return true;
355

    
356
            //Ignore first-level items under the "others" folder (ie at the accounts folders level).
357
            var othersPath = Path.Combine(RootPath, FolderConstants.OthersFolder);
358
            if (FoundBelowRoot(filePath, othersPath, 1))
359
                return true;
360

    
361
            //Ignore second-level (container) folders under the "others" folder (ie at the container folders level). 
362
            if (FoundBelowRoot(filePath, othersPath, 2))
363
                return true;
364

    
365

    
366
            //Ignore anything happening in the cache path
367
            if (filePath.StartsWith(CachePath))
368
                return true;
369
            
370
            //Finally, ignore events about one of the ignored files
371
            return _ignoreFiles.ContainsKey(filePath.ToLower());
372
        }
373

    
374
/*        private static bool FoundInRoot(string filePath, string rootPath)
375
        {
376
            //var rootDirectory = new DirectoryInfo(rootPath);
377

    
378
            //If the paths are equal, return true
379
            if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))
380
                return true;
381

    
382
            //If the filepath is below the root path
383
            if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))
384
            {
385
                //Get the relative path
386
                var relativePath = filePath.Substring(rootPath.Length + 1);
387
                //If the relativePath does NOT contains a path separator, we found a match
388
                return (!relativePath.Contains(@"\"));
389
            }
390

    
391
            //If the filepath is not under the root path, return false
392
            return false;            
393
        }*/
394

    
395

    
396
        private static bool FoundBelowRoot(string filePath, string rootPath,int level)
397
        {
398
            //var rootDirectory = new DirectoryInfo(rootPath);
399

    
400
            //If the paths are equal, return true
401
            if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))
402
                return true;
403

    
404
            //If the filepath is below the root path
405
            if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))
406
            {
407
                //Get the relative path
408
                var relativePath = filePath.Substring(rootPath.Length + 1);
409
                //If the relativePath does NOT contains a path separator, we found a match
410
                var levels=relativePath.ToCharArray().Count(c=>c=='\\')+1;                
411
                return levels==level;
412
            }
413

    
414
            //If the filepath is not under the root path, return false
415
            return false;            
416
        }
417

    
418
        //Post a Change message for all events except rename
419
        void OnFileEvent(object sender, FileSystemEventArgs e)
420
        {
421
            //Ignore events that affect the cache folder
422
            var filePath = e.FullPath;
423
            if (Ignore(filePath)) 
424
                return;
425
            _eventIdleBatch.Post(e);
426
        }
427

    
428

    
429
/*
430
        //Post a Change message for renames containing the old and new names
431
        void OnRenameEvent(object sender, RenamedEventArgs e)
432
        {
433
            var oldFullPath = e.OldFullPath;
434
            var fullPath = e.FullPath;
435
            if (Ignore(oldFullPath) || Ignore(fullPath))
436
                return;
437

    
438
            _agent.Post(new WorkflowState
439
            {
440
                AccountInfo=AccountInfo,
441
                OldPath = oldFullPath,
442
                OldFileName = e.OldName,
443
                Path = fullPath,
444
                FileName = e.Name,
445
                TriggeringChange = e.ChangeType
446
            });
447
        }
448
*/
449

    
450
        //Post a Change message for moves containing the old and new names
451
        void OnMoveEvent(object sender, MovedEventArgs e)
452
        {
453
            var oldFullPath = e.OldFullPath;
454
            var fullPath = e.FullPath;
455
            
456

    
457
            //If the source path is one of the ignored folders, ignore
458
            if (IgnorePaths(oldFullPath)) 
459
                return;
460

    
461
            //TODO: Must prevent move propagation if the source folder is blocked by selective sync
462
            //Ignore takes into account Selective Sync
463
            if (Ignore(fullPath))
464
                return;
465

    
466
            _eventIdleBatch.Post(e);
467
        }
468

    
469

    
470

    
471
        private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
472
                                                                             {
473
            {WatcherChangeTypes.Created,FileStatus.Created},
474
            {WatcherChangeTypes.Changed,FileStatus.Modified},
475
            {WatcherChangeTypes.Deleted,FileStatus.Deleted},
476
            {WatcherChangeTypes.Renamed,FileStatus.Renamed}
477
        };
478

    
479
        private Dictionary<string, string> _ignoreFiles=new Dictionary<string, string>();
480

    
481
        private WorkflowState UpdateFileStatus(WorkflowState state)
482
        {
483
            if (state==null)
484
                throw new ArgumentNullException("state");
485
            if (String.IsNullOrWhiteSpace(state.Path))
486
                throw new ArgumentException("The state's Path can't be empty","state");
487
            Contract.EndContractBlock();
488

    
489
            var path = state.Path;
490
            var status = _statusDict[state.TriggeringChange];
491
            var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
492
            if (status == oldStatus)
493
            {
494
                state.Status = status;
495
                state.Skip = true;
496
                return state;
497
            }
498
            if (state.Status == FileStatus.Renamed)
499
                Workflow.ClearFileStatus(path);
500

    
501
            state.Status = Workflow.SetFileStatus(path, status);
502
            return state;
503
        }
504

    
505
        private WorkflowState UpdateOverlayStatus(WorkflowState state)
506
        {
507
            if (state==null)
508
                throw new ArgumentNullException("state");
509
            Contract.EndContractBlock();
510

    
511
            if (state.Skip)
512
                return state;
513

    
514
            switch (state.Status)
515
            {
516
                case FileStatus.Created:
517
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ShortHash).Wait();
518
                    break;
519
                case FileStatus.Modified:
520
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ShortHash).Wait();
521
                    break;
522
                case FileStatus.Deleted:
523
                    //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
524
                    break;
525
                case FileStatus.Renamed:
526
                    this.StatusKeeper.ClearFileStatus(state.OldPath);
527
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ShortHash).Wait();
528
                    break;
529
                case FileStatus.Unchanged:
530
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal, state.ShortHash).Wait();
531
                    break;
532
            }
533

    
534
            if (state.Status == FileStatus.Deleted)
535
                NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
536
            else
537
                NativeMethods.RaiseChangeNotification(state.Path);
538
            return state;
539
        }
540

    
541

    
542
        private WorkflowState UpdateFileChecksum(WorkflowState state)
543
        {
544
            if (state.Skip)
545
                return state;
546

    
547
            if (state.Status == FileStatus.Deleted)
548
                return state;
549

    
550
            var path = state.Path;
551
            //Skip calculation for folders
552
            if (Directory.Exists(path))
553
                return state;
554

    
555
            var info = new FileInfo(path);
556

    
557
            using (StatusNotification.GetNotifier("Hashing {0}", "Finished Hashing {0}", info.Name))
558
            {
559

    
560
                var shortHash = info.ComputeShortHash();
561

    
562
                string merkleHash = info.CalculateHash(StatusKeeper.BlockSize, StatusKeeper.BlockHash);
563
                StatusKeeper.UpdateFileChecksum(path, shortHash, merkleHash);
564

    
565
                state.Hash = merkleHash;
566
                return state;
567
            }
568
        }
569

    
570
        //Does the file exist in the container's local folder?
571
        public bool Exists(string relativePath)
572
        {
573
            if (String.IsNullOrWhiteSpace(relativePath))
574
                throw new ArgumentNullException("relativePath");
575
            //A RootPath must be set before calling this method
576
            if (String.IsNullOrWhiteSpace(RootPath))
577
                throw new InvalidOperationException("RootPath was not set");
578
            Contract.EndContractBlock();
579
            //Create the absolute path by combining the RootPath with the relativePath
580
            var absolutePath=Path.Combine(RootPath, relativePath);
581
            //Is this a valid file?
582
            if (File.Exists(absolutePath))
583
                return true;
584
            //Or a directory?
585
            if (Directory.Exists(absolutePath))
586
                return true;
587
            //Fail if it is neither
588
            return false;
589
        }
590

    
591
        public static FileAgent GetFileAgent(AccountInfo accountInfo)
592
        {
593
            return GetFileAgent(accountInfo.AccountPath);
594
        }
595

    
596
        public static FileAgent GetFileAgent(string rootPath)
597
        {
598
            return AgentLocator<FileAgent>.Get(rootPath.ToLower());
599
        }
600

    
601

    
602
        public FileSystemInfo GetFileSystemInfo(string relativePath)
603
        {
604
            if (String.IsNullOrWhiteSpace(relativePath))
605
                throw new ArgumentNullException("relativePath");
606
            //A RootPath must be set before calling this method
607
            if (String.IsNullOrWhiteSpace(RootPath))
608
                throw new InvalidOperationException("RootPath was not set");            
609
            Contract.EndContractBlock();            
610

    
611
            var absolutePath = Path.Combine(RootPath, relativePath);
612

    
613
            if (Directory.Exists(absolutePath))
614
                return new DirectoryInfo(absolutePath).WithProperCapitalization();
615
            else
616
                return new FileInfo(absolutePath).WithProperCapitalization();
617
            
618
        }
619

    
620
        public void Delete(string relativePath)
621
        {
622
            var absolutePath = Path.Combine(RootPath, relativePath).ToLower();
623
            if (Log.IsDebugEnabled)
624
                Log.DebugFormat("Deleting {0}", absolutePath);
625
            if (File.Exists(absolutePath))
626
            {    
627
                try
628
                {
629
                    File.Delete(absolutePath);
630
                }
631
                //The file may have been deleted by another thread. Just ignore the relevant exception
632
                catch (FileNotFoundException) { }
633
            }
634
            else if (Directory.Exists(absolutePath))
635
            {
636
                try
637
                {
638
                    Directory.Delete(absolutePath, true);
639
                }
640
                //The directory may have been deleted by another thread. Just ignore the relevant exception
641
                catch (DirectoryNotFoundException){}                
642
            }
643
        
644
            //_ignoreFiles[absolutePath] = absolutePath;                
645
            StatusKeeper.ClearFileStatus(absolutePath);
646
        }
647
    }
648
}