#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);
}
}
}
}