Statistics
| Branch: | Revision:

root / trunk / Pithos.Client.WPF / Shell / ShellViewModel.cs @ 2dc6f765

History | View | Annotate | Download (25.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 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
		///<summary>
83
		/// The Shell depends on MEF to provide implementations for windowManager, events, the status checker service and the settings
84
		///</summary>
85
		///<remarks>
86
		/// The PithosSettings class encapsulates the app's settings to abstract their storage mechanism (App settings, a database or registry)
87
		///</remarks>
88
		[ImportingConstructor]		
89
		public ShellViewModel(IWindowManager windowManager, IEventAggregator events, IStatusChecker statusChecker, PithosSettings settings)
90
		{
91
			try
92
			{
93

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

    
102
				Settings = settings;
103

    
104
				StatusMessage = "In Synch";
105

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

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

    
120

    
121
		protected override void OnActivate()
122
		{
123
			base.OnActivate();
124

    
125
			StartMonitoring();                    
126
		}
127

    
128

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

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

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

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

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

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

    
200
			    monitor.AuthenticationUrl = account.ServerUrl;
201

    
202
				_monitors[accountName] = monitor;
203

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

    
215

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

    
224

    
225
		#region Status Properties
226

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

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

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

    
249

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

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

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

    
277

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

    
290
		#endregion
291

    
292
		#region Commands
293

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

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

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

    
314
		//public PithosCommand OpenPithosFolderCommand { get; private set; }
315

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

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

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

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

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

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

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

    
369
			if (accountMonitor == null)
370
				return;
371

    
372
			var infoTask=Task.Factory.StartNew(()=>accountMonitor.GetObjectInfo(filePath));
373

    
374
			
375

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

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

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

    
404
			
405

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

    
410
        public void SynchNow()
411
        {
412
            var agent = IoC.Get<NetworkAgent>();
413
            agent.SynchNow();
414
        }
415

    
416
		public ObjectInfo RefreshObjectInfo(ObjectInfo currentInfo)
417
		{
418
			if (currentInfo==null)
419
				throw new ArgumentNullException("currentInfo");
420
			Contract.EndContractBlock();
421

    
422
			var monitor = Monitors[currentInfo.Account];
423
			var newInfo=monitor.CloudClient.GetObjectInfo(currentInfo.Account, currentInfo.Container, currentInfo.Name);
424
			return newInfo;
425
		}
426

    
427
		public ContainerInfo RefreshContainerInfo(ContainerInfo container)
428
		{
429
			if (container == null)
430
				throw new ArgumentNullException("container");
431
			Contract.EndContractBlock();
432

    
433
			var monitor = Monitors[container.Account];
434
			var newInfo = monitor.CloudClient.GetContainerInfo(container.Account, container.Name);
435
			return newInfo;
436
		}
437

    
438

    
439
		public void ToggleSynching()
440
		{
441
			bool isPaused=false;
442
			foreach (var pair in Monitors)
443
			{
444
				var monitor = pair.Value;
445
				monitor.Pause = !monitor.Pause;
446
				isPaused = monitor.Pause;
447
			}
448

    
449
			PauseSyncCaption = isPaused ? "Resume syncing" : "Pause syncing";
450
			var iconKey = isPaused? "TraySyncPaused" : "TrayInSynch";
451
			StatusIcon = String.Format(@"../Images/{0}.ico", iconKey);
452
		}
453

    
454
		public void ExitPithos()
455
		{
456
			foreach (var pair in Monitors)
457
			{
458
				var monitor = pair.Value;
459
				monitor.Stop();
460
			}
461

    
462
			((Window)GetView()).Close();
463
		}
464
		#endregion
465

    
466

    
467
		private readonly Dictionary<PithosStatus, StatusInfo> _iconNames = new List<StatusInfo>
468
			{
469
				new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"),
470
				new StatusInfo(PithosStatus.Syncing, "Syncing Files", "TraySynching"),
471
				new StatusInfo(PithosStatus.SyncPaused, "Sync Paused", "TraySyncPaused")
472
			}.ToDictionary(s => s.Status);
473

    
474
		readonly IWindowManager _windowManager;
475

    
476

    
477
		///<summary>
478
		/// Updates the visual status indicators of the application depending on status changes, e.g. icon, stat		
479
		///</summary>
480
		public void UpdateStatus()
481
		{
482
			var pithosStatus = _statusChecker.GetPithosStatus();
483

    
484
			if (_iconNames.ContainsKey(pithosStatus))
485
			{
486
				var info = _iconNames[pithosStatus];
487
				StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName);
488

    
489
				Assembly assembly = Assembly.GetExecutingAssembly();                               
490
				var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
491

    
492

    
493
				StatusMessage = String.Format("Pithos {0}\r\n{1}", fileVersion.FileVersion,info.StatusText);
494
			}
495
			
496
			//_events.Publish(new Notification { Title = "Start", Message = "Start Monitoring", Level = TraceLevel.Info});
497
		}
498

    
499

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

    
511
						monitor.Start();
512
					}
513
					catch (WebException exc)
514
					{
515
						if (AbandonRetry(monitor, retries))
516
							return;
517

    
518
                        HttpStatusCode statusCode =HttpStatusCode.OK;
519
			            var response = exc.Response as HttpWebResponse;
520
                        if(response!=null)
521
					        statusCode = response.StatusCode;
522

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

    
544
						TryLater(monitor,exc,retries);
545
					}
546
				}
