Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / PithosMonitor.cs @ 3c43ec9b

History | View | Annotate | Download (26.9 kB)

1
using System;
2
using System.Collections.Concurrent;
3
using System.Collections.Generic;
4
using System.ComponentModel.Composition;
5
using System.Diagnostics;
6
using System.Diagnostics.Contracts;
7
using System.IO;
8
using System.Linq;
9
using System.Net.NetworkInformation;
10
using System.Security.Cryptography;
11
using System.ServiceModel.Description;
12
using System.Text;
13
using System.Threading;
14
using System.Threading.Tasks;
15
using Castle.ActiveRecord.Queries;
16
using Microsoft.WindowsAPICodePack.Net;
17
using Pithos.Interfaces;
18
using System.ServiceModel;
19

    
20
namespace Pithos.Core
21
{
22
    [Export(typeof(PithosMonitor))]
23
    public class PithosMonitor:IDisposable
24
    {
25
        private const string PithosContainer = "pithos";
26
        private const string TrashContainer = "trash";
27

    
28
        [Import]
29
        public IPithosSettings Settings{get;set;}
30

    
31
        [Import]
32
        public IStatusKeeper StatusKeeper { get; set; }
33

    
34
        [Import]
35
        public IPithosWorkflow Workflow { get; set; }
36

    
37
        [Import]
38
        public ICloudClient CloudClient { get; set; }
39

    
40
        [Import]
41
        public ICloudClient CloudListeningClient { get; set; }
42

    
43
        public string UserName { get; set; }
44
        public string ApiKey { get; set; }
45

    
46
        private ServiceHost _statusService { get; set; }
47

    
48
        private FileSystemWatcher _watcher;
49

    
50
        public bool Pause
51
        {
52
            get { return _watcher == null || !_watcher.EnableRaisingEvents; }
53
            set
54
            {
55
                if (_watcher!=null)
56
                    _watcher.EnableRaisingEvents = !value;
57
                if (value)
58
                {
59
                    StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused);
60
                }
61
                else
62
                {
63
                    StatusKeeper.SetPithosStatus(PithosStatus.InSynch);
64
                }
65
            }
66
        }
67

    
68
        public string RootPath { get; set; }
69

    
70

    
71
        CancellationTokenSource _cancellationSource;
72

    
73
        readonly BlockingCollection<WorkflowState> _fileEvents = new BlockingCollection<WorkflowState>();
74
        readonly BlockingCollection<WorkflowState> _uploadEvents = new BlockingCollection<WorkflowState>();
75

    
76
        
77

    
78
        public void Start()
79
        {
80

    
81
            if (_cancellationSource != null)
82
            {
83
                if (!_cancellationSource.IsCancellationRequested)
84
                    return;
85
            }
86
            _cancellationSource = new CancellationTokenSource();
87

    
88
            var proxyUri = ProxyFromSettings();            
89
            CloudClient.Proxy = proxyUri;
90
            CloudClient.UsePithos = this.UsePithos;
91
            EnsurePithosContainers();
92
            StatusKeeper.StartProcessing(_cancellationSource.Token);
93
            IndexLocalFiles(RootPath);
94
            StartMonitoringFiles(RootPath);
95

    
96
            StartStatusService();
97

    
98
            StartNetwork();
99
        }
100

    
101
        private void EnsurePithosContainers()
102
        {
103
            CloudClient.UsePithos = this.UsePithos;
104
            CloudClient.Authenticate(UserName, ApiKey);
105

    
106
            var pithosContainers = new[] {PithosContainer, TrashContainer};
107
            foreach (var container in pithosContainers)
108
            {
109
                if (!CloudClient.ContainerExists(container))
110
                    CloudClient.CreateContainer(container);                
111
            }
112
        }
113

    
114
        private Uri ProxyFromSettings()
115
        {            
116
            if (Settings.UseManualProxy)
117
            {
118
                var proxyUri = new UriBuilder
119
                                   {
120
                                       Host = Settings.ProxyServer, 
121
                                       Port = Settings.ProxyPort
122
                                   };
123
                if (Settings.ProxyAuthentication)
124
                {
125
                    proxyUri.UserName = Settings.ProxyUsername;
126
                    proxyUri.Password = Settings.ProxyPassword;
127
                }
128
                return proxyUri.Uri;
129
            }
130
            return null;
131
        }
132

    
133
        private void IndexLocalFiles(string path)
134
        {
135
            Trace.TraceInformation("[START] Inxed Local");
136
            try
137
            {
138
                var files =
139
                    from filePath in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).AsParallel()
140
                    select filePath;
141
                StatusKeeper.StoreUnversionedFiles(files);
142

    
143
                RestartInterruptedFiles();
144
            }
145
            catch (Exception exc)
146
            {
147
                Trace.TraceError("[ERROR] Index Local - {0}", exc);
148
            }
149
            finally
150
            {
151
                Trace.TraceInformation("[END] Inxed Local");
152
            }
153
        }
