2 /* -----------------------------------------------------------------------
3 * <copyright file="ShellViewModel.cs" company="GRNet">
5 * Copyright 2011-2012 GRNET S.A. All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
11 * 1. Redistributions of source code must retain the above
12 * copyright notice, this list of conditions and the following
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.
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.
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.
39 * -----------------------------------------------------------------------
42 using System.Collections.Concurrent;
43 using System.Diagnostics;
44 using System.Diagnostics.Contracts;
47 using System.Reflection;
48 using System.Runtime.InteropServices;
49 using System.ServiceModel;
50 using System.Threading.Tasks;
52 using System.Windows.Controls.Primitives;
53 using AppLimit.NetSparkle;
55 using Hardcodet.Wpf.TaskbarNotification;
56 using Pithos.Client.WPF.Configuration;
57 using Pithos.Client.WPF.FileProperties;
58 using Pithos.Client.WPF.Preferences;
59 using Pithos.Client.WPF.SelectiveSynch;
60 using Pithos.Client.WPF.Services;
61 using Pithos.Client.WPF.Shell;
63 using Pithos.Core.Agents;
64 using Pithos.Interfaces;
66 using System.Collections.Generic;
69 using StatusService = Pithos.Client.WPF.Services.StatusService;
71 namespace Pithos.Client.WPF {
72 using System.ComponentModel.Composition;
76 /// The "shell" of the Pithos application displays the taskbar icon, menu and notifications.
77 /// The shell also hosts the status service called by shell extensions to retrieve file info
80 /// It is a strange "shell" as its main visible element is an icon instead of a window
81 /// The shell subscribes to the following events:
82 /// * Notification: Raised by components that want to notify the user. Usually displayed in a balloon
83 /// * 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
84 /// * ShowFilePropertiesEvent: Raised when a shell command requests the display of the file/container properties dialog
86 //TODO: CODE SMELL Why does the shell handle the SelectiveSynchChanges?
87 [Export(typeof(IShell))]
88 public class ShellViewModel : Screen, IStatusNotification, IShell,
89 IHandle<Notification>, IHandle<SelectiveSynchChanges>, IHandle<ShowFilePropertiesEvent>
92 //The Status Checker provides the current synch state
93 //TODO: Could we remove the status checker and use events in its place?
94 private readonly IStatusChecker _statusChecker;
95 private readonly IEventAggregator _events;
97 public PithosSettings Settings { get; private set; }
100 private readonly ConcurrentDictionary<string, PithosMonitor> _monitors = new ConcurrentDictionary<string, PithosMonitor>();
102 /// Dictionary of account monitors, keyed by account
105 /// One monitor class is created for each account. The Shell needs access to the monitors to execute start/stop/pause commands,
106 /// retrieve account and boject info
108 // TODO: Does the Shell REALLY need access to the monitors? Could we achieve the same results with a better design?
109 // TODO: The monitors should be internal to Pithos.Core, even though exposing them makes coding of the Object and Container windows easier
110 public ConcurrentDictionary<string, PithosMonitor> Monitors
112 get { return _monitors; }
117 /// The status service is used by Shell extensions to retrieve file status information
119 //TODO: CODE SMELL! This is the shell! While hosting in the shell makes executing start/stop commands easier, it is still a smell
120 private ServiceHost _statusService;
122 //Logging in the Pithos client is provided by log4net
123 private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
125 private readonly PollAgent _pollAgent;
128 private MiniStatusViewModel _miniStatus;
131 public MiniStatusViewModel MiniStatus
133 get { return _miniStatus; }
137 _miniStatus.Shell = this;
138 _miniStatus.Deactivated += (sender, arg) =>
140 _statusVisible = false;
141 NotifyOfPropertyChange(()=>MiniStatusCaption);
147 /// The Shell depends on MEF to provide implementations for windowManager, events, the status checker service and the settings
150 /// The PithosSettings class encapsulates the app's settings to abstract their storage mechanism (App settings, a database or registry)
152 [ImportingConstructor]
153 public ShellViewModel(IWindowManager windowManager, IEventAggregator events, IStatusChecker statusChecker, PithosSettings settings,PollAgent pollAgent)
158 _windowManager = windowManager;
159 //CHECK: Caliburn doesn't need explicit command construction
160 //OpenPithosFolderCommand = new PithosCommand(OpenPithosFolder);
161 _statusChecker = statusChecker;
164 _events.Subscribe(this);
166 _pollAgent = pollAgent;
169 Proxy.SetFromSettings(settings);
171 StatusMessage = Settings.Accounts.Count==0
172 ? "No Accounts added. Please add an account"
175 _accounts.CollectionChanged += (sender, e) =>
177 NotifyOfPropertyChange(() => OpenFolderCaption);
178 NotifyOfPropertyChange(() => HasAccounts);
181 Assembly assembly = Assembly.GetExecutingAssembly();
182 var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
183 VersionMessage = String.Format("Pithos+ {0}", fileVersion.FileVersion);
186 catch (Exception exc)
188 Log.Error("Error while starting the ShellViewModel",exc);
195 protected override void OnActivate()
199 _sparkle = new Sparkle(Settings.UpdateUrl);
200 _sparkle.updateDetected += OnUpgradeDetected;
201 _sparkle.ShowDiagnosticWindow = Settings.UpdateDiagnostics;
203 //Must delay opening the upgrade window
204 //to avoid Windows Messages sent by the TaskbarIcon
205 TaskEx.Delay(5000).ContinueWith(_=>
206 Execute.OnUIThread(()=> _sparkle.StartLoop(true,Settings.UpdateForceCheck,Settings.UpdateCheckInterval)));
212 private void OnUpgradeDetected(object sender, UpdateDetectedEventArgs e)
214 Log.InfoFormat("Update detected {0}",e.LatestVersion);
217 public void CheckForUpgrade()
219 Log.Error("Test Error message");
222 _sparkle=new Sparkle(Settings.UpdateUrl);
223 _sparkle.StartLoop(true,true,Settings.UpdateCheckInterval);
226 private async void StartMonitoring()
230 var accounts = Settings.Accounts.Select(MonitorAccount);
231 await TaskEx.WhenAll(accounts);
232 _statusService = StatusService.Start();
235 foreach (var account in Settings.Accounts)
237 await MonitorAccount(account);
242 catch (AggregateException exc)
246 Log.Error("Error while starting monitoring", e);
253 protected override void OnDeactivate(bool close)
255 base.OnDeactivate(close);
258 StatusService.Stop(_statusService);
259 _statusService = null;
263 public Task MonitorAccount(AccountSettings account)
265 return Task.Factory.StartNew(() =>
267 PithosMonitor monitor;
268 var accountName = account.AccountName;
270 if (_monitors.TryGetValue(accountName, out monitor))
272 //If the account is active
273 if (account.IsActive)
275 //The Api Key may have changed throuth the Preferences dialog
276 monitor.ApiKey = account.ApiKey;
277 Debug.Assert(monitor.StatusNotification == this,"An existing monitor should already have a StatusNotification service object");
278 monitor.RootPath = account.RootPath;
279 //Start the monitor. It's OK to start an already started monitor,
280 //it will just ignore the call
281 StartMonitor(monitor).Wait();
285 //If the account is inactive
286 //Stop and remove the monitor
287 RemoveMonitor(accountName);
293 //Create a new monitor/ Can't use MEF here, it would return a single instance for all monitors
294 monitor = new PithosMonitor
296 UserName = accountName,
297 ApiKey = account.ApiKey,
298 StatusNotification = this,
299 RootPath = account.RootPath
301 //PithosMonitor uses MEF so we need to resolve it
302 IoC.BuildUp(monitor);
304 monitor.AuthenticationUrl = account.ServerUrl;
306 _monitors[accountName] = monitor;
308 if (account.IsActive)
310 //Don't start a monitor if it doesn't have an account and ApiKey
311 if (String.IsNullOrWhiteSpace(monitor.UserName) ||
312 String.IsNullOrWhiteSpace(monitor.ApiKey))
314 StartMonitor(monitor);
320 protected override void OnViewLoaded(object view)
323 var window = (Window)view;
324 TaskEx.Delay(1000).ContinueWith(t => Execute.OnUIThread(window.Hide));
325 base.OnViewLoaded(view);
329 #region Status Properties
331 private string _statusMessage;
332 public string StatusMessage
334 get { return _statusMessage; }
337 _statusMessage = value;
338 NotifyOfPropertyChange(() => StatusMessage);
339 NotifyOfPropertyChange(() => TooltipMessage);
343 public string VersionMessage { get; set; }
345 private string _tooltipMessage;
346 public string TooltipMessage
350 return String.Format("{0}\r\n{1}",VersionMessage,StatusMessage);
354 private readonly ObservableConcurrentCollection<AccountInfo> _accounts = new ObservableConcurrentCollection<AccountInfo>();
355 public ObservableConcurrentCollection<AccountInfo> Accounts
357 get { return _accounts; }
360 public bool HasAccounts
362 get { return _accounts.Count > 0; }
366 public string OpenFolderCaption
370 return (_accounts.Count == 0)
371 ? "No Accounts Defined"
372 : "Open Pithos Folder";
376 private string _pauseSyncCaption="Pause Synching";
377 public string PauseSyncCaption
379 get { return _pauseSyncCaption; }
382 _pauseSyncCaption = value;
383 NotifyOfPropertyChange(() => PauseSyncCaption);
387 private readonly ObservableConcurrentCollection<FileEntry> _recentFiles = new ObservableConcurrentCollection<FileEntry>();
388 public ObservableConcurrentCollection<FileEntry> RecentFiles
390 get { return _recentFiles; }
394 private string _statusIcon="../Images/Pithos.ico";
395 public string StatusIcon
397 get { return _statusIcon; }
400 //TODO: Ensure all status icons use the Pithos logo
402 NotifyOfPropertyChange(() => StatusIcon);
410 public void ShowPreferences()
412 ShowPreferences(null);
415 public void ShowPreferences(string currentTab)
418 var preferences = new PreferencesViewModel(_windowManager, _events, this, Settings,currentTab);
419 _windowManager.ShowDialog(preferences);
423 public void AboutPithos()
425 var about = IoC.Get<AboutViewModel>();
426 about.LatestVersion=_sparkle.LatestVersion;
427 _windowManager.ShowWindow(about);
430 public void SendFeedback()
432 var feedBack = IoC.Get<FeedbackViewModel>();
433 _windowManager.ShowWindow(feedBack);
436 //public PithosCommand OpenPithosFolderCommand { get; private set; }
438 public void OpenPithosFolder()
440 var account = Settings.Accounts.FirstOrDefault(acc => acc.IsActive);
443 Process.Start(account.RootPath);
446 public void OpenPithosFolder(AccountInfo account)
448 Process.Start(account.AccountPath);
453 public void GoToSite()
455 var site = Properties.Settings.Default.PithosSite;
460 public void GoToSite(AccountInfo account)
462 var uri = account.SiteUri.Replace("http://","https://");
466 private bool _statusVisible;
468 public string MiniStatusCaption
472 return _statusVisible ? "Hide Status Window" : "Show Status Window";
476 public void ShowMiniStatus()
479 _windowManager.ShowWindow(MiniStatus);
482 if (MiniStatus.IsActive)
483 MiniStatus.TryClose();
485 _statusVisible=!_statusVisible;
487 NotifyOfPropertyChange(()=>MiniStatusCaption);
491 /// Open an explorer window to the target path's directory
492 /// and select the file
494 /// <param name="entry"></param>
495 public void GoToFile(FileEntry entry)
497 var fullPath = entry.FullPath;
498 if (!File.Exists(fullPath) && !Directory.Exists(fullPath))
500 Process.Start("explorer.exe","/select, " + fullPath);
503 public void OpenLogPath()
505 var pithosDataPath = PithosSettings.PithosDataPath;
507 Process.Start(pithosDataPath);
510 public void ShowFileProperties()
512 var account = Settings.Accounts.First(acc => acc.IsActive);
513 var dir = new DirectoryInfo(account.RootPath + @"\pithos");
514 var files=dir.GetFiles();
516 var idx=r.Next(0, files.Length);
517 ShowFileProperties(files[idx].FullName);
520 public void ShowFileProperties(string filePath)
522 if (String.IsNullOrWhiteSpace(filePath))
523 throw new ArgumentNullException("filePath");
524 if (!File.Exists(filePath) && !Directory.Exists(filePath))
525 throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
526 Contract.EndContractBlock();
528 var pair=(from monitor in Monitors
529 where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
530 select monitor).FirstOrDefault();
531 var accountMonitor = pair.Value;
533 if (accountMonitor == null)
536 var infoTask=Task.Factory.StartNew(()=>accountMonitor.GetObjectInfo(filePath));
540 var fileProperties = new FilePropertiesViewModel(this, infoTask,filePath);
541 _windowManager.ShowWindow(fileProperties);
544 public void ShowContainerProperties()
546 var account = Settings.Accounts.First(acc => acc.IsActive);
547 var dir = new DirectoryInfo(account.RootPath);
548 var fullName = (from folder in dir.EnumerateDirectories()
549 where (folder.Attributes & FileAttributes.Hidden) == 0
550 select folder.FullName).First();
551 ShowContainerProperties(fullName);
554 public void ShowContainerProperties(string filePath)
556 if (String.IsNullOrWhiteSpace(filePath))
557 throw new ArgumentNullException("filePath");
558 if (!Directory.Exists(filePath))
559 throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
560 Contract.EndContractBlock();
562 var pair=(from monitor in Monitors
563 where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
564 select monitor).FirstOrDefault();
565 var accountMonitor = pair.Value;
566 var info = accountMonitor.GetContainerInfo(filePath);
570 var containerProperties = new ContainerPropertiesViewModel(this, info,filePath);
571 _windowManager.ShowWindow(containerProperties);
574 public void SynchNow()
576 _pollAgent.SynchNow();
579 public ObjectInfo RefreshObjectInfo(ObjectInfo currentInfo)
581 if (currentInfo==null)
582 throw new ArgumentNullException("currentInfo");
583 Contract.EndContractBlock();
585 var monitor = Monitors[currentInfo.Account];
586 var newInfo=monitor.CloudClient.GetObjectInfo(currentInfo.Account, currentInfo.Container, currentInfo.Name);
590 public ContainerInfo RefreshContainerInfo(ContainerInfo container)
592 if (container == null)
593 throw new ArgumentNullException("container");
594 Contract.EndContractBlock();
596 var monitor = Monitors[container.Account];
597 var newInfo = monitor.CloudClient.GetContainerInfo(container.Account, container.Name);
602 public void ToggleSynching()
605 foreach (var pair in Monitors)
607 var monitor = pair.Value;
608 monitor.Pause = !monitor.Pause;
609 isPaused = monitor.Pause;
613 PauseSyncCaption = isPaused ? "Resume syncing" : "Pause syncing";
614 var iconKey = isPaused? "TraySyncPaused" : "TrayInSynch";
615 StatusIcon = String.Format(@"../Images/{0}.ico", iconKey);
618 public void ExitPithos()
620 foreach (var pair in Monitors)
622 var monitor = pair.Value;
626 ((Window)GetView()).Close();
631 private readonly Dictionary<PithosStatus, StatusInfo> _iconNames = new List<StatusInfo>
633 new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"),
634 new StatusInfo(PithosStatus.PollSyncing, "Polling Files", "TraySynching"),
635 new StatusInfo(PithosStatus.LocalSyncing, "Syncing Files", "TraySynching"),
636 new StatusInfo(PithosStatus.SyncPaused, "Sync Paused", "TraySyncPaused")
637 }.ToDictionary(s => s.Status);
639 readonly IWindowManager _windowManager;
641 //private int _syncCount=0;
644 private PithosStatus _pithosStatus = PithosStatus.Disconnected;
646 public void SetPithosStatus(PithosStatus status)
648 if (_pithosStatus == PithosStatus.LocalSyncing && status == PithosStatus.PollComplete)
650 if (_pithosStatus == PithosStatus.PollSyncing && status == PithosStatus.LocalComplete)
652 if (status == PithosStatus.LocalComplete || status == PithosStatus.PollComplete)
653 _pithosStatus = PithosStatus.InSynch;
655 _pithosStatus = status;
659 public void SetPithosStatus(PithosStatus status,string message)
661 StatusMessage = message;
662 SetPithosStatus(status);
668 /// Updates the visual status indicators of the application depending on status changes, e.g. icon, stat
670 public void UpdateStatus()
673 if (_iconNames.ContainsKey(_pithosStatus))
675 var info = _iconNames[_pithosStatus];
676 StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName);
679 if (_pithosStatus == PithosStatus.InSynch)
680 StatusMessage = "All files up to date";
685 private Task StartMonitor(PithosMonitor monitor,int retries=0)
687 return Task.Factory.StartNew(() =>
689 using (log4net.ThreadContext.Stacks["Monitor"].Push("Start"))
693 Log.InfoFormat("Start Monitoring {0}", monitor.UserName);
697 catch (WebException exc)
699 if (AbandonRetry(monitor, retries))
702 HttpStatusCode statusCode =HttpStatusCode.OK;
703 var response = exc.Response as HttpWebResponse;
705 statusCode = response.StatusCode;
709 case HttpStatusCode.Unauthorized:
710 var message = String.Format("API Key Expired for {0}. Starting Renewal",
712 Log.Error(message, exc);
713 var account = Settings.Accounts.Find(acc => acc.AccountName == monitor.UserName);
714 account.IsExpired = true;
715 Notify(new ExpirationNotification(account));
716 //TryAuthorize(monitor.UserName, retries).Wait();
718 case HttpStatusCode.ProxyAuthenticationRequired:
719 TryAuthenticateProxy(monitor,retries);
722 TryLater(monitor, exc, retries);
726 catch (Exception exc)
728 if (AbandonRetry(monitor, retries))
731 TryLater(monitor,exc,retries);
737 private void TryAuthenticateProxy(PithosMonitor monitor,int retries)
739 Execute.OnUIThread(() =>
741 var proxyAccount = IoC.Get<ProxyAccountViewModel>();
742 proxyAccount.Settings = Settings;
743 if (true != _windowManager.ShowDialog(proxyAccount))
745 StartMonitor(monitor, retries);
746 NotifyOfPropertyChange(() => Accounts);
750 private bool AbandonRetry(PithosMonitor monitor, int retries)
754 var message = String.Format("Monitoring of account {0} has failed too many times. Will not retry",
756 _events.Publish(new Notification
757 {Title = "Account monitoring failed", Message = message, Level = TraceLevel.Error});
764 private void TryLater(PithosMonitor monitor, Exception exc,int retries)
766 var message = String.Format("An exception occured. Can't start monitoring\nWill retry in 10 seconds");
767 Task.Factory.StartNewDelayed(10000, () => StartMonitor(monitor,retries+1));
768 _events.Publish(new Notification
769 {Title = "Error", Message = message, Level = TraceLevel.Error});
770 Log.Error(message, exc);
774 public void NotifyChange(string status, TraceLevel level=TraceLevel.Info)
776 StatusMessage = status;
778 _events.Publish(new Notification { Title = "Pithos+", Message = status, Level = level });
781 public void NotifyChangedFile(string filePath)
783 if (RecentFiles.Any(e => e.FullPath == filePath))
786 IProducerConsumerCollection<FileEntry> files=RecentFiles;
788 while (files.Count > 5)
789 files.TryTake(out popped);
790 var entry = new FileEntry { FullPath = filePath };
794 public void NotifyAccount(AccountInfo account)
798 //TODO: What happens to an existing account whose Token has changed?
799 account.SiteUri= String.Format("{0}/ui/?token={1}&user={2}",
800 account.SiteUri, Uri.EscapeDataString(account.Token),
801 Uri.EscapeDataString(account.UserName));
803 if (Accounts.All(item => item.UserName != account.UserName))
804 Accounts.TryAdd(account);
808 public void NotifyConflicts(IEnumerable<FileSystemInfo> conflictFiles, string message)
810 if (conflictFiles == null)
812 //Convert to list to avoid multiple iterations
813 var files = conflictFiles.ToList();
818 //TODO: Create a more specific message. For now, just show a warning
819 NotifyForFiles(files,message,TraceLevel.Warning);
823 public void NotifyForFiles(IEnumerable<FileSystemInfo> files, string message,TraceLevel level=TraceLevel.Info)
830 StatusMessage = message;
832 _events.Publish(new Notification { Title = "Pithos+", Message = message, Level = level});
835 public void Notify(Notification notification)
837 _events.Publish(notification);
841 public void RemoveMonitor(string accountName)
843 if (String.IsNullOrWhiteSpace(accountName))
846 var accountInfo=_accounts.FirstOrDefault(account => account.UserName == accountName);
847 if (accountInfo != null)
849 _accounts.TryRemove(accountInfo);
850 _pollAgent.RemoveAccount(accountInfo);
853 PithosMonitor monitor;
854 if (Monitors.TryRemove(accountName, out monitor))
857 //TODO: Also remove any pending actions for this account
858 //from the network queue
862 public void RefreshOverlays()
864 foreach (var pair in Monitors)
866 var monitor = pair.Value;
868 var path = monitor.RootPath;
870 if (String.IsNullOrWhiteSpace(path))
873 if (!Directory.Exists(path) && !File.Exists(path))
876 IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path);
880 NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM,
881 HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT,
882 pathPointer, IntPtr.Zero);
886 Marshal.FreeHGlobal(pathPointer);
891 #region Event Handlers
893 public void Handle(SelectiveSynchChanges message)
895 var accountName = message.Account.AccountName;
896 PithosMonitor monitor;
897 if (_monitors.TryGetValue(accountName, out monitor))
899 monitor.SetSelectivePaths(message.Uris,message.Added,message.Removed);
906 private bool _pollStarted;
907 private Sparkle _sparkle;
909 //SMELL: Doing so much work for notifications in the shell is wrong
910 //The notifications should be moved to their own view/viewmodel pair
911 //and different templates should be used for different message types
912 //This will also allow the addition of extra functionality, eg. actions
914 public void Handle(Notification notification)
918 if (!Settings.ShowDesktopNotifications)
921 if (notification is PollNotification)
926 if (notification is CloudNotification)
931 notification.Title = "Pithos+";
932 notification.Message = "Start Synchronisation";
935 var deleteNotification = notification as CloudDeleteNotification;
936 if (deleteNotification != null)
938 StatusMessage = String.Format("Deleted {0}", deleteNotification.Data.Name);
942 var progress = notification as ProgressNotification;
945 if (progress != null)
947 StatusMessage = String.Format("{0} {1:p2} of {2} - {3}",
949 progress.Block/(double)progress.TotalBlocks,
950 progress.FileSize.ToByteSize(),
955 var info = notification as StatusNotification;
958 StatusMessage = info.Title;
961 if (String.IsNullOrWhiteSpace(notification.Message) && String.IsNullOrWhiteSpace(notification.Title))
964 ShowBalloonFor(notification);
967 private void ShowBalloonFor(Notification notification)
969 Contract.Requires(notification!=null);
971 if (!Settings.ShowDesktopNotifications)
975 switch (notification.Level)
977 case TraceLevel.Info:
978 case TraceLevel.Verbose:
980 case TraceLevel.Error:
981 icon = BalloonIcon.Error;
983 case TraceLevel.Warning:
984 icon = BalloonIcon.Warning;
990 var tv = (ShellView) GetView();
991 System.Action clickAction = null;
992 if (notification is ExpirationNotification)
994 clickAction = () => ShowPreferences("AccountTab");
996 var balloon = new PithosBalloon
998 Title = notification.Title,
999 Message = notification.Message,
1001 ClickAction = clickAction
1003 tv.TaskbarView.ShowCustomBalloon(balloon, PopupAnimation.Fade, 4000);
1008 public void Handle(ShowFilePropertiesEvent message)
1010 if (message == null)
1011 throw new ArgumentNullException("message");
1012 if (String.IsNullOrWhiteSpace(message.FileName) )
1013 throw new ArgumentException("message");
1014 Contract.EndContractBlock();
1016 var fileName = message.FileName;
1017 //TODO: Display file properties for non-container folders
1018 if (File.Exists(fileName))
1019 //Retrieve the full name with exact casing. Pithos names are case sensitive
1020 ShowFileProperties(FileInfoExtensions.GetProperFilePathCapitalization(fileName));
1021 else if (Directory.Exists(fileName))
1022 //Retrieve the full name with exact casing. Pithos names are case sensitive
1024 var path = FileInfoExtensions.GetProperDirectoryCapitalization(fileName);
1025 if (IsContainer(path))
1026 ShowContainerProperties(path);
1028 ShowFileProperties(path);
1032 private bool IsContainer(string path)
1034 var matchingFolders = from account in _accounts
1035 from rootFolder in Directory.GetDirectories(account.AccountPath)
1036 where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase)
1038 return matchingFolders.Any();
1041 public FileStatus GetFileStatus(string localFileName)
1043 if (String.IsNullOrWhiteSpace(localFileName))
1044 throw new ArgumentNullException("localFileName");
1045 Contract.EndContractBlock();
1047 var statusKeeper = IoC.Get<IStatusKeeper>();
1048 var status=statusKeeper.GetFileStatus(localFileName);
1052 public void RemoveAccountFromDatabase(AccountSettings account)
1054 var statusKeeper = IoC.Get<IStatusKeeper>();
1055 statusKeeper.ClearFolderStatus(account.RootPath);