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;
49 using System.Reflection;
50 using System.Threading.Tasks;
52 using System.Windows.Forms;
54 using Pithos.Client.WPF.Configuration;
55 using Pithos.Client.WPF.Properties;
56 using Pithos.Client.WPF.SelectiveSynch;
58 using Pithos.Interfaces;
61 using MessageBox = System.Windows.MessageBox;
62 using Screen = Caliburn.Micro.Screen;
64 namespace Pithos.Client.WPF.Preferences
67 /// The preferences screen displays user and account settings and updates the PithosMonitor
68 /// classes when account settings change.
71 /// The class is a single ViewModel for all Preferences tabs. It can be broken in separate
72 /// ViewModels, one for each tab.
75 public class PreferencesViewModel : Screen
77 private readonly IEventAggregator _events;
79 //Logging in the Pithos client is provided by log4net
80 private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
82 private PithosSettings _settings;
83 public PithosSettings Settings
85 get { return _settings; }
89 NotifyOfPropertyChange(()=>Settings);
93 private ObservableConcurrentCollection<AccountViewModel> _accounts;
94 public ObservableConcurrentCollection<AccountViewModel> Accounts
96 get { return _accounts; }
100 NotifyOfPropertyChange(()=>Accounts);
104 public bool StartOnSystemStartup { get; set; }
106 public ShellViewModel Shell { get; set; }
107 //ShellExtensionController _extensionController=new ShellExtensionController();
109 public PreferencesViewModel(IWindowManager windowManager, IEventAggregator events, ShellViewModel shell, PithosSettings settings, string currentTab)
111 // ReSharper disable DoNotCallOverridableMethodsInConstructor
112 //Caliburn.Micro uses DisplayName for the view's title
113 DisplayName = "Pithos+ Preferences";
114 // ReSharper restore DoNotCallOverridableMethodsInConstructor
116 _windowManager = windowManager;
122 Accounts = new ObservableConcurrentCollection<AccountViewModel>();
123 if (settings.Accounts == null)
125 settings.Accounts=new AccountsCollection();
128 var accountVMs = from account in settings.Accounts
129 select new AccountViewModel(account);
131 Accounts.AddFromEnumerable(accountVMs);
133 var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
134 _shortcutPath = Path.Combine(startupPath, "Pithos.lnk");
137 StartOnSystemStartup = File.Exists(_shortcutPath);
139 SelectedTab = currentTab;
142 private string _selectedTab="General";
143 public string SelectedTab
145 get { return _selectedTab; }
148 _selectedTab = value??"General";
149 NotifyOfPropertyChange(()=>SelectedTab);
150 NotifyOfPropertyChange(() => AccountTabSelected);
155 public bool AccountTabSelected
157 get { return _selectedTab == "AccountTab"; }
159 #region Preferences Properties
161 private bool _noProxy;
164 get { return _noProxy; }
168 NotifyOfPropertyChange(()=>NoProxy);
173 private bool _defaultProxy;
175 public bool DefaultProxy
177 get { return _defaultProxy; }
180 _defaultProxy = value;
181 NotifyOfPropertyChange(() => DefaultProxy);
186 private bool _manualProxy;
188 public bool ManualProxy
190 get { return _manualProxy; }
193 _manualProxy = value;
194 NotifyOfPropertyChange(() => ManualProxy);
200 public int StartupDelay
202 get { return (int) Settings.StartupDelay.TotalMinutes; }
206 throw new ArgumentOutOfRangeException("value",Resources.PreferencesViewModel_StartupDelay_Greater_or_equal_to_0);
207 Settings.StartupDelay = TimeSpan.FromMinutes(value);
208 NotifyOfPropertyChange(()=>StartupDelay);
214 public bool CanSelectiveSyncFolders
216 get { return CurrentAccount != null; }
219 public void SelectiveSyncFolders()
221 var monitor = Shell.Monitors[CurrentAccount.AccountName];
224 var model = new SelectiveSynchViewModel(monitor,_events,CurrentAccount.Account);
225 if (_windowManager.ShowDialog(model) == true)
231 public void RefreshApiKey()
233 //_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 });
234 if (CurrentAccount == null)
239 var name = CurrentAccount.AccountName;
241 var loginUri = new Uri(new Uri(CurrentAccount.ServerUrl), "login");
242 var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString(),name);
243 if (credentials==null)
245 //The server will return credentials for a different account, not just the current account
246 //We need to find the correct account first
247 var account = Accounts.First(act => act.AccountName == credentials.UserName && act.ServerUrl == CurrentAccount.ServerUrl);
248 account.ApiKey = credentials.Password;
249 account.IsExpired = false;
251 TaskEx.Delay(10000).ContinueWith(_ =>Shell.MonitorAccount(account.Account));
252 NotifyOfPropertyChange(() => Accounts);
254 catch (AggregateException exc)
256 string message = String.Format("API Key retrieval failed");
257 Log.Error(message, exc.InnerException);
258 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
260 catch (Exception exc)
262 string message = String.Format("API Key retrieval failed");
263 Log.Error(message, exc);
264 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
270 public void OpenLogPath()
275 public void OpenLogConsole()
277 var logView=IoC.Get<LogConsole.LogConsoleViewModel>();
278 _windowManager.ShowWindow(logView);
281 public void SaveChanges()
287 public void RejectChanges()
293 public void ApplyChanges()
298 private void DoSave()
302 //Ensure we save the settings changes first
303 foreach (var account in _accountsToRemove)
305 Settings.Accounts.Remove(account);
308 foreach (var account in _accountsToAdd)
310 Settings.Accounts.Add(account);
318 foreach (var account in _accountsToRemove)
320 Shell.RemoveMonitor(account.AccountName);
321 Shell.RemoveAccountFromDatabase(account);
324 foreach (var account in Settings.Accounts)
326 Shell.MonitorAccount(account);
331 _accountsToRemove.Clear();
332 _accountsToAdd.Clear();
335 NotifyOfPropertyChange(()=>Settings);
338 /* public void ChangePithosFolder()
340 var browser = new FolderBrowserDialog();
341 browser.SelectedPath = Settings.PithosPath;
342 var result = browser.ShowDialog((IWin32Window)GetView());
343 if (result == DialogResult.OK)
345 var newPath = browser.SelectedPath;
346 var accountName = CurrentAccount.AccountName;
347 var monitor = Shell.Monitors[accountName];
350 Shell.Monitors.Remove(accountName);
352 Directory.Move(Settings.PithosPath, newPath);
353 Settings.PithosPath = newPath;
356 Shell.MonitorAccount(CurrentAccount);
358 NotifyOfPropertyChange(() => Settings);
364 readonly List<AccountSettings> _accountsToAdd=new List<AccountSettings>();
365 public void AddAccount()
367 var wizard = new AddAccountViewModel();
368 if (_windowManager.ShowDialog(wizard) == true)
370 string selectedPath = wizard.AccountPath;
371 var initialRootPath = wizard.ShouldCreateOkeanosFolder?
372 Path.Combine(selectedPath, "Okeanos")
374 var actualRootPath= initialRootPath;
375 if (wizard.ShouldCreateOkeanosFolder)
378 while (Directory.Exists(actualRootPath) || File.Exists(actualRootPath))
380 actualRootPath = String.Format("{0} {1}", initialRootPath, attempt++);
386 var account = Accounts.FirstOrDefault(act => act.AccountName == wizard.AccountName && act.ServerUrl == wizard.CurrentServer);
389 if (MessageBox.Show("The account you specified already exists. Do you want to update it?","The account exists") == MessageBoxResult.Yes)
391 account.ApiKey = wizard.Token;
392 account.IsExpired = false;
393 CurrentAccount = account;
398 var newAccount = new AccountSettings
400 AccountName = wizard.AccountName,
401 ServerUrl = wizard.CurrentServer,
402 ApiKey = wizard.Token,
403 RootPath = actualRootPath,
404 IsActive = wizard.IsAccountActive
406 _accountsToAdd.Add(newAccount);
407 var accountVm = new AccountViewModel(newAccount);
408 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVm);
409 CurrentAccount = accountVm;
411 NotifyOfPropertyChange(() => Accounts);
412 NotifyOfPropertyChange(() => Settings);
420 public void AddPithosAccount()
422 var credentials=PithosAccount.RetrieveCredentials(null);
423 if (credentials == null)
425 var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
426 var accountVM = new AccountViewModel(account);
429 account=new AccountSettings{
430 AccountName=credentials.UserName,
431 ApiKey=credentials.Password
433 Settings.Accounts.Add(account);
434 accountVM = new AccountViewModel(account);
435 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVM);
439 account.ApiKey=credentials.Password;
441 //SelectedAccountIndex= Settings.Accounts.IndexOf(account);
442 CurrentAccount = accountVM;
443 NotifyOfPropertyChange(() => Accounts);
444 NotifyOfPropertyChange(()=>Settings);
449 readonly List<AccountSettings> _accountsToRemove = new List<AccountSettings>();
450 public void RemoveAccount()
452 Accounts.TryRemove(CurrentAccount);
453 _accountsToRemove.Add(CurrentAccount.Account);
455 CurrentAccount = null;
456 NotifyOfPropertyChange(() => Accounts);
459 //NotifyOfPropertyChange("Settings.Accounts");
462 public bool CanRemoveAccount
464 get { return (CurrentAccount != null); }
469 public bool ExtensionsActivated
471 get { return Settings.ExtensionsActivated; }
474 if (Settings.ExtensionsActivated == value)
477 Settings.ExtensionsActivated = value;
481 _extensionController.RegisterExtensions();
484 _extensionController.UnregisterExtensions();
487 NotifyOfPropertyChange(() => ExtensionsActivated);
491 public bool DebugLoggingEnabled
493 get { return Settings.DebugLoggingEnabled; }
495 Settings.DebugLoggingEnabled = value;
496 NotifyOfPropertyChange(()=>DebugLoggingEnabled);
502 /* private int _selectedAccountIndex;
503 public int SelectedAccountIndex
505 get { return _selectedAccountIndex; }
508 //var accountCount=Settings.Accounts.Count;
509 //if (accountCount == 0)
511 //if (0 <= value && value < accountCount)
512 // _selectedAccountIndex = value;
514 // _selectedAccountIndex = 0;
515 _selectedAccountIndex = value;
516 NotifyOfPropertyChange(() => CurrentAccount);
517 NotifyOfPropertyChange(() => CanRemoveAccount);
518 NotifyOfPropertyChange(()=>SelectedAccountIndex);
522 private AccountViewModel _currentAccount;
523 private readonly IWindowManager _windowManager;
524 private readonly string _shortcutPath;
528 public AccountViewModel CurrentAccount
530 get { return _currentAccount; }
533 _currentAccount = value;
534 NotifyOfPropertyChange(()=>CurrentAccount);
535 NotifyOfPropertyChange(() => CanRemoveAccount);
536 NotifyOfPropertyChange(() => CanSelectiveSyncFolders);
537 NotifyOfPropertyChange(() => CanMoveAccountFolder);
542 public AccountSettings CurrentAccount
545 if (0 <= SelectedAccountIndex && SelectedAccountIndex < Settings.Accounts.Count)
546 return Settings.Accounts[SelectedAccountIndex];
554 public bool CanMoveAccountFolder
556 get { return CurrentAccount != null; }
559 public void MoveAccountFolder()
562 using (var dlg = new FolderBrowserDialog())
564 var currentFolder = CurrentAccount.RootPath;
565 dlg.SelectedPath = currentFolder;
566 //Ask the user to select a folder
567 //Note: We need a parent window here, which we retrieve with GetView
568 var view = (Window)GetView();
569 if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view)))
572 var newPath= dlg.SelectedPath;
573 //Find the account's monitor and stop it
574 PithosMonitor monitor;
575 if (Shell.Monitors.TryGetValue(CurrentAccount.AccountName, out monitor))
580 var oldPath = monitor.RootPath;
581 //The old directory may not exist eg. if we create an account for the first time
582 if (Directory.Exists(oldPath))
584 //If it does, do the move
586 //Now Create all of the directories
587 foreach (string dirPath in Directory.EnumerateDirectories(oldPath, "*",
588 SearchOption.AllDirectories))
589 Directory.CreateDirectory(dirPath.Replace(oldPath, newPath));
592 foreach (string newFilePath in Directory.EnumerateFiles(oldPath, "*.*",
593 SearchOption.AllDirectories))
594 File.Copy(newFilePath, newFilePath.Replace(oldPath, newPath));
596 Log.InfoFormat("Deleting account folder {0}",oldPath);
597 Directory.Delete(oldPath, true);
599 //We also need to change the path of the existing file states
600 monitor.MoveFileStates(oldPath, newPath);
603 //Replace the old rootpath with the new
604 CurrentAccount.RootPath = newPath;
605 //TODO: This will save all settings changes. Too coarse grained, need to fix at a later date
607 //And start the monitor on the new RootPath
610 monitor.RootPath = newPath;
611 if (CurrentAccount.IsActive)
615 Shell.MonitorAccount(CurrentAccount.Account);
616 //Finally, notify that the Settings, CurrentAccount have changed
617 NotifyOfPropertyChange(() => CurrentAccount);
618 NotifyOfPropertyChange(() => Settings);