154

    
155
        private void RestartInterruptedFiles()
156
        {
157
            var interruptedStates = new[] { FileOverlayStatus.Unversioned, FileOverlayStatus.Modified };
158
            var filesQuery = from state in FileState.Queryable
159
                             where interruptedStates.Contains(state.OverlayStatus)
160
                             select new WorkflowState
161
                                      {
162
                                          Path = state.FilePath.ToLower(),
163
                                          FileName = Path.GetFileName(state.FilePath).ToLower(),
164
                                          Status=state.OverlayStatus==FileOverlayStatus.Unversioned?
165
                                                            FileStatus.Created:
166
                                                            FileStatus.Modified,
167
                                          TriggeringChange = state.OverlayStatus==FileOverlayStatus.Unversioned?
168
                                                            WatcherChangeTypes.Created:
169
                                                            WatcherChangeTypes.Changed
170
                                      };
171
            _uploadEvents.AddFromEnumerable(filesQuery,false);           
172
            
173
        }
174

    
175
        private void StartStatusService()
176
        {
177
            // Create a ServiceHost for the CalculatorService type and provide the base address.
178
            var baseAddress = new Uri("net.pipe://localhost/pithos");
179
            _statusService = new ServiceHost(typeof(StatusService), baseAddress);
180
            
181
            var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
182
            
183
            _statusService.AddServiceEndpoint(typeof(IStatusService), binding, "net.pipe://localhost/pithos/statuscache");
184
            _statusService.AddServiceEndpoint(typeof (ISettingsService), binding, "net.pipe://localhost/pithos/settings");
185

    
186

    
187
            //// Add a mex endpoint
188
            var smb = new ServiceMetadataBehavior
189
                          {
190
                              HttpGetEnabled = true, 
191
                              HttpGetUrl = new Uri("http://localhost:30000/pithos/mex")
192
                          };
193
            _statusService.Description.Behaviors.Add(smb);
194

    
195

    
196
            _statusService.Open();
197
        }
198

    
199
        private void StopStatusService()
200
        {
201
            if (_statusService == null)
202
                return;
203

    
204
            if (_statusService.State == CommunicationState.Faulted)
205
                _statusService.Abort();
206
            else if (_statusService.State != CommunicationState.Closed)
207
                _statusService.Close();
208
            _statusService = null;
209

    
210
        }
211

    
212

    
213
        private void StartNetwork()
214
        {
215

    
216
            bool connected = NetworkListManager.IsConnectedToInternet;
217
            //If we are not connected retry later
218
            if (!connected)
219
            {
220
                Task.Factory.StartNewDelayed(10000, StartNetwork);
221
                return;
222
            }
223

    
224
            try
225
            {
226
                CloudClient.UsePithos = this.UsePithos;
227
                CloudClient.Authenticate(UserName, ApiKey);
228

    
229
                StartListening(RootPath);
230
                StartSending();
231
            }
232
            catch (Exception)
233
            {
234
                //Faild to authenticate due to network or account error
235
                //Retry after a while
236
                Task.Factory.StartNewDelayed(10000, StartNetwork);
237
            }
238
        }
239

    
240
        public bool UsePithos { get; set; }
241

    
242
        internal enum CloudActionType
243
        {
244
            Upload=0,
245
            Download,
246
            UploadUnconditional,
247
            DownloadUnconditional,
248
            DeleteLocal,
249
            DeleteCloud
250
        }
251

    
252
        internal class ListenerAction
