Revision 255f5f86 trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs

b/trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs
1
using System.Collections.Concurrent;
1
#region
2
/* -----------------------------------------------------------------------
3
 * <copyright file="ShellViewModel.cs" company="GRNet">
4
 * 
5
 * Copyright 2011-2012 GRNET S.A. All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or
8
 * without modification, are permitted provided that the following
9
 * conditions are met:
10
 *
11
 *   1. Redistributions of source code must retain the above
12
 *      copyright notice, this list of conditions and the following
13
 *      disclaimer.
14
 *
15
 *   2. Redistributions in binary form must reproduce the above
16
 *      copyright notice, this list of conditions and the following
17
 *      disclaimer in the documentation and/or other materials
18
 *      provided with the distribution.
19
 *
20
 *
21
 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
 * POSSIBILITY OF SUCH DAMAGE.
33
 *
34
 * The views and conclusions contained in the software and
35
 * documentation are those of the authors and should not be
36
 * interpreted as representing official policies, either expressed
37
 * or implied, of GRNET S.A.
38
 * </copyright>
39
 * -----------------------------------------------------------------------
40
 */
41
#endregion
42
using System.Collections.Concurrent;
2 43
using System.Diagnostics;
3 44
using System.Diagnostics.Contracts;
4 45
using System.IO;
......
54 95
		public PithosSettings Settings { get; private set; }
55 96

  
56 97

  
57
        private readonly ConcurrentDictionary<string, PithosMonitor> _monitors = new ConcurrentDictionary<string, PithosMonitor>();
98
		private readonly ConcurrentDictionary<string, PithosMonitor> _monitors = new ConcurrentDictionary<string, PithosMonitor>();
58 99
		///<summary>
59 100
		/// Dictionary of account monitors, keyed by account
60 101
		///</summary>
......
64 105
		///</remarks>
65 106
		// TODO: Does the Shell REALLY need access to the monitors? Could we achieve the same results with a better design?
66 107
		// 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
108
		public ConcurrentDictionary<string, PithosMonitor> Monitors
68 109
		{
69 110
			get { return _monitors; }
70 111
		}
71 112

  
72 113

  
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;
114
		///<summary>
115
		/// The status service is used by Shell extensions to retrieve file status information
116
		///</summary>
117
		//TODO: CODE SMELL! This is the shell! While hosting in the shell makes executing start/stop commands easier, it is still a smell
118
		private ServiceHost _statusService;
78 119

  
79 120
		//Logging in the Pithos client is provided by log4net
80 121
		private static readonly log4net.ILog Log = log4net.LogManager.GetLogger("Pithos");
81 122

  
82
        //Lazily initialized File Version info. This is done once and lazily to avoid blocking the UI
83
        private Lazy<FileVersionInfo> _fileVersion;
123
		//Lazily initialized File Version info. This is done once and lazily to avoid blocking the UI
124
		private Lazy<FileVersionInfo> _fileVersion;
84 125

  
85 126
		///<summary>
86 127
		/// The Shell depends on MEF to provide implementations for windowManager, events, the status checker service and the settings
......
104 145

  
105 146
				Settings = settings;
106 147

  
107
                Proxy.SetFromSettings(settings);
148
				Proxy.SetFromSettings(settings);
108 149

  
109 150
				StatusMessage = "In Synch";
110 151

  
111
                _fileVersion=  new Lazy<FileVersionInfo>(() =>
112
                {
113
                    Assembly assembly = Assembly.GetExecutingAssembly();
114
                    var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
115
                    return fileVersion;
116
                });
152
				_fileVersion=  new Lazy<FileVersionInfo>(() =>
153
				{
154
					Assembly assembly = Assembly.GetExecutingAssembly();
155
					var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
156
					return fileVersion;
157
				});
117 158
				_accounts.CollectionChanged += (sender, e) =>
118 159
												   {
119 160
													   NotifyOfPropertyChange(() => OpenFolderCaption);
......
133 174
		{
134 175
			base.OnActivate();
135 176

  
136
            
177
			
137 178

  
138 179
			StartMonitoring();                    
139 180
		}
......
200 241
					return;
201 242
				}
202 243

  
203
                
244
				
204 245
				//Create a new monitor/ Can't use MEF here, it would return a single instance for all monitors
205 246
				monitor = new PithosMonitor
