Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / PithosMonitor.cs @ 0c02aa65

History | View | Annotate | Download (28.6 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 IStatusNotification StatusNotification { get; set; }
44

    
45
        public string UserName { get; set; }
46
        public string ApiKey { get; set; }
47

    
48
        private ServiceHost _statusService { get; set; }
49

    
50
        private FileSystemWatcher _watcher;
51

    
52
        public bool Pause
53
        {
54
            get { return _watcher == null || !_watcher.EnableRaisingEvents; }
55
            set
56
            {
57
                if (_watcher!=null)
58
                    _watcher.EnableRaisingEvents = !value;
59
                if (value)
60
                {
61
                    StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused);
62
                    StatusNotification.NotifyChange("Paused");
63
                }
64
                else
65
                {
66
                    StatusKeeper.SetPithosStatus(PithosStatus.InSynch);
67
                    StatusNotification.NotifyChange("In Synch");
68
                }
69
            }
70
        }
71

    
72
        public string RootPath { get; set; }
73

    
74

    
75
        CancellationTokenSource _cancellationSource;
76

    
77
        readonly BlockingCollection<WorkflowState> _fileEvents = new BlockingCollection<WorkflowState>();
78
        readonly BlockingCollection<WorkflowState> _uploadEvents = new BlockingCollection<WorkflowState>();
79

    
80
        
81

    
82
        public void Start()
83
        {
84
            StatusNotification.NotifyChange("Starting");
85
            if (_cancellationSource != null)
86
            {
87
                if (!_cancellationSource.IsCancellationRequested)
88
                    return;
89
            }
90
            _cancellationSource = new CancellationTokenSource();
91

    
92
            var proxyUri = ProxyFromSettings();            
93
            CloudClient.Proxy = proxyUri;
94
            CloudClient.UsePithos = this.UsePithos;
95
            EnsurePithosContainers();
96
            StatusKeeper.StartProcessing(_cancellationSource.Token);
97
            IndexLocalFiles(RootPath);
98
            StartMonitoringFiles(RootPath);
99

    
100
            StartStatusService();
101

    
102
            StartNetwork();
103
        }
104

    
105
        private void EnsurePithosContainers()
106
        {
107
            CloudClient.UsePithos = this.UsePithos;
108
            CloudClient.AuthenticationUrl = this.AuthenticationUrl;
109
            CloudClient.Authenticate(UserName, ApiKey);
110

    
111
            var pithosContainers = new[] {PithosContainer, TrashContainer};
112
            foreach (var container in pithosContainers)
113
            {
114
                if (!CloudClient.ContainerExists(container))
115
                    CloudClient.CreateContainer(container);                
116
            }
117
        }
118

    
119
        public string AuthenticationUrl { get; set; }
120

    
121
        private Uri ProxyFromSettings()
122
        {            
123
            if (Settings.UseManualProxy)
124
            {
125
                var proxyUri = new UriBuilder
126
                                   {
127
                                       Host = Settings.ProxyServer, 
128
                                       Port = Settings.ProxyPort
129
                                   };
130
                if (Settings.ProxyAuthentication)
131
                {
132
                    proxyUri.UserName = Settings.ProxyUsername;
133
                    proxyUri.Password = Settings.ProxyPassword;
134
                }
135
                return proxyUri.Uri;
136
            }
137
            return null;
138
        }
139

    
140
        private void IndexLocalFiles(string path)
141
        {
142
            StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
143
            Trace.TraceInformation("[START] Index Local");
144
            try
145
            {
146
                var files =
147
                    from filePath in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).AsParallel()
148
                    select filePath;
149
                StatusKeeper.StoreUnversionedFiles(files);
150

    
151
                RestartInterruptedFiles();
152
            }
153
            catch (Exception exc)
154
            {
155
                Trace.TraceError("[ERROR] Index Local - {0}", exc);
156
            }
157
            finally
158
            {
159
                Trace.TraceInformation("[END] Inxed Local");
160
            }
161
        }
162

    
163
        private void RestartInterruptedFiles()