253
        {
254
            public CloudActionType Action { get; set; }
255
            public FileInfo LocalFile { get; set; }
256
            public ObjectInfo CloudFile { get; set; }
257

    
258
            public Lazy<string> LocalHash { get; set; }
259

    
260
            public ListenerAction(CloudActionType action, FileInfo localFile, ObjectInfo cloudFile)
261
            {
262
                Action = action;
263
                LocalFile = localFile;
264
                CloudFile = cloudFile;
265
                LocalHash=new Lazy<string>(()=>Signature.CalculateHash(LocalFile.FullName),LazyThreadSafetyMode.ExecutionAndPublication);
266
            }
267
            
268
        }
269

    
270
        internal class LocalFileComparer:EqualityComparer<ListenerAction>
271
        {
272
            public override bool Equals(ListenerAction x, ListenerAction y)
273
            {
274
                if (x.Action != y.Action)
275
                    return false;
276
                if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
277
                    return false;
278
                if (x.CloudFile != null && y.CloudFile != null && !x.CloudFile.Hash.Equals(y.CloudFile.Hash))
279
                    return false;
280
                if (x.CloudFile == null ^ y.CloudFile == null ||
281
                    x.LocalFile == null ^ y.LocalFile == null)
282
                    return false;
283
                return true;
284
            }
285

    
286
            public override int GetHashCode(ListenerAction obj)
287
            {
288
                var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
289
                var hash2 = (obj.CloudFile == null) ? int.MaxValue : obj.CloudFile.Hash.GetHashCode();
290
                var hash3 = obj.Action.GetHashCode();
291
                return hash1 ^ hash2 & hash3;
292
            }
293
        }
294

    
295
        private BlockingCollection<ListenerAction> _networkActions=new BlockingCollection<ListenerAction>();
296

    
297
        private Timer timer;
298

    
299
        private void StartListening(string accountPath)
300
        {
301
            
302
            ProcessRemoteFiles(accountPath);
303

    
304
            Task.Factory.StartNew(ProcessListenerActions);
305
                        
306
        }
307

    
308
        private Task ProcessRemoteFiles(string accountPath)
309
        {
310
            Trace.TraceInformation("[LISTENER] Scheduled");    
311
            return Task.Factory.StartNewDelayed(10000)
312
                .ContinueWith(t=>CloudClient.ListObjects(PithosContainer))
313
                .ContinueWith(task =>
314
                                  {
315
                                      Trace.TraceInformation("[LISTENER] Start Processing");
316

    
317
                                      var remoteObjects = task.Result;
318
/*
319
                                      if (remoteObjects.Count == 0)
320
                                          return;
321
*/
322

    
323
                                      var pithosDir = new DirectoryInfo(accountPath);
324
                                      
325
                                      var remoteFiles = from info in remoteObjects
326
                                                    select info.Name.ToLower();
327
                                      
328
                                      var onlyLocal = from localFile in pithosDir.EnumerateFiles()
329
                                                      where !remoteFiles.Contains(localFile.Name.ToLower()) 
330
                                                      select new ListenerAction(CloudActionType.UploadUnconditional, localFile,null);
331

    
332

    
333

    
334
                                      var localNames = from info in pithosDir.EnumerateFiles()
335
                                                           select info.Name.ToLower();
336

    
337
                                      var onlyRemote = from upFile in remoteObjects
338
                                                       where !localNames.Contains(upFile.Name.ToLower())
339
                                                       select new ListenerAction(CloudActionType.DownloadUnconditional,null,upFile);
340

    
341

    
342
                                      var commonObjects = from  upFile in remoteObjects
343
                                                            join  localFile in pithosDir.EnumerateFiles()
344
                                                                on upFile.Name.ToLower() equals localFile.Name.ToLower() 
345
                                                            select new ListenerAction(CloudActionType.Download, localFile, upFile);
346

    
347
                                      var uniques =
348
                                          onlyLocal.Union(onlyRemote).Union(commonObjects)
349
                                              .Except(_networkActions,new LocalFileComparer());
350
                                      
351
                                      _networkActions.AddFromEnumerable(uniques, false);
352

    
353
                                      Trace.TraceInformation("[LISTENER] End Processing");
354
                                      
355
                                  }
356
                ).ContinueWith(t=>
357
                {
358
                    if (t.IsFaulted)
359
                    {
360
                        Trace.TraceError("[LISTENER] Exception: {0}",t.Exception);                                           
361
                    }
362
                    else
363
                    {
364
                        Trace.TraceInformation("[LISTENER] Finished");                                           
365
                    }                    
366
                    ProcessRemoteFiles(accountPath);
367
                });
368
        }
