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.Collections.Specialized;
47 using System.ComponentModel.Composition;
48 using System.Diagnostics;
51 using System.Reflection;
52 using System.Threading.Tasks;
54 using System.Windows.Forms;
56 using Pithos.Client.WPF.Configuration;
57 using Pithos.Client.WPF.Properties;
58 using Pithos.Client.WPF.SelectiveSynch;
59 using Pithos.Client.WPF.Utils;
61 using Pithos.Interfaces;
65 using MessageBox = System.Windows.MessageBox;
66 using Screen = Caliburn.Micro.Screen;
68 namespace Pithos.Client.WPF.Preferences
71 /// The preferences screen displays user and account settings and updates the PithosMonitor
72 /// classes when account settings change.
75 /// The class is a single ViewModel for all Preferences tabs. It can be broken in separate
76 /// ViewModels, one for each tab.
78 [Export, PartCreationPolicy(CreationPolicy.Shared)]
79 public class PreferencesViewModel : Screen
81 private readonly IEventAggregator _events;
83 //Logging in the Pithos client is provided by log4net
84 private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
86 private PithosSettings _settings;
87 public PithosSettings Settings
89 get { return _settings; }
93 NotifyOfPropertyChange(()=>Settings);
97 private ObservableConcurrentCollection<AccountViewModel> _accounts;
98 public ObservableConcurrentCollection<AccountViewModel> Accounts
100 get { return _accounts; }
104 NotifyOfPropertyChange(()=>Accounts);
108 public bool StartOnSystemStartup { get; set; }
110 public ShellViewModel Shell { get; set; }
111 //ShellExtensionController _extensionController=new ShellExtensionController();
113 public PreferencesViewModel(IWindowManager windowManager, IEventAggregator events, ShellViewModel shell, PithosSettings settings, string currentTab)
115 // ReSharper disable DoNotCallOverridableMethodsInConstructor
116 //Caliburn.Micro uses DisplayName for the view's title
117 DisplayName = "Pithos+ Preferences";
118 // ReSharper restore DoNotCallOverridableMethodsInConstructor
120 _windowManager = windowManager;
126 Accounts = new ObservableConcurrentCollection<AccountViewModel>();
127 if (settings.Accounts == null)
129 settings.Accounts=new AccountsCollection();
132 var accountVMs = from account in settings.Accounts
133 select new AccountViewModel(account);
135 Accounts.AddFromEnumerable(accountVMs);
137 var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
138 _shortcutPath = Path.Combine(startupPath, "Pithos.lnk");
141 StartOnSystemStartup = File.Exists(_shortcutPath);
143 SelectedTab = currentTab;
146 private string _selectedTab="General";
147 public string SelectedTab
149 get { return _selectedTab; }
152 _selectedTab = value??"General";
153 NotifyOfPropertyChange(()=>SelectedTab);
154 NotifyOfPropertyChange(() => AccountTabSelected);
159 public bool AccountTabSelected
161 get { return _selectedTab == "AccountTab"; }
163 #region Preferences Properties
165 private bool _noProxy;
168 get { return _noProxy; }
172 NotifyOfPropertyChange(()=>NoProxy);
177 private bool _defaultProxy;
179 public bool DefaultProxy
181 get { return _defaultProxy; }
184 _defaultProxy = value;
185 NotifyOfPropertyChange(() => DefaultProxy);
190 private bool _manualProxy;
192 public bool ManualProxy
194 get { return _manualProxy; }
197 _manualProxy = value;
198 NotifyOfPropertyChange(() => ManualProxy);
204 public int StartupDelay
206 get { return (int) Settings.StartupDelay.TotalMinutes; }
210 throw new ArgumentOutOfRangeException("value",Resources.PreferencesViewModel_StartupDelay_Greater_or_equal_to_0);
211 Settings.StartupDelay = TimeSpan.FromMinutes(value);
212 NotifyOfPropertyChange(()=>StartupDelay);
218 public bool CanSelectiveSyncFolders
220 get { return CurrentAccount != null; }
223 public void SelectiveSyncFolders()
225 //var monitor = Shell.Monitors[CurrentAccount.AccountKey];
228 var model = new SelectiveSynchViewModel(/*monitor,*/_events,CurrentAccount.Account,CurrentAccount.ApiKey);
229 if (_windowManager.ShowDialog(model) == true)
235 /* private bool _networkTracing;
236 public bool NetworkTracing
238 get { return _networkTracing; }
241 _networkTracing = value;
246 public void RefreshApiKey()
248 //_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 });
249 if (CurrentAccount == null)
254 var name = CurrentAccount.AccountName;
256 var loginUri = new Uri(new Uri(CurrentAccount.ServerUrl), "login");
257 var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString(),name);
258 if (credentials==null)
260 //The server will return credentials for a different account, not just the current account
261 //We need to find the correct account first
262 var account = Accounts.First(act => act.AccountName == credentials.UserName && act.ServerUrl == CurrentAccount.ServerUrl);
263 account.ApiKey = credentials.Password;
264 account.IsExpired = false;
266 TaskEx.Delay(10000).ContinueWith(_ =>Shell.MonitorAccount(account.Account));
267 NotifyOfPropertyChange(() => Accounts);
269 catch (AggregateException exc)
271 string message = String.Format("API Key retrieval failed");
272 Log.Error(message, exc.InnerException);
273 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
275 catch (Exception exc)
277 string message = String.Format("API Key retrieval failed");
278 Log.Error(message, exc);
279 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
285 public void OpenLogPath()
290 public void OpenLogConsole()
292 var logView=IoC.Get<LogConsole.LogConsoleViewModel>();
293 _windowManager.ShowWindow(logView);
296 public void SaveChanges()
302 public void RejectChanges()
308 public void ApplyChanges()
313 private void DoSave()
317 //Ensure we save the settings changes first
318 foreach (var account in _accountsToRemove)
320 Settings.Accounts.Remove(account);
323 foreach (var account in _accountsToAdd)
325 Settings.Accounts.Add(account);
333 foreach (var account in _accountsToRemove)
335 Shell.RemoveMonitor(account.ServerUrl, account.AccountName);
336 Shell.RemoveAccountFromDatabase(account);
339 foreach (var account in Settings.Accounts)
341 Shell.MonitorAccount(account);
346 _accountsToRemove.Clear();
347 _accountsToAdd.Clear();
350 NotifyOfPropertyChange(()=>Settings);
352 if (IgnoreCertificateErrors)
353 ServicePointManager.ServerCertificateValidationCallback= (sender,certificate,chain,errors)=> true;
356 ServicePointManager.ServerCertificateValidationCallback = null;
360 /* public void ChangePithosFolder()
362 var browser = new FolderBrowserDialog();
363 browser.SelectedPath = Settings.PithosPath;
364 var result = browser.ShowDialog((IWin32Window)GetView());
365 if (result == DialogResult.OK)
367 var newPath = browser.SelectedPath;
368 var accountName = CurrentAccount.AccountName;
369 var monitor = Shell.Monitors[accountName];
372 Shell.Monitors.Remove(accountName);
374 Directory.Move(Settings.PithosPath, newPath);
375 Settings.PithosPath = newPath;
378 Shell.MonitorAccount(CurrentAccount);
380 NotifyOfPropertyChange(() => Settings);
386 readonly List<AccountSettings> _accountsToAdd=new List<AccountSettings>();
387 public void AddAccount()
389 var wizard = new AddAccountViewModel();
390 if (_windowManager.ShowDialog(wizard) == true)
392 string selectedPath = wizard.AccountPath;
393 var initialRootPath = wizard.ShouldCreateOkeanosFolder?
394 Path.Combine(selectedPath, "Okeanos")
396 var actualRootPath= initialRootPath;
397 if (wizard.ShouldCreateOkeanosFolder)
400 while (Directory.Exists(actualRootPath) || File.Exists(actualRootPath))
402 actualRootPath = String.Format("{0} {1}", initialRootPath, attempt++);
408 var account = Accounts.FirstOrDefault(act => act.AccountName == wizard.AccountName && act.ServerUrl == wizard.CurrentServer);
411 if (MessageBox.Show("The account you specified already exists. Do you want to update it?", "The account exists") == MessageBoxResult.Yes)
413 account.ApiKey = wizard.Token;
414 account.IsExpired = false;
415 CurrentAccount = account;
420 var newAccount = new AccountSettings
422 AccountName = wizard.AccountName,
423 ServerUrl = wizard.CurrentServer,
424 ApiKey = wizard.Token,
425 RootPath = actualRootPath,
426 IsActive = wizard.IsAccountActive,
430 var client = new CloudFilesClient(newAccount.AccountName, newAccount.ApiKey)
432 AuthenticationUrl = newAccount.ServerUrl, UsePithos = true
434 client.Authenticate();
437 var dirs = (from container in client.ListContainers(newAccount.AccountName)
438 from dir in client.ListObjects(newAccount.AccountName, container.Name)
439 where container.Name != "trash"
442 .Select(d=>d.Uri.ToString())
445 newAccount.SelectiveFolders.AddRange(dirs);
447 //TODO:Add the "pithos" container as a default selection
449 _accountsToAdd.Add(newAccount);
450 var accountVm = new AccountViewModel(newAccount);
451 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVm);
452 CurrentAccount = accountVm;
454 NotifyOfPropertyChange(() => Accounts);
455 NotifyOfPropertyChange(() => Settings);
463 public void AddPithosAccount()
465 var credentials=PithosAccount.RetrieveCredentials(null);
466 if (credentials == null)
468 var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
469 var accountVM = new AccountViewModel(account);
472 account=new AccountSettings{
473 AccountName=credentials.UserName,
474 ApiKey=credentials.Password
476 Settings.Accounts.Add(account);
477 accountVM = new AccountViewModel(account);
478 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVM);
482 account.ApiKey=credentials.Password;
484 //SelectedAccountIndex= Settings.Accounts.IndexOf(account);
485 CurrentAccount = accountVM;
486 NotifyOfPropertyChange(() => Accounts);
487 NotifyOfPropertyChange(()=>Settings);
492 readonly List<AccountSettings> _accountsToRemove = new List<AccountSettings>();
493 public void RemoveAccount()
495 Accounts.TryRemove(CurrentAccount);
496 _accountsToRemove.Add(CurrentAccount.Account);
498 CurrentAccount = null;
499 NotifyOfPropertyChange(() => Accounts);
502 //NotifyOfPropertyChange("Settings.Accounts");
505 public bool CanRemoveAccount
507 get { return (CurrentAccount != null); }
510 public bool CanClearAccountCache
512 get { return (CurrentAccount != null); }
515 public void ClearAccountCache()
517 if (MessageBoxResult.Yes == MessageBox.Show("You are about to delete all partially downloaded files from the account's cache.\n" +
518 " You will have to download all partially downloaded data again\n" +
519 "This change can not be undone\n\n" +
520 "Do you wish to delete all partially downloaded data?", "Warning! Clearing account cache",
521 MessageBoxButton.YesNo,MessageBoxImage.Question,MessageBoxResult.No))
524 var cachePath = Path.Combine(CurrentAccount.RootPath, FolderConstants.CacheFolder);
525 var dir = new DirectoryInfo(cachePath);
526 //The file may not exist if we just created the account
529 dir.EnumerateFiles().Apply(file=>file.Delete());
530 dir.EnumerateDirectories().Apply(folder => folder.Delete(true));
535 public bool ExtensionsActivated
537 get { return Settings.ExtensionsActivated; }
540 if (Settings.ExtensionsActivated == value)
543 Settings.ExtensionsActivated = value;
547 _extensionController.RegisterExtensions();
550 _extensionController.UnregisterExtensions();
553 NotifyOfPropertyChange(() => ExtensionsActivated);
557 public bool DebugLoggingEnabled
559 get { return Settings.DebugLoggingEnabled; }
561 Settings.DebugLoggingEnabled = value;
562 NotifyOfPropertyChange(()=>DebugLoggingEnabled);
566 public bool IgnoreCertificateErrors
568 get { return Settings.IgnoreCertificateErrors; }
570 Settings.IgnoreCertificateErrors = value;
571 NotifyOfPropertyChange(() => IgnoreCertificateErrors);
577 /* private int _selectedAccountIndex;
578 public int SelectedAccountIndex
580 get { return _selectedAccountIndex; }
583 //var accountCount=Settings.Accounts.Count;
584 //if (accountCount == 0)
586 //if (0 <= value && value < accountCount)
587 // _selectedAccountIndex = value;
589 // _selectedAccountIndex = 0;
590 _selectedAccountIndex = value;
591 NotifyOfPropertyChange(() => CurrentAccount);
592 NotifyOfPropertyChange(() => CanRemoveAccount);
593 NotifyOfPropertyChange(()=>SelectedAccountIndex);
597 private AccountViewModel _currentAccount;
598 private readonly IWindowManager _windowManager;
599 private readonly string _shortcutPath;
603 public AccountViewModel CurrentAccount
605 get { return _currentAccount; }
608 _currentAccount = value;
609 NotifyOfPropertyChange(()=>CurrentAccount);
610 NotifyOfPropertyChange(() => CanRemoveAccount);
611 NotifyOfPropertyChange(() => CanSelectiveSyncFolders);
612 NotifyOfPropertyChange(() => CanMoveAccountFolder);
613 NotifyOfPropertyChange(() => CanClearAccountCache);
618 public AccountSettings CurrentAccount
621 if (0 <= SelectedAccountIndex && SelectedAccountIndex < Settings.Accounts.Count)
622 return Settings.Accounts[SelectedAccountIndex];
630 public bool CanMoveAccountFolder
632 get { return CurrentAccount != null; }
635 public void MoveAccountFolder()
638 using (var dlg = new FolderBrowserDialog())
640 var currentFolder = CurrentAccount.RootPath;
641 dlg.SelectedPath = currentFolder;
642 //Ask the user to select a folder
643 //Note: We need a parent window here, which we retrieve with GetView
644 var view = (Window)GetView();
645 if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view)))
648 var newPath= dlg.SelectedPath;
649 //Find the account's monitor and stop it
650 PithosMonitor monitor;
651 if (Shell.Monitors.TryGetValue(CurrentAccount.AccountKey, out monitor))
656 var oldPath = monitor.RootPath;
657 //The old directory may not exist eg. if we create an account for the first time
658 if (Directory.Exists(oldPath))
660 //If it does, do the move
662 //Now Create all of the directories
663 foreach (string dirPath in Directory.EnumerateDirectories(oldPath, "*",
664 SearchOption.AllDirectories))
665 Directory.CreateDirectory(dirPath.Replace(oldPath, newPath));
668 foreach (string newFilePath in Directory.EnumerateFiles(oldPath, "*.*",
669 SearchOption.AllDirectories))
670 File.Copy(newFilePath, newFilePath.Replace(oldPath, newPath));
672 Log.InfoFormat("Deleting account folder {0}",oldPath);
673 Directory.Delete(oldPath, true);
675 //We also need to change the path of the existing file states
676 monitor.MoveFileStates(oldPath, newPath);
679 //Replace the old rootpath with the new
680 CurrentAccount.RootPath = newPath;
681 //TODO: This will save all settings changes. Too coarse grained, need to fix at a later date
683 //And start the monitor on the new RootPath
686 monitor.RootPath = newPath;
687 if (CurrentAccount.IsActive)
691 Shell.MonitorAccount(CurrentAccount.Account);
692 //Finally, notify that the Settings, CurrentAccount have changed
693 NotifyOfPropertyChange(() => CurrentAccount);
694 NotifyOfPropertyChange(() => Settings);