164
        {
165
            StatusNotification.NotifyChange("Restart processing interrupted files", TraceLevel.Verbose);
166
            var interruptedStates = new[] { FileOverlayStatus.Unversioned, FileOverlayStatus.Modified };
167
            var filesQuery = from state in FileState.Queryable
168
                             where interruptedStates.Contains(state.OverlayStatus)
169
                             select new WorkflowState
170
                                      {
171
                                          Path = state.FilePath.ToLower(),
172
                                          FileName = Path.GetFileName(state.FilePath).ToLower(),
173
                                          Status=state.OverlayStatus==FileOverlayStatus.Unversioned?
174
                                                            FileStatus.Created:
175
                                                            FileStatus.Modified,
176
                                          TriggeringChange = state.OverlayStatus==FileOverlayStatus.Unversioned?
177
                                                            WatcherChangeTypes.Created:
178
                                                            WatcherChangeTypes.Changed
179
                                      };
180
            _uploadEvents.AddFromEnumerable(filesQuery,false);           
181
            
182
        }
183

    
184
        private void StartStatusService()
185
        {
186
            // Create a ServiceHost for the CalculatorService type and provide the base address.
187
            var baseAddress = new Uri("net.pipe://localhost/pithos");
188
            _statusService = new ServiceHost(typeof(StatusService), baseAddress);
189
            
190
            var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
191
            
192
            _statusService.AddServiceEndpoint(typeof(IStatusService), binding, "net.pipe://localhost/pithos/statuscache");
193
            _statusService.AddServiceEndpoint(typeof (ISettingsService), binding, "net.pipe://localhost/pithos/settings");
194

    
195

    
196
            //// Add a mex endpoint
197
            var smb = new ServiceMetadataBehavior
198
                          {
199
                              HttpGetEnabled = true, 
200
                              HttpGetUrl = new Uri("http://localhost:30000/pithos/mex")
201
                          };
202
            _statusService.Description.Behaviors.Add(smb);
203

    
204

    
205
            _statusService.Open();
206
        }
207

    
208
        private void StopStatusService()
209
        {
210
            if (_statusService == null)
211
                return;
212

    
213
            if (_statusService.State == CommunicationState.Faulted)
214
                _statusService.Abort();
215
            else if (_statusService.State != CommunicationState.Closed)
216
                _statusService.Close();
217
            _statusService = null;
218

    
219
        }
220

    
221

    
222
        private void StartNetwork()
223
        {
224

    
225
            bool connected = NetworkListManager.IsConnectedToInternet;
226
            //If we are not connected retry later
227
            if (!connected)
228
            {
229
                Task.Factory.StartNewDelayed(10000, StartNetwork);
230
                return;
231
            }
232

    
233
            try
234
            {
235
                CloudClient.UsePithos = this.UsePithos;
236
                CloudClient.AuthenticationUrl = this.AuthenticationUrl;
237
                CloudClient.Authenticate(UserName, ApiKey);
238

    
239
                StartListening(RootPath);
240
                StartSending();
241
            }
242
            catch (Exception)
243
            {
244
                //Faild to authenticate due to network or account error
245
                //Retry after a while
246
                Task.Factory.StartNewDelayed(10000, StartNetwork);
247
            }
248
        }
249

    
250
        public bool UsePithos { get; set; }
251

    
252
        internal enum CloudActionType
253
        {
254
            Upload=0,
255
            Download,
256
            UploadUnconditional,
257
            DownloadUnconditional,
258
            DeleteLocal,
259
            DeleteCloud
260
        }
261

    
262
        internal class ListenerAction
263
        {
264
            public CloudActionType Action { get; set; }
265
            public FileInfo LocalFile { get; set; }
266
            public ObjectInfo CloudFile { get; set; }
267

    
268
            public Lazy<string> LocalHash { get; set; }
269

    
270
            public ListenerAction(CloudActionType action, FileInfo localFile, ObjectInfo cloudFile)
271
            {
272
                Action = action;
273
                LocalFile = localFile;
274
                CloudFile = cloudFile;
275
                LocalHash=new Lazy<string>(()=>Signature.CalculateHash(LocalFile.FullName),LazyThreadSafetyMode.ExecutionAndPublication);
276
            }
277
            
278
        }
