Statistics
| Branch: | Revision:

root / trunk / Pithos.Client.WPF / Shell / ShellViewModel.cs @ f3d080df

History | View | Annotate | Download (21.4 kB)

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 Caliburn.Micro;
12
using Hardcodet.Wpf.TaskbarNotification;
13
using Pithos.Client.WPF.Configuration;
14
using Pithos.Client.WPF.FileProperties;
15
using Pithos.Client.WPF.SelectiveSynch;
16
using Pithos.Client.WPF.Services;
17
using Pithos.Client.WPF.Shell;
18
using Pithos.Core;
19
using Pithos.Interfaces;
20
using System;
21
using System.Collections.Generic;
22
using System.Linq;
23
using Pithos.Network;
24
using StatusService = Pithos.Client.WPF.Services.StatusService;
25

    
26
namespace Pithos.Client.WPF {
27
	using System.ComponentModel.Composition;
28

    
29
	
30
	///<summary>
31
	/// The "shell" of the Pithos application displays the taskbar  icon, menu and notifications.
32
	/// The shell also hosts the status service called by shell extensions to retrieve file info
33
	///</summary>
34
	///<remarks>
35
	/// It is a strange "shell" as its main visible element is an icon instead of a window
36
	/// The shell subscribes to the following events:
37
	/// * Notification:  Raised by components that want to notify the user. Usually displayed in a balloon
38
	/// * 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
39
	/// * ShowFilePropertiesEvent: Raised when a shell command requests the display of the file/container properties dialog
40
	///</remarks>		
41
	//TODO: CODE SMELL Why does the shell handle the SelectiveSynchChanges?
42
	[Export(typeof(IShell))]
43
	public class ShellViewModel : Screen, IStatusNotification, IShell,
44
		IHandle<Notification>, IHandle<SelectiveSynchChanges>, IHandle<ShowFilePropertiesEvent>
45
	{
46
		//The Status Checker provides the current synch state
47
		//TODO: Could we remove the status checker and use events in its place?
48
		private readonly IStatusChecker _statusChecker;
49
		private readonly IEventAggregator _events;
50

    
51
		public PithosSettings Settings { get; private set; }
52

    
53

    
54
        private readonly ConcurrentDictionary<string, PithosMonitor> _monitors = new ConcurrentDictionary<string, PithosMonitor>();
55
		///<summary>
56
		/// Dictionary of account monitors, keyed by account
57
		///</summary>
58
		///<remarks>
59
		/// One monitor class is created for each account. The Shell needs access to the monitors to execute start/stop/pause commands,
60
		/// retrieve account and boject info		
61
		///</remarks>
62
		// TODO: Does the Shell REALLY need access to the monitors? Could we achieve the same results with a better design?
63
		// TODO: The monitors should be internal to Pithos.Core, even though exposing them makes coding of the Object and Container windows easier
64
        public ConcurrentDictionary<string, PithosMonitor> Monitors
65
		{
66
			get { return _monitors; }
67
		}
68

    
69

    
70
	    ///<summary>
71
	    /// The status service is used by Shell extensions to retrieve file status information
72
	    ///</summary>
73
	    //TODO: CODE SMELL! This is the shell! While hosting in the shell makes executing start/stop commands easier, it is still a smell
74
	    private ServiceHost _statusService;
75

    
76
		//Logging in the Pithos client is provided by log4net
77
		private static readonly log4net.ILog Log = log4net.LogManager.GetLogger("Pithos");
78

    
79
		///<summary>
80
		/// The Shell depends on MEF to provide implementations for windowManager, events, the status checker service and the settings
81
		///</summary>
82
		///<remarks>
83
		/// The PithosSettings class encapsulates the app's settings to abstract their storage mechanism (App settings, a database or registry)
84
		///</remarks>
85
		[ImportingConstructor]		
86
		public ShellViewModel(IWindowManager windowManager, IEventAggregator events, IStatusChecker statusChecker, PithosSettings settings)
87
		{
88
			try
89
			{
90

    
91
				_windowManager = windowManager;
92
				//CHECK: Caliburn doesn't need explicit command construction
93
				//OpenPithosFolderCommand = new PithosCommand(OpenPithosFolder);
94
				_statusChecker = statusChecker;
95
				//The event subst
96
				_events = events;
97
				_events.Subscribe(this);
98

    
99
				Settings = settings;
100

    
101
				StatusMessage = "In Synch";
102

    
103
				_accounts.CollectionChanged += (sender, e) =>
104
												   {
105
													   NotifyOfPropertyChange(() => OpenFolderCaption);
106
													   NotifyOfPropertyChange(() => HasAccounts);
107
												   };
108

    
109
			}
110
			catch (Exception exc)
111
			{
112
				Log.Error("Error while starting the ShellViewModel",exc);
113
				throw;
114
			}
115
		}
116

    
117

    
118
		protected override void OnActivate()
119
		{
120
			base.OnActivate();
121

    
122
			StartMonitoring();                    
123
		}
124

    
125

    
126
		private async void StartMonitoring()
127
		{
128
			try
129
			{
130
				var accounts = Settings.Accounts.Select(MonitorAccount);
131
				await TaskEx.WhenAll(accounts);
132
				_statusService = StatusService.Start();
133

    
134
/*
135
				foreach (var account in Settings.Accounts)
136
				{
137
					await MonitorAccount(account);
138
				}
139
*/
140
				
141
			}
142
			catch (AggregateException exc)
143
			{
144
				exc.Handle(e =>
145
				{
146
					Log.Error("Error while starting monitoring", e);
147
					return true;
148
				});
149
				throw;
150
			}
151
		}
152

    
153
		protected override void OnDeactivate(bool close)
154
		{
155
			base.OnDeactivate(close);
156
			if (close)
157
			{
158
				StatusService.Stop(_statusService);
159
				_statusService = null;
160
			}
161
		}
162

    
163
		public Task MonitorAccount(AccountSettings account)
164
		{
165
			return Task.Factory.StartNew(() =>
166
			{                                                
167
				PithosMonitor monitor;
168
				var accountName = account.AccountName;
169

    
170
				if (_monitors.TryGetValue(accountName, out monitor))
171
				{
172
					//If the account is active
173
					if (account.IsActive)
174
						//Start the monitor. It's OK to start an already started monitor,
175
						//it will just ignore the call                        
176
						StartMonitor(monitor).Wait();                        
177
					else
178
					{
179
						//If the account is inactive
180
						//Stop and remove the monitor
181
						RemoveMonitor(accountName);
182
					}
183
					return;
184
				}
185

    
186
				//Create a new monitor/ Can't use MEF here, it would return a single instance for all monitors
187
				monitor = new PithosMonitor
188
							  {
189
								  UserName = accountName,
190
								  ApiKey = account.ApiKey,                                  
191
								  StatusNotification = this,
192
								  RootPath = account.RootPath
193
							  };
194
				//PithosMonitor uses MEF so we need to resolve it
195
				IoC.BuildUp(monitor);
196

    
197
			    monitor.AuthenticationUrl = account.ServerUrl;
198

    
199
				_monitors[accountName] = monitor;
200

    
201
				if (account.IsActive)
202
				{
203
					//Don't start a monitor if it doesn't have an account and ApiKey
204
					if (String.IsNullOrWhiteSpace(monitor.UserName) ||
205
						String.IsNullOrWhiteSpace(monitor.ApiKey))
206
						return;
207
					StartMonitor(monitor);
208
				}
209
			});
210
		}
211

    
212

    
213
		protected override void OnViewLoaded(object view)
214
		{
215
			UpdateStatus();
216
			var window = (Window)view;            
217
			TaskEx.Delay(1000).ContinueWith(t => Execute.OnUIThread(window.Hide));
218
			base.OnViewLoaded(view);
219
		}
220

    
221

    
222
		#region Status Properties
223

    
224
		private string _statusMessage;
225
		public string StatusMessage
226
		{
227
			get { return _statusMessage; }
228
			set
229
			{
230
				_statusMessage = value;
231
				NotifyOfPropertyChange(() => StatusMessage);
232
			}
233
		}
234

    
235
		private readonly ObservableConcurrentCollection<AccountInfo> _accounts = new ObservableConcurrentCollection<AccountInfo>();
236
		public ObservableConcurrentCollection<AccountInfo> Accounts
237
		{
238
			get { return _accounts; }
239
		}
240

    
241
		public bool HasAccounts
242
		{
243
			get { return _accounts.Count > 0; }
244
		}
245

    
246

    
247
		public string OpenFolderCaption
248
		{
249
			get
250
			{
251
				return (_accounts.Count == 0)
252
						? "No Accounts Defined"
253
						: "Open Pithos Folder";
254
			}
255
		}
256

    
257
		private string _pauseSyncCaption="Pause Synching";
258
		public string PauseSyncCaption
259
		{
260
			get { return _pauseSyncCaption; }
261
			set
262
			{
263
				_pauseSyncCaption = value;
264
				NotifyOfPropertyChange(() => PauseSyncCaption);
265
			}
266
		}
267

    
268
		private readonly ObservableConcurrentCollection<FileEntry> _recentFiles = new ObservableConcurrentCollection<FileEntry>();
269
		public ObservableConcurrentCollection<FileEntry> RecentFiles
270
		{
271
			get { return _recentFiles; }
272
		}
273

    
274

    
275
		private string _statusIcon="../Images/Pithos.ico";
276
		public string StatusIcon
277
		{
278
			get { return _statusIcon; }
279
			set
280
			{
281
                //TODO: Ensure all status icons use the Pithos logo
282
				_statusIcon = value;
283
				NotifyOfPropertyChange(() => StatusIcon);
284
			}
285
		}
286

    
287
		#endregion
288

    
289
		#region Commands
290

    
291
		public void ShowPreferences()
292
		{
293
			Settings.Reload();
294
			var preferences = new PreferencesViewModel(_windowManager,_events, this,Settings);            
295
			_windowManager.ShowDialog(preferences);
296
			
297
		}
298

    
299
		public void AboutPithos()
300
		{
301
			var about = new AboutViewModel();
302
			_windowManager.ShowWindow(about);
303
		}
304

    
305
		public void SendFeedback()
306
		{
307
			var feedBack =  IoC.Get<FeedbackViewModel>();
308
			_windowManager.ShowWindow(feedBack);
309
		}
310

    
311
		//public PithosCommand OpenPithosFolderCommand { get; private set; }
312

    
313
		public void OpenPithosFolder()
314
		{
315
			var account = Settings.Accounts.FirstOrDefault(acc => acc.IsActive);
316
			if (account == null)
317
				return;
318
			Process.Start(account.RootPath);
319
		}
320

    
321
		public void OpenPithosFolder(AccountInfo account)
322
		{
323
			Process.Start(account.AccountPath);
324
		}
325

    
326
		
327
/*
328
		public void GoToSite()
329
		{            
330
			var site = Properties.Settings.Default.PithosSite;
331
			Process.Start(site);            
332
		}
333
*/
334

    
335
		public void GoToSite(AccountInfo account)
336
		{
337
			/*var site = String.Format("{0}/ui/?token={1}&user={2}",
338
				account.SiteUri,account.Token,
339
				account.UserName);*/
340
			Process.Start(account.SiteUri);
341
		}
342

    
343
		public void ShowFileProperties()
344
		{
345
			var account = Settings.Accounts.First(acc => acc.IsActive);            
346
			var dir = new DirectoryInfo(account.RootPath + @"\pithos");
347
			var files=dir.GetFiles();
348
			var r=new Random();
349
			var idx=r.Next(0, files.Length);
350
			ShowFileProperties(files[idx].FullName);            
351
		}
352

    
353
		public void ShowFileProperties(string filePath)
354
		{
355
			if (String.IsNullOrWhiteSpace(filePath))
356
				throw new ArgumentNullException("filePath");
357
			if (!File.Exists(filePath) && !Directory.Exists(filePath))
358
				throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
359
			Contract.EndContractBlock();
360

    
361
			var pair=(from monitor in  Monitors
362
							   where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
363
								   select monitor).FirstOrDefault();
364
		    var accountMonitor = pair.Value;
365

    
366
			if (accountMonitor == null)
367
				return;
368

    
369
			var infoTask=Task.Factory.StartNew(()=>accountMonitor.GetObjectInfo(filePath));
370

    
371
			
372

    
373
			var fileProperties = new FilePropertiesViewModel(this, infoTask,filePath);
374
			_windowManager.ShowWindow(fileProperties);
375
		} 
376
		
377
		public void ShowContainerProperties()
378
		{
379
			var account = Settings.Accounts.First(acc => acc.IsActive);            
380
			var dir = new DirectoryInfo(account.RootPath);
381
			var fullName = (from folder in dir.EnumerateDirectories()
382
							where (folder.Attributes & FileAttributes.Hidden) == 0
383
							select folder.FullName).First();
384
			ShowContainerProperties(fullName);            
385
		}
386

    
387
		public void ShowContainerProperties(string filePath)
388
		{
389
			if (String.IsNullOrWhiteSpace(filePath))
390
				throw new ArgumentNullException("filePath");
391
			if (!Directory.Exists(filePath))
392
				throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
393
			Contract.EndContractBlock();
394

    
395
			var pair=(from monitor in  Monitors
396
							   where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
397
								   select monitor).FirstOrDefault();
398
		    var accountMonitor = pair.Value;            
399
			var info = accountMonitor.GetContainerInfo(filePath);
400

    
401
			
402

    
403
			var containerProperties = new ContainerPropertiesViewModel(this, info,filePath);
404
			_windowManager.ShowWindow(containerProperties);
405
		}
406

    
407
		public ObjectInfo RefreshObjectInfo(ObjectInfo currentInfo)
408
		{
409
			if (currentInfo==null)
410
				throw new ArgumentNullException("currentInfo");
411
			Contract.EndContractBlock();
412

    
413
			var monitor = Monitors[currentInfo.Account];
414
			var newInfo=monitor.CloudClient.GetObjectInfo(currentInfo.Account, currentInfo.Container, currentInfo.Name);
415
			return newInfo;
416
		}
417

    
418
		public ContainerInfo RefreshContainerInfo(ContainerInfo container)
419
		{
420
			if (container == null)
421
				throw new ArgumentNullException("container");
422
			Contract.EndContractBlock();
423

    
424
			var monitor = Monitors[container.Account];
425
			var newInfo = monitor.CloudClient.GetContainerInfo(container.Account, container.Name);
426
			return newInfo;
427
		}
428

    
429

    
430
		public void ToggleSynching()
431
		{
432
			bool isPaused=false;
433
			foreach (var pair in Monitors)
434
			{
435
				var monitor = pair.Value;
436
				monitor.Pause = !monitor.Pause;
437
				isPaused = monitor.Pause;
438
			}
439

    
440
			PauseSyncCaption = isPaused ? "Resume syncing" : "Pause syncing";
441
			var iconKey = isPaused? "TraySyncPaused" : "TrayInSynch";
442
			StatusIcon = String.Format(@"../Images/{0}.ico", iconKey);
443
		}
444

    
445
		public void ExitPithos()
446
		{
447
			foreach (var pair in Monitors)
448
			{
449
				var monitor = pair.Value;
450
				monitor.Stop();
451
			}
452

    
453
			((Window)GetView()).Close();
454
		}
455
		#endregion
456

    
457

    
458
		private readonly Dictionary<PithosStatus, StatusInfo> _iconNames = new List<StatusInfo>
459
			{
460
				new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"),
461
				new StatusInfo(PithosStatus.Syncing, "Syncing Files", "TraySynching"),
462
				new StatusInfo(PithosStatus.SyncPaused, "Sync Paused", "TraySyncPaused")
463
			}.ToDictionary(s => s.Status);
464

    
465
		readonly IWindowManager _windowManager;
466

    
467

    
468
		///<summary>
469
		/// Updates the visual status indicators of the application depending on status changes, e.g. icon, stat		
470
		///</summary>
471
		public void UpdateStatus()
472
		{
473
			var pithosStatus = _statusChecker.GetPithosStatus();
474

    
475
			if (_iconNames.ContainsKey(pithosStatus))
476
			{
477
				var info = _iconNames[pithosStatus];
478
				StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName);
479

    
480
				Assembly assembly = Assembly.GetExecutingAssembly();                               
481
				var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
482

    
483

    
484
				StatusMessage = String.Format("Pithos {0}\r\n{1}", fileVersion.FileVersion,info.StatusText);
485
			}
486
			
487
			_events.Publish(new Notification { Title = "Start", Message = "Start Monitoring", Level = TraceLevel.Info});
488
		}
489

    
490

    
491
	   
492
		private Task StartMonitor(PithosMonitor monitor,int retries=0)
493
		{
494
			return Task.Factory.StartNew(() =>
495
			{
496
				using (log4net.ThreadContext.Stacks["Monitor"].Push("Start"))
497
				{
498
					try
499
					{
500
						Log.InfoFormat("Start Monitoring {0}", monitor.UserName);
501

    
502
						monitor.Start();
503
					}
504
					catch (WebException exc)
505
					{
506
						if (AbandonRetry(monitor, retries))
507
							return;
508

    
509
						if (IsUnauthorized(exc))
510
						{
511
							var message = String.Format("API Key Expired for {0}. Starting Renewal",monitor.UserName);                            
512
							Log.Error(message,exc);
513
							TryAuthorize(monitor,retries).Wait();
514
						}
515
						else
516
						{
517
							TryLater(monitor, exc,retries);
518
						}
519
					}
520
					catch (Exception exc)
521
					{
522
						if (AbandonRetry(monitor, retries)) 
523
							return;
524

    
525
						TryLater(monitor,exc,retries);
526
					}
527
				}
528
			});
529
		}
530

    
531
		private bool AbandonRetry(PithosMonitor monitor, int retries)
532
		{
533
			if (retries > 1)
534
			{
535
				var message = String.Format("Monitoring of account {0} has failed too many times. Will not retry",
536
											monitor.UserName);
537
				_events.Publish(new Notification
538
									{Title = "Account monitoring failed", Message = message, Level = TraceLevel.Error});
539
				return true;
540
			}
541
			return false;
542
		}
543

    
544

    
545
		private async Task TryAuthorize(PithosMonitor monitor,int retries)
546
		{
547
			_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 });
