Statistics
| Branch: | Revision:

root / trunk / Pithos.Client.WPF / Shell / ShellViewModel.cs @ 65282d58

History | View | Annotate | Download (22.8 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.Preferences;
16
using Pithos.Client.WPF.SelectiveSynch;
17
using Pithos.Client.WPF.Services;
18
using Pithos.Client.WPF.Shell;
19
using Pithos.Core;
20
using Pithos.Interfaces;
21
using System;
22
using System.Collections.Generic;
23
using System.Linq;
24
using Pithos.Network;
25
using StatusService = Pithos.Client.WPF.Services.StatusService;
26

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

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

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

    
54

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

    
70

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

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

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

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

    
100
				Settings = settings;
101

    
102
				StatusMessage = "In Synch";
103

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

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

    
118

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

    
123
			StartMonitoring();                    
124
		}
125

    
126

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

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

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

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

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

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

    
198
			    monitor.AuthenticationUrl = account.ServerUrl;
199

    
200
				_monitors[accountName] = monitor;
201

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

    
213

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

    
222

    
223
		#region Status Properties
224

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

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

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

    
247

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

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

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

    
275

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

    
288
		#endregion
289

    
290
		#region Commands
291

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

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

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

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

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

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

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

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

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

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

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

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

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

    
372
			
373

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

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

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

    
402
			
403

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

    
408
        public void SynchNow()
409
        {}
410

    
411
		public ObjectInfo RefreshObjectInfo(ObjectInfo currentInfo)
412
		{
413
			if (currentInfo==null)
414
				throw new ArgumentNullException("currentInfo");
415
			Contract.EndContractBlock();
416

    
417
			var monitor = Monitors[currentInfo.Account];
418
			var newInfo=monitor.CloudClient.GetObjectInfo(currentInfo.Account, currentInfo.Container, currentInfo.Name);
419
			return newInfo;
420
		}
421

    
422
		public ContainerInfo RefreshContainerInfo(ContainerInfo container)
423
		{
424
			if (container == null)
425
				throw new ArgumentNullException("container");
426
			Contract.EndContractBlock();
427

    
428
			var monitor = Monitors[container.Account];
429
			var newInfo = monitor.CloudClient.GetContainerInfo(container.Account, container.Name);
430
			return newInfo;
431
		}
432

    
433

    
434
		public void ToggleSynching()
435
		{
436
			bool isPaused=false;
437
			foreach (var pair in Monitors)
438
			{
439
				var monitor = pair.Value;
440
				monitor.Pause = !monitor.Pause;
441
				isPaused = monitor.Pause;
442
			}
443

    
444
			PauseSyncCaption = isPaused ? "Resume syncing" : "Pause syncing";
445
			var iconKey = isPaused? "TraySyncPaused" : "TrayInSynch";
446
			StatusIcon = String.Format(@"../Images/{0}.ico", iconKey);
447
		}
448

    
449
		public void ExitPithos()
450
		{
451
			foreach (var pair in Monitors)
452
			{
453
				var monitor = pair.Value;
454
				monitor.Stop();
455
			}
456

    
457
			((Window)GetView()).Close();
458
		}
459
		#endregion
460

    
461

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

    
469
		readonly IWindowManager _windowManager;
470

    
471

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

    
479
			if (_iconNames.ContainsKey(pithosStatus))
480
			{
481
				var info = _iconNames[pithosStatus];
482
				StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName);
483

    
484
				Assembly assembly = Assembly.GetExecutingAssembly();                               
485
				var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
486

    
487

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

    
494

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

    
506
						monitor.Start();
507
					}
508
					catch (WebException exc)
509
					{
510
						if (AbandonRetry(monitor, retries))
511
							return;
512

    
513
                        HttpStatusCode statusCode =HttpStatusCode.OK;
514
			            var response = exc.Response as HttpWebResponse;
515
                        if(response!=null)
516
					        statusCode = response.StatusCode;
517

    
518
                        switch (statusCode)
519
                        {
520
                            case HttpStatusCode.Unauthorized:
521
                                var message = String.Format("API Key Expired for {0}. Starting Renewal",
522
                                                            monitor.UserName);
523
                                Log.Error(message, exc);
524
                                TryAuthorize(monitor, retries).Wait();
525
                                break;
526
                            case HttpStatusCode.ProxyAuthenticationRequired:
527
                                TryAuthenticateProxy(monitor,retries);
528
                                break;
529
                            default:
530
                                TryLater(monitor, exc, retries);
531
                                break;
532
                        }
533
					}