279

    
280
        internal class LocalFileComparer:EqualityComparer<ListenerAction>
281
        {
282
            public override bool Equals(ListenerAction x, ListenerAction y)
283
            {
284
                if (x.Action != y.Action)
285
                    return false;
286
                if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
287
                    return false;
288
                if (x.CloudFile != null && y.CloudFile != null && !x.CloudFile.Hash.Equals(y.CloudFile.Hash))
289
                    return false;
290
                if (x.CloudFile == null ^ y.CloudFile == null ||
291
                    x.LocalFile == null ^ y.LocalFile == null)
292
                    return false;
293
                return true;
294
            }
295

    
296
            public override int GetHashCode(ListenerAction obj)
297
            {
298
                var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
299
                var hash2 = (obj.CloudFile == null) ? int.MaxValue : obj.CloudFile.Hash.GetHashCode();
300
                var hash3 = obj.Action.GetHashCode();
301
                return hash1 ^ hash2 & hash3;
302
            }
303
        }
304

    
305
        private BlockingCollection<ListenerAction> _networkActions=new BlockingCollection<ListenerAction>();
306

    
307
        private Timer timer;
308

    
309
        private void StartListening(string accountPath)
310
        {
311
            
312
            ProcessRemoteFiles(accountPath);
313

    
314
            Task.Factory.StartNew(ProcessListenerActions);
315
                        
316
        }
317

    
318
        private Task ProcessRemoteFiles(string accountPath)
319
        {
320
            Trace.TraceInformation("[LISTENER] Scheduled");    
321
            return Task.Factory.StartNewDelayed(10000)
322
                .ContinueWith(t=>CloudClient.ListObjects(PithosContainer))
323
                .ContinueWith(task =>
324
                                  {
325
                                      Trace.TraceInformation("[LISTENER] Start Processing");
326

    
327
                                      var remoteObjects = task.Result;
328
/*
329
                                      if (remoteObjects.Count == 0)
330
                                          return;
331
*/
332

    
333
                                      var pithosDir = new DirectoryInfo(accountPath);
334
                                      
335
                                      var remoteFiles = from info in remoteObjects
336
                                                    select info.Name.ToLower();
337
                                      
338
                                      var onlyLocal = from localFile in pithosDir.EnumerateFiles()
339
                                                      where !remoteFiles.Contains(localFile.Name.ToLower()) 
340
                                                      select new ListenerAction(CloudActionType.UploadUnconditional, localFile,ObjectInfo.Empty);
341

    
342

    
343

    
344
                                      var localNames = from info in pithosDir.EnumerateFiles()
345
                                                           select info.Name.ToLower();
346

    
347
                                      var onlyRemote = from upFile in remoteObjects
348
                                                       where !localNames.Contains(upFile.Name.ToLower())
349
                                                       select new ListenerAction(CloudActionType.DownloadUnconditional,new FileInfo(""), upFile);
350

    
351

    
352
                                      var commonObjects = from  upFile in remoteObjects
353
                                                            join  localFile in pithosDir.EnumerateFiles()
354
                                                                on upFile.Name.ToLower() equals localFile.Name.ToLower() 
355
                                                            select new ListenerAction(CloudActionType.Download, localFile, upFile);
356

    
357
                                      var uniques =
358
                                          onlyLocal.Union(onlyRemote).Union(commonObjects)
359
                                              .Except(_networkActions,new LocalFileComparer());                                      
360

    
361
                                      _networkActions.AddFromEnumerable(uniques, false);
362

    
363
                                      StatusNotification.NotifyChange(String.Format("Processing {0} files", _networkActions.Count));
364

    
365
                                      Trace.TraceInformation("[LISTENER] End Processing");
366
                                      
367
                                  }
368
                ).ContinueWith(t=>
369
                {
370
                    if (t.IsFaulted)
371
                    {
372
                        Trace.TraceError("[LISTENER] Exception: {0}",t.Exception);                                           
373
                    }
374
                    else
375
                    {
376
                        Trace.TraceInformation("[LISTENER] Finished");                                           
377
                    }                    
378
                    ProcessRemoteFiles(accountPath);
379
                });
380
        }
