#region /* ----------------------------------------------------------------------- * * * Copyright 2011-2012 GRNET S.A. All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * 1. Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and * documentation are those of the authors and should not be * interpreted as representing official policies, either expressed * or implied, of GRNET S.A. * * ----------------------------------------------------------------------- */ #endregion using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel.Composition; using System.Diagnostics; using System.IO; using System.Net; using System.Reflection; using System.Threading.Tasks; using System.Windows; using System.Windows.Forms; using Caliburn.Micro; using Pithos.Client.WPF.Configuration; using Pithos.Client.WPF.Properties; using Pithos.Client.WPF.SelectiveSynch; using Pithos.Client.WPF.Utils; using Pithos.Core; using Pithos.Core.Agents; using Pithos.Interfaces; using System; using System.Linq; using Pithos.Network; using MessageBox = System.Windows.MessageBox; using Screen = Caliburn.Micro.Screen; namespace Pithos.Client.WPF.Preferences { /// /// The preferences screen displays user and account settings and updates the PithosMonitor /// classes when account settings change. /// /// /// The class is a single ViewModel for all Preferences tabs. It can be broken in separate /// ViewModels, one for each tab. /// [Export, PartCreationPolicy(CreationPolicy.Shared)] public class PreferencesViewModel : Screen { private readonly IEventAggregator _events; //Logging in the Pithos client is provided by log4net private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private PithosSettings _settings; public PithosSettings Settings { get { return _settings; } set { _settings = value; NotifyOfPropertyChange(()=>Settings); } } private ObservableConcurrentCollection _accounts; public ObservableConcurrentCollection Accounts { get { return _accounts; } set { _accounts = value; NotifyOfPropertyChange(()=>Accounts); } } public bool StartOnSystemStartup { get; set; } public ShellViewModel Shell { get; set; } //ShellExtensionController _extensionController=new ShellExtensionController(); public PreferencesViewModel(IWindowManager windowManager, IEventAggregator events, ShellViewModel shell, PithosSettings settings, string currentTab) { // ReSharper disable DoNotCallOverridableMethodsInConstructor //Caliburn.Micro uses DisplayName for the view's title DisplayName = "Pithos+ Preferences"; // ReSharper restore DoNotCallOverridableMethodsInConstructor _windowManager = windowManager; _events = events; Shell = shell; Settings=settings; Accounts = new ObservableConcurrentCollection(); if (settings.Accounts == null) { settings.Accounts=new AccountsCollection(); settings.Save(); } var accountVMs = from account in settings.Accounts select new AccountViewModel(account); Accounts.AddFromEnumerable(accountVMs); var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup); _shortcutPath = Path.Combine(startupPath, "Pithos.lnk"); StartOnSystemStartup = File.Exists(_shortcutPath); SelectedTab = currentTab; } private string _selectedTab="General"; public string SelectedTab { get { return _selectedTab; } set { _selectedTab = value??"General"; NotifyOfPropertyChange(()=>SelectedTab); NotifyOfPropertyChange(() => AccountTabSelected); } } public bool AccountTabSelected { get { return _selectedTab == "AccountTab"; } } #region Preferences Properties private bool _noProxy; public bool NoProxy { get { return _noProxy; } set { _noProxy = value; NotifyOfPropertyChange(()=>NoProxy); } } private bool _defaultProxy; public bool DefaultProxy { get { return _defaultProxy; } set { _defaultProxy = value; NotifyOfPropertyChange(() => DefaultProxy); } } private bool _manualProxy; public bool ManualProxy { get { return _manualProxy; } set { _manualProxy = value; NotifyOfPropertyChange(() => ManualProxy); } } #endregion public int StartupDelay { get { return (int) Settings.StartupDelay.TotalMinutes; } set { if (value<0) throw new ArgumentOutOfRangeException("value",Resources.PreferencesViewModel_StartupDelay_Greater_or_equal_to_0); Settings.StartupDelay = TimeSpan.FromMinutes(value); NotifyOfPropertyChange(()=>StartupDelay); } } #region Commands public bool CanSelectiveSyncFolders { get { return CurrentAccount != null; } } public void SelectiveSyncFolders() { //var monitor = Shell.Monitors[CurrentAccount.AccountKey]; var model = new SelectiveSynchViewModel(_events,CurrentAccount.Account,CurrentAccount.ApiKey); if (_windowManager.ShowDialog(model) == true) { } } /* private bool _networkTracing; public bool NetworkTracing { get { return _networkTracing; } set { _networkTracing = value; } }*/ public void RefreshApiKey() { //_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 }); if (CurrentAccount == null) return; try { var name = CurrentAccount.AccountName; var loginUri = new Uri(new Uri(CurrentAccount.ServerUrl), "login"); var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString(),name); if (credentials==null) return; //The server will return credentials for a different account, not just the current account //We need to find the correct account first var account = Accounts.First(act => act.AccountName == credentials.UserName && act.ServerUrl == CurrentAccount.ServerUrl); account.ApiKey = credentials.Password; account.IsExpired = false; Settings.Save(); TaskEx.Delay(10000).ContinueWith(_ =>Shell.MonitorAccount(account.Account)); NotifyOfPropertyChange(() => Accounts); } catch (AggregateException exc) { string message = String.Format("API Key retrieval failed"); Log.Error(message, exc.InnerException); _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error }); } catch (Exception exc) { string message = String.Format("API Key retrieval failed"); Log.Error(message, exc); _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error }); } } public void OpenLogPath() { Shell.OpenLogPath(); } public void OpenLogConsole() { var logView=IoC.Get(); _windowManager.ShowWindow(logView); } public void SaveChanges() { DoSave(); TryClose(true); } public void RejectChanges() { Settings.Reload(); TryClose(false); } public void ApplyChanges() { DoSave(); } private void DoSave() { //SetStartupMode(); //Ensure we save the settings changes first foreach (var account in _accountsToRemove) { Settings.Accounts.Remove(account); } foreach (var account in _accountsToAdd) { Settings.Accounts.Add(account); } Settings.Save(); try { foreach (var account in _accountsToRemove) { Shell.RemoveMonitor(account.ServerUrl, account.AccountName); Shell.RemoveAccountFromDatabase(account); } foreach (var account in Settings.Accounts) { Shell.MonitorAccount(account); } var poller=IoC.Get(); poller.SynchNow(); } finally { _accountsToRemove.Clear(); _accountsToAdd.Clear(); } NotifyOfPropertyChange(()=>Settings); if (IgnoreCertificateErrors) ServicePointManager.ServerCertificateValidationCallback= (sender,certificate,chain,errors)=> true; else { ServicePointManager.ServerCertificateValidationCallback = null; } } /* public void ChangePithosFolder() { var browser = new FolderBrowserDialog(); browser.SelectedPath = Settings.PithosPath; var result = browser.ShowDialog((IWin32Window)GetView()); if (result == DialogResult.OK) { var newPath = browser.SelectedPath; var accountName = CurrentAccount.AccountName; var monitor = Shell.Monitors[accountName]; monitor.Stop(); Shell.Monitors.Remove(accountName); Directory.Move(Settings.PithosPath, newPath); Settings.PithosPath = newPath; Settings.Save(); Shell.MonitorAccount(CurrentAccount); NotifyOfPropertyChange(() => Settings); } } */ readonly List _accountsToAdd=new List(); public void AddAccount() { var wizard = new AddAccountViewModel(); if (_windowManager.ShowDialog(wizard) == true) { string selectedPath = wizard.AccountPath; var initialRootPath = wizard.ShouldCreateOkeanosFolder? Path.Combine(selectedPath, "Okeanos") :selectedPath; var actualRootPath= initialRootPath; if (wizard.ShouldCreateOkeanosFolder) { int attempt = 1; while (Directory.Exists(actualRootPath) || File.Exists(actualRootPath)) { actualRootPath = String.Format("{0} {1}", initialRootPath, attempt++); } } var account = Accounts.FirstOrDefault(act => act.AccountName == wizard.AccountName && act.ServerUrl == wizard.CurrentServer); if (account != null) { if (MessageBox.Show("The account you specified already exists. Do you want to update it?", "The account exists") == MessageBoxResult.Yes) { account.ApiKey = wizard.Token; account.IsExpired = false; CurrentAccount = account; } } else { var newAccount = new AccountSettings { AccountName = wizard.AccountName, ServerUrl = wizard.CurrentServer, ApiKey = wizard.Token, RootPath = actualRootPath, IsActive = wizard.IsAccountActive, }; var client = new CloudFilesClient(newAccount.AccountName, newAccount.ApiKey) { AuthenticationUrl = newAccount.ServerUrl, UsePithos = true }; client.Authenticate(); var containers = client.ListContainers(newAccount.AccountName); var containerUris = from container in containers select String.Format(@"{0}/v1/{1}/{2}", newAccount.ServerUrl, newAccount.AccountName, container.Name); newAccount.SelectiveFolders.AddRange(containerUris.ToArray()); var objectInfos = (from container in containers from dir in client.ListObjects(newAccount.AccountName, container.Name) where container.Name != "trash" select dir).ToList(); var tree = objectInfos.ToTree(); var selected = (from root in tree from child in root select child.Uri.ToString()).ToArray(); newAccount.SelectiveFolders.AddRange(selected); //TODO:Add the "pithos" container as a default selection _accountsToAdd.Add(newAccount); var accountVm = new AccountViewModel(newAccount); (Accounts as IProducerConsumerCollection).TryAdd(accountVm); CurrentAccount = accountVm; } NotifyOfPropertyChange(() => Accounts); NotifyOfPropertyChange(() => Settings); } } /* public void AddPithosAccount() { var credentials=PithosAccount.RetrieveCredentials(null); if (credentials == null) return; var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName); var accountVM = new AccountViewModel(account); if (account == null) { account=new AccountSettings{ AccountName=credentials.UserName, ApiKey=credentials.Password }; Settings.Accounts.Add(account); accountVM = new AccountViewModel(account); (Accounts as IProducerConsumerCollection).TryAdd(accountVM); } else { account.ApiKey=credentials.Password; } //SelectedAccountIndex= Settings.Accounts.IndexOf(account); CurrentAccount = accountVM; NotifyOfPropertyChange(() => Accounts); NotifyOfPropertyChange(()=>Settings); } */ readonly List _accountsToRemove = new List(); public void RemoveAccount() { Accounts.TryRemove(CurrentAccount); _accountsToRemove.Add(CurrentAccount.Account); CurrentAccount = null; NotifyOfPropertyChange(() => Accounts); //NotifyOfPropertyChange("Settings.Accounts"); } public bool CanRemoveAccount { get { return (CurrentAccount != null); } } public bool CanClearAccountCache { get { return (CurrentAccount != null); } } public void ClearAccountCache() { if (MessageBoxResult.Yes == MessageBox.Show("You are about to delete all partially downloaded files from the account's cache.\n" + " You will have to download all partially downloaded data again\n" + "This change can not be undone\n\n" + "Do you wish to delete all partially downloaded data?", "Warning! Clearing account cache", MessageBoxButton.YesNo,MessageBoxImage.Question,MessageBoxResult.No)) { var cachePath = Path.Combine(CurrentAccount.RootPath, FolderConstants.CacheFolder); var dir = new DirectoryInfo(cachePath); //The file may not exist if we just created the account if (!dir.Exists) return; dir.EnumerateFiles().Apply(file=>file.Delete()); dir.EnumerateDirectories().Apply(folder => folder.Delete(true)); } } public bool ExtensionsActivated { get { return Settings.ExtensionsActivated; } set { if (Settings.ExtensionsActivated == value) return; Settings.ExtensionsActivated = value; /* if (value) _extensionController.RegisterExtensions(); else { _extensionController.UnregisterExtensions(); } */ NotifyOfPropertyChange(() => ExtensionsActivated); } } public bool DebugLoggingEnabled { get { return Settings.DebugLoggingEnabled; } set { Settings.DebugLoggingEnabled = value; NotifyOfPropertyChange(()=>DebugLoggingEnabled); } } public bool IgnoreCertificateErrors { get { return Settings.IgnoreCertificateErrors; } set { Settings.IgnoreCertificateErrors = value; NotifyOfPropertyChange(() => IgnoreCertificateErrors); } } #endregion /* private int _selectedAccountIndex; public int SelectedAccountIndex { get { return _selectedAccountIndex; } set { //var accountCount=Settings.Accounts.Count; //if (accountCount == 0) // return; //if (0 <= value && value < accountCount) // _selectedAccountIndex = value; //else // _selectedAccountIndex = 0; _selectedAccountIndex = value; NotifyOfPropertyChange(() => CurrentAccount); NotifyOfPropertyChange(() => CanRemoveAccount); NotifyOfPropertyChange(()=>SelectedAccountIndex); } }*/ private AccountViewModel _currentAccount; private readonly IWindowManager _windowManager; private readonly string _shortcutPath; public AccountViewModel CurrentAccount { get { return _currentAccount; } set { _currentAccount = value; NotifyOfPropertyChange(()=>CurrentAccount); NotifyOfPropertyChange(() => CanRemoveAccount); NotifyOfPropertyChange(() => CanSelectiveSyncFolders); NotifyOfPropertyChange(() => CanMoveAccountFolder); NotifyOfPropertyChange(() => CanClearAccountCache); } } /* public AccountSettings CurrentAccount { get { if (0 <= SelectedAccountIndex && SelectedAccountIndex < Settings.Accounts.Count) return Settings.Accounts[SelectedAccountIndex]; return null; } } */ public bool CanMoveAccountFolder { get { return CurrentAccount != null; } } public void MoveAccountFolder() { using (var dlg = new FolderBrowserDialog()) { var currentFolder = CurrentAccount.RootPath; dlg.SelectedPath = currentFolder; //Ask the user to select a folder //Note: We need a parent window here, which we retrieve with GetView var view = (Window)GetView(); if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view))) return; var newPath= dlg.SelectedPath; //Find the account's monitor and stop it PithosMonitor monitor; if (Shell.Monitors.TryGetValue(CurrentAccount.AccountKey, out monitor)) { monitor.Stop(); var oldPath = monitor.RootPath; //The old directory may not exist eg. if we create an account for the first time if (Directory.Exists(oldPath)) { //If it does, do the move //Now Create all of the directories foreach (string dirPath in Directory.EnumerateDirectories(oldPath, "*", SearchOption.AllDirectories)) Directory.CreateDirectory(dirPath.Replace(oldPath, newPath)); //Copy all the files foreach (string newFilePath in Directory.EnumerateFiles(oldPath, "*.*", SearchOption.AllDirectories)) File.Copy(newFilePath, newFilePath.Replace(oldPath, newPath)); Log.InfoFormat("Deleting account folder {0}",oldPath); Directory.Delete(oldPath, true); //We also need to change the path of the existing file states monitor.MoveFileStates(oldPath, newPath); } } //Replace the old rootpath with the new CurrentAccount.RootPath = newPath; //TODO: This will save all settings changes. Too coarse grained, need to fix at a later date Settings.Save(); //And start the monitor on the new RootPath if (monitor != null) { monitor.RootPath = newPath; if (CurrentAccount.IsActive) monitor.Start(); } else Shell.MonitorAccount(CurrentAccount.Account); //Finally, notify that the Settings, CurrentAccount have changed NotifyOfPropertyChange(() => CurrentAccount); NotifyOfPropertyChange(() => Settings); } } } }