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.Threading.Tasks;
51 using System.Windows.Forms;
53 using Pithos.Client.WPF.Configuration;
54 using Pithos.Client.WPF.Properties;
55 using Pithos.Client.WPF.SelectiveSynch;
57 using Pithos.Interfaces;
60 using Screen = Caliburn.Micro.Screen;
62 namespace Pithos.Client.WPF.Preferences
65 /// The preferences screen displays user and account settings and updates the PithosMonitor
66 /// classes when account settings change.
69 /// The class is a single ViewModel for all Preferences tabs. It can be broken in separate
70 /// ViewModels, one for each tab.
73 public class PreferencesViewModel : Screen
75 private readonly IEventAggregator _events;
77 //Logging in the Pithos client is provided by log4net
78 private static readonly log4net.ILog Log = log4net.LogManager.GetLogger("Pithos");
80 private PithosSettings _settings;
81 public PithosSettings Settings
83 get { return _settings; }
87 NotifyOfPropertyChange(()=>Settings);
91 private ObservableConcurrentCollection<AccountViewModel> _accounts;
92 public ObservableConcurrentCollection<AccountViewModel> Accounts
94 get { return _accounts; }
98 NotifyOfPropertyChange(()=>Accounts);
102 public bool StartOnSystemStartup { get; set; }
104 public ShellViewModel Shell { get; set; }
105 //ShellExtensionController _extensionController=new ShellExtensionController();
107 public PreferencesViewModel(IWindowManager windowManager, IEventAggregator events, ShellViewModel shell, PithosSettings settings, string currentTab)
109 // ReSharper disable DoNotCallOverridableMethodsInConstructor
110 //Caliburn.Micro uses DisplayName for the view's title
111 DisplayName = "Pithos Preferences";
112 // ReSharper restore DoNotCallOverridableMethodsInConstructor
114 _windowManager = windowManager;
120 Accounts = new ObservableConcurrentCollection<AccountViewModel>();
121 if (settings.Accounts == null)
123 settings.Accounts=new AccountsCollection();
126 var accountVMs = from account in settings.Accounts
127 select new AccountViewModel(account);
129 Accounts.AddFromEnumerable(accountVMs);
131 var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
132 _shortcutPath = Path.Combine(startupPath, "Pithos.lnk");
135 StartOnSystemStartup = File.Exists(_shortcutPath);
137 SelectedTab = currentTab;
140 private string _selectedTab="General";
141 public string SelectedTab
143 get { return _selectedTab; }
146 _selectedTab = value??"General";
147 NotifyOfPropertyChange(()=>SelectedTab);
148 NotifyOfPropertyChange(() => AccountTabSelected);
153 public bool AccountTabSelected
155 get { return _selectedTab == "AccountTab"; }
157 #region Preferences Properties
159 private bool _noProxy;
162 get { return _noProxy; }
166 NotifyOfPropertyChange(()=>NoProxy);
171 private bool _defaultProxy;
173 public bool DefaultProxy
175 get { return _defaultProxy; }
178 _defaultProxy = value;
179 NotifyOfPropertyChange(() => DefaultProxy);
184 private bool _manualProxy;
186 public bool ManualProxy
188 get { return _manualProxy; }
191 _manualProxy = value;
192 NotifyOfPropertyChange(() => ManualProxy);
198 public int StartupDelay
200 get { return (int) Settings.StartupDelay.TotalMinutes; }
204 throw new ArgumentOutOfRangeException("value",Resources.PreferencesViewModel_StartupDelay_Greater_or_equal_to_0);
205 Settings.StartupDelay = TimeSpan.FromMinutes(value);
206 NotifyOfPropertyChange(()=>StartupDelay);
212 public bool CanSelectiveSyncFolders
214 get { return CurrentAccount != null; }
217 public void SelectiveSyncFolders()
219 var monitor = Shell.Monitors[CurrentAccount.AccountName];
222 var model = new SelectiveSynchViewModel(monitor,_events,CurrentAccount.Account);
223 if (_windowManager.ShowDialog(model) == true)
229 public async Task RefreshApiKey()
231 _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 });
236 var credentials = await PithosAccount.RetrieveCredentials(Settings.PithosLoginUrl);
237 //The server will return credentials for a different account, not just the current account
238 //We need to find the correct account first
239 var account = Accounts.First(act => act.AccountName == credentials.UserName);
240 account.ApiKey = credentials.Password;
241 account.IsExpired = false;
243 TaskEx.Delay(10000).ContinueWith(_ =>Shell.MonitorAccount(account.Account));
244 NotifyOfPropertyChange(() => Accounts);
246 catch (AggregateException exc)
248 string message = String.Format("API Key retrieval failed");
249 Log.Error(message, exc.InnerException);
250 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
252 catch (Exception exc)
254 string message = String.Format("API Key retrieval failed");
255 Log.Error(message, exc);
256 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
262 public void SaveChanges()
268 public void RejectChanges()
274 public void ApplyChanges()
279 private void DoSave()
283 //Ensure we save the settings changes first
284 foreach (var account in _accountsToRemove)
286 Settings.Accounts.Remove(account);
289 foreach (var account in _accountsToAdd)
291 Settings.Accounts.Add(account);
297 foreach (var account in _accountsToRemove)
299 Shell.RemoveMonitor(account.AccountName);
302 foreach (var account in Settings.Accounts)
304 Shell.MonitorAccount(account);
307 NotifyOfPropertyChange(()=>Settings);
310 /* public void ChangePithosFolder()
312 var browser = new FolderBrowserDialog();
313 browser.SelectedPath = Settings.PithosPath;
314 var result = browser.ShowDialog((IWin32Window)GetView());
315 if (result == DialogResult.OK)
317 var newPath = browser.SelectedPath;
318 var accountName = CurrentAccount.AccountName;
319 var monitor = Shell.Monitors[accountName];
322 Shell.Monitors.Remove(accountName);
324 Directory.Move(Settings.PithosPath, newPath);
325 Settings.PithosPath = newPath;
328 Shell.MonitorAccount(CurrentAccount);
330 NotifyOfPropertyChange(() => Settings);
336 readonly List<AccountSettings> _accountsToAdd=new List<AccountSettings>();
337 public void AddAccount()
339 var wizard = new AddAccountViewModel();
340 if (_windowManager.ShowDialog(wizard) == true)
342 string selectedPath = wizard.AccountPath;
343 var initialRootPath = Path.Combine(selectedPath, "Okeanos");
344 var actualRootPath= initialRootPath;
346 while (Directory.Exists(actualRootPath) || File.Exists(actualRootPath))
348 actualRootPath = String.Format("{0} {1}", initialRootPath,attempt++);
351 var newAccount = new AccountSettings
353 AccountName = wizard.AccountName,
354 ServerUrl=wizard.CurrentServer,
356 RootPath = actualRootPath,
357 IsActive=wizard.IsAccountActive
359 _accountsToAdd.Add(newAccount);
360 var accountVm = new AccountViewModel(newAccount);
361 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVm);
362 CurrentAccount = accountVm;
363 NotifyOfPropertyChange(() => Accounts);
364 NotifyOfPropertyChange(() => Settings);
371 public async void AddPithosAccount()
373 var credentials=await PithosAccount.RetrieveCredentials(Settings.PithosLoginUrl);
374 var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
375 var accountVM = new AccountViewModel(account);
378 account=new AccountSettings{
379 AccountName=credentials.UserName,
380 ApiKey=credentials.Password
382 Settings.Accounts.Add(account);
383 accountVM = new AccountViewModel(account);
384 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVM);
388 account.ApiKey=credentials.Password;
390 //SelectedAccountIndex= Settings.Accounts.IndexOf(account);
391 CurrentAccount = accountVM;
392 NotifyOfPropertyChange(() => Accounts);
393 NotifyOfPropertyChange(()=>Settings);
397 readonly List<AccountSettings> _accountsToRemove = new List<AccountSettings>();
398 public void RemoveAccount()
400 Accounts.TryRemove(CurrentAccount);
401 _accountsToRemove.Add(CurrentAccount.Account);
403 CurrentAccount = null;
404 NotifyOfPropertyChange(() => Accounts);
407 //NotifyOfPropertyChange("Settings.Accounts");
410 public bool CanRemoveAccount
412 get { return (CurrentAccount != null); }
417 public bool ExtensionsActivated
419 get { return Settings.ExtensionsActivated; }
422 if (Settings.ExtensionsActivated == value)
425 Settings.ExtensionsActivated = value;
429 _extensionController.RegisterExtensions();
432 _extensionController.UnregisterExtensions();
435 NotifyOfPropertyChange(() => ExtensionsActivated);
442 /* private int _selectedAccountIndex;
443 public int SelectedAccountIndex
445 get { return _selectedAccountIndex; }
448 //var accountCount=Settings.Accounts.Count;
449 //if (accountCount == 0)
451 //if (0 <= value && value < accountCount)
452 // _selectedAccountIndex = value;
454 // _selectedAccountIndex = 0;
455 _selectedAccountIndex = value;
456 NotifyOfPropertyChange(() => CurrentAccount);
457 NotifyOfPropertyChange(() => CanRemoveAccount);
458 NotifyOfPropertyChange(()=>SelectedAccountIndex);
462 private AccountViewModel _currentAccount;
463 private readonly IWindowManager _windowManager;
464 private readonly string _shortcutPath;
468 public AccountViewModel CurrentAccount
470 get { return _currentAccount; }
473 _currentAccount = value;
474 NotifyOfPropertyChange(()=>CurrentAccount);
475 NotifyOfPropertyChange(() => CanRemoveAccount);
476 NotifyOfPropertyChange(() => CanSelectiveSyncFolders);
477 NotifyOfPropertyChange(() => CanMoveAccountFolder);
482 public AccountSettings CurrentAccount
485 if (0 <= SelectedAccountIndex && SelectedAccountIndex < Settings.Accounts.Count)
486 return Settings.Accounts[SelectedAccountIndex];
494 public bool CanMoveAccountFolder
496 get { return CurrentAccount != null; }
499 public void MoveAccountFolder()
502 using (var dlg = new FolderBrowserDialog())
504 var currentFolder = CurrentAccount.RootPath;
505 dlg.SelectedPath = currentFolder;
506 //Ask the user to select a folder
507 //Note: We need a parent window here, which we retrieve with GetView
508 var view = (Window)GetView();
509 if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view)))
512 var newPath= dlg.SelectedPath;
513 //Find the account's monitor and stop it
514 PithosMonitor monitor;
515 if (Shell.Monitors.TryGetValue(CurrentAccount.AccountName, out monitor))
520 var oldPath = monitor.RootPath;
521 //The old directory may not exist eg. if we create an account for the first time
522 if (Directory.Exists(oldPath))
524 //If it does, do the move
526 //Now Create all of the directories
527 foreach (string dirPath in Directory.EnumerateDirectories(oldPath, "*",
528 SearchOption.AllDirectories))
529 Directory.CreateDirectory(dirPath.Replace(oldPath, newPath));
532 foreach (string newFilePath in Directory.EnumerateFiles(oldPath, "*.*",
533 SearchOption.AllDirectories))
534 File.Copy(newFilePath, newFilePath.Replace(oldPath, newPath));
536 Directory.Delete(oldPath, true);
538 //We also need to change the path of the existing file states
539 monitor.MoveFileStates(oldPath, newPath);
542 //Replace the old rootpath with the new
543 CurrentAccount.RootPath = newPath;
544 //TODO: This will save all settings changes. Too coarse grained, need to fix at a later date
546 //And start the monitor on the new RootPath
549 monitor.RootPath = newPath;
550 if (CurrentAccount.IsActive)
554 Shell.MonitorAccount(CurrentAccount.Account);
555 //Finally, notify that the Settings, CurrentAccount have changed
556 NotifyOfPropertyChange(() => CurrentAccount);
557 NotifyOfPropertyChange(() => Settings);