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