369

    
370
        private void ProcessListenerActions()
371
        {
372
            foreach(var action in _networkActions.GetConsumingEnumerable())
373
            {
374
                Trace.TraceInformation("[ACTION] Start Processing {0}:{1}->{2}",action.Action,action.LocalFile,action.CloudFile.Name);
375
                var localFile = action.LocalFile;
376
                var cloudFile = action.CloudFile;
377
                var downloadPath = (cloudFile == null)? String.Empty
378
                                        : Path.Combine(RootPath,cloudFile.Name);
379
                try
380
                {
381
                    switch (action.Action)
382
                    {
383
                        case CloudActionType.UploadUnconditional:
384

    
385
                            UploadCloudFile(localFile.Name,localFile.Length,localFile.FullName,action.LocalHash.Value);
386
                            break;
387
                        case CloudActionType.DownloadUnconditional:
388
                            DownloadCloudFile(PithosContainer,cloudFile.Name,downloadPath);
389
                            break;
390
                        case CloudActionType.Download:
391
                            if (File.Exists(downloadPath))
392
                            {
393
                                if (cloudFile.Hash !=action.LocalHash.Value)
394
                                {
395
                                    var lastLocalTime =localFile.LastWriteTime;
396
                                    var lastUpTime =cloudFile.Last_Modified;
397
                                    if (lastUpTime <=lastLocalTime)
398
                                    {
399
                                        //Files in conflict
400
                                        StatusKeeper.SetFileOverlayStatus(downloadPath,FileOverlayStatus.Conflict);
401
                                    }
402
                                    else
403
                                        DownloadCloudFile(PithosContainer,action.CloudFile.Name,downloadPath);
404
                                }
405
                            }
406
                            else
407
                                DownloadCloudFile(PithosContainer,action.CloudFile.Name,downloadPath);
408
                            break;
409
                    }
410
                    Trace.TraceInformation("[ACTION] End Processing {0}:{1}->{2}", action.Action, action.LocalFile, action.CloudFile.Name);
411
                }
412
                catch (Exception exc)
413
                {
414
                    Trace.TraceError("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}",
415
                                    action.Action, action.LocalFile,action.CloudFile,exc);                    
416

    
417
                    _networkActions.Add(action);
418
                }
419
            }
420
        }
421

    
422
      
423

    
424
        private void StartMonitoringFiles(string path)
425
        {
426
            _watcher = new FileSystemWatcher(path);
427
            _watcher.Changed += OnFileEvent;
428
            _watcher.Created += OnFileEvent;
429
            _watcher.Deleted += OnFileEvent;
430
            _watcher.Renamed += OnRenameEvent;
431
            _watcher.EnableRaisingEvents = true;
432

    
433
            Task.Factory.StartNew(ProcesFileEvents,_cancellationSource.Token);
434
        }
435

    
436
        private void ProcesFileEvents()
437
        {
438
            foreach (var state in _fileEvents.GetConsumingEnumerable())
439
            {
440
                try
441
                {
442
                    var networkState=StatusKeeper.GetNetworkState(state.Path);
443
                    //Skip if the file is already being downloaded or uploaded and 
444
                    //the change is create or modify
445
                    if (networkState != NetworkState.None && 
446
                        (
447
                            state.TriggeringChange==WatcherChangeTypes.Created ||
448
                            state.TriggeringChange==WatcherChangeTypes.Changed
449
                        ))
450
                        continue;
451
                    UpdateFileStatus(state);
452
                    UpdateOverlayStatus(state);
453
                    UpdateFileChecksum(state);
454
                    _uploadEvents.Add(state);
455
                }
456
                catch (OperationCanceledException exc)
457
                {
458
                    Trace.TraceError("[ERROR] File Event Processing:\r{0}", exc);
459
                    throw;
460
                }
461
                catch (Exception exc)
462
                {
463
                    Trace.TraceError("[ERROR] File Event Processing:\r{0}",exc);
464
                }
465
            }
466
        }
467

    
468
        private void StartSending()