206 247
							  {
......
212 253
				//PithosMonitor uses MEF so we need to resolve it
213 254
				IoC.BuildUp(monitor);
214 255

  
215
			    monitor.AuthenticationUrl = account.ServerUrl;
256
				monitor.AuthenticationUrl = account.ServerUrl;
216 257

  
217 258
				_monitors[accountName] = monitor;
218 259

  
......
296 337
			get { return _statusIcon; }
297 338
			set
298 339
			{
299
                //TODO: Ensure all status icons use the Pithos logo
340
				//TODO: Ensure all status icons use the Pithos logo
300 341
				_statusIcon = value;
301 342
				NotifyOfPropertyChange(() => StatusIcon);
302 343
			}
......
379 420
			var pair=(from monitor in  Monitors
380 421
							   where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
381 422
								   select monitor).FirstOrDefault();
382
		    var accountMonitor = pair.Value;
423
			var accountMonitor = pair.Value;
383 424

  
384 425
			if (accountMonitor == null)
385 426
				return;
......
413 454
			var pair=(from monitor in  Monitors
414 455
							   where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
415 456
								   select monitor).FirstOrDefault();
416
		    var accountMonitor = pair.Value;            
457
			var accountMonitor = pair.Value;            
417 458
			var info = accountMonitor.GetContainerInfo(filePath);
418 459

  
419 460
			
......
422 463
			_windowManager.ShowWindow(containerProperties);
423 464
		}
424 465

  
425
        public void SynchNow()
426
        {
427
            var agent = IoC.Get<NetworkAgent>();
428
            agent.SynchNow();
429
        }
466
		public void SynchNow()
467
		{
468
			var agent = IoC.Get<NetworkAgent>();
469
			agent.SynchNow();
470
		}
430 471

  
431 472
		public ObjectInfo RefreshObjectInfo(ObjectInfo currentInfo)
432 473
		{
......
487 528
			}.ToDictionary(s => s.Status);
488 529

  
489 530
		readonly IWindowManager _windowManager;
490
	    
531
		
491 532

  
492 533
		///<summary>
493 534
		/// Updates the visual status indicators of the application depending on status changes, e.g. icon, stat		
......
528 569
						if (AbandonRetry(monitor, retries))
529 570
							return;
530 571

  
531
                        HttpStatusCode statusCode =HttpStatusCode.OK;
532
			            var response = exc.Response as HttpWebResponse;
533
                        if(response!=null)
534
					        statusCode = response.StatusCode;
535

  
536
                        switch (statusCode)
537
                        {
538
                            case HttpStatusCode.Unauthorized:
539
                                var message = String.Format("API Key Expired for {0}. Starting Renewal",
540
                                                            monitor.UserName);
541
                                Log.Error(message, exc);
542
                                TryAuthorize(monitor, retries).Wait();
543
                                break;
544
                            case HttpStatusCode.ProxyAuthenticationRequired:
545
                                TryAuthenticateProxy(monitor,retries);
546
                                break;
547
                            default:
548
                                TryLater(monitor, exc, retries);
549
                                break;
550
                        }
572
						HttpStatusCode statusCode =HttpStatusCode.OK;
573
						var response = exc.Response as HttpWebResponse;
574
						if(response!=null)
575
							statusCode = response.StatusCode;
576

  
577
						switch (statusCode)
578
						{
579
							case HttpStatusCode.Unauthorized:
580
								var message = String.Format("API Key Expired for {0}. Starting Renewal",
581
															monitor.UserName);
582
								Log.Error(message, exc);
583
								TryAuthorize(monitor, retries).Wait();
584
								break;
585
							case HttpStatusCode.ProxyAuthenticationRequired:
586
								TryAuthenticateProxy(monitor,retries);
587
								break;
588
							default:
589
								TryLater(monitor, exc, retries);
590
								break;
591
						}
551 592
					}
552 593
					catch (Exception exc)
553 594
					{
......
560 601
			});
561 602
		}
562 603

  
563
	    private void TryAuthenticateProxy(PithosMonitor monitor,int retries)
564
	    {
565
	        Execute.OnUIThread(() =>
566
	                               {                                       
567
	                                   var proxyAccount = IoC.Get<ProxyAccountViewModel>();
568
                                        proxyAccount.Settings = this.Settings;
569
	                                   if (true != _windowManager.ShowDialog(proxyAccount)) 
570
                                           return;
571
	                                   StartMonitor(monitor, retries);
572
	                                   NotifyOfPropertyChange(() => Accounts);
573
	                               });
574
	    }
604
		private void TryAuthenticateProxy(PithosMonitor monitor,int retries)