534
					catch (Exception exc)
535
					{
536
						if (AbandonRetry(monitor, retries)) 
537
							return;
538

    
539
						TryLater(monitor,exc,retries);
540
					}
541
				}
542
			});
543
		}
544

    
545
	    private void TryAuthenticateProxy(PithosMonitor monitor,int retries)
546
	    {
547
	        Execute.OnUIThread(() =>
548
	                               {
549
	                                   var proxyAccount = new ProxyAccountViewModel(this.Settings);
550
	                                   if (true == _windowManager.ShowDialog(proxyAccount))
551
	                                   {
552
                                           
553
	                                       StartMonitor(monitor, retries);
554
	                                       NotifyOfPropertyChange(() => Accounts);
555
	                                   }
556
	                               });
557
	    }
558

    
559
	    private bool AbandonRetry(PithosMonitor monitor, int retries)
560
		{
561
			if (retries > 1)
562
			{
563
				var message = String.Format("Monitoring of account {0} has failed too many times. Will not retry",
564
											monitor.UserName);
565
				_events.Publish(new Notification
566
									{Title = "Account monitoring failed", Message = message, Level = TraceLevel.Error});
567
				return true;
568
			}
569
			return false;
570
		}
571

    
572

    
573
		private async Task TryAuthorize(PithosMonitor monitor,int retries)
574
		{
575
			_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 });
576

    
577
			try
578
			{
579

    
580
				var credentials = await PithosAccount.RetrieveCredentials(Settings.PithosLoginUrl);
581

    
582
				var account = Settings.Accounts.First(act => act.AccountName == credentials.UserName);
583
				account.ApiKey = credentials.Password;
584
				monitor.ApiKey = credentials.Password;
585
				Settings.Save();
586
				await TaskEx.Delay(10000);
587
				StartMonitor(monitor, retries + 1);
588
				NotifyOfPropertyChange(()=>Accounts);
589
			}
590
			catch (AggregateException exc)
591
			{
592
				string message = String.Format("API Key retrieval for {0} failed", monitor.UserName);
593
				Log.Error(message, exc.InnerException);
594
				_events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
595
			}
596
			catch (Exception exc)
597
			{
598
				string message = String.Format("API Key retrieval for {0} failed", monitor.UserName);
599
				Log.Error(message, exc);
600
				_events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
601
			}
602

    
603
		}
604

    
605
		private static bool IsUnauthorized(WebException exc)
606
		{
607
			if (exc==null)
608
				throw new ArgumentNullException("exc");
609
			Contract.EndContractBlock();
610

    
611
			var response = exc.Response as HttpWebResponse;
612
			if (response == null)
613
				return false;
614
			return (response.StatusCode == HttpStatusCode.Unauthorized);
615
		}
616

    
617
		private void TryLater(PithosMonitor monitor, Exception exc,int retries)
618
		{
619
			var message = String.Format("An exception occured. Can't start monitoring\nWill retry in 10 seconds");
620
			Task.Factory.StartNewDelayed(10000, () => StartMonitor(monitor,retries+1));
621
			_events.Publish(new Notification
622
								{Title = "Error", Message = message, Level = TraceLevel.Error});
623
			Log.Error(message, exc);
624
		}
625

    
626

    
627
		public void NotifyChange(string status, TraceLevel level=TraceLevel.Info)
628
		{
629
			StatusMessage = status;
630
			
631
			_events.Publish(new Notification { Title = "Pithos", Message = status, Level = level });
632
		}
633

    
634
		public void NotifyChangedFile(string filePath)
635
		{
636
			var entry = new FileEntry {FullPath=filePath};
637
			IProducerConsumerCollection<FileEntry> files=RecentFiles;
638
			FileEntry popped;
639
			while (files.Count > 5)
640
				files.TryTake(out popped);
641
			files.TryAdd(entry);
642
		}
643

    
644
		public void NotifyAccount(AccountInfo account)