469
        {
470
            Task.Factory.StartNew(() =>
471
                                      {
472
                                          foreach (var state in _uploadEvents.GetConsumingEnumerable())
473
                                          {
474
                                              try
475
                                              {
476
                                                  SynchToCloud(state);
477
                                              }
478
                                              catch (OperationCanceledException)
479
                                              {
480
                                                  throw;
481
                                              }
482
                                              catch(Exception ex)
483
                                              {
484
                                                  Trace.TraceError("[ERROR] Synch for {0}:\r{1}",state.FileName,ex);
485
                                              }
486
                                          }
487
                                          
488
                                      },_cancellationSource.Token);
489
        }
490

    
491

    
492
        private WorkflowState SynchToCloud(WorkflowState state)
493
        {
494
            if (state.Skip)
495
                return state;
496
            string path = state.Path;
497
            string fileName = Path.GetFileName(path);
498

    
499
            switch(state.Status)
500
            {
501
                case FileStatus.Created:
502
                case FileStatus.Modified:
503
                    var info = new FileInfo(path);
504
                    long fileSize = info.Length;
505
                    UploadCloudFile(fileName, fileSize, path,state.Hash);
506
                    break;
507
                case FileStatus.Deleted:
508
                    DeleteCloudFile(fileName);
509
                    break;
510
                case FileStatus.Renamed:
511
                    RenameCloudFile(state);
512
                    break;
513
            }
514
            return state;
515
        }
516

    
517
        private void RenameCloudFile(WorkflowState state)
518
        {
519
            this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
520

    
521

    
522

    
523
            CloudClient.MoveObject(PithosContainer, state.OldFileName,PithosContainer, state.FileName);
524

    
525
            this.StatusKeeper.SetFileStatus(state.Path, FileStatus.Unchanged);
526
            this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
527
            Workflow.RaiseChangeNotification(state.Path);
528
        }
529

    
530
        private void DeleteCloudFile(string fileName)
531
        {
532
            Contract.Requires(!Path.IsPathRooted(fileName));
533

    
534
            this.StatusKeeper.SetFileOverlayStatus(fileName, FileOverlayStatus.Modified);
535

    
536
            CloudClient.MoveObject(PithosContainer,fileName,TrashContainer,fileName);
537
            this.StatusKeeper.ClearFileStatus(fileName);
538
            this.StatusKeeper.RemoveFileOverlayStatus(fileName);            
539
        }
540

    
541
        private void DownloadCloudFile(string container, string fileName, string localPath)
542
        {
543
            StatusKeeper.SetNetworkState(localPath,NetworkState.Downloading);
544
            CloudClient.GetObject(container, fileName, localPath)
545
            .ContinueWith(t=>
546
                CloudClient.GetObjectInfo(container,fileName))
547
            .ContinueWith(t=>
548
                StatusKeeper.StoreInfo(fileName,t.Result))
549
            .ContinueWith(t=>
550
                StatusKeeper.SetNetworkState(localPath,NetworkState.None))
551
            .Wait();
552
        }
553

    
554
        private void UploadCloudFile(string fileName, long fileSize, string path,string hash)
555
        {
556
            Contract.Requires(!Path.IsPathRooted(fileName));
557

    
558
            StatusKeeper.SetNetworkState(fileName,NetworkState.Uploading);
559
            
560
            //Even if GetObjectInfo times out, we can proceed with the upload            
561
            var info = CloudClient.GetObjectInfo(PithosContainer, fileName);
562
            Task.Factory.StartNew(() =>
563
            {
564
                if (hash != info.Hash)
565
                {
566
                    Task.Factory.StartNew(() => 
567
                        this.StatusKeeper.SetFileOverlayStatus(path, FileOverlayStatus.Modified))
568
                    .ContinueWith(t => 
569
                        CloudClient.PutObject(PithosContainer, fileName, path, hash));
570
                }
571
                else
572
                {
573
                    this.StatusKeeper.StoreInfo(path,info);
574
                }
575
            }
576
            )
577
            .ContinueWith(t => 
578
                this.StatusKeeper.SetFileState(path, FileStatus.Unchanged, FileOverlayStatus.Normal))
579
                .ContinueWith(t=>
580
                    this.StatusKeeper.SetNetworkState(path,NetworkState.None))
581
            .Wait();
582
            Workflow.RaiseChangeNotification(path);
583
        }
