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()
203 Log.Error("Test Error message");
206 _sparkle=new Sparkle(Settings.UpdateUrl);
207 _sparkle.StartLoop(true,true,Settings.UpdateCheckInterval);
210 private async void StartMonitoring()
214 var accounts = Settings.Accounts.Select(MonitorAccount);
215 await TaskEx.WhenAll(accounts);
216 _statusService = StatusService.Start();
219 foreach (var account in Settings.Accounts)
221 await MonitorAccount(account);
226 catch (AggregateException exc)
230 Log.Error("Error while starting monitoring", e);
237 protected override void OnDeactivate(bool close)
239 base.OnDeactivate(close);
242 StatusService.Stop(_statusService);
243 _statusService = null;
247 public Task MonitorAccount(AccountSettings account)
249 return Task.Factory.StartNew(() =>
251 PithosMonitor monitor;
252 var accountName = account.AccountName;
254 if (_monitors.TryGetValue(accountName, out monitor))
256 //If the account is active
257 if (account.IsActive)
259 //The Api Key may have changed throuth the Preferences dialog
260 monitor.ApiKey = account.ApiKey;
261 Debug.Assert(monitor.StatusNotification == this,"An existing monitor should already have a StatusNotification service object");
262 monitor.RootPath = account.RootPath;
263 //Start the monitor. It's OK to start an already started monitor,
264 //it will just ignore the call
265 StartMonitor(monitor).Wait();
269 //If the account is inactive
270 //Stop and remove the monitor
271 RemoveMonitor(accountName);
277 //Create a new monitor/ Can't use MEF here, it would return a single instance for all monitors
278 monitor = new PithosMonitor
280 UserName = accountName,
281 ApiKey = account.ApiKey,
282 StatusNotification = this,
283 RootPath = account.RootPath
285 //PithosMonitor uses MEF so we need to resolve it
286 IoC.BuildUp(monitor);
288 monitor.AuthenticationUrl = account.ServerUrl;
290 _monitors[accountName] = monitor;
292 if (account.IsActive)
294 //Don't start a monitor if it doesn't have an account and ApiKey
295 if (String.IsNullOrWhiteSpace(monitor.UserName) ||
296 String.IsNullOrWhiteSpace(monitor.ApiKey))
298 StartMonitor(monitor);
304 protected override void OnViewLoaded(object view)
307 var window = (Window)view;
308 TaskEx.Delay(1000).ContinueWith(t => Execute.OnUIThread(window.Hide));
309 base.OnViewLoaded(view);
313 #region Status Properties
315 private string _statusMessage;
316 public string StatusMessage
318 get { return _statusMessage; }
321 _statusMessage = value;
322 NotifyOfPropertyChange(() => StatusMessage);
326 private readonly ObservableConcurrentCollection<AccountInfo> _accounts = new ObservableConcurrentCollection<AccountInfo>();
327 public ObservableConcurrentCollection<AccountInfo> Accounts
329 get { return _accounts; }
332 public bool HasAccounts
334 get { return _accounts.Count > 0; }
338 public string OpenFolderCaption
342 return (_accounts.Count == 0)
343 ? "No Accounts Defined"
344 : "Open Pithos Folder";
348 private string _pauseSyncCaption="Pause Synching";
349 public string PauseSyncCaption
351 get { return _pauseSyncCaption; }
354 _pauseSyncCaption = value;
355 NotifyOfPropertyChange(() => PauseSyncCaption);
359 private readonly ObservableConcurrentCollection<FileEntry> _recentFiles = new ObservableConcurrentCollection<FileEntry>();
360 public ObservableConcurrentCollection<FileEntry> RecentFiles
362 get { return _recentFiles; }
366 private string _statusIcon="../Images/Pithos.ico";
367 public string StatusIcon
369 get { return _statusIcon; }
372 //TODO: Ensure all status icons use the Pithos logo
374 NotifyOfPropertyChange(() => StatusIcon);
382 public void ShowPreferences()
384 ShowPreferences(null);
387 public void ShowPreferences(string currentTab)
390 var preferences = new PreferencesViewModel(_windowManager, _events, this, Settings,currentTab);
391 _windowManager.ShowDialog(preferences);
395 public void AboutPithos()
397 var about = new AboutViewModel();
398 _windowManager.ShowWindow(about);
401 public void SendFeedback()
403 var feedBack = IoC.Get<FeedbackViewModel>();
404 _windowManager.ShowWindow(feedBack);
407 //public PithosCommand OpenPithosFolderCommand { get; private set; }
409 public void OpenPithosFolder()
411 var account = Settings.Accounts.FirstOrDefault(acc => acc.IsActive);
414 Process.Start(account.RootPath);
417 public void OpenPithosFolder(AccountInfo account)
419 Process.Start(account.AccountPath);
424 public void GoToSite()
426 var site = Properties.Settings.Default.PithosSite;
431 public void GoToSite(AccountInfo account)
433 var uri = account.SiteUri.Replace("http://","https://");
438 /// Open an explorer window to the target path's directory
439 /// and select the file
441 /// <param name="entry"></param>
442 public void GoToFile(FileEntry entry)
444 var fullPath = entry.FullPath;
445 if (!File.Exists(fullPath) && !Directory.Exists(fullPath))
447 Process.Start("explorer.exe","/select, " + fullPath);
450 public void OpenLogPath()
452 var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
453 var pithosDataPath = Path.Combine(appDataPath, "GRNET");
455 Process.Start(pithosDataPath);
458 public void ShowFileProperties()
460 var account = Settings.Accounts.First(acc => acc.IsActive);
461 var dir = new DirectoryInfo(account.RootPath + @"\pithos");
462 var files=dir.GetFiles();
464 var idx=r.Next(0, files.Length);
465 ShowFileProperties(files[idx].FullName);
468 public void ShowFileProperties(string filePath)
470 if (String.IsNullOrWhiteSpace(filePath))
471 throw new ArgumentNullException("filePath");
472 if (!File.Exists(filePath) && !Directory.Exists(filePath))
473 throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
474 Contract.EndContractBlock();
476 var pair=(from monitor in Monitors
477 where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
478 select monitor).FirstOrDefault();
479 var accountMonitor = pair.Value;
481 if (accountMonitor == null)
484 var infoTask=Task.Factory.StartNew(()=>accountMonitor.GetObjectInfo(filePath));
488 var fileProperties = new FilePropertiesViewModel(this, infoTask,filePath);
489 _windowManager.ShowWindow(fileProperties);
492 public void ShowContainerProperties()
494 var account = Settings.Accounts.First(acc => acc.IsActive);
495 var dir = new DirectoryInfo(account.RootPath);
496 var fullName = (from folder in dir.EnumerateDirectories()
497 where (folder.Attributes & FileAttributes.Hidden) == 0
498 select folder.FullName).First();
499 ShowContainerProperties(fullName);
502 public void ShowContainerProperties(string filePath)
504 if (String.IsNullOrWhiteSpace(filePath))
505 throw new ArgumentNullException("filePath");
506 if (!Directory.Exists(filePath))
507 throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
508 Contract.EndContractBlock();
510 var pair=(from monitor in Monitors
511 where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
512 select monitor).FirstOrDefault();
513 var accountMonitor = pair.Value;
514 var info = accountMonitor.GetContainerInfo(filePath);
518 var containerProperties = new ContainerPropertiesViewModel(this, info,filePath);
519 _windowManager.ShowWindow(containerProperties);
522 public void SynchNow()
524 _pollAgent.SynchNow();
527 public ObjectInfo RefreshObjectInfo(ObjectInfo currentInfo)
529 if (currentInfo==null)
530 throw new ArgumentNullException("currentInfo");
531 Contract.EndContractBlock();
533 var monitor = Monitors[currentInfo.Account];
534 var newInfo=monitor.CloudClient.GetObjectInfo(currentInfo.Account, currentInfo.Container, currentInfo.Name);
538 public ContainerInfo RefreshContainerInfo(ContainerInfo container)
540 if (container == null)
541 throw new ArgumentNullException("container");
542 Contract.EndContractBlock();
544 var monitor = Monitors[container.Account];
545 var newInfo = monitor.CloudClient.GetContainerInfo(container.Account, container.Name);
550 public void ToggleSynching()
553 foreach (var pair in Monitors)
555 var monitor = pair.Value;
556 monitor.Pause = !monitor.Pause;
557 isPaused = monitor.Pause;
561 PauseSyncCaption = isPaused ? "Resume syncing" : "Pause syncing";
562 var iconKey = isPaused? "TraySyncPaused" : "TrayInSynch";
563 StatusIcon = String.Format(@"../Images/{0}.ico", iconKey);
566 public void ExitPithos()
568 foreach (var pair in Monitors)
570 var monitor = pair.Value;
574 ((Window)GetView()).Close();
579 private readonly Dictionary<PithosStatus, StatusInfo> _iconNames = new List<StatusInfo>
581 new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"),
582 new StatusInfo(PithosStatus.PollSyncing, "Polling Files", "TraySynching"),
583 new StatusInfo(PithosStatus.LocalSyncing, "Syncing Files", "TraySynching"),
584 new StatusInfo(PithosStatus.SyncPaused, "Sync Paused", "TraySyncPaused")
585 }.ToDictionary(s => s.Status);
587 readonly IWindowManager _windowManager;
589 //private int _syncCount=0;
592 private PithosStatus _pithosStatus = PithosStatus.Disconnected;
594 public void SetPithosStatus(PithosStatus status)
596 if (_pithosStatus == PithosStatus.LocalSyncing && status == PithosStatus.PollComplete)
598 if (_pithosStatus == PithosStatus.PollSyncing && status == PithosStatus.LocalComplete)
600 if (status == PithosStatus.LocalComplete || status == PithosStatus.PollComplete)
601 _pithosStatus = PithosStatus.InSynch;
603 _pithosStatus = status;
607 public void SetPithosStatus(PithosStatus status,string message)
609 StatusMessage = message;
610 SetPithosStatus(status);
616 /// Updates the visual status indicators of the application depending on status changes, e.g. icon, stat
618 public void UpdateStatus()
621 if (_iconNames.ContainsKey(_pithosStatus))
623 var info = _iconNames[_pithosStatus];
624 StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName);
627 if (_pithosStatus == PithosStatus.InSynch)
628 StatusMessage = "All files up to date";
633 private Task StartMonitor(PithosMonitor monitor,int retries=0)
635 return Task.Factory.StartNew(() =>
637 using (log4net.ThreadContext.Stacks["Monitor"].Push("Start"))
641 Log.InfoFormat("Start Monitoring {0}", monitor.UserName);
645 catch (WebException exc)
647 if (AbandonRetry(monitor, retries))
650 HttpStatusCode statusCode =HttpStatusCode.OK;
651 var response = exc.Response as HttpWebResponse;
653 statusCode = response.StatusCode;
657 case HttpStatusCode.Unauthorized:
658 var message = String.Format("API Key Expired for {0}. Starting Renewal",
660 Log.Error(message, exc);
661 var account = Settings.Accounts.Find(acc => acc.AccountName == monitor.UserName);
662 account.IsExpired = true;
663 Notify(new ExpirationNotification(account));
664 //TryAuthorize(monitor.UserName, retries).Wait();
666 case HttpStatusCode.ProxyAuthenticationRequired:
667 TryAuthenticateProxy(monitor,retries);
670 TryLater(monitor, exc, retries);
674 catch (Exception exc)
676 if (AbandonRetry(monitor, retries))
679 TryLater(monitor,exc,retries);
685 private void TryAuthenticateProxy(PithosMonitor monitor,int retries)
687 Execute.OnUIThread(() =>
689 var proxyAccount = IoC.Get<ProxyAccountViewModel>();
690 proxyAccount.Settings = Settings;
691 if (true != _windowManager.ShowDialog(proxyAccount))
693 StartMonitor(monitor, retries);
694 NotifyOfPropertyChange(() => Accounts);
698 private bool AbandonRetry(PithosMonitor monitor, int retries)
702 var message = String.Format("Monitoring of account {0} has failed too many times. Will not retry",
704 _events.Publish(new Notification
705 {Title = "Account monitoring failed", Message = message, Level = TraceLevel.Error});
712 private void TryLater(PithosMonitor monitor, Exception exc,int retries)
714 var message = String.Format("An exception occured. Can't start monitoring\nWill retry in 10 seconds");
715 Task.Factory.StartNewDelayed(10000, () => StartMonitor(monitor,retries+1));
716 _events.Publish(new Notification
717 {Title = "Error", Message = message, Level = TraceLevel.Error});
718 Log.Error(message, exc);
722 public void NotifyChange(string status, TraceLevel level=TraceLevel.Info)
724 StatusMessage = status;
726 _events.Publish(new Notification { Title = "Pithos", Message = status, Level = level });
729 public void NotifyChangedFile(string filePath)
731 if (RecentFiles.Any(e => e.FullPath == filePath))
734 IProducerConsumerCollection<FileEntry> files=RecentFiles;
736 while (files.Count > 5)
737 files.TryTake(out popped);
738 var entry = new FileEntry { FullPath = filePath };
742 public void NotifyAccount(AccountInfo account)
746 //TODO: What happens to an existing account whose Token has changed?
747 account.SiteUri= String.Format("{0}/ui/?token={1}&user={2}",
748 account.SiteUri, Uri.EscapeDataString(account.Token),
749 Uri.EscapeDataString(account.UserName));
751 if (Accounts.All(item => item.UserName != account.UserName))
752 Accounts.TryAdd(account);
756 public void NotifyConflicts(IEnumerable<FileSystemInfo> conflictFiles, string message)
758 if (conflictFiles == null)
760 //Convert to list to avoid multiple iterations
761 var files = conflictFiles.ToList();
766 //TODO: Create a more specific message. For now, just show a warning
767 NotifyForFiles(files,message,TraceLevel.Warning);
771 public void NotifyForFiles(IEnumerable<FileSystemInfo> files, string message,TraceLevel level=TraceLevel.Info)
778 StatusMessage = message;
780 _events.Publish(new Notification { Title = "Pithos", Message = message, Level = level});
783 public void Notify(Notification notification)
785 _events.Publish(notification);
789 public void RemoveMonitor(string accountName)
791 if (String.IsNullOrWhiteSpace(accountName))
794 var accountInfo=_accounts.FirstOrDefault(account => account.UserName == accountName);
795 if (accountInfo != null)
797 _accounts.TryRemove(accountInfo);
798 _pollAgent.RemoveAccount(accountInfo);
801 PithosMonitor monitor;
802 if (Monitors.TryRemove(accountName, out monitor))
805 //TODO: Also remove any pending actions for this account
806 //from the network queue
810 public void RefreshOverlays()
812 foreach (var pair in Monitors)
814 var monitor = pair.Value;
816 var path = monitor.RootPath;
818 if (String.IsNullOrWhiteSpace(path))
821 if (!Directory.Exists(path) && !File.Exists(path))
824 IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path);
828 NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM,
829 HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT,
830 pathPointer, IntPtr.Zero);
834 Marshal.FreeHGlobal(pathPointer);
839 #region Event Handlers
841 public void Handle(SelectiveSynchChanges message)
843 var accountName = message.Account.AccountName;
844 PithosMonitor monitor;
845 if (_monitors.TryGetValue(accountName, out monitor))
847 monitor.SetSelectivePaths(message.Uris,message.Added,message.Removed);
854 private bool _pollStarted;
855 private Sparkle _sparkle;
857 //SMELL: Doing so much work for notifications in the shell is wrong
858 //The notifications should be moved to their own view/viewmodel pair
859 //and different templates should be used for different message types
860 //This will also allow the addition of extra functionality, eg. actions
862 public void Handle(Notification notification)
866 if (!Settings.ShowDesktopNotifications)
869 if (notification is PollNotification)
874 if (notification is CloudNotification)
879 notification.Title = "Pithos";
880 notification.Message = "Start Synchronisation";
883 var progress = notification as ProgressNotification;
884 if (progress != null)
886 StatusMessage = String.Format("Pithos {0}\r\n{1} {2:p2} of {3} - {4}",
887 _fileVersion.Value.FileVersion,
889 progress.Block/(double)progress.TotalBlocks,
890 progress.FileSize.ToByteSize(),
895 var info = notification as StatusNotification;
898 StatusMessage = String.Format("Pithos {0}\r\n{1}",
899 _fileVersion.Value.FileVersion,
903 if (String.IsNullOrWhiteSpace(notification.Message) && String.IsNullOrWhiteSpace(notification.Title))
906 ShowBalloonFor(notification);
909 private void ShowBalloonFor(Notification notification)
911 Contract.Requires(notification!=null);
913 if (!Settings.ShowDesktopNotifications)
917 switch (notification.Level)
919 case TraceLevel.Info:
920 case TraceLevel.Verbose:
922 case TraceLevel.Error:
923 icon = BalloonIcon.Error;
925 case TraceLevel.Warning:
926 icon = BalloonIcon.Warning;
932 var tv = (ShellView) GetView();
933 System.Action clickAction = null;
934 if (notification is ExpirationNotification)
936 clickAction = () => ShowPreferences("AccountTab");
938 var balloon = new PithosBalloon
940 Title = notification.Title,
941 Message = notification.Message,
943 ClickAction = clickAction
945 tv.TaskbarView.ShowCustomBalloon(balloon, PopupAnimation.Fade, 4000);
950 public void Handle(ShowFilePropertiesEvent message)
953 throw new ArgumentNullException("message");
954 if (String.IsNullOrWhiteSpace(message.FileName) )
955 throw new ArgumentException("message");
956 Contract.EndContractBlock();
958 var fileName = message.FileName;
959 //TODO: Display file properties for non-container folders
960 if (File.Exists(fileName))
961 //Retrieve the full name with exact casing. Pithos names are case sensitive
962 ShowFileProperties(FileInfoExtensions.GetProperFilePathCapitalization(fileName));
963 else if (Directory.Exists(fileName))
964 //Retrieve the full name with exact casing. Pithos names are case sensitive
966 var path = FileInfoExtensions.GetProperDirectoryCapitalization(fileName);
967 if (IsContainer(path))
968 ShowContainerProperties(path);
970 ShowFileProperties(path);
974 private bool IsContainer(string path)
976 var matchingFolders = from account in _accounts
977 from rootFolder in Directory.GetDirectories(account.AccountPath)
978 where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase)
980 return matchingFolders.Any();
983 public FileStatus GetFileStatus(string localFileName)
985 if (String.IsNullOrWhiteSpace(localFileName))
986 throw new ArgumentNullException("localFileName");
987 Contract.EndContractBlock();
989 var statusKeeper = IoC.Get<IStatusKeeper>();
990 var status=statusKeeper.GetFileStatus(localFileName);
994 public void RemoveAccountFromDatabase(AccountSettings account)
996 var statusKeeper = IoC.Get<IStatusKeeper>();
997 statusKeeper.ClearFolderStatus(account.RootPath);