645
		{
646
			if (account== null)
647
				return;
648
			//TODO: What happens to an existing account whose Token has changed?
649
			account.SiteUri= String.Format("{0}/ui/?token={1}&user={2}",
650
				account.SiteUri, Uri.EscapeUriString(account.Token),
651
				Uri.EscapeUriString(account.UserName));
652

    
653
			if (Accounts.All(item => item.UserName != account.UserName))
654
				Accounts.TryAdd(account);
655

    
656
		}
657

    
658

    
659
		public void RemoveMonitor(string accountName)
660
		{
661
			if (String.IsNullOrWhiteSpace(accountName))
662
				return;
663

    
664
			var accountInfo=_accounts.FirstOrDefault(account => account.UserName == accountName);
665
			_accounts.TryRemove(accountInfo);
666

    
667
			PithosMonitor monitor;
668
			if (Monitors.TryRemove(accountName, out monitor))
669
			{
670
				monitor.Stop();
671
			}
672
		}
673

    
674
		public void RefreshOverlays()
675
		{
676
			foreach (var pair in Monitors)
677
			{
678
				var monitor = pair.Value;
679

    
680
				var path = monitor.RootPath;
681

    
682
				if (String.IsNullOrWhiteSpace(path))
683
					continue;
684

    
685
				if (!Directory.Exists(path) && !File.Exists(path))
686
					continue;
687

    
688
				IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path);
689

    
690
				try
691
				{
692
					NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM,
693
												 HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT,
694
												 pathPointer, IntPtr.Zero);
695
				}
696
				finally
697
				{
698
					Marshal.FreeHGlobal(pathPointer);
699
				}
700
			}
701
		}
702

    
703
		#region Event Handlers
704
		
705
		public void Handle(SelectiveSynchChanges message)
706
		{
707
			var accountName = message.Account.AccountName;
708
			PithosMonitor monitor;
709
			if (_monitors.TryGetValue(accountName, out monitor))
710
			{
711
				monitor.AddSelectivePaths(message.Added);
712
				monitor.RemoveSelectivePaths(message.Removed);
713

    
714
			}
715
			
716
		}
717

    
718

    
719
		public void Handle(Notification notification)
720
		{
721
			if (!Settings.ShowDesktopNotifications)
722
				return;
723
			BalloonIcon icon;
724
			switch (notification.Level)
725
			{
726
				case TraceLevel.Error:
727
					icon = BalloonIcon.Error;
728
					break;
729
				case TraceLevel.Info:
730
				case TraceLevel.Verbose:
731
					icon = BalloonIcon.Info;
732
					break;
733
				case TraceLevel.Warning:
734
					icon = BalloonIcon.Warning;
735
					break;
736
				default:
737
					icon = BalloonIcon.None;
738
					break;
739
			}
740

    
741
			if (Settings.ShowDesktopNotifications)
742
			{
743
				var tv = (ShellView) GetView();
744
				tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
745
			}
746
		}
747
		#endregion
748

    
749
		public void Handle(ShowFilePropertiesEvent message)
750
		{
751
			if (message == null)
752
				throw new ArgumentNullException("message");
753
			if (String.IsNullOrWhiteSpace(message.FileName) )
754
				throw new ArgumentException("message");
755
			Contract.EndContractBlock();
756

    
757
			var fileName = message.FileName;
758
            //TODO: Display file properties for non-container folders
759
			if (File.Exists(fileName))
760
                //Retrieve the full name with exact casing. Pithos names are case sensitive				
761
                ShowFileProperties(FileInfoExtensions.GetProperFilePathCapitalization(fileName));
762
			else if (Directory.Exists(fileName))
763
                //Retrieve the full name with exact casing. Pithos names are case sensitive
764
			{
765
                var path = FileInfoExtensions.GetProperDirectoryCapitalization(fileName);
766
                if (IsContainer(path))
767
			        ShowContainerProperties(path);
768
                else
769
                    ShowFileProperties(path);
770
			}
771
		}
772

    
773
	    private bool IsContainer(string path)
774
	    {
775
	        var matchingFolders = from account in _accounts
776
	                              from rootFolder in Directory.GetDirectories(account.AccountPath)
777
	                              where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase)
778
	                              select rootFolder;
779
	        return matchingFolders.Any();
780
	    }
781

    
782
	}
783
}