547
			});
548
		}
549

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

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

    
577

    
578
		private async Task TryAuthorize(PithosMonitor monitor,int retries)
579
		{
580
			_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 });
581

    
582
			try
583
			{
584

    
585
				var credentials = await PithosAccount.RetrieveCredentials(Settings.PithosLoginUrl);
586

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

    
608
		}
609

    
610
		private static bool IsUnauthorized(WebException exc)
611
		{
612
			if (exc==null)
613
				throw new ArgumentNullException("exc");
614
			Contract.EndContractBlock();
615

    
616
			var response = exc.Response as HttpWebResponse;
617
			if (response == null)
618
				return false;
619
			return (response.StatusCode == HttpStatusCode.Unauthorized);
620
		}
621

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

    
631

    
632
		public void NotifyChange(string status, TraceLevel level=TraceLevel.Info)
633
		{
634
			StatusMessage = status;
635
			
636
			_events.Publish(new Notification { Title = "Pithos", Message = status, Level = level });
637
		}
638

    
639
		public void NotifyChangedFile(string filePath)
640
		{
641
			var entry = new FileEntry {FullPath=filePath};
642
			IProducerConsumerCollection<FileEntry> files=RecentFiles;
643
			FileEntry popped;
644
			while (files.Count > 5)
645
				files.TryTake(out popped);
646
			files.TryAdd(entry);
647
		}
648

    
649
		public void NotifyAccount(AccountInfo account)
650
		{
651
			if (account== null)
652
				return;
653
			//TODO: What happens to an existing account whose Token has changed?
654
			account.SiteUri= String.Format("{0}/ui/?token={1}&user={2}",
655
				account.SiteUri, Uri.EscapeDataString(account.Token),
656
                Uri.EscapeDataString(account.UserName));
657

    
658
			if (Accounts.All(item => item.UserName != account.UserName))
659
				Accounts.TryAdd(account);
660

    
661
		}
662

    
663
	    public void NotifyConflicts(IEnumerable<FileSystemInfo> conflictFiles, string message)
664
	    {
665
            if (conflictFiles == null)
666
                return;
667
            if (!conflictFiles.Any())
668
                return;
669

    
670
            UpdateStatus();
671
            //TODO: Create a more specific message. For now, just show a warning
672
            NotifyForFiles(conflictFiles,message,TraceLevel.Warning);
673

    
674
	    }
675

    
676
	    public void NotifyForFiles(IEnumerable<FileSystemInfo> files, string message,TraceLevel level=TraceLevel.Info)
677
	    {
678
            if (files == null)
679
                return;
680
            if (!files.Any())
681
                return;
682

    
683
            StatusMessage = message;
684

    
685
            _events.Publish(new Notification { Title = "Pithos", Message = message, Level = level});
686
        }
687

    
688
        public void Notify(Notification notification)
689
        {
690
            _events.Publish(notification);
691
        }
692

    
693

    
694
	    public void RemoveMonitor(string accountName)
695
		{
696
			if (String.IsNullOrWhiteSpace(accountName))
697
				return;
698

    
699
			var accountInfo=_accounts.FirstOrDefault(account => account.UserName == accountName);
700
			_accounts.TryRemove(accountInfo);
701

    
702
			PithosMonitor monitor;
703
			if (Monitors.TryRemove(accountName, out monitor))
704
			{
705
				monitor.Stop();
706
			}
707
		}
708

    
709
		public void RefreshOverlays()
