Simplified proxy management code
[pithos-ms-client] / trunk / Pithos.Client.WPF / Shell / ShellViewModel.cs
1 using System.Collections.Concurrent;
2 using System.Diagnostics;
3 using System.Diagnostics.Contracts;
4 using System.IO;
5 using System.Net;
6 using System.Reflection;
7 using System.Runtime.InteropServices;
8 using System.ServiceModel;
9 using System.Threading.Tasks;
10 using System.Windows;
11 using System.Windows.Controls.Primitives;
12 using Caliburn.Micro;
13 using Hardcodet.Wpf.TaskbarNotification;
14 using Pithos.Client.WPF.Configuration;
15 using Pithos.Client.WPF.FileProperties;
16 using Pithos.Client.WPF.Preferences;
17 using Pithos.Client.WPF.SelectiveSynch;
18 using Pithos.Client.WPF.Services;
19 using Pithos.Client.WPF.Shell;
20 using Pithos.Core;
21 using Pithos.Core.Agents;
22 using Pithos.Interfaces;
23 using System;
24 using System.Collections.Generic;
25 using System.Linq;
26 using Pithos.Network;
27 using StatusService = Pithos.Client.WPF.Services.StatusService;
28
29 namespace Pithos.Client.WPF {
30         using System.ComponentModel.Composition;
31
32         
33         ///<summary>
34         /// The "shell" of the Pithos application displays the taskbar  icon, menu and notifications.
35         /// The shell also hosts the status service called by shell extensions to retrieve file info
36         ///</summary>
37         ///<remarks>
38         /// It is a strange "shell" as its main visible element is an icon instead of a window
39         /// The shell subscribes to the following events:
40         /// * Notification:  Raised by components that want to notify the user. Usually displayed in a balloon
41         /// * SelectiveSynchChanges: Notifies that the user made changes to the selective synch folders for an account. Raised by the Selective Synch dialog. Located here because the monitors are here
42         /// * ShowFilePropertiesEvent: Raised when a shell command requests the display of the file/container properties dialog
43         ///</remarks>           
44         //TODO: CODE SMELL Why does the shell handle the SelectiveSynchChanges?
45         [Export(typeof(IShell))]
46         public class ShellViewModel : Screen, IStatusNotification, IShell,
47                 IHandle<Notification>, IHandle<SelectiveSynchChanges>, IHandle<ShowFilePropertiesEvent>
48         {
49                 //The Status Checker provides the current synch state
50                 //TODO: Could we remove the status checker and use events in its place?
51                 private readonly IStatusChecker _statusChecker;
52                 private readonly IEventAggregator _events;
53
54                 public PithosSettings Settings { get; private set; }
55
56
57         private readonly ConcurrentDictionary<string, PithosMonitor> _monitors = new ConcurrentDictionary<string, PithosMonitor>();
58                 ///<summary>
59                 /// Dictionary of account monitors, keyed by account
60                 ///</summary>
61                 ///<remarks>
62                 /// One monitor class is created for each account. The Shell needs access to the monitors to execute start/stop/pause commands,
63                 /// retrieve account and boject info            
64                 ///</remarks>
65                 // TODO: Does the Shell REALLY need access to the monitors? Could we achieve the same results with a better design?
66                 // TODO: The monitors should be internal to Pithos.Core, even though exposing them makes coding of the Object and Container windows easier
67         public ConcurrentDictionary<string, PithosMonitor> Monitors
68                 {
69                         get { return _monitors; }
70                 }
71
72
73             ///<summary>
74             /// The status service is used by Shell extensions to retrieve file status information
75             ///</summary>
76             //TODO: CODE SMELL! This is the shell! While hosting in the shell makes executing start/stop commands easier, it is still a smell
77             private ServiceHost _statusService;
78
79                 //Logging in the Pithos client is provided by log4net
80                 private static readonly log4net.ILog Log = log4net.LogManager.GetLogger("Pithos");
81
82         //Lazily initialized File Version info. This is done once and lazily to avoid blocking the UI
83         private Lazy<FileVersionInfo> _fileVersion;
84
85                 ///<summary>
86                 /// The Shell depends on MEF to provide implementations for windowManager, events, the status checker service and the settings
87                 ///</summary>
88                 ///<remarks>
89                 /// The PithosSettings class encapsulates the app's settings to abstract their storage mechanism (App settings, a database or registry)
90                 ///</remarks>
91                 [ImportingConstructor]          
92                 public ShellViewModel(IWindowManager windowManager, IEventAggregator events, IStatusChecker statusChecker, PithosSettings settings)
93                 {
94                         try
95                         {
96
97                                 _windowManager = windowManager;
98                                 //CHECK: Caliburn doesn't need explicit command construction
99                                 //OpenPithosFolderCommand = new PithosCommand(OpenPithosFolder);
100                                 _statusChecker = statusChecker;
101                                 //The event subst
102                                 _events = events;
103                                 _events.Subscribe(this);
104
105                                 Settings = settings;
106
107                 Proxy.SetFromSettings(settings);
108
109                                 StatusMessage = "In Synch";
110
111                 _fileVersion=  new Lazy<FileVersionInfo>(() =>
112                 {
113                     Assembly assembly = Assembly.GetExecutingAssembly();
114                     var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
115                     return fileVersion;
116                 });
117                                 _accounts.CollectionChanged += (sender, e) =>
118                                                                                                    {
119                                                                                                            NotifyOfPropertyChange(() => OpenFolderCaption);
120                                                                                                            NotifyOfPropertyChange(() => HasAccounts);
121                                                                                                    };
122
123                         }
124                         catch (Exception exc)
125                         {
126                                 Log.Error("Error while starting the ShellViewModel",exc);
127                                 throw;
128                         }
129                 }
130
131
132                 protected override void OnActivate()
133                 {
134                         base.OnActivate();
135
136             
137
138                         StartMonitoring();                    
139                 }
140
141
142
143                 private async void StartMonitoring()
144                 {
145                         try
146                         {
147                                 var accounts = Settings.Accounts.Select(MonitorAccount);
148                                 await TaskEx.WhenAll(accounts);
149                                 _statusService = StatusService.Start();
150
151 /*
152                                 foreach (var account in Settings.Accounts)
153                                 {
154                                         await MonitorAccount(account);
155                                 }
156 */
157                                 
158                         }
159                         catch (AggregateException exc)
160                         {
161                                 exc.Handle(e =>
162                                 {
163                                         Log.Error("Error while starting monitoring", e);
164                                         return true;
165                                 });
166                                 throw;
167                         }
168                 }
169
170                 protected override void OnDeactivate(bool close)
171                 {
172                         base.OnDeactivate(close);
173                         if (close)
174                         {
175                                 StatusService.Stop(_statusService);
176                                 _statusService = null;
177                         }
178                 }
179
180                 public Task MonitorAccount(AccountSettings account)
181                 {
182                         return Task.Factory.StartNew(() =>
183                         {                                                
184                                 PithosMonitor monitor;
185                                 var accountName = account.AccountName;
186
187                                 if (_monitors.TryGetValue(accountName, out monitor))
188                                 {
189                                         //If the account is active
190                                         if (account.IsActive)
191                                                 //Start the monitor. It's OK to start an already started monitor,
192                                                 //it will just ignore the call                        
193                                                 StartMonitor(monitor).Wait();                        
194                                         else
195                                         {
196                                                 //If the account is inactive
197                                                 //Stop and remove the monitor
198                                                 RemoveMonitor(accountName);
199                                         }
200                                         return;
201                                 }
202
203                 
204                                 //Create a new monitor/ Can't use MEF here, it would return a single instance for all monitors
205                                 monitor = new PithosMonitor
206                                                           {
207                                                                   UserName = accountName,
208                                                                   ApiKey = account.ApiKey,                                  
209                                                                   StatusNotification = this,
210                                                                   RootPath = account.RootPath
211                                                           };
212                                 //PithosMonitor uses MEF so we need to resolve it
213                                 IoC.BuildUp(monitor);
214
215                             monitor.AuthenticationUrl = account.ServerUrl;
216
217                                 _monitors[accountName] = monitor;
218
219                                 if (account.IsActive)
220                                 {
221                                         //Don't start a monitor if it doesn't have an account and ApiKey
222                                         if (String.IsNullOrWhiteSpace(monitor.UserName) ||
223                                                 String.IsNullOrWhiteSpace(monitor.ApiKey))
224                                                 return;
225                                         StartMonitor(monitor);
226                                 }
227                         });
228                 }
229
230
231                 protected override void OnViewLoaded(object view)
232                 {
233                         UpdateStatus();
234                         var window = (Window)view;            
235                         TaskEx.Delay(1000).ContinueWith(t => Execute.OnUIThread(window.Hide));
236                         base.OnViewLoaded(view);
237                 }
238
239
240                 #region Status Properties
241
242                 private string _statusMessage;
243                 public string StatusMessage
244                 {
245                         get { return _statusMessage; }
246                         set
247                         {
248                                 _statusMessage = value;
249                                 NotifyOfPropertyChange(() => StatusMessage);
250                         }
251                 }
252
253                 private readonly ObservableConcurrentCollection<AccountInfo> _accounts = new ObservableConcurrentCollection<AccountInfo>();
254                 public ObservableConcurrentCollection<AccountInfo> Accounts
255                 {
256                         get { return _accounts; }
257                 }
258
259                 public bool HasAccounts
260                 {
261                         get { return _accounts.Count > 0; }
262                 }
263
264
265                 public string OpenFolderCaption
266                 {
267                         get
268                         {
269                                 return (_accounts.Count == 0)
270                                                 ? "No Accounts Defined"
271                                                 : "Open Pithos Folder";
272                         }
273                 }
274
275                 private string _pauseSyncCaption="Pause Synching";
276                 public string PauseSyncCaption
277                 {
278                         get { return _pauseSyncCaption; }
279                         set
280                         {
281                                 _pauseSyncCaption = value;
282                                 NotifyOfPropertyChange(() => PauseSyncCaption);
283                         }
284                 }
285
286                 private readonly ObservableConcurrentCollection<FileEntry> _recentFiles = new ObservableConcurrentCollection<FileEntry>();
287                 public ObservableConcurrentCollection<FileEntry> RecentFiles
288                 {
289                         get { return _recentFiles; }
290                 }
291
292
293                 private string _statusIcon="../Images/Pithos.ico";
294                 public string StatusIcon
295                 {
296                         get { return _statusIcon; }
297                         set
298                         {
299                 //TODO: Ensure all status icons use the Pithos logo
300                                 _statusIcon = value;
301                                 NotifyOfPropertyChange(() => StatusIcon);
302                         }
303                 }
304
305                 #endregion
306
307                 #region Commands
308
309                 public void ShowPreferences()
310                 {
311                         Settings.Reload();
312                         var preferences = new PreferencesViewModel(_windowManager,_events, this,Settings);            
313                         _windowManager.ShowDialog(preferences);
314                         
315                 }
316
317                 public void AboutPithos()
318                 {
319                         var about = new AboutViewModel();
320                         _windowManager.ShowWindow(about);
321                 }
322
323                 public void SendFeedback()
324                 {
325                         var feedBack =  IoC.Get<FeedbackViewModel>();
326                         _windowManager.ShowWindow(feedBack);
327                 }
328
329                 //public PithosCommand OpenPithosFolderCommand { get; private set; }
330
331                 public void OpenPithosFolder()
332                 {
333                         var account = Settings.Accounts.FirstOrDefault(acc => acc.IsActive);
334                         if (account == null)
335                                 return;
336                         Process.Start(account.RootPath);
337                 }
338
339                 public void OpenPithosFolder(AccountInfo account)
340                 {
341                         Process.Start(account.AccountPath);
342                 }
343
344                 
345 /*
346                 public void GoToSite()
347                 {            
348                         var site = Properties.Settings.Default.PithosSite;
349                         Process.Start(site);            
350                 }
351 */
352
353                 public void GoToSite(AccountInfo account)
354                 {
355                         /*var site = String.Format("{0}/ui/?token={1}&user={2}",
356                                 account.SiteUri,account.Token,
357                                 account.UserName);*/
358                         Process.Start(account.SiteUri);
359                 }
360
361                 public void ShowFileProperties()
362                 {
363                         var account = Settings.Accounts.First(acc => acc.IsActive);            
364                         var dir = new DirectoryInfo(account.RootPath + @"\pithos");
365                         var files=dir.GetFiles();
366                         var r=new Random();
367                         var idx=r.Next(0, files.Length);
368                         ShowFileProperties(files[idx].FullName);            
369                 }
370
371                 public void ShowFileProperties(string filePath)
372                 {
373                         if (String.IsNullOrWhiteSpace(filePath))
374                                 throw new ArgumentNullException("filePath");
375                         if (!File.Exists(filePath) && !Directory.Exists(filePath))
376                                 throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
377                         Contract.EndContractBlock();
378
379                         var pair=(from monitor in  Monitors
380                                                            where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
381                                                                    select monitor).FirstOrDefault();
382                     var accountMonitor = pair.Value;
383
384                         if (accountMonitor == null)
385                                 return;
386
387                         var infoTask=Task.Factory.StartNew(()=>accountMonitor.GetObjectInfo(filePath));
388
389                         
390
391                         var fileProperties = new FilePropertiesViewModel(this, infoTask,filePath);
392                         _windowManager.ShowWindow(fileProperties);
393                 } 
394                 
395                 public void ShowContainerProperties()
396                 {
397                         var account = Settings.Accounts.First(acc => acc.IsActive);            
398                         var dir = new DirectoryInfo(account.RootPath);
399                         var fullName = (from folder in dir.EnumerateDirectories()
400                                                         where (folder.Attributes & FileAttributes.Hidden) == 0
401                                                         select folder.FullName).First();
402                         ShowContainerProperties(fullName);            
403                 }
404
405                 public void ShowContainerProperties(string filePath)
406                 {
407                         if (String.IsNullOrWhiteSpace(filePath))
408                                 throw new ArgumentNullException("filePath");
409                         if (!Directory.Exists(filePath))
410                                 throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
411                         Contract.EndContractBlock();
412
413                         var pair=(from monitor in  Monitors
414                                                            where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
415                                                                    select monitor).FirstOrDefault();
416                     var accountMonitor = pair.Value;            
417                         var info = accountMonitor.GetContainerInfo(filePath);
418
419                         
420
421                         var containerProperties = new ContainerPropertiesViewModel(this, info,filePath);
422                         _windowManager.ShowWindow(containerProperties);
423                 }
424
425         public void SynchNow()
426         {
427             var agent = IoC.Get<NetworkAgent>();
428             agent.SynchNow();
429         }
430
431                 public ObjectInfo RefreshObjectInfo(ObjectInfo currentInfo)
432                 {
433                         if (currentInfo==null)
434                                 throw new ArgumentNullException("currentInfo");
435                         Contract.EndContractBlock();
436
437                         var monitor = Monitors[currentInfo.Account];
438                         var newInfo=monitor.CloudClient.GetObjectInfo(currentInfo.Account, currentInfo.Container, currentInfo.Name);
439                         return newInfo;
440                 }
441
442                 public ContainerInfo RefreshContainerInfo(ContainerInfo container)
443                 {
444                         if (container == null)
445                                 throw new ArgumentNullException("container");
446                         Contract.EndContractBlock();
447
448                         var monitor = Monitors[container.Account];
449                         var newInfo = monitor.CloudClient.GetContainerInfo(container.Account, container.Name);
450                         return newInfo;
451                 }
452
453
454                 public void ToggleSynching()
455                 {
456                         bool isPaused=false;
457                         foreach (var pair in Monitors)
458                         {
459                                 var monitor = pair.Value;
460                                 monitor.Pause = !monitor.Pause;
461                                 isPaused = monitor.Pause;
462                         }
463
464                         PauseSyncCaption = isPaused ? "Resume syncing" : "Pause syncing";
465                         var iconKey = isPaused? "TraySyncPaused" : "TrayInSynch";
466                         StatusIcon = String.Format(@"../Images/{0}.ico", iconKey);
467                 }
468
469                 public void ExitPithos()
470                 {
471                         foreach (var pair in Monitors)
472                         {
473                                 var monitor = pair.Value;
474                                 monitor.Stop();
475                         }
476
477                         ((Window)GetView()).Close();
478                 }
479                 #endregion
480
481
482                 private readonly Dictionary<PithosStatus, StatusInfo> _iconNames = new List<StatusInfo>
483                         {
484                                 new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"),
485                                 new StatusInfo(PithosStatus.Syncing, "Syncing Files", "TraySynching"),
486                                 new StatusInfo(PithosStatus.SyncPaused, "Sync Paused", "TraySyncPaused")
487                         }.ToDictionary(s => s.Status);
488
489                 readonly IWindowManager _windowManager;
490             
491
492                 ///<summary>
493                 /// Updates the visual status indicators of the application depending on status changes, e.g. icon, stat                
494                 ///</summary>
495                 public void UpdateStatus()
496                 {
497                         var pithosStatus = _statusChecker.GetPithosStatus();
498
499                         if (_iconNames.ContainsKey(pithosStatus))
500                         {
501                                 var info = _iconNames[pithosStatus];
502                                 StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName);
503
504
505
506                                 StatusMessage = String.Format("Pithos {0}\r\n{1}", _fileVersion.Value.FileVersion,info.StatusText);
507                         }
508                         
509                         //_events.Publish(new Notification { Title = "Start", Message = "Start Monitoring", Level = TraceLevel.Info});
510                 }
511
512
513            
514                 private Task StartMonitor(PithosMonitor monitor,int retries=0)
515                 {
516                         return Task.Factory.StartNew(() =>
517                         {
518                                 using (log4net.ThreadContext.Stacks["Monitor"].Push("Start"))
519                                 {
520                                         try
521                                         {
522                                                 Log.InfoFormat("Start Monitoring {0}", monitor.UserName);
523
524                                                 monitor.Start();
525                                         }
526                                         catch (WebException exc)
527                                         {
528                                                 if (AbandonRetry(monitor, retries))
529                                                         return;
530
531                         HttpStatusCode statusCode =HttpStatusCode.OK;
532                                     var response = exc.Response as HttpWebResponse;
533                         if(response!=null)
534                                                 statusCode = response.StatusCode;
535
536                         switch (statusCode)
537                         {
538                             case HttpStatusCode.Unauthorized:
539                                 var message = String.Format("API Key Expired for {0}. Starting Renewal",
540                                                             monitor.UserName);
541                                 Log.Error(message, exc);
542                                 TryAuthorize(monitor, retries).Wait();
543                                 break;
544                             case HttpStatusCode.ProxyAuthenticationRequired:
545                                 TryAuthenticateProxy(monitor,retries);
546                                 break;
547                             default:
548                                 TryLater(monitor, exc, retries);
549                                 break;
550                         }
551                                         }
552                                         catch (Exception exc)
553                                         {
554                                                 if (AbandonRetry(monitor, retries)) 
555                                                         return;
556
557                                                 TryLater(monitor,exc,retries);
558                                         }
559                                 }
560                         });
561                 }
562
563             private void TryAuthenticateProxy(PithosMonitor monitor,int retries)
564             {
565                 Execute.OnUIThread(() =>
566                                        {                                       
567                                            var proxyAccount = IoC.Get<ProxyAccountViewModel>();
568                                         proxyAccount.Settings = this.Settings;
569                                            if (true != _windowManager.ShowDialog(proxyAccount)) 
570                                            return;
571                                            StartMonitor(monitor, retries);
572                                            NotifyOfPropertyChange(() => Accounts);
573                                        });
574             }
575
576             private bool AbandonRetry(PithosMonitor monitor, int retries)
577                 {
578                         if (retries > 1)
579                         {
580                                 var message = String.Format("Monitoring of account {0} has failed too many times. Will not retry",
581                                                                                         monitor.UserName);
582                                 _events.Publish(new Notification
583                                                                         {Title = "Account monitoring failed", Message = message, Level = TraceLevel.Error});
584                                 return true;
585                         }
586                         return false;
587                 }
588
589
590                 private async Task TryAuthorize(PithosMonitor monitor,int retries)
591                 {
592                         _events.Publish(new Notification { Title = "Authorization failed", Message = "Your API Key has probably expired. You will be directed to a page where you can renew it", Level = TraceLevel.Error });
593
594                         try
595                         {
596
597                                 var credentials = await PithosAccount.RetrieveCredentials(Settings.PithosLoginUrl);
598
599                                 var account = Settings.Accounts.First(act => act.AccountName == credentials.UserName);
600                                 account.ApiKey = credentials.Password;
601                                 monitor.ApiKey = credentials.Password;
602                                 Settings.Save();
603                                 await TaskEx.Delay(10000);
604                                 StartMonitor(monitor, retries + 1);
605                                 NotifyOfPropertyChange(()=>Accounts);
606                         }
607                         catch (AggregateException exc)
608                         {
609                                 string message = String.Format("API Key retrieval for {0} failed", monitor.UserName);
610                                 Log.Error(message, exc.InnerException);
611                                 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
612                         }
613                         catch (Exception exc)
614                         {
615                                 string message = String.Format("API Key retrieval for {0} failed", monitor.UserName);
616                                 Log.Error(message, exc);
617                                 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
618                         }
619
620                 }
621
622                 private static bool IsUnauthorized(WebException exc)
623                 {
624                         if (exc==null)
625                                 throw new ArgumentNullException("exc");
626                         Contract.EndContractBlock();
627
628                         var response = exc.Response as HttpWebResponse;
629                         if (response == null)
630                                 return false;
631                         return (response.StatusCode == HttpStatusCode.Unauthorized);
632                 }
633
634                 private void TryLater(PithosMonitor monitor, Exception exc,int retries)
635                 {
636                         var message = String.Format("An exception occured. Can't start monitoring\nWill retry in 10 seconds");
637                         Task.Factory.StartNewDelayed(10000, () => StartMonitor(monitor,retries+1));
638                         _events.Publish(new Notification
639                                                                 {Title = "Error", Message = message, Level = TraceLevel.Error});
640                         Log.Error(message, exc);
641                 }
642
643
644                 public void NotifyChange(string status, TraceLevel level=TraceLevel.Info)
645                 {
646                         StatusMessage = status;
647                         
648                         _events.Publish(new Notification { Title = "Pithos", Message = status, Level = level });
649                 }
650
651                 public void NotifyChangedFile(string filePath)
652                 {
653                         var entry = new FileEntry {FullPath=filePath};
654                         IProducerConsumerCollection<FileEntry> files=RecentFiles;
655                         FileEntry popped;
656                         while (files.Count > 5)
657                                 files.TryTake(out popped);
658                         files.TryAdd(entry);
659                 }
660
661                 public void NotifyAccount(AccountInfo account)
662                 {
663                         if (account== null)
664                                 return;
665                         //TODO: What happens to an existing account whose Token has changed?
666                         account.SiteUri= String.Format("{0}/ui/?token={1}&user={2}",
667                                 account.SiteUri, Uri.EscapeDataString(account.Token),
668                 Uri.EscapeDataString(account.UserName));
669
670                         if (Accounts.All(item => item.UserName != account.UserName))
671                                 Accounts.TryAdd(account);
672
673                 }
674
675             public void NotifyConflicts(IEnumerable<FileSystemInfo> conflictFiles, string message)
676             {
677             if (conflictFiles == null)
678                 return;
679             if (!conflictFiles.Any())
680                 return;
681
682             UpdateStatus();
683             //TODO: Create a more specific message. For now, just show a warning
684             NotifyForFiles(conflictFiles,message,TraceLevel.Warning);
685
686             }
687
688             public void NotifyForFiles(IEnumerable<FileSystemInfo> files, string message,TraceLevel level=TraceLevel.Info)
689             {
690             if (files == null)
691                 return;
692             if (!files.Any())
693                 return;
694
695             StatusMessage = message;
696
697             _events.Publish(new Notification { Title = "Pithos", Message = message, Level = level});
698         }
699
700         public void Notify(Notification notification)
701         {
702             _events.Publish(notification);
703         }
704
705
706             public void RemoveMonitor(string accountName)
707                 {
708                         if (String.IsNullOrWhiteSpace(accountName))
709                                 return;
710
711                         var accountInfo=_accounts.FirstOrDefault(account => account.UserName == accountName);
712                         _accounts.TryRemove(accountInfo);
713
714                         PithosMonitor monitor;
715                         if (Monitors.TryRemove(accountName, out monitor))
716                         {
717                                 monitor.Stop();
718                         }
719                 }
720
721                 public void RefreshOverlays()
722                 {
723                         foreach (var pair in Monitors)
724                         {
725                                 var monitor = pair.Value;
726
727                                 var path = monitor.RootPath;
728
729                                 if (String.IsNullOrWhiteSpace(path))
730                                         continue;
731
732                                 if (!Directory.Exists(path) && !File.Exists(path))
733                                         continue;
734
735                                 IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path);
736
737                                 try
738                                 {
739                                         NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM,
740                                                                                                  HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT,
741                                                                                                  pathPointer, IntPtr.Zero);
742                                 }
743                                 finally
744                                 {
745                                         Marshal.FreeHGlobal(pathPointer);
746                                 }
747                         }
748                 }
749
750                 #region Event Handlers
751                 
752                 public void Handle(SelectiveSynchChanges message)
753                 {
754                         var accountName = message.Account.AccountName;
755                         PithosMonitor monitor;
756                         if (_monitors.TryGetValue(accountName, out monitor))
757                         {
758                                 monitor.AddSelectivePaths(message.Added);
759                                 monitor.RemoveSelectivePaths(message.Removed);
760
761                         }
762                         
763                 }
764
765
766             private bool _pollStarted = false;
767
768         //SMELL: Doing so much work for notifications in the shell is wrong
769         //The notifications should be moved to their own view/viewmodel pair
770         //and different templates should be used for different message types
771         //This will also allow the addition of extra functionality, eg. actions
772         //
773                 public void Handle(Notification notification)
774                 {
775             UpdateStatus();
776
777                         if (!Settings.ShowDesktopNotifications)
778                                 return;
779
780             if (notification is PollNotification)
781             {
782                 _pollStarted = true;
783                 return;
784             }
785             if (notification is CloudNotification)
786             {
787                 if (!_pollStarted) 
788                     return;
789                 _pollStarted= false;
790                 notification.Title = "Pithos";
791                 notification.Message = "Start Synchronisation";
792             }
793
794             if (String.IsNullOrWhiteSpace(notification.Message) && String.IsNullOrWhiteSpace(notification.Title))
795                 return;
796
797                         BalloonIcon icon;
798                         switch (notification.Level)
799                         {
800                                 case TraceLevel.Error:
801                                         icon = BalloonIcon.Error;
802                                         break;
803                                 case TraceLevel.Info:
804                                 case TraceLevel.Verbose:
805                                         icon = BalloonIcon.Info;
806                                         break;
807                                 case TraceLevel.Warning:
808                                         icon = BalloonIcon.Warning;
809                                         break;
810                                 default:
811                                         icon = BalloonIcon.None;
812                                         break;
813                         }
814             
815                         if (Settings.ShowDesktopNotifications)
816                         {
817                                 var tv = (ShellView) GetView();                
818                 var balloon=new PithosBalloon{Title=notification.Title,Message=notification.Message,Icon=icon};
819                 tv.TaskbarView.ShowCustomBalloon(balloon,PopupAnimation.Fade,4000);
820 //                              tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
821                         }
822                 }
823                 #endregion
824
825                 public void Handle(ShowFilePropertiesEvent message)
826                 {
827                         if (message == null)
828                                 throw new ArgumentNullException("message");
829                         if (String.IsNullOrWhiteSpace(message.FileName) )
830                                 throw new ArgumentException("message");
831                         Contract.EndContractBlock();
832
833                         var fileName = message.FileName;
834             //TODO: Display file properties for non-container folders
835                         if (File.Exists(fileName))
836                 //Retrieve the full name with exact casing. Pithos names are case sensitive                             
837                 ShowFileProperties(FileInfoExtensions.GetProperFilePathCapitalization(fileName));
838                         else if (Directory.Exists(fileName))
839                 //Retrieve the full name with exact casing. Pithos names are case sensitive
840                         {
841                 var path = FileInfoExtensions.GetProperDirectoryCapitalization(fileName);
842                 if (IsContainer(path))
843                                 ShowContainerProperties(path);
844                 else
845                     ShowFileProperties(path);
846                         }
847                 }
848
849             private bool IsContainer(string path)
850             {
851                 var matchingFolders = from account in _accounts
852                                       from rootFolder in Directory.GetDirectories(account.AccountPath)
853                                       where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase)
854                                       select rootFolder;
855                 return matchingFolders.Any();
856             }
857
858             public FileStatus GetFileStatus(string localFileName)
859             {
860             if (String.IsNullOrWhiteSpace(localFileName))
861                 throw new ArgumentNullException("localFileName");
862             Contract.EndContractBlock();
863             
864                 var statusKeeper = IoC.Get<IStatusKeeper>();
865                 var status=statusKeeper.GetFileStatus(localFileName);
866                 return status;
867             }
868         }
869 }