605
		{
606
			Execute.OnUIThread(() =>
607
								   {                                       
608
									   var proxyAccount = IoC.Get<ProxyAccountViewModel>();
609
										proxyAccount.Settings = this.Settings;
610
									   if (true != _windowManager.ShowDialog(proxyAccount)) 
611
										   return;
612
									   StartMonitor(monitor, retries);
613
									   NotifyOfPropertyChange(() => Accounts);
614
								   });
615
		}
575 616

  
576
	    private bool AbandonRetry(PithosMonitor monitor, int retries)
617
		private bool AbandonRetry(PithosMonitor monitor, int retries)
577 618
		{
578 619
			if (retries > 1)
579 620
			{
......
665 706
			//TODO: What happens to an existing account whose Token has changed?
666 707
			account.SiteUri= String.Format("{0}/ui/?token={1}&user={2}",
667 708
				account.SiteUri, Uri.EscapeDataString(account.Token),
668
                Uri.EscapeDataString(account.UserName));
709
				Uri.EscapeDataString(account.UserName));
669 710

  
670 711
			if (Accounts.All(item => item.UserName != account.UserName))
671 712
				Accounts.TryAdd(account);
672 713

  
673 714
		}
674 715

  
675
	    public void NotifyConflicts(IEnumerable<FileSystemInfo> conflictFiles, string message)
676
	    {
677
            if (conflictFiles == null)
678
                return;
679
            if (!conflictFiles.Any())
680
                return;
716
		public void NotifyConflicts(IEnumerable<FileSystemInfo> conflictFiles, string message)
717
		{
718
			if (conflictFiles == null)
719
				return;
720
			if (!conflictFiles.Any())
721
				return;
681 722

  
682
            UpdateStatus();
683
            //TODO: Create a more specific message. For now, just show a warning
684
            NotifyForFiles(conflictFiles,message,TraceLevel.Warning);
723
			UpdateStatus();
724
			//TODO: Create a more specific message. For now, just show a warning
725
			NotifyForFiles(conflictFiles,message,TraceLevel.Warning);
685 726

  
686
	    }
727
		}
687 728

  
688
	    public void NotifyForFiles(IEnumerable<FileSystemInfo> files, string message,TraceLevel level=TraceLevel.Info)
689
	    {
690
            if (files == null)
691
                return;
692
            if (!files.Any())
693
                return;
729
		public void NotifyForFiles(IEnumerable<FileSystemInfo> files, string message,TraceLevel level=TraceLevel.Info)
730
		{
731
			if (files == null)
732
				return;
733
			if (!files.Any())
734
				return;
694 735

  
695
            StatusMessage = message;
736
			StatusMessage = message;
696 737

  
697
            _events.Publish(new Notification { Title = "Pithos", Message = message, Level = level});
698
        }
738
			_events.Publish(new Notification { Title = "Pithos", Message = message, Level = level});
739
		}
699 740

  
700
        public void Notify(Notification notification)
701
        {
702
            _events.Publish(notification);
703
        }
741
		public void Notify(Notification notification)
742
		{
743
			_events.Publish(notification);
744
		}
704 745

  
705 746

  
706
	    public void RemoveMonitor(string accountName)
747
		public void RemoveMonitor(string accountName)
707 748
		{
708 749
			if (String.IsNullOrWhiteSpace(accountName))
709 750
				return;
......
763 804
		}
764 805

  
765 806

  
766
	    private bool _pollStarted = false;
807
		private bool _pollStarted = false;
767 808

  
768
        //SMELL: Doing so much work for notifications in the shell is wrong
769
        //The notifications should be moved to their own view/viewmodel pair
770
        //and different templates should be used for different message types
771
        //This will also allow the addition of extra functionality, eg. actions
772
        //
809
		//SMELL: Doing so much work for notifications in the shell is wrong
810
		//The notifications should be moved to their own view/viewmodel pair
811
		//and different templates should be used for different message types
812
		//This will also allow the addition of extra functionality, eg. actions
813
		//
773 814
		public void Handle(Notification notification)