381

    
382
        private void ProcessListenerActions()
383
        {
384
            foreach(var action in _networkActions.GetConsumingEnumerable())
385
            {
386
                Trace.TraceInformation("[ACTION] Start Processing {0}:{1}->{2}",action.Action,action.LocalFile,action.CloudFile.Name);
387
                var localFile = action.LocalFile;
388
                var cloudFile = action.CloudFile;
389
                var downloadPath = (cloudFile == null)? String.Empty
390
                                        : Path.Combine(RootPath,cloudFile.Name);
391
                try
392
                {
393
                    switch (action.Action)
394
                    {
395
                        case CloudActionType.UploadUnconditional:
396
                            UploadCloudFile(localFile.Name,localFile.Length,localFile.FullName,action.LocalHash.Value);
397
                            break;
398
                        case CloudActionType.DownloadUnconditional:
399
                            DownloadCloudFile(PithosContainer,cloudFile.Name,downloadPath);
400
                            break;
401
                        case CloudActionType.Download:
402
                            if (File.Exists(downloadPath))
403
                            {
404
                                if (cloudFile.Hash !=action.LocalHash.Value)
405
                                {
406
                                    var lastLocalTime =localFile.LastWriteTime;
407
                                    var lastUpTime =cloudFile.Last_Modified;
408
                                    if (lastUpTime <=lastLocalTime)
409
                                    {
410
                                        //Files in conflict
411
                                        StatusKeeper.SetFileOverlayStatus(downloadPath,FileOverlayStatus.Conflict);
412
                                    }
413
                                    else
414
                                        DownloadCloudFile(PithosContainer,action.CloudFile.Name,downloadPath);
415
                                }
416
                            }
417
                            else
418
                                DownloadCloudFile(PithosContainer,action.CloudFile.Name,downloadPath);
419
                            break;
420
                    }
421
                    Trace.TraceInformation("[ACTION] End Processing {0}:{1}->{2}", action.Action, action.LocalFile, action.CloudFile.Name);
422
                }
423
                catch (Exception exc)
424
                {
425
                    Trace.TraceError("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}",
426
                                    action.Action, action.LocalFile,action.CloudFile,exc);                    
427

    
428
                    _networkActions.Add(action);
429
                }
430
            }
431
        }
432

    
433
      
434

    
435
        private void StartMonitoringFiles(string path)
436
        {
437
            _watcher = new FileSystemWatcher(path);
438
            _watcher.Changed += OnFileEvent;
439
            _watcher.Created += OnFileEvent;
440
            _watcher.Deleted += OnFileEvent;
441
            _watcher.Renamed += OnRenameEvent;
442
            _watcher.EnableRaisingEvents = true;
443

    
444
            Task.Factory.StartNew(ProcesFileEvents,_cancellationSource.Token);
445
        }
446

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

    
479
        private void StartSending()
480
        {
481
            Task.Factory.StartNew(() =>
482
                                      {
483
                                          foreach (var state in _uploadEvents.GetConsumingEnumerable())
484
                                          {
485
                                              try
486
                                              {
487
                                                  SynchToCloud(state);
488
                                              }
489
                                              catch (OperationCanceledException)
490
                                              {
491
                                                  throw;
492
                                              }
493
                                              catch(Exception ex)
494
                                              {
495
                                                  Trace.TraceError("[ERROR] Synch for {0}:\r{1}",state.FileName,ex);
496
                                              }
497
                                          }
498
                                          
499
                                      },_cancellationSource.Token);
500
        }
501

    
502

    
503
        private WorkflowState SynchToCloud(WorkflowState state)
