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);
226 if (_windowManager.ShowDialog(model) == true)
232 public void RefreshApiKey()
234 //_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 });
235 if (CurrentAccount == null)
240 var name = CurrentAccount.AccountName;
242 var loginUri = new Uri(new Uri(CurrentAccount.ServerUrl), "login");
243 var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString(),name);
244 if (credentials==null)
246 //The server will return credentials for a different account, not just the current account
247 //We need to find the correct account first
248 var account = Accounts.First(act => act.AccountName == credentials.UserName && act.ServerUrl == CurrentAccount.ServerUrl);
249 account.ApiKey = credentials.Password;
250 account.IsExpired = false;
252 TaskEx.Delay(10000).ContinueWith(_ =>Shell.MonitorAccount(account.Account));
253 NotifyOfPropertyChange(() => Accounts);
255 catch (AggregateException exc)
257 string message = String.Format("API Key retrieval failed");
258 Log.Error(message, exc.InnerException);
259 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
261 catch (Exception exc)
263 string message = String.Format("API Key retrieval failed");
264 Log.Error(message, exc);
265 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
271 public void OpenLogPath()
276 public void OpenLogConsole()
278 var logView=IoC.Get<LogConsole.LogConsoleViewModel>();
279 _windowManager.ShowWindow(logView);
282 public void SaveChanges()
288 public void RejectChanges()
294 public void ApplyChanges()
299 private void DoSave()
303 //Ensure we save the settings changes first
304 foreach (var account in _accountsToRemove)
306 Settings.Accounts.Remove(account);
309 foreach (var account in _accountsToAdd)
311 Settings.Accounts.Add(account);
319 foreach (var account in _accountsToRemove)
321 Shell.RemoveMonitor(account.ServerUrl, account.AccountName);
322 Shell.RemoveAccountFromDatabase(account);
325 foreach (var account in Settings.Accounts)
327 Shell.MonitorAccount(account);
332 _accountsToRemove.Clear();
333 _accountsToAdd.Clear();
336 NotifyOfPropertyChange(()=>Settings);
338 if (IgnoreCertificateErrors)
339 ServicePointManager.ServerCertificateValidationCallback= (sender,certificate,chain,errors)=> true;
342 ServicePointManager.ServerCertificateValidationCallback = null;
346 /* public void ChangePithosFolder()
348 var browser = new FolderBrowserDialog();
349 browser.SelectedPath = Settings.PithosPath;
350 var result = browser.ShowDialog((IWin32Window)GetView());
351 if (result == DialogResult.OK)
353 var newPath = browser.SelectedPath;
354 var accountName = CurrentAccount.AccountName;
355 var monitor = Shell.Monitors[accountName];
358 Shell.Monitors.Remove(accountName);
360 Directory.Move(Settings.PithosPath, newPath);
361 Settings.PithosPath = newPath;
364 Shell.MonitorAccount(CurrentAccount);
366 NotifyOfPropertyChange(() => Settings);
372 readonly List<AccountSettings> _accountsToAdd=new List<AccountSettings>();
373 public void AddAccount()
375 var wizard = new AddAccountViewModel();
376 if (_windowManager.ShowDialog(wizard) == true)
378 string selectedPath = wizard.AccountPath;
379 var initialRootPath = wizard.ShouldCreateOkeanosFolder?
380 Path.Combine(selectedPath, "Okeanos")
382 var actualRootPath= initialRootPath;
383 if (wizard.ShouldCreateOkeanosFolder)
386 while (Directory.Exists(actualRootPath) || File.Exists(actualRootPath))
388 actualRootPath = String.Format("{0} {1}", initialRootPath, attempt++);
394 var account = Accounts.FirstOrDefault(act => act.AccountName == wizard.AccountName && act.ServerUrl == wizard.CurrentServer);
397 if (MessageBox.Show("The account you specified already exists. Do you want to update it?","The account exists") == MessageBoxResult.Yes)
399 account.ApiKey = wizard.Token;
400 account.IsExpired = false;
401 CurrentAccount = account;
406 var newAccount = new AccountSettings
408 AccountName = wizard.AccountName,
409 ServerUrl = wizard.CurrentServer,
410 ApiKey = wizard.Token,
411 RootPath = actualRootPath,
412 IsActive = wizard.IsAccountActive
414 _accountsToAdd.Add(newAccount);
415 var accountVm = new AccountViewModel(newAccount);
416 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVm);
417 CurrentAccount = accountVm;
419 NotifyOfPropertyChange(() => Accounts);
420 NotifyOfPropertyChange(() => Settings);
428 public void AddPithosAccount()
430 var credentials=PithosAccount.RetrieveCredentials(null);
431 if (credentials == null)
433 var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
434 var accountVM = new AccountViewModel(account);
437 account=new AccountSettings{
438 AccountName=credentials.UserName,
439 ApiKey=credentials.Password
441 Settings.Accounts.Add(account);
442 accountVM = new AccountViewModel(account);
443 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVM);
447 account.ApiKey=credentials.Password;
449 //SelectedAccountIndex= Settings.Accounts.IndexOf(account);
450 CurrentAccount = accountVM;
451 NotifyOfPropertyChange(() => Accounts);
452 NotifyOfPropertyChange(()=>Settings);
457 readonly List<AccountSettings> _accountsToRemove = new List<AccountSettings>();
458 public void RemoveAccount()
460 Accounts.TryRemove(CurrentAccount);
461 _accountsToRemove.Add(CurrentAccount.Account);
463 CurrentAccount = null;
464 NotifyOfPropertyChange(() => Accounts);
467 //NotifyOfPropertyChange("Settings.Accounts");
470 public bool CanRemoveAccount
472 get { return (CurrentAccount != null); }
475 public bool CanClearAccountCache
477 get { return (CurrentAccount != null); }
480 public void ClearAccountCache()
482 if (MessageBoxResult.Yes == MessageBox.Show("You are about to delete all partially downloaded files from the account's cache.\n" +
483 " You will have to download all partially downloaded data again\n" +
484 "This change can not be undone\n\n" +
485 "Do you wish to delete all partially downloaded data?", "Warning! Clearing account cache",
486 MessageBoxButton.YesNo,MessageBoxImage.Question,MessageBoxResult.No))
489 var cachePath = Path.Combine(CurrentAccount.RootPath, FolderConstants.CacheFolder);
490 var dir = new DirectoryInfo(cachePath);
491 dir.EnumerateFiles().Apply(file=>file.Delete());
492 dir.EnumerateDirectories().Apply(folder => folder.Delete(true));
497 public bool ExtensionsActivated
499 get { return Settings.ExtensionsActivated; }
502 if (Settings.ExtensionsActivated == value)
505 Settings.ExtensionsActivated = value;
509 _extensionController.RegisterExtensions();
512 _extensionController.UnregisterExtensions();
515 NotifyOfPropertyChange(() => ExtensionsActivated);
519 public bool DebugLoggingEnabled
521 get { return Settings.DebugLoggingEnabled; }
523 Settings.DebugLoggingEnabled = value;
524 NotifyOfPropertyChange(()=>DebugLoggingEnabled);
528 public bool IgnoreCertificateErrors
530 get { return Settings.IgnoreCertificateErrors; }
532 Settings.IgnoreCertificateErrors = value;
533 NotifyOfPropertyChange(() => IgnoreCertificateErrors);
539 /* private int _selectedAccountIndex;
540 public int SelectedAccountIndex
542 get { return _selectedAccountIndex; }
545 //var accountCount=Settings.Accounts.Count;
546 //if (accountCount == 0)
548 //if (0 <= value && value < accountCount)
549 // _selectedAccountIndex = value;
551 // _selectedAccountIndex = 0;
552 _selectedAccountIndex = value;
553 NotifyOfPropertyChange(() => CurrentAccount);
554 NotifyOfPropertyChange(() => CanRemoveAccount);
555 NotifyOfPropertyChange(()=>SelectedAccountIndex);
559 private AccountViewModel _currentAccount;
560 private readonly IWindowManager _windowManager;
561 private readonly string _shortcutPath;
565 public AccountViewModel CurrentAccount
567 get { return _currentAccount; }
570 _currentAccount = value;
571 NotifyOfPropertyChange(()=>CurrentAccount);
572 NotifyOfPropertyChange(() => CanRemoveAccount);
573 NotifyOfPropertyChange(() => CanSelectiveSyncFolders);
574 NotifyOfPropertyChange(() => CanMoveAccountFolder);
575 NotifyOfPropertyChange(() => CanClearAccountCache);
580 public AccountSettings CurrentAccount
583 if (0 <= SelectedAccountIndex && SelectedAccountIndex < Settings.Accounts.Count)
584 return Settings.Accounts[SelectedAccountIndex];
592 public bool CanMoveAccountFolder
594 get { return CurrentAccount != null; }
597 public void MoveAccountFolder()
600 using (var dlg = new FolderBrowserDialog())
602 var currentFolder = CurrentAccount.RootPath;
603 dlg.SelectedPath = currentFolder;
604 //Ask the user to select a folder
605 //Note: We need a parent window here, which we retrieve with GetView
606 var view = (Window)GetView();
607 if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view)))
610 var newPath= dlg.SelectedPath;
611 //Find the account's monitor and stop it
612 PithosMonitor monitor;
613 if (Shell.Monitors.TryGetValue(CurrentAccount.AccountKey, out monitor))
618 var oldPath = monitor.RootPath;
619 //The old directory may not exist eg. if we create an account for the first time
620 if (Directory.Exists(oldPath))
622 //If it does, do the move
624 //Now Create all of the directories
625 foreach (string dirPath in Directory.EnumerateDirectories(oldPath, "*",
626 SearchOption.AllDirectories))
627 Directory.CreateDirectory(dirPath.Replace(oldPath, newPath));
630 foreach (string newFilePath in Directory.EnumerateFiles(oldPath, "*.*",
631 SearchOption.AllDirectories))
632 File.Copy(newFilePath, newFilePath.Replace(oldPath, newPath));
634 Log.InfoFormat("Deleting account folder {0}",oldPath);
635 Directory.Delete(oldPath, true);
637 //We also need to change the path of the existing file states
638 monitor.MoveFileStates(oldPath, newPath);
641 //Replace the old rootpath with the new
642 CurrentAccount.RootPath = newPath;
643 //TODO: This will save all settings changes. Too coarse grained, need to fix at a later date
645 //And start the monitor on the new RootPath
648 monitor.RootPath = newPath;
649 if (CurrentAccount.IsActive)
653 Shell.MonitorAccount(CurrentAccount.Account);
654 //Finally, notify that the Settings, CurrentAccount have changed
655 NotifyOfPropertyChange(() => CurrentAccount);
656 NotifyOfPropertyChange(() => Settings);