774 815
		{
775
            UpdateStatus();
816
			UpdateStatus();
776 817

  
777 818
			if (!Settings.ShowDesktopNotifications)
778 819
				return;
779 820

  
780
            if (notification is PollNotification)
781
            {
782
                _pollStarted = true;
783
                return;
784
            }
785
            if (notification is CloudNotification)
786
            {
787
                if (!_pollStarted) 
788
                    return;
789
                _pollStarted= false;
790
                notification.Title = "Pithos";
791
                notification.Message = "Start Synchronisation";
792
            }
793

  
794
            if (String.IsNullOrWhiteSpace(notification.Message) && String.IsNullOrWhiteSpace(notification.Title))
795
                return;
821
			if (notification is PollNotification)
822
			{
823
				_pollStarted = true;
824
				return;
825
			}
826
			if (notification is CloudNotification)
827
			{
828
				if (!_pollStarted) 
829
					return;
830
				_pollStarted= false;
831
				notification.Title = "Pithos";
832
				notification.Message = "Start Synchronisation";
833
			}
834

  
835
			if (String.IsNullOrWhiteSpace(notification.Message) && String.IsNullOrWhiteSpace(notification.Title))
836
				return;
796 837

  
797 838
			BalloonIcon icon;
798 839
			switch (notification.Level)
......
811 852
					icon = BalloonIcon.None;
812 853
					break;
813 854
			}
814
            
855
			
815 856
			if (Settings.ShowDesktopNotifications)
816 857
			{
817 858
				var tv = (ShellView) GetView();                
818
                var balloon=new PithosBalloon{Title=notification.Title,Message=notification.Message,Icon=icon};
819
                tv.TaskbarView.ShowCustomBalloon(balloon,PopupAnimation.Fade,4000);
859
				var balloon=new PithosBalloon{Title=notification.Title,Message=notification.Message,Icon=icon};
860
				tv.TaskbarView.ShowCustomBalloon(balloon,PopupAnimation.Fade,4000);
820 861
//				tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
821 862
			}
822 863
		}
......
831 872
			Contract.EndContractBlock();
832 873

  
833 874
			var fileName = message.FileName;
834
            //TODO: Display file properties for non-container folders
875
			//TODO: Display file properties for non-container folders
835 876
			if (File.Exists(fileName))
836
                //Retrieve the full name with exact casing. Pithos names are case sensitive				
837
                ShowFileProperties(FileInfoExtensions.GetProperFilePathCapitalization(fileName));
877
				//Retrieve the full name with exact casing. Pithos names are case sensitive				
878
				ShowFileProperties(FileInfoExtensions.GetProperFilePathCapitalization(fileName));
838 879
			else if (Directory.Exists(fileName))
839
                //Retrieve the full name with exact casing. Pithos names are case sensitive
880
				//Retrieve the full name with exact casing. Pithos names are case sensitive
840 881
			{
841
                var path = FileInfoExtensions.GetProperDirectoryCapitalization(fileName);
842
                if (IsContainer(path))
843
			        ShowContainerProperties(path);
844
                else
845
                    ShowFileProperties(path);
882
				var path = FileInfoExtensions.GetProperDirectoryCapitalization(fileName);
883
				if (IsContainer(path))
884
					ShowContainerProperties(path);
885
				else
886
					ShowFileProperties(path);
846 887
			}
847 888
		}
848 889

  
849
	    private bool IsContainer(string path)
850
	    {
851
	        var matchingFolders = from account in _accounts
852
	                              from rootFolder in Directory.GetDirectories(account.AccountPath)
853
	                              where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase)
854
	                              select rootFolder;
855
	        return matchingFolders.Any();
856
	    }
857

  
858
	    public FileStatus GetFileStatus(string localFileName)
859
	    {
860
            if (String.IsNullOrWhiteSpace(localFileName))
861
                throw new ArgumentNullException("localFileName");
862
            Contract.EndContractBlock();
863
            
864
	        var statusKeeper = IoC.Get<IStatusKeeper>();
865
	        var status=statusKeeper.GetFileStatus(localFileName);
866
	        return status;
867
	    }
890
		private bool IsContainer(string path)
891
		{
892
			var matchingFolders = from account in _accounts
893
								  from rootFolder in Directory.GetDirectories(account.AccountPath)
894
								  where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase)
895
								  select rootFolder;
896
			return matchingFolders.Any();
897
		}
898

  
899
		public FileStatus GetFileStatus(string localFileName)
900
		{
901
			if (String.IsNullOrWhiteSpace(localFileName))
902
				throw new ArgumentNullException("localFileName");
903
			Contract.EndContractBlock();
904
			
905
			var statusKeeper = IoC.Get<IStatusKeeper>();
906
			var status=statusKeeper.GetFileStatus(localFileName);
907
			return status;
908
		}
868 909
	}
869 910
}

Also available in: Unified diff