504
        {
505
            if (state.Skip)
506
                return state;
507
            string path = state.Path.ToLower();            
508
            string fileName = Path.GetFileName(path);
509

    
510
            //Bypass deleted files, unless the status is Deleted
511
            if (!(File.Exists(path) || state.Status == FileStatus.Deleted))
512
            {
513
                state.Skip = true;
514
                this.StatusKeeper.RemoveFileOverlayStatus(path);
515
                return state;
516
            }
517

    
518
            switch(state.Status)
519
            {
520
                case FileStatus.Created:
521
                case FileStatus.Modified:
522
                    var info = new FileInfo(path);
523
                    long fileSize = info.Length;
524
                    UploadCloudFile(fileName, fileSize, path,state.Hash);
525
                    break;
526
                case FileStatus.Deleted:
527
                    DeleteCloudFile(fileName);
528
                    break;
529
                case FileStatus.Renamed:
530
                    RenameCloudFile(state);
531
                    break;
532
            }
533
            return state;
534
        }
535

    
536
        private void RenameCloudFile(WorkflowState state)
537
        {
538
            this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
539

    
540

    
541

    
542
            CloudClient.MoveObject(PithosContainer, state.OldFileName,PithosContainer, state.FileName);
543

    
544
            this.StatusKeeper.SetFileStatus(state.Path, FileStatus.Unchanged);
545
            this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
546
            Workflow.RaiseChangeNotification(state.Path);
547
        }
548

    
549
        private void DeleteCloudFile(string fileName)
550
        {
551
            Contract.Requires(!Path.IsPathRooted(fileName));
552

    
553
            this.StatusKeeper.SetFileOverlayStatus(fileName, FileOverlayStatus.Modified);
554

    
555
            CloudClient.MoveObject(PithosContainer,fileName,TrashContainer,fileName);
556
            this.StatusKeeper.ClearFileStatus(fileName);
557
            this.StatusKeeper.RemoveFileOverlayStatus(fileName);            
558
        }
559

    
560
        private void DownloadCloudFile(string container, string fileName, string localPath)
561
        {
562
            var state = StatusKeeper.GetNetworkState(fileName);
563
            //Abort if the file is already being uploaded or downloaded
564
            if (state != NetworkState.None)
565
                return;
566

    
567
            StatusKeeper.SetNetworkState(localPath,NetworkState.Downloading);
568
            CloudClient.GetObject(container, fileName, localPath)
569
            .ContinueWith(t=>
570
                CloudClient.GetObjectInfo(container,fileName))
571
            .ContinueWith(t=>
572
                StatusKeeper.StoreInfo(fileName,t.Result))
573
            .ContinueWith(t=>
574
                StatusKeeper.SetNetworkState(localPath,NetworkState.None))
575
            .Wait();
576
        }
577

    
578
        private void UploadCloudFile(string fileName, long fileSize, string path,string hash)
579
        {
580
            Contract.Requires(!Path.IsPathRooted(fileName));
581
            var state=StatusKeeper.GetNetworkState(fileName);
582
            //Abort if the file is already being uploaded or downloaded
583
            if (state != NetworkState.None)
584
                return;
585

    
586
            StatusKeeper.SetNetworkState(fileName,NetworkState.Uploading);
587
            
588
            //Even if GetObjectInfo times out, we can proceed with the upload            
589
            var info = CloudClient.GetObjectInfo(PithosContainer, fileName);
590
            Task.Factory.StartNew(() =>
591
            {
592
                if (hash != info.Hash)
593
                {
594
                    Task.Factory.StartNew(() => 
595
                        this.StatusKeeper.SetFileOverlayStatus(path, FileOverlayStatus.Modified))
596
                    .ContinueWith(t => 
597
                        CloudClient.PutObject(PithosContainer, fileName, path, hash));
598
                }
599
                else
600
                {
601
                    this.StatusKeeper.StoreInfo(path,info);
602
                }
603
            })
604
            .ContinueWith(t => 
605
                this.StatusKeeper.SetFileState(path, FileStatus.Unchanged, FileOverlayStatus.Normal))
606
                .ContinueWith(t=>
607
                    this.StatusKeeper.SetNetworkState(path,NetworkState.None))
608
            .Wait();
609
            Workflow.RaiseChangeNotification(path);
610
        }
611

    
612
        private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
613
        {
614
            {WatcherChangeTypes.Created,FileStatus.Created},
615
            {WatcherChangeTypes.Changed,FileStatus.Modified},
616
            {WatcherChangeTypes.Deleted,FileStatus.Deleted},
617
            {WatcherChangeTypes.Renamed,FileStatus.Renamed}
618
        };
