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 //Lazily initialized File Version info. This is done once and lazily to avoid blocking the UI
126 private readonly Lazy<FileVersionInfo> _fileVersion;
128 private readonly PollAgent _pollAgent;
131 /// The Shell depends on MEF to provide implementations for windowManager, events, the status checker service and the settings
134 /// The PithosSettings class encapsulates the app's settings to abstract their storage mechanism (App settings, a database or registry)
136 [ImportingConstructor]
137 public ShellViewModel(IWindowManager windowManager, IEventAggregator events, IStatusChecker statusChecker, PithosSettings settings,PollAgent pollAgent)
142 _windowManager = windowManager;
143 //CHECK: Caliburn doesn't need explicit command construction
144 //OpenPithosFolderCommand = new PithosCommand(OpenPithosFolder);
145 _statusChecker = statusChecker;
148 _events.Subscribe(this);
150 _pollAgent = pollAgent;
153 Proxy.SetFromSettings(settings);
155 StatusMessage = "In Synch";
157 _fileVersion= new Lazy<FileVersionInfo>(() =>
159 Assembly assembly = Assembly.GetExecutingAssembly();
160 var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
163 _accounts.CollectionChanged += (sender, e) =>
165 NotifyOfPropertyChange(() => OpenFolderCaption);
166 NotifyOfPropertyChange(() => HasAccounts);
170 catch (Exception exc)
172 Log.Error("Error while starting the ShellViewModel",exc);
179 protected override void OnActivate()
183 _sparkle = new Sparkle(Settings.UpdateUrl);
184 _sparkle.updateDetected += OnUpgradeDetected;
185 _sparkle.ShowDiagnosticWindow = Settings.UpdateDiagnostics;
187 //Must delay opening the upgrade window
188 //to avoid Windows Messages sent by the TaskbarIcon
189 TaskEx.Delay(5000).ContinueWith(_=>
190 Execute.OnUIThread(()=> _sparkle.StartLoop(true,Settings.UpdateForceCheck,Settings.UpdateCheckInterval)));
196 private void OnUpgradeDetected(object sender, UpdateDetectedEventArgs e)
198 Log.InfoFormat("Update detected {0}",e.LatestVersion);
201 public void CheckForUpgrade()
205 _sparkle=new Sparkle(Settings.UpdateUrl);
206 _sparkle.StartLoop(true,true,Settings.UpdateCheckInterval);
209 private async void StartMonitoring()
213 var accounts = Settings.Accounts.Select(MonitorAccount);
214 await TaskEx.WhenAll(accounts);
215 _statusService = StatusService.Start();
218 foreach (var account in Settings.Accounts)
220 await MonitorAccount(account);
225 catch (AggregateException exc)
229 Log.Error("Error while starting monitoring", e);
236 protected override void OnDeactivate(bool close)
238 base.OnDeactivate(close);
241 StatusService.Stop(_statusService);
242 _statusService = null;
246 public Task MonitorAccount(AccountSettings account)
248 return Task.Factory.StartNew(() =>
250 PithosMonitor monitor;
251 var accountName = account.AccountName;
253 if (_monitors.TryGetValue(accountName, out monitor))
255 //If the account is active
256 if (account.IsActive)
258 //The Api Key may have changed throuth the Preferences dialog
259 monitor.ApiKey = account.ApiKey;
260 Debug.Assert(monitor.StatusNotification == this,"An existing monitor should already have a StatusNotification service object");
261 monitor.RootPath = account.RootPath;
262 //Start the monitor. It's OK to start an already started monitor,
263 //it will just ignore the call
264 StartMonitor(monitor).Wait();
268 //If the account is inactive
269 //Stop and remove the monitor
270 RemoveMonitor(accountName);
276 //Create a new monitor/ Can't use MEF here, it would return a single instance for all monitors
277 monitor = new PithosMonitor
279 UserName = accountName,
280 ApiKey = account.ApiKey,
281 StatusNotification = this,
282 RootPath = account.RootPath
284 //PithosMonitor uses MEF so we need to resolve it
285 IoC.BuildUp(monitor);
287 monitor.AuthenticationUrl = account.ServerUrl;
289 _monitors[accountName] = monitor;
291 if (account.IsActive)
293 //Don't start a monitor if it doesn't have an account and ApiKey
294 if (String.IsNullOrWhiteSpace(monitor.UserName) ||
295 String.IsNullOrWhiteSpace(monitor.ApiKey))
297 StartMonitor(monitor);
303 protected override void OnViewLoaded(object view)
306 var window = (Window)view;
307 TaskEx.Delay(1000).ContinueWith(t => Execute.OnUIThread(window.Hide));
308 base.OnViewLoaded(view);
312 #region Status Properties
314 private string _statusMessage;
315 public string StatusMessage
317 get { return _statusMessage; }
320 _statusMessage = value;
321 NotifyOfPropertyChange(() => StatusMessage);
325 private readonly ObservableConcurrentCollection<AccountInfo> _accounts = new ObservableConcurrentCollection<AccountInfo>();
326 public ObservableConcurrentCollection<AccountInfo> Accounts
328 get { return _accounts; }
331 public bool HasAccounts
333 get { return _accounts.Count > 0; }
337 public string OpenFolderCaption
341 return (_accounts.Count == 0)
342 ? "No Accounts Defined"
343 : "Open Pithos Folder";
347 private string _pauseSyncCaption="Pause Synching";
348 public string PauseSyncCaption
350 get { return _pauseSyncCaption; }
353 _pauseSyncCaption = value;
354 NotifyOfPropertyChange(() => PauseSyncCaption);
358 private readonly ObservableConcurrentCollection<FileEntry> _recentFiles = new ObservableConcurrentCollection<FileEntry>();
359 public ObservableConcurrentCollection<FileEntry> RecentFiles
361 get { return _recentFiles; }
365 private string _statusIcon="../Images/Pithos.ico";
366 public string StatusIcon
368 get { return _statusIcon; }
371 //TODO: Ensure all status icons use the Pithos logo
373 NotifyOfPropertyChange(() => StatusIcon);
381 public void ShowPreferences()
383 ShowPreferences(null);
386 public void ShowPreferences(string currentTab)
389 var preferences = new PreferencesViewModel(_windowManager, _events, this, Settings,currentTab);
390 _windowManager.ShowDialog(preferences);
394 public void AboutPithos()
396 var about = new AboutViewModel();
397 _windowManager.ShowWindow(about);
400 public void SendFeedback()
402 var feedBack = IoC.Get<FeedbackViewModel>();
403 _windowManager.ShowWindow(feedBack);
406 //public PithosCommand OpenPithosFolderCommand { get; private set; }
408 public void OpenPithosFolder()
410 var account = Settings.Accounts.FirstOrDefault(acc => acc.IsActive);
413 Process.Start(account.RootPath);
416 public void OpenPithosFolder(AccountInfo account)
418 Process.Start(account.AccountPath);
423 public void GoToSite()
425 var site = Properties.Settings.Default.PithosSite;
430 public void GoToSite(AccountInfo account)
432 var uri = account.SiteUri.Replace("http://","https://");
437 /// Open an explorer window to the target path's directory
438 /// and select the file
440 /// <param name="entry"></param>
441 public void GoToFile(FileEntry entry)
443 var fullPath = entry.FullPath;
444 if (!File.Exists(fullPath) && !Directory.Exists(fullPath))
446 Process.Start("explorer.exe","/select, " + fullPath);
449 public void OpenLogPath()
451 var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
452 var pithosDataPath = Path.Combine(appDataPath, "GRNET");
454 Process.Start(pithosDataPath);
457 public void ShowFileProperties()
459 var account = Settings.Accounts.First(acc => acc.IsActive);
460 var dir = new DirectoryInfo(account.RootPath + @"\pithos");
461 var files=dir.GetFiles();
463 var idx=r.Next(0, files.Length);
464 ShowFileProperties(files[idx].FullName);
467 public void ShowFileProperties(string filePath)
469 if (String.IsNullOrWhiteSpace(filePath))
470 throw new ArgumentNullException("filePath");
471 if (!File.Exists(filePath) && !Directory.Exists(filePath))
472 throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
473 Contract.EndContractBlock();
475 var pair=(from monitor in Monitors
476 where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
477 select monitor).FirstOrDefault();
478 var accountMonitor = pair.Value;
480 if (accountMonitor == null)
483 var infoTask=Task.Factory.StartNew(()=>accountMonitor.GetObjectInfo(filePath));
487 var fileProperties = new FilePropertiesViewModel(this, infoTask,filePath);
488 _windowManager.ShowWindow(fileProperties);
491 public void ShowContainerProperties()
493 var account = Settings.Accounts.First(acc => acc.IsActive);
494 var dir = new DirectoryInfo(account.RootPath);
495 var fullName = (from folder in dir.EnumerateDirectories()
496 where (folder.Attributes & FileAttributes.Hidden) == 0
497 select folder.FullName).First();
498 ShowContainerProperties(fullName);
501 public void ShowContainerProperties(string filePath)
503 if (String.IsNullOrWhiteSpace(filePath))
504 throw new ArgumentNullException("filePath");
505 if (!Directory.Exists(filePath))
506 throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
507 Contract.EndContractBlock();
509 var pair=(from monitor in Monitors
510 where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
511 select monitor).FirstOrDefault();
512 var accountMonitor = pair.Value;
513 var info = accountMonitor.GetContainerInfo(filePath);
517 var containerProperties = new ContainerPropertiesViewModel(this, info,filePath);
518 _windowManager.ShowWindow(containerProperties);
521 public void SynchNow()
523 _pollAgent.SynchNow();
526 public ObjectInfo RefreshObjectInfo(ObjectInfo currentInfo)
528 if (currentInfo==null)
529 throw new ArgumentNullException("currentInfo");
530 Contract.EndContractBlock();
532 var monitor = Monitors[currentInfo.Account];
533 var newInfo=monitor.CloudClient.GetObjectInfo(currentInfo.Account, currentInfo.Container, currentInfo.Name);
537 public ContainerInfo RefreshContainerInfo(ContainerInfo container)
539 if (container == null)
540 throw new ArgumentNullException("container");
541 Contract.EndContractBlock();
543 var monitor = Monitors[container.Account];
544 var newInfo = monitor.CloudClient.GetContainerInfo(container.Account, container.Name);
549 public void ToggleSynching()
552 foreach (var pair in Monitors)
554 var monitor = pair.Value;
555 monitor.Pause = !monitor.Pause;
556 isPaused = monitor.Pause;
560 PauseSyncCaption = isPaused ? "Resume syncing" : "Pause syncing";
561 var iconKey = isPaused? "TraySyncPaused" : "TrayInSynch";
562 StatusIcon = String.Format(@"../Images/{0}.ico", iconKey);
565 public void ExitPithos()
567 foreach (var pair in Monitors)
569 var monitor = pair.Value;
573 ((Window)GetView()).Close();
578 private readonly Dictionary<PithosStatus, StatusInfo> _iconNames = new List<StatusInfo>
580 new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"),
581 new StatusInfo(PithosStatus.PollSyncing, "Polling Files", "TraySynching"),
582 new StatusInfo(PithosStatus.LocalSyncing, "Syncing Files", "TraySynching"),
583 new StatusInfo(PithosStatus.SyncPaused, "Sync Paused", "TraySyncPaused")
584 }.ToDictionary(s => s.Status);
586 readonly IWindowManager _windowManager;
588 //private int _syncCount=0;
591 private PithosStatus _pithosStatus = PithosStatus.Disconnected;
593 public void SetPithosStatus(PithosStatus status)
595 if (_pithosStatus == PithosStatus.LocalSyncing && status == PithosStatus.PollComplete)
597 if (_pithosStatus == PithosStatus.PollSyncing && status == PithosStatus.LocalComplete)
599 if (status == PithosStatus.LocalComplete || status == PithosStatus.PollComplete)
600 _pithosStatus = PithosStatus.InSynch;
602 _pithosStatus = status;
606 public void SetPithosStatus(PithosStatus status,string message)
608 StatusMessage = message;
609 SetPithosStatus(status);
615 /// Updates the visual status indicators of the application depending on status changes, e.g. icon, stat
617 public void UpdateStatus()
620 if (_iconNames.ContainsKey(_pithosStatus))
622 var info = _iconNames[_pithosStatus];
623 StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName);
626 if (_pithosStatus == PithosStatus.InSynch)
627 StatusMessage = "All files up to date";
632 private Task StartMonitor(PithosMonitor monitor,int retries=0)
634 return Task.Factory.StartNew(() =>
636 using (log4net.ThreadContext.Stacks["Monitor"].Push("Start"))
640 Log.InfoFormat("Start Monitoring {0}", monitor.UserName);
644 catch (WebException exc)
646 if (AbandonRetry(monitor, retries))
649 HttpStatusCode statusCode =HttpStatusCode.OK;
650 var response = exc.Response as HttpWebResponse;
652 statusCode = response.StatusCode;
656 case HttpStatusCode.Unauthorized:
657 var message = String.Format("API Key Expired for {0}. Starting Renewal",
659 Log.Error(message, exc);
660 var account = Settings.Accounts.Find(acc => acc.AccountName == monitor.UserName);
661 account.IsExpired = true;
662 Notify(new ExpirationNotification(account));
663 //TryAuthorize(monitor.UserName, retries).Wait();
665 case HttpStatusCode.ProxyAuthenticationRequired:
666 TryAuthenticateProxy(monitor,retries);
669 TryLater(monitor, exc, retries);
673 catch (Exception exc)
675 if (AbandonRetry(monitor, retries))
678 TryLater(monitor,exc,retries);
684 private void TryAuthenticateProxy(PithosMonitor monitor,int retries)
686 Execute.OnUIThread(() =>
688 var proxyAccount = IoC.Get<ProxyAccountViewModel>();
689 proxyAccount.Settings = Settings;
690 if (true != _windowManager.ShowDialog(proxyAccount))
692 StartMonitor(monitor, retries);
693 NotifyOfPropertyChange(() => Accounts);
697 private bool AbandonRetry(PithosMonitor monitor, int retries)
701 var message = String.Format("Monitoring of account {0} has failed too many times. Will not retry",
703 _events.Publish(new Notification
704 {Title = "Account monitoring failed", Message = message, Level = TraceLevel.Error});
711 private void TryLater(PithosMonitor monitor, Exception exc,int retries)
713 var message = String.Format("An exception occured. Can't start monitoring\nWill retry in 10 seconds");
714 Task.Factory.StartNewDelayed(10000, () => StartMonitor(monitor,retries+1));
715 _events.Publish(new Notification
716 {Title = "Error", Message = message, Level = TraceLevel.Error});
717 Log.Error(message, exc);
721 public void NotifyChange(string status, TraceLevel level=TraceLevel.Info)
723 StatusMessage = status;
725 _events.Publish(new Notification { Title = "Pithos", Message = status, Level = level });
728 public void NotifyChangedFile(string filePath)
730 if (RecentFiles.Any(e => e.FullPath == filePath))
733 IProducerConsumerCollection<FileEntry> files=RecentFiles;
735 while (files.Count > 5)
736 files.TryTake(out popped);
737 var entry = new FileEntry { FullPath = filePath };
741 public void NotifyAccount(AccountInfo account)
745 //TODO: What happens to an existing account whose Token has changed?
746 account.SiteUri= String.Format("{0}/ui/?token={1}&user={2}",
747 account.SiteUri, Uri.EscapeDataString(account.Token),
748 Uri.EscapeDataString(account.UserName));
750 if (Accounts.All(item => item.UserName != account.UserName))
751 Accounts.TryAdd(account);
755 public void NotifyConflicts(IEnumerable<FileSystemInfo> conflictFiles, string message)
757 if (conflictFiles == null)
759 //Convert to list to avoid multiple iterations
760 var files = conflictFiles.ToList();
765 //TODO: Create a more specific message. For now, just show a warning
766 NotifyForFiles(files,message,TraceLevel.Warning);
770 public void NotifyForFiles(IEnumerable<FileSystemInfo> files, string message,TraceLevel level=TraceLevel.Info)
777 StatusMessage = message;
779 _events.Publish(new Notification { Title = "Pithos", Message = message, Level = level});
782 public void Notify(Notification notification)
784 _events.Publish(notification);
788 public void RemoveMonitor(string accountName)
790 if (String.IsNullOrWhiteSpace(accountName))
793 var accountInfo=_accounts.FirstOrDefault(account => account.UserName == accountName);
794 if (accountInfo != null)
796 _accounts.TryRemove(accountInfo);
797 _pollAgent.RemoveAccount(accountInfo);
800 PithosMonitor monitor;
801 if (Monitors.TryRemove(accountName, out monitor))
804 //TODO: Also remove any pending actions for this account
805 //from the network queue
809 public void RefreshOverlays()
811 foreach (var pair in Monitors)
813 var monitor = pair.Value;
815 var path = monitor.RootPath;
817 if (String.IsNullOrWhiteSpace(path))
820 if (!Directory.Exists(path) && !File.Exists(path))
823 IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path);
827 NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM,
828 HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT,
829 pathPointer, IntPtr.Zero);
833 Marshal.FreeHGlobal(pathPointer);
838 #region Event Handlers
840 public void Handle(SelectiveSynchChanges message)
842 var accountName = message.Account.AccountName;
843 PithosMonitor monitor;
844 if (_monitors.TryGetValue(accountName, out monitor))
846 monitor.SetSelectivePaths(message.Uris,message.Added,message.Removed);
853 private bool _pollStarted;
854 private Sparkle _sparkle;
856 //SMELL: Doing so much work for notifications in the shell is wrong
857 //The notifications should be moved to their own view/viewmodel pair
858 //and different templates should be used for different message types
859 //This will also allow the addition of extra functionality, eg. actions
861 public void Handle(Notification notification)
865 if (!Settings.ShowDesktopNotifications)
868 if (notification is PollNotification)
873 if (notification is CloudNotification)
878 notification.Title = "Pithos";
879 notification.Message = "Start Synchronisation";
882 var progress = notification as ProgressNotification;
883 if (progress != null)
885 StatusMessage = String.Format("Pithos {0}\r\n{1} {2:p2} of {3} - {4}",
886 _fileVersion.Value.FileVersion,
888 progress.Block/(double)progress.TotalBlocks,
889 progress.FileSize.ToByteSize(),
894 var info = notification as StatusNotification;
897 StatusMessage = String.Format("Pithos {0}\r\n{1}",
898 _fileVersion.Value.FileVersion,
902 if (String.IsNullOrWhiteSpace(notification.Message) && String.IsNullOrWhiteSpace(notification.Title))
905 ShowBalloonFor(notification);
908 private void ShowBalloonFor(Notification notification)
910 Contract.Requires(notification!=null);
912 if (!Settings.ShowDesktopNotifications)
916 switch (notification.Level)
918 case TraceLevel.Info:
919 case TraceLevel.Verbose:
921 case TraceLevel.Error:
922 icon = BalloonIcon.Error;
924 case TraceLevel.Warning:
925 icon = BalloonIcon.Warning;
931 var tv = (ShellView) GetView();
932 System.Action clickAction = null;
933 if (notification is ExpirationNotification)
935 clickAction = () => ShowPreferences("AccountTab");
937 var balloon = new PithosBalloon
939 Title = notification.Title,
940 Message = notification.Message,
942 ClickAction = clickAction
944 tv.TaskbarView.ShowCustomBalloon(balloon, PopupAnimation.Fade, 4000);
949 public void Handle(ShowFilePropertiesEvent message)
952 throw new ArgumentNullException("message");
953 if (String.IsNullOrWhiteSpace(message.FileName) )
954 throw new ArgumentException("message");
955 Contract.EndContractBlock();
957 var fileName = message.FileName;
958 //TODO: Display file properties for non-container folders
959 if (File.Exists(fileName))
960 //Retrieve the full name with exact casing. Pithos names are case sensitive
961 ShowFileProperties(FileInfoExtensions.GetProperFilePathCapitalization(fileName));
962 else if (Directory.Exists(fileName))
963 //Retrieve the full name with exact casing. Pithos names are case sensitive
965 var path = FileInfoExtensions.GetProperDirectoryCapitalization(fileName);
966 if (IsContainer(path))
967 ShowContainerProperties(path);
969 ShowFileProperties(path);
973 private bool IsContainer(string path)
975 var matchingFolders = from account in _accounts
976 from rootFolder in Directory.GetDirectories(account.AccountPath)
977 where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase)
979 return matchingFolders.Any();
982 public FileStatus GetFileStatus(string localFileName)
984 if (String.IsNullOrWhiteSpace(localFileName))
985 throw new ArgumentNullException("localFileName");
986 Contract.EndContractBlock();
988 var statusKeeper = IoC.Get<IStatusKeeper>();
989 var status=statusKeeper.GetFileStatus(localFileName);
993 public void RemoveAccountFromDatabase(AccountSettings account)
995 var statusKeeper = IoC.Get<IStatusKeeper>();
996 statusKeeper.ClearFolderStatus(account.RootPath);