New SQLite version
[pithos-ms-client] / trunk / Pithos.Client.WPF / Preferences / PreferencesViewModel.cs
1 #region
2 /* -----------------------------------------------------------------------
3  * <copyright file="PreferencesViewModel.cs" company="GRNet">
4  * 
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or
8  * without modification, are permitted provided that the following
9  * conditions are met:
10  *
11  *   1. Redistributions of source code must retain the above
12  *      copyright notice, this list of conditions and the following
13  *      disclaimer.
14  *
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.
19  *
20  *
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.
33  *
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.
38  * </copyright>
39  * -----------------------------------------------------------------------
40  */
41 #endregion
42
43
44 using System.Collections.Concurrent;
45 using System.Collections.Generic;
46 using System.ComponentModel.Composition;
47 using System.Diagnostics;
48 using System.IO;
49 using System.Net;
50 using System.Reflection;
51 using System.Threading.Tasks;
52 using System.Windows;
53 using System.Windows.Forms;
54 using Caliburn.Micro;
55 using Pithos.Client.WPF.Configuration;
56 using Pithos.Client.WPF.Properties;
57 using Pithos.Client.WPF.SelectiveSynch;
58 using Pithos.Core;
59 using Pithos.Interfaces;
60 using System;
61 using System.Linq;
62 using MessageBox = System.Windows.MessageBox;
63 using Screen = Caliburn.Micro.Screen;
64
65 namespace Pithos.Client.WPF.Preferences
66 {
67     /// <summary>
68     /// The preferences screen displays user and account settings and updates the PithosMonitor
69     /// classes when account settings change.
70     /// </summary>
71     /// <remarks>
72     /// The class is a single ViewModel for all Preferences tabs. It can be broken in separate
73     /// ViewModels, one for each tab.
74     /// </remarks>
75     [Export, PartCreationPolicy(CreationPolicy.Shared)]
76     public class PreferencesViewModel : Screen
77     {
78         private readonly IEventAggregator _events;
79
80         //Logging in the Pithos client is provided by log4net
81         private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
82
83         private PithosSettings _settings;
84         public PithosSettings Settings
85         {
86             get { return _settings; }
87             set
88             {
89                 _settings = value;
90                 NotifyOfPropertyChange(()=>Settings);
91             }
92         }
93
94         private ObservableConcurrentCollection<AccountViewModel> _accounts;
95         public ObservableConcurrentCollection<AccountViewModel> Accounts
96         {
97             get { return _accounts; }
98             set 
99             { 
100                 _accounts = value;
101                 NotifyOfPropertyChange(()=>Accounts);
102             }
103         }
104         
105         public bool StartOnSystemStartup { get; set; }
106
107         public ShellViewModel Shell { get;  set; }
108         //ShellExtensionController _extensionController=new ShellExtensionController();
109
110         public PreferencesViewModel(IWindowManager windowManager, IEventAggregator events, ShellViewModel shell, PithosSettings settings, string currentTab)
111         {
112             // ReSharper disable DoNotCallOverridableMethodsInConstructor
113             //Caliburn.Micro uses DisplayName for the view's title
114             DisplayName = "Pithos+ Preferences";
115             // ReSharper restore DoNotCallOverridableMethodsInConstructor
116
117             _windowManager = windowManager;
118             _events = events;
119
120             Shell = shell;
121             
122             Settings=settings;
123             Accounts = new ObservableConcurrentCollection<AccountViewModel>();
124             if (settings.Accounts == null)
125             {
126                 settings.Accounts=new AccountsCollection();
127                 settings.Save();
128             }
129             var accountVMs = from account in settings.Accounts
130                              select new AccountViewModel(account);
131
132             Accounts.AddFromEnumerable(accountVMs);
133             
134             var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
135             _shortcutPath = Path.Combine(startupPath, "Pithos.lnk");
136
137
138             StartOnSystemStartup = File.Exists(_shortcutPath);
139
140             SelectedTab = currentTab;
141         }
142
143         private string _selectedTab="General";
144         public string SelectedTab
145         {
146             get { return _selectedTab; }
147             set
148             {
149                 _selectedTab = value??"General";
150                 NotifyOfPropertyChange(()=>SelectedTab);
151                 NotifyOfPropertyChange(() => AccountTabSelected);
152             }
153         }
154
155
156         public bool AccountTabSelected
157         {
158             get { return _selectedTab == "AccountTab"; }
159         }
160         #region Preferences Properties
161
162         private bool _noProxy;
163         public bool NoProxy
164         {
165             get { return _noProxy; }
166             set
167             {
168                 _noProxy = value;
169                 NotifyOfPropertyChange(()=>NoProxy);
170             }
171         }
172
173
174         private bool _defaultProxy;
175
176         public bool DefaultProxy
177         {
178             get { return _defaultProxy; }
179             set
180             {
181                 _defaultProxy = value;
182                 NotifyOfPropertyChange(() => DefaultProxy);
183             }
184         }
185
186
187         private bool _manualProxy;
188
189         public bool ManualProxy
190         {
191             get { return _manualProxy; }
192             set
193             {
194                 _manualProxy = value;
195                 NotifyOfPropertyChange(() => ManualProxy);
196             }
197         }
198         #endregion
199
200
201         public int StartupDelay
202         {
203             get { return (int) Settings.StartupDelay.TotalMinutes; }
204             set
205             {
206                 if (value<0)
207                     throw new ArgumentOutOfRangeException("value",Resources.PreferencesViewModel_StartupDelay_Greater_or_equal_to_0);
208                 Settings.StartupDelay = TimeSpan.FromMinutes(value);
209                 NotifyOfPropertyChange(()=>StartupDelay);
210             }
211         }
212        
213         #region Commands
214         
215         public bool CanSelectiveSyncFolders
216         {
217             get { return CurrentAccount != null; }
218         }
219
220         public void SelectiveSyncFolders()
221         {
222             var monitor = Shell.Monitors[CurrentAccount.AccountKey];
223             
224
225             var model = new SelectiveSynchViewModel(monitor,_events,CurrentAccount.Account);
226             if (_windowManager.ShowDialog(model) == true)
227             {
228                 
229             }
230         }
231
232         public void RefreshApiKey()
233         {
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)
236                 return;
237             try
238             {
239
240                 var name = CurrentAccount.AccountName;
241
242                 var loginUri = new Uri(new Uri(CurrentAccount.ServerUrl), "login");
243                 var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString(),name);
244                 if (credentials==null)
245                     return;
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;
251                 Settings.Save();
252                 TaskEx.Delay(10000).ContinueWith(_ =>Shell.MonitorAccount(account.Account));
253                 NotifyOfPropertyChange(() => Accounts);
254             }
255             catch (AggregateException exc)
256             {
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 });
260             }
261             catch (Exception exc)
262             {
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 });
266             }
267
268         }
269
270     
271         public void OpenLogPath()
272         {
273             Shell.OpenLogPath();
274         }
275
276         public void OpenLogConsole()
277         {
278             var logView=IoC.Get<LogConsole.LogConsoleViewModel>();            
279             _windowManager.ShowWindow(logView);
280         }
281
282         public void SaveChanges()
283         {
284             DoSave();
285             TryClose(true);
286         }
287
288         public void RejectChanges()
289         {
290             Settings.Reload();
291             TryClose(false);
292         }
293
294         public void ApplyChanges()
295         {
296             DoSave();
297         }
298
299         private void DoSave()
300         {
301             //SetStartupMode();            
302
303             //Ensure we save the settings changes first
304             foreach (var account in _accountsToRemove)
305             {
306                 Settings.Accounts.Remove(account);
307             }
308
309             foreach (var account in _accountsToAdd)
310             {
311                 Settings.Accounts.Add(account);    
312             }
313
314             Settings.Save();
315
316
317             try
318             {
319                 foreach (var account in _accountsToRemove)
320                 {
321                     Shell.RemoveMonitor(account.ServerUrl, account.AccountName);
322                     Shell.RemoveAccountFromDatabase(account);
323                 }
324
325                 foreach (var account in Settings.Accounts)
326                 {
327                     Shell.MonitorAccount(account);
328                 }
329             }                
330             finally
331             {
332                 _accountsToRemove.Clear();
333                 _accountsToAdd.Clear();
334             }
335
336             NotifyOfPropertyChange(()=>Settings);
337
338             if (IgnoreCertificateErrors)
339                 ServicePointManager.ServerCertificateValidationCallback= (sender,certificate,chain,errors)=> true;
340             else
341             {
342                 ServicePointManager.ServerCertificateValidationCallback = null;
343             }
344         }
345
346      /*   public void ChangePithosFolder()
347         {
348             var browser = new FolderBrowserDialog();
349             browser.SelectedPath = Settings.PithosPath;
350             var result = browser.ShowDialog((IWin32Window)GetView());
351             if (result == DialogResult.OK)
352             {
353                 var newPath = browser.SelectedPath;
354                 var accountName = CurrentAccount.AccountName;
355                 var monitor = Shell.Monitors[accountName];
356                 monitor.Stop();
357                 
358                 Shell.Monitors.Remove(accountName);
359
360                 Directory.Move(Settings.PithosPath, newPath);
361                 Settings.PithosPath = newPath;
362                 Settings.Save();
363
364                 Shell.MonitorAccount(CurrentAccount);
365
366                 NotifyOfPropertyChange(() => Settings);                
367             }
368         }
369 */
370
371
372         readonly List<AccountSettings> _accountsToAdd=new List<AccountSettings>();
373        public void AddAccount()
374        {
375            var wizard = new AddAccountViewModel();
376            if (_windowManager.ShowDialog(wizard) == true)
377            {
378                string selectedPath = wizard.AccountPath;
379                var initialRootPath = wizard.ShouldCreateOkeanosFolder?
380                    Path.Combine(selectedPath, "Okeanos")
381                    :selectedPath;
382                var actualRootPath= initialRootPath;
383                if (wizard.ShouldCreateOkeanosFolder)
384                {
385                    int attempt = 1;
386                    while (Directory.Exists(actualRootPath) || File.Exists(actualRootPath))
387                    {
388                        actualRootPath = String.Format("{0} {1}", initialRootPath, attempt++);
389                    }
390                }
391
392
393
394                var account = Accounts.FirstOrDefault(act => act.AccountName == wizard.AccountName && act.ServerUrl == wizard.CurrentServer);
395                if (account != null)
396                {
397                    if (MessageBox.Show("The account you specified already exists. Do you want to update it?","The account exists") == MessageBoxResult.Yes)
398                    {
399                        account.ApiKey = wizard.Token;
400                        account.IsExpired = false;
401                        CurrentAccount = account;
402                    }
403                }
404                else
405                {
406                    var newAccount = new AccountSettings
407                                         {
408                                             AccountName = wizard.AccountName,
409                                             ServerUrl = wizard.CurrentServer,
410                                             ApiKey = wizard.Token,
411                                             RootPath = actualRootPath,
412                                             IsActive = wizard.IsAccountActive
413                                         };
414                    _accountsToAdd.Add(newAccount);
415                    var accountVm = new AccountViewModel(newAccount);
416                    (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVm);
417                    CurrentAccount = accountVm;
418                }
419                NotifyOfPropertyChange(() => Accounts);
420                NotifyOfPropertyChange(() => Settings);   
421            }
422
423
424             
425        }
426
427 /*
428         public void AddPithosAccount()
429        {
430             var credentials=PithosAccount.RetrieveCredentials(null);
431             if (credentials == null)
432                 return;
433             var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
434             var accountVM = new AccountViewModel(account);
435             if (account == null)
436             {
437                 account=new AccountSettings{
438                     AccountName=credentials.UserName,
439                     ApiKey=credentials.Password
440                 };
441                 Settings.Accounts.Add(account);
442                 accountVM = new AccountViewModel(account);
443                 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVM);
444             }
445             else
446             {
447                 account.ApiKey=credentials.Password;
448             }
449             //SelectedAccountIndex= Settings.Accounts.IndexOf(account);
450             CurrentAccount = accountVM;
451             NotifyOfPropertyChange(() => Accounts);
452             NotifyOfPropertyChange(()=>Settings);                       
453        }
454 */
455
456
457         readonly List<AccountSettings> _accountsToRemove = new List<AccountSettings>();
458         public void RemoveAccount()
459         {
460             Accounts.TryRemove(CurrentAccount);
461             _accountsToRemove.Add(CurrentAccount.Account);
462
463             CurrentAccount = null;
464             NotifyOfPropertyChange(() => Accounts);
465
466             
467             //NotifyOfPropertyChange("Settings.Accounts");
468         }
469
470         public bool CanRemoveAccount
471         {
472             get { return (CurrentAccount != null); }
473         }
474
475         public bool CanClearAccountCache
476         {
477             get { return (CurrentAccount != null); }
478         }
479
480         public void ClearAccountCache()
481         {
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))
487             {
488
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));
493             }
494         }
495
496
497         public bool ExtensionsActivated
498         {
499             get { return Settings.ExtensionsActivated; }
500             set
501             {
502                 if (Settings.ExtensionsActivated == value)
503                     return;
504
505                 Settings.ExtensionsActivated = value;
506
507 /*
508                 if (value)
509                     _extensionController.RegisterExtensions();
510                 else
511                 {
512                     _extensionController.UnregisterExtensions();
513                 }
514 */
515                 NotifyOfPropertyChange(() => ExtensionsActivated);
516             }
517         }
518
519         public bool DebugLoggingEnabled
520         {
521             get { return Settings.DebugLoggingEnabled; }
522             set { 
523                 Settings.DebugLoggingEnabled = value;
524                 NotifyOfPropertyChange(()=>DebugLoggingEnabled);
525             }
526         }
527
528         public bool IgnoreCertificateErrors
529         {
530             get { return Settings.IgnoreCertificateErrors; }
531             set {
532                 Settings.IgnoreCertificateErrors = value;
533                 NotifyOfPropertyChange(() => IgnoreCertificateErrors);
534             }
535         }
536        
537         #endregion
538
539        /* private int _selectedAccountIndex;
540         public int SelectedAccountIndex
541         {
542             get { return _selectedAccountIndex; }
543             set
544             {
545                 //var accountCount=Settings.Accounts.Count;
546                 //if (accountCount == 0)
547                 //    return;
548                 //if (0 <= value && value < accountCount)
549                 //    _selectedAccountIndex = value;
550                 //else
551                 //    _selectedAccountIndex = 0;
552                 _selectedAccountIndex = value;
553                 NotifyOfPropertyChange(() => CurrentAccount);
554                 NotifyOfPropertyChange(() => CanRemoveAccount);
555                 NotifyOfPropertyChange(()=>SelectedAccountIndex);
556             }
557         }*/
558
559         private AccountViewModel _currentAccount;
560         private readonly IWindowManager _windowManager;
561         private readonly string _shortcutPath;
562
563
564         
565         public AccountViewModel CurrentAccount
566         {
567             get { return _currentAccount; }
568             set
569             {
570                 _currentAccount = value;
571                 NotifyOfPropertyChange(()=>CurrentAccount);
572                 NotifyOfPropertyChange(() => CanRemoveAccount);
573                 NotifyOfPropertyChange(() => CanSelectiveSyncFolders);
574                 NotifyOfPropertyChange(() => CanMoveAccountFolder);
575                 NotifyOfPropertyChange(() => CanClearAccountCache);
576             }
577         }
578
579 /*
580         public AccountSettings CurrentAccount
581         {
582             get {
583                 if (0 <= SelectedAccountIndex && SelectedAccountIndex < Settings.Accounts.Count)                    
584                     return Settings.Accounts[SelectedAccountIndex];
585                 return null;
586             }
587
588         }
589 */
590
591
592         public bool CanMoveAccountFolder
593         {
594             get { return CurrentAccount != null; }
595         }
596
597     public void MoveAccountFolder()
598     {
599
600         using (var dlg = new FolderBrowserDialog())
601         {
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)))
608                 return;            
609
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))
614             {
615                 monitor.Stop();
616
617
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))
621                 {
622                     //If it does, do the move
623
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));
628
629                     //Copy all the files
630                     foreach (string newFilePath in Directory.EnumerateFiles(oldPath, "*.*",
631                                                                             SearchOption.AllDirectories))
632                         File.Copy(newFilePath, newFilePath.Replace(oldPath, newPath));
633
634                     Log.InfoFormat("Deleting account folder {0}",oldPath);
635                     Directory.Delete(oldPath, true);
636
637                     //We also need to change the path of the existing file states
638                     monitor.MoveFileStates(oldPath, newPath);
639                 }
640             }
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
644             Settings.Save();            
645             //And start the monitor on the new RootPath            
646             if (monitor != null)
647             {
648                 monitor.RootPath = newPath;
649                 if (CurrentAccount.IsActive)
650                     monitor.Start();
651             }
652             else
653                 Shell.MonitorAccount(CurrentAccount.Account);
654             //Finally, notify that the Settings, CurrentAccount have changed
655             NotifyOfPropertyChange(() => CurrentAccount);
656             NotifyOfPropertyChange(() => Settings);
657
658         }
659     }
660     }
661 }