548

    
549
			try
550
			{
551

    
552
				var credentials = await PithosAccount.RetrieveCredentials(Settings.PithosLoginUrl);
553

    
554
				var account = Settings.Accounts.First(act => act.AccountName == credentials.UserName);
555
				account.ApiKey = credentials.Password;
556
				monitor.ApiKey = credentials.Password;
557
				Settings.Save();
558
				await TaskEx.Delay(10000);
559
				StartMonitor(monitor, retries + 1);
560
				NotifyOfPropertyChange(()=>Accounts);
561
			}
562
			catch (AggregateException exc)
563
			{
564
				string message = String.Format("API Key retrieval for {0} failed", monitor.UserName);
565
				Log.Error(message, exc.InnerException);
566
				_events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
567
			}
568
			catch (Exception exc)
569
			{
570
				string message = String.Format("API Key retrieval for {0} failed", monitor.UserName);
571
				Log.Error(message, exc);
572
				_events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
573
			}
574

    
575
		}
576

    
577
		private static bool IsUnauthorized(WebException exc)
578
		{
579
			if (exc==null)
580
				throw new ArgumentNullException("exc");
581
			Contract.EndContractBlock();
582

    
583
			var response = exc.Response as HttpWebResponse;
584
			if (response == null)
585
				return false;
586
			return (response.StatusCode == HttpStatusCode.Unauthorized);
587
		}
