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