a6a379072e71d9e986f7cb9b50fd575065ca65ea
[pithos-ms-client] / trunk%2FPithos.Client.WPF%2FPreferences%2FPreferencesViewModel.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.Reflection;
50 using System.Threading.Tasks;
51 using System.Windows;
52 using System.Windows.Forms;
53 using Caliburn.Micro;
54 using Pithos.Client.WPF.Configuration;
55 using Pithos.Client.WPF.Properties;
56 using Pithos.Client.WPF.SelectiveSynch;
57 using Pithos.Core;
58 using Pithos.Interfaces;
59 using System;
60 using System.Linq;
61 using Screen = Caliburn.Micro.Screen;
62
63 namespace Pithos.Client.WPF.Preferences
64 {
65     /// <summary>
66     /// The preferences screen displays user and account settings and updates the PithosMonitor
67     /// classes when account settings change.
68     /// </summary>
69     /// <remarks>
70     /// The class is a single ViewModel for all Preferences tabs. It can be broken in separate
71     /// ViewModels, one for each tab.
72     /// </remarks>
73     [Export]
74     public class PreferencesViewModel : Screen
75     {
76         private readonly IEventAggregator _events;
77
78         //Logging in the Pithos client is provided by log4net
79         private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
80
81         private PithosSettings _settings;
82         public PithosSettings Settings
83         {
84             get { return _settings; }
85             set
86             {
87                 _settings = value;
88                 NotifyOfPropertyChange(()=>Settings);
89             }
90         }
91
92         private ObservableConcurrentCollection<AccountViewModel> _accounts;
93         public ObservableConcurrentCollection<AccountViewModel> Accounts
94         {
95             get { return _accounts; }
96             set 
97             { 
98                 _accounts = value;
99                 NotifyOfPropertyChange(()=>Accounts);
100             }
101         }
102         
103         public bool StartOnSystemStartup { get; set; }
104
105         public ShellViewModel Shell { get;  set; }
106         //ShellExtensionController _extensionController=new ShellExtensionController();
107
108         public PreferencesViewModel(IWindowManager windowManager, IEventAggregator events, ShellViewModel shell, PithosSettings settings, string currentTab)
109         {
110             // ReSharper disable DoNotCallOverridableMethodsInConstructor
111             //Caliburn.Micro uses DisplayName for the view's title
112             DisplayName = "Pithos+ Preferences";
113             // ReSharper restore DoNotCallOverridableMethodsInConstructor
114
115             _windowManager = windowManager;
116             _events = events;
117
118             Shell = shell;
119             
120             Settings=settings;
121             Accounts = new ObservableConcurrentCollection<AccountViewModel>();
122             if (settings.Accounts == null)
123             {
124                 settings.Accounts=new AccountsCollection();
125                 settings.Save();
126             }
127             var accountVMs = from account in settings.Accounts
128                              select new AccountViewModel(account);
129
130             Accounts.AddFromEnumerable(accountVMs);
131             
132             var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
133             _shortcutPath = Path.Combine(startupPath, "Pithos.lnk");
134
135
136             StartOnSystemStartup = File.Exists(_shortcutPath);
137
138             SelectedTab = currentTab;
139         }
140
141         private string _selectedTab="General";
142         public string SelectedTab
143         {
144             get { return _selectedTab; }
145             set
146             {
147                 _selectedTab = value??"General";
148                 NotifyOfPropertyChange(()=>SelectedTab);
149                 NotifyOfPropertyChange(() => AccountTabSelected);
150             }
151         }
152
153
154         public bool AccountTabSelected
155         {
156             get { return _selectedTab == "AccountTab"; }
157         }
158         #region Preferences Properties
159
160         private bool _noProxy;
161         public bool NoProxy
162         {
163             get { return _noProxy; }
164             set
165             {
166                 _noProxy = value;
167                 NotifyOfPropertyChange(()=>NoProxy);
168             }
169         }
170
171
172         private bool _defaultProxy;
173
174         public bool DefaultProxy
175         {
176             get { return _defaultProxy; }
177             set
178             {
179                 _defaultProxy = value;
180                 NotifyOfPropertyChange(() => DefaultProxy);
181             }
182         }
183
184
185         private bool _manualProxy;
186
187         public bool ManualProxy
188         {
189             get { return _manualProxy; }
190             set
191             {
192                 _manualProxy = value;
193                 NotifyOfPropertyChange(() => ManualProxy);
194             }
195         }
196         #endregion
197
198
199         public int StartupDelay
200         {
201             get { return (int) Settings.StartupDelay.TotalMinutes; }
202             set
203             {
204                 if (value<0)
205                     throw new ArgumentOutOfRangeException("value",Resources.PreferencesViewModel_StartupDelay_Greater_or_equal_to_0);
206                 Settings.StartupDelay = TimeSpan.FromMinutes(value);
207                 NotifyOfPropertyChange(()=>StartupDelay);
208             }
209         }
210        
211         #region Commands
212         
213         public bool CanSelectiveSyncFolders
214         {
215             get { return CurrentAccount != null; }
216         }
217
218         public void SelectiveSyncFolders()
219         {
220             var monitor = Shell.Monitors[CurrentAccount.AccountName];
221             
222
223             var model = new SelectiveSynchViewModel(monitor,_events,CurrentAccount.Account);
224             if (_windowManager.ShowDialog(model) == true)
225             {
226                 
227             }
228         }
229
230         public void RefreshApiKey()
231         {
232             //_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 });
233             if (CurrentAccount == null)
234                 return;
235             try
236             {
237
238                 var name = CurrentAccount.AccountName;
239
240                 var loginUri = new Uri(new Uri(CurrentAccount.ServerUrl), "login");
241                 var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString(),name);
242                 if (credentials==null)
243                     return;
244                 //The server will return credentials for a different account, not just the current account
245                 //We need to find the correct account first
246                 var account = Accounts.First(act => act.AccountName == credentials.UserName);
247                 account.ApiKey = credentials.Password;                
248                 account.IsExpired = false;
249                 Settings.Save();
250                 TaskEx.Delay(10000).ContinueWith(_ =>Shell.MonitorAccount(account.Account));
251                 NotifyOfPropertyChange(() => Accounts);
252             }
253             catch (AggregateException exc)
254             {
255                 string message = String.Format("API Key retrieval failed");
256                 Log.Error(message, exc.InnerException);
257                 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
258             }
259             catch (Exception exc)
260             {
261                 string message = String.Format("API Key retrieval failed");
262                 Log.Error(message, exc);
263                 _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
264             }
265
266         }
267
268     
269         public void OpenLogPath()
270         {
271             Shell.OpenLogPath();
272         }
273
274         public void OpenLogConsole()
275         {
276             var logView=IoC.Get<LogConsole.LogConsoleViewModel>();            
277             _windowManager.ShowWindow(logView);
278         }
279
280         public void SaveChanges()
281         {
282             DoSave();
283             TryClose(true);
284         }
285
286         public void RejectChanges()
287         {
288             Settings.Reload();
289             TryClose(false);
290         }
291
292         public void ApplyChanges()
293         {
294             DoSave();
295         }
296
297         private void DoSave()
298         {
299             //SetStartupMode();            
300
301             //Ensure we save the settings changes first
302             foreach (var account in _accountsToRemove)
303             {
304                 Settings.Accounts.Remove(account);
305             }
306
307             foreach (var account in _accountsToAdd)
308             {
309                 Settings.Accounts.Add(account);    
310             }
311
312             Settings.Save();
313
314
315             try
316             {
317                 foreach (var account in _accountsToRemove)
318                 {
319                     Shell.RemoveMonitor(account.AccountName);
320                     Shell.RemoveAccountFromDatabase(account);
321                 }
322
323                 foreach (var account in Settings.Accounts)
324                 {
325                     Shell.MonitorAccount(account);
326                 }
327             }                
328             finally
329             {
330                 _accountsToRemove.Clear();
331                 _accountsToAdd.Clear();
332             }
333
334             NotifyOfPropertyChange(()=>Settings);
335         }
336
337      /*   public void ChangePithosFolder()
338         {
339             var browser = new FolderBrowserDialog();
340             browser.SelectedPath = Settings.PithosPath;
341             var result = browser.ShowDialog((IWin32Window)GetView());
342             if (result == DialogResult.OK)
343             {
344                 var newPath = browser.SelectedPath;
345                 var accountName = CurrentAccount.AccountName;
346                 var monitor = Shell.Monitors[accountName];
347                 monitor.Stop();
348                 
349                 Shell.Monitors.Remove(accountName);
350
351                 Directory.Move(Settings.PithosPath, newPath);
352                 Settings.PithosPath = newPath;
353                 Settings.Save();
354
355                 Shell.MonitorAccount(CurrentAccount);
356
357                 NotifyOfPropertyChange(() => Settings);                
358             }
359         }
360 */
361
362
363         readonly List<AccountSettings> _accountsToAdd=new List<AccountSettings>();
364        public void AddAccount()
365        {
366            var wizard = new AddAccountViewModel();
367            if (_windowManager.ShowDialog(wizard) == true)
368            {
369                string selectedPath = wizard.AccountPath;
370                var initialRootPath = wizard.ShouldCreateOkeanosFolder?
371                    Path.Combine(selectedPath, "Okeanos")
372                    :selectedPath;
373                var actualRootPath= initialRootPath;
374                if (wizard.ShouldCreateOkeanosFolder)
375                {
376                    int attempt = 1;
377                    while (Directory.Exists(actualRootPath) || File.Exists(actualRootPath))
378                    {
379                        actualRootPath = String.Format("{0} {1}", initialRootPath, attempt++);
380                    }
381                }
382
383                var newAccount = new AccountSettings
384                                     {
385                                         AccountName = wizard.AccountName,
386                                         ServerUrl=wizard.CurrentServer,
387                                         ApiKey=wizard.Token,
388                                         RootPath = actualRootPath,
389                                         IsActive=wizard.IsAccountActive
390                                     };
391                _accountsToAdd.Add(newAccount);
392                var accountVm = new AccountViewModel(newAccount);
393                (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVm);
394                CurrentAccount = accountVm;
395                NotifyOfPropertyChange(() => Accounts);
396                NotifyOfPropertyChange(() => Settings);   
397            }
398
399
400             
401        }
402
403 /*
404         public void AddPithosAccount()
405        {
406             var credentials=PithosAccount.RetrieveCredentials(null);
407             if (credentials == null)
408                 return;
409             var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
410             var accountVM = new AccountViewModel(account);
411             if (account == null)
412             {
413                 account=new AccountSettings{
414                     AccountName=credentials.UserName,
415                     ApiKey=credentials.Password
416                 };
417                 Settings.Accounts.Add(account);
418                 accountVM = new AccountViewModel(account);
419                 (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVM);
420             }
421             else
422             {
423                 account.ApiKey=credentials.Password;
424             }
425             //SelectedAccountIndex= Settings.Accounts.IndexOf(account);
426             CurrentAccount = accountVM;
427             NotifyOfPropertyChange(() => Accounts);
428             NotifyOfPropertyChange(()=>Settings);                       
429        }
430 */
431
432
433         readonly List<AccountSettings> _accountsToRemove = new List<AccountSettings>();
434         public void RemoveAccount()
435         {
436             Accounts.TryRemove(CurrentAccount);
437             _accountsToRemove.Add(CurrentAccount.Account);
438
439             CurrentAccount = null;
440             NotifyOfPropertyChange(() => Accounts);
441
442             
443             //NotifyOfPropertyChange("Settings.Accounts");
444         }
445
446         public bool CanRemoveAccount
447         {
448             get { return (CurrentAccount != null); }
449         }
450
451
452
453         public bool ExtensionsActivated
454         {
455             get { return Settings.ExtensionsActivated; }
456             set
457             {
458                 if (Settings.ExtensionsActivated == value)
459                     return;
460
461                 Settings.ExtensionsActivated = value;
462
463 /*
464                 if (value)
465                     _extensionController.RegisterExtensions();
466                 else
467                 {
468                     _extensionController.UnregisterExtensions();
469                 }
470 */
471                 NotifyOfPropertyChange(() => ExtensionsActivated);
472             }
473         }
474
475         public bool DebugLoggingEnabled
476         {
477             get { return Settings.DebugLoggingEnabled; }
478             set { 
479                 Settings.DebugLoggingEnabled = value;
480                 NotifyOfPropertyChange(()=>DebugLoggingEnabled);
481             }
482         }
483        
484         #endregion
485
486        /* private int _selectedAccountIndex;
487         public int SelectedAccountIndex
488         {
489             get { return _selectedAccountIndex; }
490             set
491             {
492                 //var accountCount=Settings.Accounts.Count;
493                 //if (accountCount == 0)
494                 //    return;
495                 //if (0 <= value && value < accountCount)
496                 //    _selectedAccountIndex = value;
497                 //else
498                 //    _selectedAccountIndex = 0;
499                 _selectedAccountIndex = value;
500                 NotifyOfPropertyChange(() => CurrentAccount);
501                 NotifyOfPropertyChange(() => CanRemoveAccount);
502                 NotifyOfPropertyChange(()=>SelectedAccountIndex);
503             }
504         }*/
505
506         private AccountViewModel _currentAccount;
507         private readonly IWindowManager _windowManager;
508         private readonly string _shortcutPath;
509
510
511         
512         public AccountViewModel CurrentAccount
513         {
514             get { return _currentAccount; }
515             set
516             {
517                 _currentAccount = value;
518                 NotifyOfPropertyChange(()=>CurrentAccount);
519                 NotifyOfPropertyChange(() => CanRemoveAccount);
520                 NotifyOfPropertyChange(() => CanSelectiveSyncFolders);
521                 NotifyOfPropertyChange(() => CanMoveAccountFolder);
522             }
523         }
524
525 /*
526         public AccountSettings CurrentAccount
527         {
528             get {
529                 if (0 <= SelectedAccountIndex && SelectedAccountIndex < Settings.Accounts.Count)                    
530                     return Settings.Accounts[SelectedAccountIndex];
531                 return null;
532             }
533
534         }
535 */
536
537
538         public bool CanMoveAccountFolder
539         {
540             get { return CurrentAccount != null; }
541         }
542
543     public void MoveAccountFolder()
544     {
545
546         using (var dlg = new FolderBrowserDialog())
547         {
548             var currentFolder = CurrentAccount.RootPath;
549             dlg.SelectedPath = currentFolder;
550             //Ask the user to select a folder
551             //Note: We need a parent window here, which we retrieve with GetView            
552             var view = (Window)GetView();            
553             if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view)))
554                 return;            
555
556             var newPath= dlg.SelectedPath;                
557             //Find the account's monitor and stop it
558             PithosMonitor monitor;
559             if (Shell.Monitors.TryGetValue(CurrentAccount.AccountName, out monitor))
560             {
561                 monitor.Stop();
562
563
564                 var oldPath = monitor.RootPath;
565                 //The old directory may not exist eg. if we create an account for the first time
566                 if (Directory.Exists(oldPath))
567                 {
568                     //If it does, do the move
569
570                     //Now Create all of the directories
571                     foreach (string dirPath in Directory.EnumerateDirectories(oldPath, "*",
572                                                            SearchOption.AllDirectories))
573                         Directory.CreateDirectory(dirPath.Replace(oldPath, newPath));
574
575                     //Copy all the files
576                     foreach (string newFilePath in Directory.EnumerateFiles(oldPath, "*.*",
577                                                                             SearchOption.AllDirectories))
578                         File.Copy(newFilePath, newFilePath.Replace(oldPath, newPath));
579
580                     Log.InfoFormat("Deleting account folder {0}",oldPath);
581                     Directory.Delete(oldPath, true);
582
583                     //We also need to change the path of the existing file states
584                     monitor.MoveFileStates(oldPath, newPath);
585                 }
586             }
587             //Replace the old rootpath with the new
588             CurrentAccount.RootPath = newPath;
589             //TODO: This will save all settings changes. Too coarse grained, need to fix at a later date
590             Settings.Save();            
591             //And start the monitor on the new RootPath            
592             if (monitor != null)
593             {
594                 monitor.RootPath = newPath;
595                 if (CurrentAccount.IsActive)
596                     monitor.Start();
597             }
598             else
599                 Shell.MonitorAccount(CurrentAccount.Account);
600             //Finally, notify that the Settings, CurrentAccount have changed
601             NotifyOfPropertyChange(() => CurrentAccount);
602             NotifyOfPropertyChange(() => Settings);
603
604         }
605     }
606     }
607 }