588

    
589
		private void TryLater(PithosMonitor monitor, Exception exc,int retries)
590
		{
591
			var message = String.Format("An exception occured. Can't start monitoring\nWill retry in 10 seconds");
592
			Task.Factory.StartNewDelayed(10000, () => StartMonitor(monitor,retries+1));
593
			_events.Publish(new Notification
594
								{Title = "Error", Message = message, Level = TraceLevel.Error});
595
			Log.Error(message, exc);
596
		}
597

    
598

    
599
		public void NotifyChange(string status, TraceLevel level=TraceLevel.Info)
600
		{
601
			StatusMessage = status;
602
			
603
			_events.Publish(new Notification { Title = "Pithos", Message = status, Level = level });
604
		}
605

    
606
		public void NotifyChangedFile(string filePath)
607
		{
608
			var entry = new FileEntry {FullPath=filePath};
609
			IProducerConsumerCollection<FileEntry> files=RecentFiles;
610
			FileEntry popped;
611
			while (files.Count > 5)
612
				files.TryTake(out popped);
613
			files.TryAdd(entry);
614
		}
615

    
616
		public void NotifyAccount(AccountInfo account)
617
		{
618
			if (account== null)
619
				return;
620
			//TODO: What happens to an existing account whose Token has changed?
621
			account.SiteUri= String.Format("{0}/ui/?token={1}&user={2}",
622
				account.SiteUri, account.Token,
623
				account.UserName);
624

    
625
			if (Accounts.All(item => item.UserName != account.UserName))
626
				Accounts.TryAdd(account);
627

    
628
		}