710
		{
711
			foreach (var pair in Monitors)
712
			{
713
				var monitor = pair.Value;
714

    
715
				var path = monitor.RootPath;
716

    
717
				if (String.IsNullOrWhiteSpace(path))
718
					continue;
719

    
720
				if (!Directory.Exists(path) && !File.Exists(path))
721
					continue;
722

    
723
				IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path);
724

    
725
				try
726
				{
727
					NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM,
728
												 HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT,
729
												 pathPointer, IntPtr.Zero);
730
				}
731
				finally
732
				{
733
					Marshal.FreeHGlobal(pathPointer);
734
				}
735
			}
736
		}
737

    
738
		#region Event Handlers
739
		
740
		public void Handle(SelectiveSynchChanges message)
741
		{
742
			var accountName = message.Account.AccountName;
743
			PithosMonitor monitor;
744
			if (_monitors.TryGetValue(accountName, out monitor))
745
			{
746
				monitor.AddSelectivePaths(message.Added);
747
				monitor.RemoveSelectivePaths(message.Removed);
748

    
749
			}
750
			
751
		}
752

    
753

    
754
	    private bool _pollStarted = false;
755

    
756
        //SMELL: Doing so much work for notifications in the shell is wrong
757
        //The notifications should be moved to their own view/viewmodel pair
758
        //and different templates should be used for different message types
759
        //This will also allow the addition of extra functionality, eg. actions
760
        //
761
		public void Handle(Notification notification)
762
		{
763
            UpdateStatus();
764

    
765
			if (!Settings.ShowDesktopNotifications)
766
				return;
767

    
768
            if (notification is PollNotification)
769
            {
770
                _pollStarted = true;
771
                return;
772
            }
773
            if (notification is CloudNotification)
774
            {
775
                if (!_pollStarted) 
776
                    return;
777
                _pollStarted= false;
778
                notification.Title = "Pithos";
779
                notification.Message = "Start Synchronisation";
780
            }
781

    
782
            if (String.IsNullOrWhiteSpace(notification.Message) && String.IsNullOrWhiteSpace(notification.Title))
783
                return;
784

    
785
			BalloonIcon icon;
786
			switch (notification.Level)
787
			{
788
				case TraceLevel.Error:
789
					icon = BalloonIcon.Error;
790
					break;
791
				case TraceLevel.Info:
792
				case TraceLevel.Verbose:
793
					icon = BalloonIcon.Info;
794
					break;
795
				case TraceLevel.Warning:
796
					icon = BalloonIcon.Warning;
797
					break;
798
				default:
799
					icon = BalloonIcon.None;
800
					break;
801
			}
802
            
803
			if (Settings.ShowDesktopNotifications)
804
			{
805
				var tv = (ShellView) GetView();                
806
                var balloon=new PithosBalloon{Title=notification.Title,Message=notification.Message,Icon=icon};
807
                tv.TaskbarView.ShowCustomBalloon(balloon,PopupAnimation.Fade,4000);
808
//				tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
809
			}
810
		}
811
		#endregion
812

    
813
		public void Handle(ShowFilePropertiesEvent message)
814
		{
815
			if (message == null)
816
				throw new ArgumentNullException("message");
817
			if (String.IsNullOrWhiteSpace(message.FileName) )
818
				throw new ArgumentException("message");
819
			Contract.EndContractBlock();
820

    
821
			var fileName = message.FileName;
822
            //TODO: Display file properties for non-container folders
823
			if (File.Exists(fileName))
824
                //Retrieve the full name with exact casing. Pithos names are case sensitive				
825
                ShowFileProperties(FileInfoExtensions.GetProperFilePathCapitalization(fileName));
826
			else if (Directory.Exists(fileName))
827
                //Retrieve the full name with exact casing. Pithos names are case sensitive
828
			{
829
                var path = FileInfoExtensions.GetProperDirectoryCapitalization(fileName);
830
                if (IsContainer(path))
831
			        ShowContainerProperties(path);
832
                else
833
                    ShowFileProperties(path);
834
			}
835
		}
836

    
837
	    private bool IsContainer(string path)
838
	    {
839
	        var matchingFolders = from account in _accounts
840
	                              from rootFolder in Directory.GetDirectories(account.AccountPath)
841
	                              where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase)
842
	                              select rootFolder;
843
	        return matchingFolders.Any();
844
	    }
845

    
846
	    public FileStatus GetFileStatus(string localFileName)
847
	    {
848
            if (String.IsNullOrWhiteSpace(localFileName))
849
                throw new ArgumentNullException("localFileName");
850
            Contract.EndContractBlock();
851
            
852
	        var statusKeeper = IoC.Get<IStatusKeeper>();
853
	        var status=statusKeeper.GetFileStatus(localFileName);
854
	        return status;
855
	    }
856
	}
857
}