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