584

    
585
        private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
586
        {
587
            {WatcherChangeTypes.Created,FileStatus.Created},
588
            {WatcherChangeTypes.Changed,FileStatus.Modified},
589
            {WatcherChangeTypes.Deleted,FileStatus.Deleted},
590
            {WatcherChangeTypes.Renamed,FileStatus.Renamed}
591
        };
592

    
593
        private WorkflowState UpdateFileStatus(WorkflowState  state)
594
        {
595
            string path = state.Path;
596
            FileStatus status = _statusDict[state.TriggeringChange];
597
            var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
598
            if (status == oldStatus)
599
            {
600
                state.Status = status;
601
                state.Skip = true;
602
                return state;
603
            }
604
            if (state.Status == FileStatus.Renamed)
605
                Workflow.ClearFileStatus(path);                
606

    
607
            state.Status = Workflow.SetFileStatus(path, status);                
608
            return state;
609
        }
610

    
611
        private WorkflowState UpdateOverlayStatus(WorkflowState state)
612
        {            
613
            if (state.Skip)
614
                return state;
615

    
616
            switch (state.Status)
617
            {
618
                case FileStatus.Created:
619
                case FileStatus.Modified:
620
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
621
                    break;
622
                case FileStatus.Deleted:
623
                    this.StatusKeeper.RemoveFileOverlayStatus(state.Path);
624
                    break;
625
                case FileStatus.Renamed:
626
                    this.StatusKeeper.RemoveFileOverlayStatus(state.OldPath);
627
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
628
                    break;
629
                case FileStatus.Unchanged:
630
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
631
                    break;
632
            }
633

    
634
            if (state.Status==FileStatus.Deleted)
635
                Workflow.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
636
            else
637
                Workflow.RaiseChangeNotification(state.Path);
638
            return state;
639
        }
640

    
641

    
642
        private WorkflowState UpdateFileChecksum(WorkflowState state)
643
        {
644
            if (state.Skip)
645
                return state;
646

    
647
            if (state.Status == FileStatus.Deleted)
648
                return state;
649

    
650
            string path = state.Path;
651
            string hash = Signature.CalculateHash(path);
652

    
653
            StatusKeeper.UpdateFileChecksum(path, hash);
654

    
655
            state.Hash = hash;
656
            return state;
657
        }
658

    
659
       
660

    
661
        private FileSystemEventArgs CalculateSignature(FileSystemEventArgs arg)
662
        {
663
            Debug.WriteLine(String.Format("{0} {1} {2}", arg.ChangeType, arg.Name, arg.FullPath), "INFO");
664
            return arg;
665
        }
666

    
667
        void OnFileEvent(object sender, FileSystemEventArgs e)
668
        {
669
            _fileEvents.Add(new WorkflowState{Path=e.FullPath,FileName = e.Name,TriggeringChange=e.ChangeType});            
670
        }
671

    
672
        void OnRenameEvent(object sender, RenamedEventArgs e)
673
        {
674
            _fileEvents.Add(new WorkflowState { OldPath=e.OldFullPath,OldFileName=e.OldName,
675
                Path = e.FullPath, FileName = e.Name, TriggeringChange = e.ChangeType });
676
        }
677

    
678
        public void Stop()
679
        {
680
            if (_watcher != null)
681
            {
682
                _watcher.Changed -= OnFileEvent;
683
                _watcher.Created -= OnFileEvent;
684
                _watcher.Deleted -= OnFileEvent;
685
                _watcher.Renamed -= OnRenameEvent;
686
                _watcher.Dispose();
687
            }
688
            _watcher = null;
689
            _fileEvents.CompleteAdding();
690
            if (timer != null)
691
                timer.Dispose();
692
            timer = null;
693
            StopStatusService();
694
        }
695

    
696

    
697
        ~PithosMonitor()
698
        {
699
            Dispose(false);
700
        }
701

    
702
        public void Dispose()
703
        {
704
            Dispose(true);
705
            GC.SuppressFinalize(this);
706
        }
707

    
708
        protected virtual void Dispose(bool disposing)
709
        {
710
            if (disposing)
711
            {
712
                Stop();
713
            }
714
        }
715

    
716

    
717
    }
718
}