2 /* -----------------------------------------------------------------------
3 * <copyright file="PreferencesViewModel.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 * -----------------------------------------------------------------------
44 using System.Collections.Concurrent;
45 using System.Collections.Generic;
46 using System.ComponentModel.Composition;
47 using System.Diagnostics;
50 using System.Reflection;
51 using System.Threading.Tasks;
53 using System.Windows.Forms;
55 using Pithos.Client.WPF.Configuration;
56 using Pithos.Client.WPF.Properties;
57 using Pithos.Client.WPF.SelectiveSynch;
59 using Pithos.Interfaces;
62 using MessageBox = System.Windows.MessageBox;
63 using Screen = Caliburn.Micro.Screen;
65 namespace Pithos.Client.WPF.Preferences
68 /// The preferences screen displays user and account settings and updates the PithosMonitor
69 /// classes when account settings change.
72 /// The class is a single ViewModel for all Preferences tabs. It can be broken in separate
73 /// ViewModels, one for each tab.
75 [Export, PartCreationPolicy(CreationPolicy.Shared)]
76 public class PreferencesViewModel : Screen
78 private readonly IEventAggregator _events;
80 //Logging in the Pithos client is provided by log4net
81 private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
83 private PithosSettings _settings;
84 public PithosSettings Settings
86 get { return _settings; }
90 NotifyOfPropertyChange(()=>Settings);
94 private ObservableConcurrentCollection<AccountViewModel> _accounts;
95 public ObservableConcurrentCollection<AccountViewModel> Accounts
97 get { return _accounts; }
101 NotifyOfPropertyChange(()=>Accounts);
105 public bool StartOnSystemStartup { get; set; }
107 public ShellViewModel Shell { get; set; }
108 //ShellExtensionController _extensionController=new ShellExtensionController();
110 public PreferencesViewModel(IWindowManager windowManager, IEventAggregator events, ShellViewModel shell, PithosSettings settings, string currentTab)
112 // ReSharper disable DoNotCallOverridableMethodsInConstructor
113 //Caliburn.Micro uses DisplayName for the view's title
114 DisplayName = "Pithos+ Preferences";
115 // ReSharper restore DoNotCallOverridableMethodsInConstructor
117 _windowManager = windowManager;
123 Accounts = new ObservableConcurrentCollection<AccountViewModel>();
124 if (settings.Accounts == null)
126 settings.Accounts=new AccountsCollection();
129 var accountVMs = from account in settings.Accounts
130 select new AccountViewModel(account);
132 Accounts.AddFromEnumerable(accountVMs);
134 var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
135 _shortcutPath = Path.Combine(startupPath, "Pithos.lnk");
138 StartOnSystemStartup = File.Exists(_shortcutPath);
140 SelectedTab = currentTab;
143 private string _selectedTab="General";
144 public string SelectedTab
146 get { return _selectedTab; }
149 _selectedTab = value??"General";
150 NotifyOfPropertyChange(()=>SelectedTab);
151 NotifyOfPropertyChange(() => AccountTabSelected);
156 public bool AccountTabSelected
158 get { return _selectedTab == "AccountTab"; }
160 #region Preferences Properties
162 private bool _noProxy;
165 get { return _noProxy; }
169 NotifyOfPropertyChange(()=>NoProxy);
174 private bool _defaultProxy;
176 public bool DefaultProxy
178 get { return _defaultProxy; }
181 _defaultProxy = value;
182 NotifyOfPropertyChange(() => DefaultProxy);
187 private bool _manualProxy;
189 public bool ManualProxy
191 get { return _manualProxy; }
194 _manualProxy = value;
195 NotifyOfPropertyChange(() => ManualProxy);
201 public int StartupDelay
203 get { return (int) Settings.StartupDelay.TotalMinutes; }
207 throw new ArgumentOutOfRangeException("value",Resources.PreferencesViewModel_StartupDelay_Greater_or_equal_to_0);
208 Settings.StartupDelay = TimeSpan.FromMinutes(value);
209 NotifyOfPropertyChange(()=>StartupDelay);
215 public bool CanSelectiveSyncFolders
217 get { return CurrentAccount != null; }
220 public void SelectiveSyncFolders()
222 //var monitor = Shell.Monitors[CurrentAccount.AccountKey];
225 var model = new SelectiveSynchViewModel(/*monitor,*/_events,CurrentAccount.Account,CurrentAccount.ApiKey);
226 if (_windowManager.ShowDialog(model) == true)
232 /* private bool _networkTracing;
233 public bool NetworkTracing
235 get { return _networkTracing; }
238 _networkTracing = value;
243 public void RefreshApiKey()
245 //_events.Publish(new Notification { Title = "Authorization failed", Message = "Your API Key has probably expired. You will be directed to a page where you can renew it", Level = TraceLevel.Error });
246 if (CurrentAccount == null)
251 var name = CurrentAccount.AccountName;
253 var loginUri = new Uri(new Uri(CurrentAccount.ServerUrl), "login");
254 var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString(),name);
255 if (credentials==null)
257 //The server will return credentials for a different account, not just the current account
258 //We need to find the correct account first
259 var account = Accounts.First(act => act.AccountName == credentials.UserName && act.ServerUrl == CurrentAccount.ServerUrl);
260 account.ApiKey = credentials.Password;
261 account.IsExpired = false;
263 TaskEx.Delay(10000).ContinueWith(_ =>Shell.MonitorAccount(account.Account));
264 NotifyOfPropertyChange(() => Accounts);
266 catch (AggregateException exc)
268 string message = String.Format("API Key retrieval failed");
269 Log.Error(message, exc.InnerException);
270 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
272 catch (Exception exc)
274 string message = String.Format("API Key retrieval failed");
275 Log.Error(message, exc);
276 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
282 public void OpenLogPath()
287 public void OpenLogConsole()
289 var logView=IoC.Get<LogConsole.LogConsoleViewModel>();
290 _windowManager.ShowWindow(logView);
293 public void SaveChanges()
299 public void RejectChanges()
305 public void ApplyChanges()
310 private void DoSave()
314 //Ensure we save the settings changes first
315 foreach (var account in _accountsToRemove)
317 Settings.Accounts.Remove(account);
320 foreach (var account in _accountsToAdd)
322 Settings.Accounts.Add(account);
330 foreach (var account in _accountsToRemove)
332 Shell.RemoveMonitor(account.ServerUrl, account.AccountName);
333 Shell.RemoveAccountFromDatabase(account);
336 foreach (var account in Settings.Accounts)
338 Shell.MonitorAccount(account);
343 _accountsToRemove.Clear();
344 _accountsToAdd.Clear();
347 NotifyOfPropertyChange(()=>Settings);
349 if (IgnoreCertificateErrors)
350 ServicePointManager.ServerCertificateValidationCallback= (sender,certificate,chain,errors)=> true;
353 ServicePointManager.ServerCertificateValidationCallback = null;
357 /* public void ChangePithosFolder()
359 var browser = new FolderBrowserDialog();
360 browser.SelectedPath = Settings.PithosPath;
361 var result = browser.ShowDialog((IWin32Window)GetView());
362 if (result == DialogResult.OK)
364 var newPath = browser.SelectedPath;
365 var accountName = CurrentAccount.AccountName;
366 var monitor = Shell.Monitors[accountName];
369 Shell.Monitors.Remove(accountName);
371 Directory.Move(Settings.PithosPath, newPath);
372 Settings.PithosPath = newPath;
375 Shell.MonitorAccount(CurrentAccount);
377 NotifyOfPropertyChange(() => Settings);
383 readonly List<AccountSettings> _accountsToAdd=new List<AccountSettings>();
384 public void AddAccount()
386 var wizard = new AddAccountViewModel();
387 if (_windowManager.ShowDialog(wizard) == true)
389 string selectedPath = wizard.AccountPath;
390 var initialRootPath = wizard.ShouldCreateOkeanosFolder?
391 Path.Combine(selectedPath, "Okeanos")
393 var actualRootPath= initialRootPath;
394 if (wizard.ShouldCreateOkeanosFolder)
397 while (Directory.Exists(actualRootPath) || File.Exists(actualRootPath))
399 actualRootPath = String.Format("{0} {1}", initialRootPath, attempt++);
405 var account = Accounts.FirstOrDefault(act => act.AccountName == wizard.AccountName && act.ServerUrl == wizard.CurrentServer);
408 if (MessageBox.Show("The account you specified already exists. Do you want to update it?","The account exists") == MessageBoxResult.Yes)
410 account.ApiKey = wizard.Token;
411 account.IsExpired = false;
412 CurrentAccount = account;
417 var newAccount = new AccountSettings
419 AccountName = wizard.AccountName,
420 ServerUrl = wizard.CurrentServer,
421 ApiKey = wizard.Token,
422 RootPath = actualRootPath,
423 IsActive = wizard.IsAccountActive
425 _accountsToAdd.Add(newAccount);
426 var accountVm = new AccountViewModel(newAccount);
427 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVm);
428 CurrentAccount = accountVm;
430 NotifyOfPropertyChange(() => Accounts);
431 NotifyOfPropertyChange(() => Settings);
439 public void AddPithosAccount()
441 var credentials=PithosAccount.RetrieveCredentials(null);
442 if (credentials == null)
444 var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
445 var accountVM = new AccountViewModel(account);
448 account=new AccountSettings{
449 AccountName=credentials.UserName,
450 ApiKey=credentials.Password
452 Settings.Accounts.Add(account);
453 accountVM = new AccountViewModel(account);
454 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVM);
458 account.ApiKey=credentials.Password;
460 //SelectedAccountIndex= Settings.Accounts.IndexOf(account);
461 CurrentAccount = accountVM;
462 NotifyOfPropertyChange(() => Accounts);
463 NotifyOfPropertyChange(()=>Settings);
468 readonly List<AccountSettings> _accountsToRemove = new List<AccountSettings>();
469 public void RemoveAccount()
471 Accounts.TryRemove(CurrentAccount);
472 _accountsToRemove.Add(CurrentAccount.Account);
474 CurrentAccount = null;
475 NotifyOfPropertyChange(() => Accounts);
478 //NotifyOfPropertyChange("Settings.Accounts");
481 public bool CanRemoveAccount
483 get { return (CurrentAccount != null); }
486 public bool CanClearAccountCache
488 get { return (CurrentAccount != null); }
491 public void ClearAccountCache()
493 if (MessageBoxResult.Yes == MessageBox.Show("You are about to delete all partially downloaded files from the account's cache.\n" +
494 " You will have to download all partially downloaded data again\n" +
495 "This change can not be undone\n\n" +
496 "Do you wish to delete all partially downloaded data?", "Warning! Clearing account cache",
497 MessageBoxButton.YesNo,MessageBoxImage.Question,MessageBoxResult.No))
500 var cachePath = Path.Combine(CurrentAccount.RootPath, FolderConstants.CacheFolder);
501 var dir = new DirectoryInfo(cachePath);
502 //The file may not exist if we just created the account
505 dir.EnumerateFiles().Apply(file=>file.Delete());
506 dir.EnumerateDirectories().Apply(folder => folder.Delete(true));
511 public bool ExtensionsActivated
513 get { return Settings.ExtensionsActivated; }
516 if (Settings.ExtensionsActivated == value)
519 Settings.ExtensionsActivated = value;
523 _extensionController.RegisterExtensions();
526 _extensionController.UnregisterExtensions();
529 NotifyOfPropertyChange(() => ExtensionsActivated);
533 public bool DebugLoggingEnabled
535 get { return Settings.DebugLoggingEnabled; }
537 Settings.DebugLoggingEnabled = value;
538 NotifyOfPropertyChange(()=>DebugLoggingEnabled);
542 public bool IgnoreCertificateErrors
544 get { return Settings.IgnoreCertificateErrors; }
546 Settings.IgnoreCertificateErrors = value;
547 NotifyOfPropertyChange(() => IgnoreCertificateErrors);
553 /* private int _selectedAccountIndex;
554 public int SelectedAccountIndex
556 get { return _selectedAccountIndex; }
559 //var accountCount=Settings.Accounts.Count;
560 //if (accountCount == 0)
562 //if (0 <= value && value < accountCount)
563 // _selectedAccountIndex = value;
565 // _selectedAccountIndex = 0;
566 _selectedAccountIndex = value;
567 NotifyOfPropertyChange(() => CurrentAccount);
568 NotifyOfPropertyChange(() => CanRemoveAccount);
569 NotifyOfPropertyChange(()=>SelectedAccountIndex);
573 private AccountViewModel _currentAccount;
574 private readonly IWindowManager _windowManager;
575 private readonly string _shortcutPath;
579 public AccountViewModel CurrentAccount
581 get { return _currentAccount; }
584 _currentAccount = value;
585 NotifyOfPropertyChange(()=>CurrentAccount);
586 NotifyOfPropertyChange(() => CanRemoveAccount);
587 NotifyOfPropertyChange(() => CanSelectiveSyncFolders);
588 NotifyOfPropertyChange(() => CanMoveAccountFolder);
589 NotifyOfPropertyChange(() => CanClearAccountCache);
594 public AccountSettings CurrentAccount
597 if (0 <= SelectedAccountIndex && SelectedAccountIndex < Settings.Accounts.Count)
598 return Settings.Accounts[SelectedAccountIndex];
606 public bool CanMoveAccountFolder
608 get { return CurrentAccount != null; }
611 public void MoveAccountFolder()
614 using (var dlg = new FolderBrowserDialog())
616 var currentFolder = CurrentAccount.RootPath;
617 dlg.SelectedPath = currentFolder;
618 //Ask the user to select a folder
619 //Note: We need a parent window here, which we retrieve with GetView
620 var view = (Window)GetView();
621 if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view)))
624 var newPath= dlg.SelectedPath;
625 //Find the account's monitor and stop it
626 PithosMonitor monitor;
627 if (Shell.Monitors.TryGetValue(CurrentAccount.AccountKey, out monitor))
632 var oldPath = monitor.RootPath;
633 //The old directory may not exist eg. if we create an account for the first time
634 if (Directory.Exists(oldPath))
636 //If it does, do the move
638 //Now Create all of the directories
639 foreach (string dirPath in Directory.EnumerateDirectories(oldPath, "*",
640 SearchOption.AllDirectories))
641 Directory.CreateDirectory(dirPath.Replace(oldPath, newPath));
644 foreach (string newFilePath in Directory.EnumerateFiles(oldPath, "*.*",
645 SearchOption.AllDirectories))
646 File.Copy(newFilePath, newFilePath.Replace(oldPath, newPath));
648 Log.InfoFormat("Deleting account folder {0}",oldPath);
649 Directory.Delete(oldPath, true);
651 //We also need to change the path of the existing file states
652 monitor.MoveFileStates(oldPath, newPath);
655 //Replace the old rootpath with the new
656 CurrentAccount.RootPath = newPath;
657 //TODO: This will save all settings changes. Too coarse grained, need to fix at a later date
659 //And start the monitor on the new RootPath
662 monitor.RootPath = newPath;
663 if (CurrentAccount.IsActive)
667 Shell.MonitorAccount(CurrentAccount.Account);
668 //Finally, notify that the Settings, CurrentAccount have changed
669 NotifyOfPropertyChange(() => CurrentAccount);
670 NotifyOfPropertyChange(() => Settings);