629

    
630

    
631
		public void RemoveMonitor(string accountName)
632
		{
633
			if (String.IsNullOrWhiteSpace(accountName))
634
				return;
635

    
636
			var accountInfo=_accounts.FirstOrDefault(account => account.UserName == accountName);
637
			_accounts.TryRemove(accountInfo);
638

    
639
			PithosMonitor monitor;
640
			if (Monitors.TryRemove(accountName, out monitor))
641
			{
642
				monitor.Stop();
643
			}
644
		}
645

    
646
		public void RefreshOverlays()
647
		{
648
			foreach (var pair in Monitors)
649
			{
650
				var monitor = pair.Value;
651

    
652
				var path = monitor.RootPath;
653

    
654
				if (String.IsNullOrWhiteSpace(path))
655
					continue;
656

    
657
				if (!Directory.Exists(path) && !File.Exists(path))
658
					continue;
659

    
660
				IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path);
661

    
662
				try
663
				{
664
					NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM,
665
												 HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT,
666
												 pathPointer, IntPtr.Zero);
667
				}
668
				finally
669
				{
670
					Marshal.FreeHGlobal(pathPointer);
671
				}
672
			}
673
		}
674

    
675
		#region Event Handlers
676
		
677
		public void Handle(SelectiveSynchChanges message)