619

    
620
        private WorkflowState UpdateFileStatus(WorkflowState  state)
621
        {
622
            string path = state.Path;
623
            FileStatus status = _statusDict[state.TriggeringChange];
624
            var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
625
            if (status == oldStatus)
626
            {
627
                state.Status = status;
628
                state.Skip = true;
629
                return state;
630
            }
631
            if (state.Status == FileStatus.Renamed)
632
                Workflow.ClearFileStatus(path);                
633

    
634
            state.Status = Workflow.SetFileStatus(path, status);                
635
            return state;
636
        }
637

    
638
        private WorkflowState UpdateOverlayStatus(WorkflowState state)
639
        {            
640
            if (state.Skip)
641
                return state;
642

    
643
            switch (state.Status)
644
            {
645
                case FileStatus.Created:
646
                case FileStatus.Modified:
647
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
648
                    break;
649
                case FileStatus.Deleted:
650
                    this.StatusKeeper.RemoveFileOverlayStatus(state.Path);
651
                    break;
652
                case FileStatus.Renamed:
653
                    this.StatusKeeper.RemoveFileOverlayStatus(state.OldPath);
654
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
655
                    break;
656
                case FileStatus.Unchanged:
657
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
658
                    break;
659
            }
660

    
661
            if (state.Status==FileStatus.Deleted)
662
                Workflow.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
663
            else
664
                Workflow.RaiseChangeNotification(state.Path);
665
            return state;
666
        }
667

    
668

    
669
        private WorkflowState UpdateFileChecksum(WorkflowState state)
670
        {
671
            if (state.Skip)
672
                return state;
673

    
674
            if (state.Status == FileStatus.Deleted)
675
                return state;
676

    
677
            string path = state.Path;
678
            string hash = Signature.CalculateHash(path);
679

    
680
            StatusKeeper.UpdateFileChecksum(path, hash);
681

    
682
            state.Hash = hash;
683
            return state;
684
        }
685

    
686
       
687

    
688
        private FileSystemEventArgs CalculateSignature(FileSystemEventArgs arg)
689
        {
690
            Debug.WriteLine(String.Format("{0} {1} {2}", arg.ChangeType, arg.Name, arg.FullPath), "INFO");
691
            return arg;
692
        }
693

    
694
        void OnFileEvent(object sender, FileSystemEventArgs e)
695
        {
696
            _fileEvents.Add(new WorkflowState{Path=e.FullPath,FileName = e.Name,TriggeringChange=e.ChangeType});            
697
        }
698

    
699
        void OnRenameEvent(object sender, RenamedEventArgs e)
700
        {
701
            _fileEvents.Add(new WorkflowState { OldPath=e.OldFullPath,OldFileName=e.OldName,
702
                Path = e.FullPath, FileName = e.Name, TriggeringChange = e.ChangeType });
703
        }
704

    
705
        public void Stop()
706
        {
707
            if (_watcher != null)
708
            {
709
                _watcher.Changed -= OnFileEvent;
710
                _watcher.Created -= OnFileEvent;
711
                _watcher.Deleted -= OnFileEvent;
712
                _watcher.Renamed -= OnRenameEvent;
713
                _watcher.Dispose();
714
            }
715
            _watcher = null;
716
            _fileEvents.CompleteAdding();
717
            if (timer != null)
718
                timer.Dispose();
719
            timer = null;
720
            StopStatusService();
721
        }
722

    
723

    
724
        ~PithosMonitor()
725
        {
726
            Dispose(false);
727
        }
728

    
729
        public void Dispose()
730
        {
731
            Dispose(true);
732
            GC.SuppressFinalize(this);
733
        }
734

    
735
        protected virtual void Dispose(bool disposing)
736
        {
737
            if (disposing)
738
            {
739
                Stop();
740
            }
741
        }
742

    
743

    
744
    }
745

    
746
    public interface IStatusNotification
747
    {        
748
        void NotifyChange(string status,TraceLevel level=TraceLevel.Info);
749
    }
750
}