Version
[pithos-ms-client] / trunk / Pithos.Client.WPF / Shell / ShellViewModel.cs
index 1aba5f5..f86f46e 100644 (file)
@@ -50,12 +50,12 @@ using System.ServiceModel;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls.Primitives;
+using AppLimit.NetSparkle;
 using Caliburn.Micro;
 using Hardcodet.Wpf.TaskbarNotification;
 using Pithos.Client.WPF.Configuration;
 using Pithos.Client.WPF.FileProperties;
 using Pithos.Client.WPF.Preferences;
-using Pithos.Client.WPF.Properties;
 using Pithos.Client.WPF.SelectiveSynch;
 using Pithos.Client.WPF.Services;
 using Pithos.Client.WPF.Shell;
@@ -84,10 +84,11 @@ namespace Pithos.Client.WPF {
        /// * ShowFilePropertiesEvent: Raised when a shell command requests the display of the file/container properties dialog
        ///</remarks>           
        //TODO: CODE SMELL Why does the shell handle the SelectiveSynchChanges?
-       [Export(typeof(IShell))]
+    [Export(typeof(IShell)), Export(typeof(ShellViewModel))]
        public class ShellViewModel : Screen, IStatusNotification, IShell,
                IHandle<Notification>, IHandle<SelectiveSynchChanges>, IHandle<ShowFilePropertiesEvent>
        {
+
                //The Status Checker provides the current synch state
                //TODO: Could we remove the status checker and use events in its place?
                private readonly IStatusChecker _statusChecker;
@@ -96,7 +97,7 @@ namespace Pithos.Client.WPF {
                public PithosSettings Settings { get; private set; }
 
 
-               private readonly ConcurrentDictionary<string, PithosMonitor> _monitors = new ConcurrentDictionary<string, PithosMonitor>();
+               private readonly ConcurrentDictionary<Uri, PithosMonitor> _monitors = new ConcurrentDictionary<Uri, PithosMonitor>();
                ///<summary>
                /// Dictionary of account monitors, keyed by account
                ///</summary>
@@ -106,7 +107,7 @@ namespace Pithos.Client.WPF {
                ///</remarks>
                // TODO: Does the Shell REALLY need access to the monitors? Could we achieve the same results with a better design?
                // TODO: The monitors should be internal to Pithos.Core, even though exposing them makes coding of the Object and Container windows easier
-               public ConcurrentDictionary<string, PithosMonitor> Monitors
+               public ConcurrentDictionary<Uri, PithosMonitor> Monitors
                {
                        get { return _monitors; }
                }
@@ -119,21 +120,40 @@ namespace Pithos.Client.WPF {
                private ServiceHost _statusService;
 
                //Logging in the Pithos client is provided by log4net
-               private static readonly log4net.ILog Log = log4net.LogManager.GetLogger("Pithos");
-
-               //Lazily initialized File Version info. This is done once and lazily to avoid blocking the UI
-               private Lazy<FileVersionInfo> _fileVersion;
-
-           private PollAgent _pollAgent;
-
-               ///<summary>
+        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+           private readonly PollAgent _pollAgent;
+           private readonly NetworkAgent _networkAgent;
+
+           [Import]
+           public Selectives Selectives { get; set; }
+
+           private MiniStatusViewModel _miniStatus;
+
+           [Import]
+        public MiniStatusViewModel MiniStatus
+           {
+               get { return _miniStatus; }
+               set
+               {
+                   _miniStatus = value;
+                   _miniStatus.Shell = this;
+                   _miniStatus.Deactivated += (sender, arg) =>
+                                                  {
+                                                      _statusVisible = false;
+                                                   NotifyOfPropertyChange(()=>MiniStatusCaption);
+                                                  };
+               }
+           }
+
+           ///<summary>
                /// The Shell depends on MEF to provide implementations for windowManager, events, the status checker service and the settings
                ///</summary>
                ///<remarks>
                /// The PithosSettings class encapsulates the app's settings to abstract their storage mechanism (App settings, a database or registry)
                ///</remarks>
                [ImportingConstructor]          
-               public ShellViewModel(IWindowManager windowManager, IEventAggregator events, IStatusChecker statusChecker, PithosSettings settings,PollAgent pollAgent)
+               public ShellViewModel(IWindowManager windowManager, IEventAggregator events, IStatusChecker statusChecker, PithosSettings settings,PollAgent pollAgent,NetworkAgent networkAgent)
                {
                        try
                        {
@@ -147,59 +167,128 @@ namespace Pithos.Client.WPF {
                                _events.Subscribe(this);
 
                            _pollAgent = pollAgent;
+                           _networkAgent = networkAgent;
                                Settings = settings;
 
                                Proxy.SetFromSettings(settings);
 
-                               StatusMessage = "In Synch";
+                StatusMessage = Settings.Accounts.Count==0 
+                    ? "No Accounts added\r\nPlease add an account" 
+                    : "Starting";
 
-                               _fileVersion=  new Lazy<FileVersionInfo>(() =>
-                               {
-                                       Assembly assembly = Assembly.GetExecutingAssembly();
-                                       var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
-                                       return fileVersion;
-                               });
                                _accounts.CollectionChanged += (sender, e) =>
                                                                                                   {
                                                                                                           NotifyOfPropertyChange(() => OpenFolderCaption);
                                                                                                           NotifyOfPropertyChange(() => HasAccounts);
                                                                                                   };
 
+                SetVersionMessage();
                        }
                        catch (Exception exc)
                        {
                                Log.Error("Error while starting the ShellViewModel",exc);
                                throw;
                        }
+
                }
 
+           private void SetVersionMessage()
+           {
+               Assembly assembly = Assembly.GetExecutingAssembly();
+               var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
+               VersionMessage = String.Format("Pithos+ {0}", fileVersion.FileVersion);
+           }
+
+        public void CurrentSyncStatus()
+        {
+            if (Accounts.Count == 0)
+            {
+                ShowPreferences("AccountTab");
+            }
+            else
+            {
+                if (!_statusVisible)
+                    _windowManager.ShowWindow(MiniStatus);
+                else
+                {
+                    if (MiniStatus.IsActive)
+                        MiniStatus.TryClose();
+                }
+                _statusVisible = !_statusVisible;
+
+                NotifyOfPropertyChange(() => MiniStatusCaption);
+            }
+        }
 
-               protected override void OnActivate()
+           protected override void OnActivate()
                {
                        base.OnActivate();
 
-                       
+            InitializeSparkle();
+
+               //Must delay opening the upgrade window
+            //to avoid Windows Messages sent by the TaskbarIcon
+            TaskEx.Delay(5000).ContinueWith(_=>
+                Execute.OnUIThread(()=> _sparkle.StartLoop(true,Settings.UpdateForceCheck,Settings.UpdateCheckInterval)));
+
 
                        StartMonitoring();                    
                }
 
 
+           private void OnCheckFinished(object sender, bool updaterequired)
+           {
+            
+            Log.InfoFormat("Upgrade check finished. Need Upgrade: {0}", updaterequired);
+            if (_manualUpgradeCheck)
+            {
+                _manualUpgradeCheck = false;
+                if (!updaterequired)
+                    //Sparkle raises events on a background thread
+                    Execute.OnUIThread(()=>
+                        ShowBalloonFor(new Notification{Title="Pithos+ is up to date",Message="You have the latest Pithos+ version. No update is required"}));
+            }
+           }
+
+           private void OnUpgradeDetected(object sender, UpdateDetectedEventArgs e)
+           {            
+               Log.InfoFormat("Update detected {0}",e.LatestVersion);
+           }
+
+        public void CheckForUpgrade()
+        {
+            ShowBalloonFor(new Notification{Title="Checking for upgrades",Message="Contacting the server to retrieve the latest Pithos+ version."});
+            _sparkle.StopLoop();
+            _sparkle.updateDetected -= OnUpgradeDetected;
+            _sparkle.checkLoopFinished -= OnCheckFinished;
+            _sparkle.Dispose();
+
+            _manualUpgradeCheck = true;
+            InitializeSparkle();
+            _sparkle.StartLoop(true,true,Settings.UpdateCheckInterval);
+        }
+
+        private void InitializeSparkle()
+        {
+            _sparkle = new Sparkle(Settings.UpdateUrl);
+            _sparkle.updateDetected += OnUpgradeDetected;
+            _sparkle.checkLoopFinished += OnCheckFinished;
+            _sparkle.ShowDiagnosticWindow = Settings.UpdateDiagnostics;
+        }
 
-               private async void StartMonitoring()
+           private async void StartMonitoring()
                {
                        try
                        {
+                if (Settings.IgnoreCertificateErrors)
+                {
+                    ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
+                }
+                    
                                var accounts = Settings.Accounts.Select(MonitorAccount);
                                await TaskEx.WhenAll(accounts);
                                _statusService = StatusService.Start();
 
-/*
-                               foreach (var account in Settings.Accounts)
-                               {
-                                       await MonitorAccount(account);
-                               }
-*/
-                               
                        }
                        catch (AggregateException exc)
                        {
@@ -229,19 +318,29 @@ namespace Pithos.Client.WPF {
                                PithosMonitor monitor;
                                var accountName = account.AccountName;
 
-                               if (_monitors.TryGetValue(accountName, out monitor))
+                           MigrateFolders(account);
+
+                           Selectives.SetIsSelectiveEnabled(account.AccountKey, account.SelectiveSyncEnabled);
+
+                               if (Monitors.TryGetValue(account.AccountKey, out monitor))
                                {
                                        //If the account is active
-                                       if (account.IsActive)
-                                               //Start the monitor. It's OK to start an already started monitor,
-                                               //it will just ignore the call                        
-                                               StartMonitor(monitor).Wait();                        
-                                       else
-                                       {
-                                               //If the account is inactive
-                                               //Stop and remove the monitor
-                                               RemoveMonitor(accountName);
-                                       }
+                    if (account.IsActive)
+                    {
+                        //The Api Key may have changed throuth the Preferences dialog
+                        monitor.ApiKey = account.ApiKey;
+                                               Debug.Assert(monitor.StatusNotification == this,"An existing monitor should already have a StatusNotification service object");
+                        monitor.RootPath = account.RootPath;
+                        //Start the monitor. It's OK to start an already started monitor,
+                        //it will just ignore the call                        
+                        StartMonitor(monitor).Wait();
+                    }
+                    else
+                    {
+                        //If the account is inactive
+                        //Stop and remove the monitor
+                        RemoveMonitor(account.ServerUrl,accountName);
+                    }
                                        return;
                                }
 
@@ -259,7 +358,7 @@ namespace Pithos.Client.WPF {
 
                                monitor.AuthenticationUrl = account.ServerUrl;
 
-                               _monitors[accountName] = monitor;
+                               Monitors[account.AccountKey] = monitor;
 
                                if (account.IsActive)
                                {
@@ -272,8 +371,21 @@ namespace Pithos.Client.WPF {
                        });
                }
 
+           private void MigrateFolders(AccountSettings account)
+           {
+               var oldOthersFolder=Path.Combine(account.RootPath, FolderConstants.OldOthersFolder);
+               var newOthersFolder = Path.Combine(account.RootPath, FolderConstants.OthersFolder);
+               var oldFolder = new DirectoryInfo(oldOthersFolder);
+               var newFolder = new DirectoryInfo(newOthersFolder);
+
+            if (oldFolder.Exists && !newFolder.Exists)
+            {
+                oldFolder.MoveTo(newOthersFolder);
+            }
+           }
 
-               protected override void OnViewLoaded(object view)
+
+           protected override void OnViewLoaded(object view)
                {
                        UpdateStatus();
                        var window = (Window)view;            
@@ -292,10 +404,37 @@ namespace Pithos.Client.WPF {
                        {
                                _statusMessage = value;
                                NotifyOfPropertyChange(() => StatusMessage);
+                NotifyOfPropertyChange(() => TooltipMessage);
                        }
                }
 
-               private readonly ObservableConcurrentCollection<AccountInfo> _accounts = new ObservableConcurrentCollection<AccountInfo>();
+        public string VersionMessage { get; set; }
+
+           public string TooltipMessage
+           {
+               get
+               {
+                   return String.Format("{0}\r\n{1}",VersionMessage,StatusMessage);
+               }
+           }
+
+        public string TooltipMiniStatus
+        {
+            get
+            {
+                return String.Format("{0}\r\n{1}", "Status Window", "Enable / Disable the status window");
+            }
+        }
+
+        public string ToggleStatusWindowMessage
+        {
+            get
+            {
+                return String.Format("{0}" + Environment.NewLine + "{1} Toggle Mini Status");
+            }
+        }
+
+           private readonly ObservableConcurrentCollection<AccountInfo> _accounts = new ObservableConcurrentCollection<AccountInfo>();
                public ObservableConcurrentCollection<AccountInfo> Accounts
                {
                        get { return _accounts; }
@@ -351,6 +490,11 @@ namespace Pithos.Client.WPF {
 
                #region Commands
 
+        public void CancelCurrentOperation()
+        {
+            _networkAgent.CancelCurrentOperation();
+        }
+
         public void ShowPreferences()
         {
             ShowPreferences(null);
@@ -359,6 +503,7 @@ namespace Pithos.Client.WPF {
                public void ShowPreferences(string currentTab)
                {
                        //Settings.Reload();
+            
                    var preferences = new PreferencesViewModel(_windowManager, _events, this, Settings,currentTab);
                    _windowManager.ShowDialog(preferences);
                        
@@ -366,7 +511,8 @@ namespace Pithos.Client.WPF {
 
                public void AboutPithos()
                {
-                       var about = new AboutViewModel();
+                       var about = IoC.Get<AboutViewModel>();
+                   about.LatestVersion=_sparkle.LatestVersion;
                        _windowManager.ShowWindow(about);
                }
 
@@ -392,23 +538,60 @@ namespace Pithos.Client.WPF {
                }
 
                
-/*
+
                public void GoToSite()
                {            
-                       var site = Properties.Settings.Default.PithosSite;
+                       var site = Properties.Settings.Default.ProductionServer;
                        Process.Start(site);            
                }
-*/
+
 
                public void GoToSite(AccountInfo account)
                {
-                       /*var site = String.Format("{0}/ui/?token={1}&user={2}",
-                               account.SiteUri,account.Token,
-                               account.UserName);*/
-                       Process.Start(account.SiteUri);
+                   var uri = account.SiteUri.Replace("http://","https://");            
+                   Process.Start(uri);
                }
 
-               public void ShowFileProperties()
+           private bool _statusVisible;
+
+           public string MiniStatusCaption
+           {
+               get
+               {
+                   return  _statusVisible ? "Hide Status Window" : "Show Status Window";
+               }
+           }
+
+           public bool HasConflicts
+           {
+            get { return true; }
+           }
+        public void ShowConflicts()
+        {
+            _windowManager.ShowWindow(IoC.Get<ConflictsViewModel>());            
+        }
+
+           /// <summary>
+        /// Open an explorer window to the target path's directory
+        /// and select the file
+        /// </summary>
+        /// <param name="entry"></param>
+        public void GoToFile(FileEntry entry)
+        {
+            var fullPath = entry.FullPath;
+            if (!File.Exists(fullPath) && !Directory.Exists(fullPath))
+                return;
+            Process.Start("explorer.exe","/select, " + fullPath);
+        }
+
+        public void OpenLogPath()
+        {
+            var pithosDataPath = PithosSettings.PithosDataPath;
+
+            Process.Start(pithosDataPath);
+        }
+        
+        public void ShowFileProperties()
                {
                        var account = Settings.Accounts.First(acc => acc.IsActive);            
                        var dir = new DirectoryInfo(account.RootPath + @"\pithos");
@@ -481,9 +664,8 @@ namespace Pithos.Client.WPF {
                {
                        if (currentInfo==null)
                                throw new ArgumentNullException("currentInfo");
-                       Contract.EndContractBlock();
-
-                       var monitor = Monitors[currentInfo.Account];
+                       Contract.EndContractBlock();                
+            var monitor = Monitors[currentInfo.AccountKey];
                        var newInfo=monitor.CloudClient.GetObjectInfo(currentInfo.Account, currentInfo.Container, currentInfo.Name);
                        return newInfo;
                }
@@ -494,68 +676,127 @@ namespace Pithos.Client.WPF {
                                throw new ArgumentNullException("container");
                        Contract.EndContractBlock();
 
-                       var monitor = Monitors[container.Account];
+                       var monitor = Monitors[container.AccountKey];
                        var newInfo = monitor.CloudClient.GetContainerInfo(container.Account, container.Name);
                        return newInfo;
                }
 
-
-               public void ToggleSynching()
+           private bool _isPaused;
+           public bool IsPaused
+           {
+               get { return _isPaused; }
+               set
+               {
+                   _isPaused = value;
+                PauseSyncCaption = IsPaused ? "Resume syncing" : "Pause syncing";
+                var iconKey = IsPaused ? "TraySyncPaused" : "TrayInSynch";
+                StatusIcon = String.Format(@"../Images/{0}.ico", iconKey);
+
+                NotifyOfPropertyChange(() => IsPaused);
+               }
+           }
+
+           public void ToggleSynching()
                {
-                       bool isPaused=false;
-                       foreach (var pair in Monitors)
+                       IsPaused=!IsPaused;
+                       foreach (var monitor in Monitors.Values)
                        {
-                               var monitor = pair.Value;
-                               monitor.Pause = !monitor.Pause;
-                               isPaused = monitor.Pause;
+                           monitor.Pause = IsPaused ;
                        }
+            _pollAgent.Pause = IsPaused;
+            _networkAgent.Pause = IsPaused;
+
 
-                       PauseSyncCaption = isPaused ? "Resume syncing" : "Pause syncing";
-                       var iconKey = isPaused? "TraySyncPaused" : "TrayInSynch";
-                       StatusIcon = String.Format(@"../Images/{0}.ico", iconKey);
                }
 
-               public void ExitPithos()
-               {
-                       foreach (var pair in Monitors)
-                       {
-                               var monitor = pair.Value;
-                               monitor.Stop();
-                       }
+        public void ExitPithos()
+        {
+            try
+            {
 
-                       ((Window)GetView()).Close();
-               }
-               #endregion
+                foreach (var monitor in Monitors.Select(pair => pair.Value))
+                {
+                    monitor.Stop();
+                }
+
+                var view = GetView() as Window;
+                if (view != null)
+                    view.Close();
+            }
+            catch (Exception exc)
+            {
+                Log.Info("Exception while exiting", exc);                
+            }
+            finally
+            {
+                Application.Current.Shutdown();
+            }
+        }
+
+           #endregion
 
 
                private readonly Dictionary<PithosStatus, StatusInfo> _iconNames = new List<StatusInfo>
                        {
                                new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"),
-                               new StatusInfo(PithosStatus.Syncing, "Syncing Files", "TraySynching"),
+                               new StatusInfo(PithosStatus.PollSyncing, "Polling Files", "TraySynching"),
+                new StatusInfo(PithosStatus.LocalSyncing, "Syncing Files", "TraySynching"),
                                new StatusInfo(PithosStatus.SyncPaused, "Sync Paused", "TraySyncPaused")
                        }.ToDictionary(s => s.Status);
 
                readonly IWindowManager _windowManager;
                
+        //private int _syncCount=0;
 
-               ///<summary>
+
+        private PithosStatus _pithosStatus = PithosStatus.Disconnected;
+
+        public void SetPithosStatus(PithosStatus status)
+        {
+            if (_pithosStatus == PithosStatus.LocalSyncing && status == PithosStatus.PollComplete)
+                return;
+            if (_pithosStatus == PithosStatus.PollSyncing && status == PithosStatus.LocalComplete)
+                return;
+            if (status == PithosStatus.LocalComplete || status == PithosStatus.PollComplete)
+                _pithosStatus = PithosStatus.InSynch;
+            else
+                _pithosStatus = status;
+            UpdateStatus();
+        }
+
+        public void SetPithosStatus(PithosStatus status,string message)
+        {
+            StatusMessage = message;
+            SetPithosStatus(status);
+        }
+
+         /*  public Notifier GetNotifier(Notification startNotification, Notification endNotification)
+           {
+               return new Notifier(this, startNotification, endNotification);
+           }*/
+
+           public Notifier GetNotifier(string startMessage, string endMessage, params object[] args)
+           {
+               return new Notifier(this, 
+                new StatusNotification(String.Format(startMessage,args)), 
+                new StatusNotification(String.Format(endMessage,args)));
+           }
+
+
+           ///<summary>
                /// Updates the visual status indicators of the application depending on status changes, e.g. icon, stat                
                ///</summary>
                public void UpdateStatus()
                {
-                       var pithosStatus = _statusChecker.GetPithosStatus();
 
-                       if (_iconNames.ContainsKey(pithosStatus))
+                       if (_iconNames.ContainsKey(_pithosStatus))
                        {
-                               var info = _iconNames[pithosStatus];
+                               var info = _iconNames[_pithosStatus];
                                StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName);
-
-
-
-                               StatusMessage = String.Format("Pithos {0}\r\n{1}", _fileVersion.Value.FileVersion,info.StatusText);
                        }
-                       
-                       //_events.Publish(new Notification { Title = "Start", Message = "Start Monitoring", Level = TraceLevel.Info});
+
+            if (_pithosStatus == PithosStatus.InSynch)
+                StatusMessage = "All files up to date";
                }
 
 
@@ -588,7 +829,7 @@ namespace Pithos.Client.WPF {
                                                                var message = String.Format("API Key Expired for {0}. Starting Renewal",
                                                                                                                        monitor.UserName);
                                                                Log.Error(message, exc);
-                                                       var account = Settings.Accounts.Find(acc => acc.AccountName == monitor.UserName);                                
+                                var account = Settings.Accounts.Find(acc => acc.AccountKey == new Uri(new Uri(monitor.AuthenticationUrl), monitor.UserName));                                
                                                        account.IsExpired = true;
                                 Notify(new ExpirationNotification(account));
                                                                //TryAuthorize(monitor.UserName, retries).Wait();
@@ -617,7 +858,7 @@ namespace Pithos.Client.WPF {
                        Execute.OnUIThread(() =>
                                                                   {                                       
                                                                           var proxyAccount = IoC.Get<ProxyAccountViewModel>();
-                                                                               proxyAccount.Settings = this.Settings;
+                                                                               proxyAccount.Settings = Settings;
                                                                           if (true != _windowManager.ShowDialog(proxyAccount)) 
                                                                                   return;
                                                                           StartMonitor(monitor, retries);
@@ -639,20 +880,7 @@ namespace Pithos.Client.WPF {
                }
 
 
-
-               private static bool IsUnauthorized(WebException exc)
-               {
-                       if (exc==null)
-                               throw new ArgumentNullException("exc");
-                       Contract.EndContractBlock();
-
-                       var response = exc.Response as HttpWebResponse;
-                       if (response == null)
-                               return false;
-                       return (response.StatusCode == HttpStatusCode.Unauthorized);
-               }
-
-               private void TryLater(PithosMonitor monitor, Exception exc,int retries)
+           private void TryLater(PithosMonitor monitor, Exception exc,int retries)
                {
                        var message = String.Format("An exception occured. Can't start monitoring\nWill retry in 10 seconds");
                        Task.Factory.StartNewDelayed(10000, () => StartMonitor(monitor,retries+1));
@@ -666,16 +894,19 @@ namespace Pithos.Client.WPF {
                {
                        StatusMessage = status;
                        
-                       _events.Publish(new Notification { Title = "Pithos", Message = status, Level = level });
+                       _events.Publish(new Notification { Title = "Pithos+", Message = status, Level = level });
                }
 
                public void NotifyChangedFile(string filePath)
                {
-                       var entry = new FileEntry {FullPath=filePath};
+            if (RecentFiles.Any(e => e.FullPath == filePath))
+                return;
+            
                        IProducerConsumerCollection<FileEntry> files=RecentFiles;
                        FileEntry popped;
                        while (files.Count > 5)
                                files.TryTake(out popped);
+            var entry = new FileEntry { FullPath = filePath };
                        files.TryAdd(entry);
                }
 
@@ -688,7 +919,7 @@ namespace Pithos.Client.WPF {
                                account.SiteUri, Uri.EscapeDataString(account.Token),
                                Uri.EscapeDataString(account.UserName));
 
-                       if (Accounts.All(item => item.UserName != account.UserName))
+                       if (!Accounts.Any(item => item.UserName == account.UserName && item.SiteUri == account.SiteUri))
                                Accounts.TryAdd(account);
 
                }
@@ -697,12 +928,14 @@ namespace Pithos.Client.WPF {
                {
                        if (conflictFiles == null)
                                return;
-                       if (!conflictFiles.Any())
+                   //Convert to list to avoid multiple iterations
+            var files = conflictFiles.ToList();
+                       if (files.Count==0)
                                return;
 
                        UpdateStatus();
                        //TODO: Create a more specific message. For now, just show a warning
-                       NotifyForFiles(conflictFiles,message,TraceLevel.Warning);
+                       NotifyForFiles(files,message,TraceLevel.Warning);
 
                }
 
@@ -715,7 +948,7 @@ namespace Pithos.Client.WPF {
 
                        StatusMessage = message;
 
-                       _events.Publish(new Notification { Title = "Pithos", Message = message, Level = level});
+                       _events.Publish(new Notification { Title = "Pithos+", Message = message, Level = level});
                }
 
                public void Notify(Notification notification)
@@ -724,18 +957,25 @@ namespace Pithos.Client.WPF {
                }
 
 
-               public void RemoveMonitor(string accountName)
+               public void RemoveMonitor(string serverUrl,string accountName)
                {
                        if (String.IsNullOrWhiteSpace(accountName))
                                return;
 
-                       var accountInfo=_accounts.FirstOrDefault(account => account.UserName == accountName);
-                       _accounts.TryRemove(accountInfo);
+                       var accountInfo=_accounts.FirstOrDefault(account => account.UserName == accountName && account.StorageUri.ToString().StartsWith(serverUrl));
+            if (accountInfo != null)
+            {
+                _accounts.TryRemove(accountInfo);
+                _pollAgent.RemoveAccount(accountInfo);
+            }
 
-                       PithosMonitor monitor;
-                       if (Monitors.TryRemove(accountName, out monitor))
+            var accountKey = new Uri(new Uri(serverUrl),accountName);
+                   PithosMonitor monitor;
+                       if (Monitors.TryRemove(accountKey, out monitor))
                        {
                                monitor.Stop();
+                //TODO: Also remove any pending actions for this account
+                //from the network queue                
                        }
                }
 
@@ -772,20 +1012,30 @@ namespace Pithos.Client.WPF {
                
                public void Handle(SelectiveSynchChanges message)
                {
-                       var accountName = message.Account.AccountName;
-                       PithosMonitor monitor;
-                       if (_monitors.TryGetValue(accountName, out monitor))
-                       {
-                               monitor.SetSelectivePaths(message.Uris,message.Added,message.Removed);
+                   TaskEx.Run(() =>
+                   {
+                       PithosMonitor monitor;
+                       if (Monitors.TryGetValue(message.Account.AccountKey, out monitor))
+                       {
+                    Selectives.SetIsSelectiveEnabled(message.Account.AccountKey, message.Enabled);
+                           monitor.SetSelectivePaths(message.Uris, message.Added, message.Removed);
+                       }
+
+                       var account = Accounts.FirstOrDefault(acc => acc.AccountKey == message.Account.AccountKey);
+                       if (account != null)
+                       {
+                           this._pollAgent.SetSelectivePaths(account, message.Added, message.Removed);
+                       }
+                   });
 
-                       }
-                       
                }
 
 
-               private bool _pollStarted = false;
+               private bool _pollStarted;
+           private Sparkle _sparkle;
+           private bool _manualUpgradeCheck;
 
-               //SMELL: Doing so much work for notifications in the shell is wrong
+           //SMELL: Doing so much work for notifications in the shell is wrong
                //The notifications should be moved to their own view/viewmodel pair
                //and different templates should be used for different message types
                //This will also allow the addition of extra functionality, eg. actions
@@ -807,45 +1057,85 @@ namespace Pithos.Client.WPF {
                                if (!_pollStarted) 
                                        return;
                                _pollStarted= false;
-                               notification.Title = "Pithos";
+                               notification.Title = "Pithos+";
                                notification.Message = "Start Synchronisation";
                        }
 
+                   var deleteNotification = notification as CloudDeleteNotification;
+            if (deleteNotification != null)
+            {
+                StatusMessage = String.Format("Deleted {0}", deleteNotification.Data.Name);
+                return;
+            }
+
+                   var progress = notification as ProgressNotification;
+                   
+                   
+            if (progress != null)
+                   {
+                       StatusMessage = String.Format("{0} {1:p2} of {2} - {3}",                                                      
+                                              progress.Action,
+                                                     progress.Block/(double)progress.TotalBlocks,
+                                                     progress.FileSize.ToByteSize(),
+                                                     progress.FileName);
+                       return;
+                   }
+
+                   var info = notification as StatusNotification;
+            if (info != null)
+            {
+                StatusMessage = info.Title;
+                return;
+            }
                        if (String.IsNullOrWhiteSpace(notification.Message) && String.IsNullOrWhiteSpace(notification.Title))
                                return;
 
-                       BalloonIcon icon;
-                       switch (notification.Level)
-                       {
-                               case TraceLevel.Error:
-                                       icon = BalloonIcon.Error;
-                                       break;
-                               case TraceLevel.Info:
-                               case TraceLevel.Verbose:
-                                       icon = BalloonIcon.Info;
-                                       break;
-                               case TraceLevel.Warning:
-                                       icon = BalloonIcon.Warning;
-                                       break;
-                               default:
-                                       icon = BalloonIcon.None;
-                                       break;
-                       }
-                       
-                       if (Settings.ShowDesktopNotifications)
-                       {
-                               var tv = (ShellView) GetView();
-                           System.Action clickAction = null;
-                if (notification is ExpirationNotification)
-                {
-                    clickAction = ()=>ShowPreferences("AccountTab");
-                }
-                               var balloon=new PithosBalloon{Title=notification.Title,Message=notification.Message,Icon=icon,ClickAction=clickAction};
-                               tv.TaskbarView.ShowCustomBalloon(balloon,PopupAnimation.Fade,4000);
-//                             tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
-                       }
+            if (notification.Level <= TraceLevel.Warning)
+                           ShowBalloonFor(notification);
                }
-               #endregion
+
+           private void ShowBalloonFor(Notification notification)
+           {
+            Contract.Requires(notification!=null);
+            
+            if (!Settings.ShowDesktopNotifications) 
+                return;
+            
+            BalloonIcon icon;
+               switch (notification.Level)
+               {
+                case TraceLevel.Verbose:
+                       return;
+                   case TraceLevel.Info:                   
+                       icon = BalloonIcon.Info;
+                       break;
+                case TraceLevel.Error:
+                    icon = BalloonIcon.Error;
+                    break;
+                case TraceLevel.Warning:
+                       icon = BalloonIcon.Warning;
+                       break;
+                   default:
+                       return;
+               }
+
+               var tv = (ShellView) GetView();
+               System.Action clickAction = null;
+               if (notification is ExpirationNotification)
+               {
+                   clickAction = () => ShowPreferences("AccountTab");
+               }
+               var balloon = new PithosBalloon
+                                 {
+                                     Title = notification.Title,
+                                     Message = notification.Message,
+                                     Icon = icon,
+                                     ClickAction = clickAction
+                                 };
+               tv.TaskbarView.ShowCustomBalloon(balloon, PopupAnimation.Fade, 4000);
+           }
+
+           #endregion
 
                public void Handle(ShowFilePropertiesEvent message)
                {
@@ -890,5 +1180,11 @@ namespace Pithos.Client.WPF {
                        var status=statusKeeper.GetFileStatus(localFileName);
                        return status;
                }
+
+           public void RemoveAccountFromDatabase(AccountSettings account)
+           {
+            var statusKeeper = IoC.Get<IStatusKeeper>();
+            statusKeeper.ClearFolderStatus(account.RootPath);          
+           }
        }
 }