678
		{
679
			var accountName = message.Account.AccountName;
680
			PithosMonitor monitor;
681
			if (_monitors.TryGetValue(accountName, out monitor))
682
			{
683
				monitor.AddSelectivePaths(message.Added);
684
				monitor.RemoveSelectivePaths(message.Removed);
685

    
686
			}
687
			
688
		}
689

    
690

    
691
		public void Handle(Notification notification)
692
		{
693
			if (!Settings.ShowDesktopNotifications)
694
				return;
695
			BalloonIcon icon;
696
			switch (notification.Level)
697
			{
698
				case TraceLevel.Error:
699
					icon = BalloonIcon.Error;
700
					break;
701
				case TraceLevel.Info:
702
				case TraceLevel.Verbose:
703
					icon = BalloonIcon.Info;
704
					break;
705
				case TraceLevel.Warning:
706
					icon = BalloonIcon.Warning;
707
					break;
708
				default:
709
					icon = BalloonIcon.None;
710
					break;
711
			}
712

    
713
			if (Settings.ShowDesktopNotifications)
714
			{
715
				var tv = (ShellView) GetView();
716
				tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
717
			}
718
		}
719
		#endregion
720

    
721
		public void Handle(ShowFilePropertiesEvent message)
722
		{
723
			if (message == null)
724
				throw new ArgumentNullException("message");
725
			if (String.IsNullOrWhiteSpace(message.FileName) )
726
				throw new ArgumentException("message");
727
			Contract.EndContractBlock();
728

    
729
			var fileName = message.FileName;
730
            //TODO: Display file properties for non-container folders
731
			if (File.Exists(fileName))
732
                //Retrieve the full name with exact casing. Pithos names are case sensitive				
733
                ShowFileProperties(FileInfoExtensions.GetProperFilePathCapitalization(fileName));
734
			else if (Directory.Exists(fileName))
735
                //Retrieve the full name with exact casing. Pithos names are case sensitive
736
			{
737
                var path = FileInfoExtensions.GetProperDirectoryCapitalization(fileName);
738
                if (IsContainer(path))
739
			        ShowContainerProperties(path);
740
                else
741
                    ShowFileProperties(path);
742
			}
743
		}
744

    
745
	    private bool IsContainer(string path)
746
	    {
747
	        var matchingFolders = from account in _accounts
748
	                              from rootFolder in Directory.GetDirectories(account.AccountPath)
749
	                              where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase)
750
	                              select rootFolder;
751
	        return matchingFolders.Any();
752
	    }
753

    
754
	}
755
}