bf22074ddb67726f53071a79f6b6f4d1ea75e36e
[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,CurrentAccount.ApiKey);
226             if (_windowManager.ShowDialog(model) == true)
227             {
228                 
229             }
230         }
231
232        /* private bool _networkTracing;
233         public bool NetworkTracing
234         {
235             get { return _networkTracing; }
236             set
237             {
238                 _networkTracing = value;
239                 
240             }
241         }*/
242
243         public void RefreshApiKey()
244         {
245             //_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 });
246             if (CurrentAccount == null)
247                 return;
248             try
249             {
250
251                 var name = CurrentAccount.AccountName;
252
253                 var loginUri = new Uri(new Uri(CurrentAccount.ServerUrl), "login");
254                 var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString(),name);
255                 if (credentials==null)
256                     return;
257                 //The server will return credentials for a different account, not just the current account
258                 //We need to find the correct account first
259                 var account = Accounts.First(act => act.AccountName == credentials.UserName && act.ServerUrl == CurrentAccount.ServerUrl);
260                 account.ApiKey = credentials.Password;                
261                 account.IsExpired = false;
262                 Settings.Save();
263                 TaskEx.Delay(10000).ContinueWith(_ =>Shell.MonitorAccount(account.Account));
264                 NotifyOfPropertyChange(() => Accounts);
265             }
266             catch (AggregateException exc)
267             {
268                 string message = String.Format("API Key retrieval failed");
269                 Log.Error(message, exc.InnerException);
270                 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
271             }
272             catch (Exception exc)
273             {
274                 string message = String.Format("API Key retrieval failed");
275                 Log.Error(message, exc);
276                 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
277             }
278
279         }
280
281     
282         public void OpenLogPath()
283         {
284             Shell.OpenLogPath();
285         }
286
287         public void OpenLogConsole()
288         {
289             var logView=IoC.Get<LogConsole.LogConsoleViewModel>();            
290             _windowManager.ShowWindow(logView);
291         }
292
293         public void SaveChanges()
294         {
295             DoSave();
296             TryClose(true);
297         }
298
299         public void RejectChanges()
300         {
301             Settings.Reload();
302             TryClose(false);
303         }
304
305         public void ApplyChanges()
306         {
307             DoSave();
308         }
309
310         private void DoSave()
311         {
312             //SetStartupMode();            
313
314             //Ensure we save the settings changes first
315             foreach (var account in _accountsToRemove)
316             {
317                 Settings.Accounts.Remove(account);
318             }
319
320             foreach (var account in _accountsToAdd)
321             {
322                 Settings.Accounts.Add(account);    
323             }
324
325             Settings.Save();
326
327
328             try
329             {
330                 foreach (var account in _accountsToRemove)
331                 {
332                     Shell.RemoveMonitor(account.ServerUrl, account.AccountName);
333                     Shell.RemoveAccountFromDatabase(account);
334                 }
335
336                 foreach (var account in Settings.Accounts)
337                 {
338                     Shell.MonitorAccount(account);
339                 }
340             }                
341             finally
342             {
343                 _accountsToRemove.Clear();
344                 _accountsToAdd.Clear();
345             }
346
347             NotifyOfPropertyChange(()=>Settings);
348
349             if (IgnoreCertificateErrors)
350                 ServicePointManager.ServerCertificateValidationCallback= (sender,certificate,chain,errors)=> true;
351             else
352             {
353                 ServicePointManager.ServerCertificateValidationCallback = null;
354             }
355         }
356
357      /*   public void ChangePithosFolder()
358         {
359             var browser = new FolderBrowserDialog();
360             browser.SelectedPath = Settings.PithosPath;
361             var result = browser.ShowDialog((IWin32Window)GetView());
362             if (result == DialogResult.OK)
363             {
364                 var newPath = browser.SelectedPath;
365                 var accountName = CurrentAccount.AccountName;
366                 var monitor = Shell.Monitors[accountName];
367                 monitor.Stop();
368                 
369                 Shell.Monitors.Remove(accountName);
370
371                 Directory.Move(Settings.PithosPath, newPath);
372                 Settings.PithosPath = newPath;
373                 Settings.Save();
374
375                 Shell.MonitorAccount(CurrentAccount);
376
377                 NotifyOfPropertyChange(() => Settings);                
378             }
379         }
380 */
381
382
383         readonly List<AccountSettings> _accountsToAdd=new List<AccountSettings>();
384        public void AddAccount()
385        {
386            var wizard = new AddAccountViewModel();
387            if (_windowManager.ShowDialog(wizard) == true)
388            {
389                string selectedPath = wizard.AccountPath;
390                var initialRootPath = wizard.ShouldCreateOkeanosFolder?
391                    Path.Combine(selectedPath, "Okeanos")
392                    :selectedPath;
393                var actualRootPath= initialRootPath;
394                if (wizard.ShouldCreateOkeanosFolder)
395                {
396                    int attempt = 1;
397                    while (Directory.Exists(actualRootPath) || File.Exists(actualRootPath))
398                    {
399                        actualRootPath = String.Format("{0} {1}", initialRootPath, attempt++);
400                    }
401                }
402
403
404
405                var account = Accounts.FirstOrDefault(act => act.AccountName == wizard.AccountName && act.ServerUrl == wizard.CurrentServer);
406                if (account != null)
407                {
408                    if (MessageBox.Show("The account you specified already exists. Do you want to update it?","The account exists") == MessageBoxResult.Yes)
409                    {
410                        account.ApiKey = wizard.Token;
411                        account.IsExpired = false;
412                        CurrentAccount = account;
413                    }
414                }
415                else
416                {
417                    var newAccount = new AccountSettings
418                                         {
419                                             AccountName = wizard.AccountName,
420                                             ServerUrl = wizard.CurrentServer,
421                                             ApiKey = wizard.Token,
422                                             RootPath = actualRootPath,
423                                             IsActive = wizard.IsAccountActive
424                                         };
425                    _accountsToAdd.Add(newAccount);
426                    var accountVm = new AccountViewModel(newAccount);
427                    (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVm);
428                    CurrentAccount = accountVm;
429                }
430                NotifyOfPropertyChange(() => Accounts);
431                NotifyOfPropertyChange(() => Settings);   
432            }
433
434
435             
436        }
437
438 /*
439         public void AddPithosAccount()
440        {
441             var credentials=PithosAccount.RetrieveCredentials(null);
442             if (credentials == null)
443                 return;
444             var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
445             var accountVM = new AccountViewModel(account);
446             if (account == null)
447             {
448                 account=new AccountSettings{
449                     AccountName=credentials.UserName,
450                     ApiKey=credentials.Password
451                 };
452                 Settings.Accounts.Add(account);
453                 accountVM = new AccountViewModel(account);
454                 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVM);
455             }
456             else
457             {
458                 account.ApiKey=credentials.Password;
459             }
460             //SelectedAccountIndex= Settings.Accounts.IndexOf(account);
461             CurrentAccount = accountVM;
462             NotifyOfPropertyChange(() => Accounts);
463             NotifyOfPropertyChange(()=>Settings);                       
464        }
465 */
466
467
468         readonly List<AccountSettings> _accountsToRemove = new List<AccountSettings>();
469         public void RemoveAccount()
470         {
471             Accounts.TryRemove(CurrentAccount);
472             _accountsToRemove.Add(CurrentAccount.Account);
473
474             CurrentAccount = null;
475             NotifyOfPropertyChange(() => Accounts);
476
477             
478             //NotifyOfPropertyChange("Settings.Accounts");
479         }
480
481         public bool CanRemoveAccount
482         {
483             get { return (CurrentAccount != null); }
484         }
485
486         public bool CanClearAccountCache
487         {
488             get { return (CurrentAccount != null); }
489         }
490
491         public void ClearAccountCache()
492         {
493             if (MessageBoxResult.Yes == MessageBox.Show("You are about to delete all partially downloaded files from the account's cache.\n" +
494                             " You will have to download all partially downloaded data again\n" +
495                             "This change can not be undone\n\n" +
496             "Do you wish to delete all partially downloaded data?", "Warning! Clearing account cache",
497                             MessageBoxButton.YesNo,MessageBoxImage.Question,MessageBoxResult.No))
498             {
499
500                 var cachePath = Path.Combine(CurrentAccount.RootPath, FolderConstants.CacheFolder);
501                 var dir = new DirectoryInfo(cachePath);
502                 //The file may not exist if we just created the account
503                 if (!dir.Exists)
504                     return;
505                 dir.EnumerateFiles().Apply(file=>file.Delete());
506                 dir.EnumerateDirectories().Apply(folder => folder.Delete(true));
507             }
508         }
509
510
511         public bool ExtensionsActivated
512         {
513             get { return Settings.ExtensionsActivated; }
514             set
515             {
516                 if (Settings.ExtensionsActivated == value)
517                     return;
518
519                 Settings.ExtensionsActivated = value;
520
521 /*
522                 if (value)
523                     _extensionController.RegisterExtensions();
524                 else
525                 {
526                     _extensionController.UnregisterExtensions();
527                 }
528 */
529                 NotifyOfPropertyChange(() => ExtensionsActivated);
530             }
531         }
532
533         public bool DebugLoggingEnabled
534         {
535             get { return Settings.DebugLoggingEnabled; }
536             set { 
537                 Settings.DebugLoggingEnabled = value;
538                 NotifyOfPropertyChange(()=>DebugLoggingEnabled);
539             }
540         }
541
542         public bool IgnoreCertificateErrors
543         {
544             get { return Settings.IgnoreCertificateErrors; }
545             set {
546                 Settings.IgnoreCertificateErrors = value;
547                 NotifyOfPropertyChange(() => IgnoreCertificateErrors);
548             }
549         }
550        
551         #endregion
552
553        /* private int _selectedAccountIndex;
554         public int SelectedAccountIndex
555         {
556             get { return _selectedAccountIndex; }
557             set
558             {
559                 //var accountCount=Settings.Accounts.Count;
560                 //if (accountCount == 0)
561                 //    return;
562                 //if (0 <= value && value < accountCount)
563                 //    _selectedAccountIndex = value;
564                 //else
565                 //    _selectedAccountIndex = 0;
566                 _selectedAccountIndex = value;
567                 NotifyOfPropertyChange(() => CurrentAccount);
568                 NotifyOfPropertyChange(() => CanRemoveAccount);
569                 NotifyOfPropertyChange(()=>SelectedAccountIndex);
570             }
571         }*/
572
573         private AccountViewModel _currentAccount;
574         private readonly IWindowManager _windowManager;
575         private readonly string _shortcutPath;
576
577
578         
579         public AccountViewModel CurrentAccount
580         {
581             get { return _currentAccount; }
582             set
583             {
584                 _currentAccount = value;
585                 NotifyOfPropertyChange(()=>CurrentAccount);
586                 NotifyOfPropertyChange(() => CanRemoveAccount);
587                 NotifyOfPropertyChange(() => CanSelectiveSyncFolders);
588                 NotifyOfPropertyChange(() => CanMoveAccountFolder);
589                 NotifyOfPropertyChange(() => CanClearAccountCache);
590             }
591         }
592
593 /*
594         public AccountSettings CurrentAccount
595         {
596             get {
597                 if (0 <= SelectedAccountIndex && SelectedAccountIndex < Settings.Accounts.Count)                    
598                     return Settings.Accounts[SelectedAccountIndex];
599                 return null;
600             }
601
602         }
603 */
604
605
606         public bool CanMoveAccountFolder
607         {
608             get { return CurrentAccount != null; }
609         }
610
611     public void MoveAccountFolder()
612     {
613
614         using (var dlg = new FolderBrowserDialog())
615         {
616             var currentFolder = CurrentAccount.RootPath;
617             dlg.SelectedPath = currentFolder;
618             //Ask the user to select a folder
619             //Note: We need a parent window here, which we retrieve with GetView            
620             var view = (Window)GetView();            
621             if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view)))
622                 return;            
623
624             var newPath= dlg.SelectedPath;                
625             //Find the account's monitor and stop it
626             PithosMonitor monitor;
627             if (Shell.Monitors.TryGetValue(CurrentAccount.AccountKey, out monitor))
628             {
629                 monitor.Stop();
630
631
632                 var oldPath = monitor.RootPath;
633                 //The old directory may not exist eg. if we create an account for the first time
634                 if (Directory.Exists(oldPath))
635                 {
636                     //If it does, do the move
637
638                     //Now Create all of the directories
639                     foreach (string dirPath in Directory.EnumerateDirectories(oldPath, "*",
640                                                            SearchOption.AllDirectories))
641                         Directory.CreateDirectory(dirPath.Replace(oldPath, newPath));
642
643                     //Copy all the files
644                     foreach (string newFilePath in Directory.EnumerateFiles(oldPath, "*.*",
645                                                                             SearchOption.AllDirectories))
646                         File.Copy(newFilePath, newFilePath.Replace(oldPath, newPath));
647
648                     Log.InfoFormat("Deleting account folder {0}",oldPath);
649                     Directory.Delete(oldPath, true);
650
651                     //We also need to change the path of the existing file states
652                     monitor.MoveFileStates(oldPath, newPath);
653                 }
654             }
655             //Replace the old rootpath with the new
656             CurrentAccount.RootPath = newPath;
657             //TODO: This will save all settings changes. Too coarse grained, need to fix at a later date
658             Settings.Save();            
659             //And start the monitor on the new RootPath            
660             if (monitor != null)
661             {
662                 monitor.RootPath = newPath;
663                 if (CurrentAccount.IsActive)
664                     monitor.Start();
665             }
666             else
667                 Shell.MonitorAccount(CurrentAccount.Account);
668             //Finally, notify that the Settings, CurrentAccount have changed
669             NotifyOfPropertyChange(() => CurrentAccount);
670             NotifyOfPropertyChange(() => Settings);
671
672         }
673     }
674     }
675 }