Statistics
| Branch: | Revision:

root / trunk / Pithos.Client.WPF / Preferences / PreferencesViewModel.cs @ 7d855abd

History | View | Annotate | Download (24.1 kB)

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.Collections.Specialized;
47
using System.ComponentModel.Composition;
48
using System.Diagnostics;
49
using System.IO;
50
using System.Net;
51
using System.Reflection;
52
using System.Threading.Tasks;
53
using System.Windows;
54
using System.Windows.Forms;
55
using Caliburn.Micro;
56
using Pithos.Client.WPF.Configuration;
57
using Pithos.Client.WPF.Properties;
58
using Pithos.Client.WPF.SelectiveSynch;
59
using Pithos.Client.WPF.Utils;
60
using Pithos.Core;
61
using Pithos.Interfaces;
62
using System;
63
using System.Linq;
64
using Pithos.Network;
65
using MessageBox = System.Windows.MessageBox;
66
using Screen = Caliburn.Micro.Screen;
67

    
68
namespace Pithos.Client.WPF.Preferences
69
{
70
    /// <summary>
71
    /// The preferences screen displays user and account settings and updates the PithosMonitor
72
    /// classes when account settings change.
73
    /// </summary>
74
    /// <remarks>
75
    /// The class is a single ViewModel for all Preferences tabs. It can be broken in separate
76
    /// ViewModels, one for each tab.
77
    /// </remarks>
78
    [Export, PartCreationPolicy(CreationPolicy.Shared)]
79
    public class PreferencesViewModel : Screen
80
    {
81
        private readonly IEventAggregator _events;
82

    
83
        //Logging in the Pithos client is provided by log4net
84
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
85

    
86
        private PithosSettings _settings;
87
        public PithosSettings Settings
88
        {
89
            get { return _settings; }
90
            set
91
            {
92
                _settings = value;
93
                NotifyOfPropertyChange(()=>Settings);
94
            }
95
        }
96

    
97
        private ObservableConcurrentCollection<AccountViewModel> _accounts;
98
        public ObservableConcurrentCollection<AccountViewModel> Accounts
99
        {
100
            get { return _accounts; }
101
            set 
102
            { 
103
                _accounts = value;
104
                NotifyOfPropertyChange(()=>Accounts);
105
            }
106
        }
107
        
108
        public bool StartOnSystemStartup { get; set; }
109

    
110
        public ShellViewModel Shell { get;  set; }
111
        //ShellExtensionController _extensionController=new ShellExtensionController();
112

    
113
        public PreferencesViewModel(IWindowManager windowManager, IEventAggregator events, ShellViewModel shell, PithosSettings settings, string currentTab)
114
        {
115
            // ReSharper disable DoNotCallOverridableMethodsInConstructor
116
            //Caliburn.Micro uses DisplayName for the view's title
117
            DisplayName = "Pithos+ Preferences";
118
            // ReSharper restore DoNotCallOverridableMethodsInConstructor
119

    
120
            _windowManager = windowManager;
121
            _events = events;
122

    
123
            Shell = shell;
124
            
125
            Settings=settings;
126
            Accounts = new ObservableConcurrentCollection<AccountViewModel>();
127
            if (settings.Accounts == null)
128
            {
129
                settings.Accounts=new AccountsCollection();
130
                settings.Save();
131
            }
132
            var accountVMs = from account in settings.Accounts
133
                             select new AccountViewModel(account);
134

    
135
            Accounts.AddFromEnumerable(accountVMs);
136
            
137
            var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
138
            _shortcutPath = Path.Combine(startupPath, "Pithos.lnk");
139

    
140

    
141
            StartOnSystemStartup = File.Exists(_shortcutPath);
142

    
143
            SelectedTab = currentTab;
144
        }
145

    
146
        private string _selectedTab="General";
147
        public string SelectedTab
148
        {
149
            get { return _selectedTab; }
150
            set
151
            {
152
                _selectedTab = value??"General";
153
                NotifyOfPropertyChange(()=>SelectedTab);
154
                NotifyOfPropertyChange(() => AccountTabSelected);
155
            }
156
        }
157

    
158

    
159
        public bool AccountTabSelected
160
        {
161
            get { return _selectedTab == "AccountTab"; }
162
        }
163
        #region Preferences Properties
164

    
165
        private bool _noProxy;
166
        public bool NoProxy
167
        {
168
            get { return _noProxy; }
169
            set
170
            {
171
                _noProxy = value;
172
                NotifyOfPropertyChange(()=>NoProxy);
173
            }
174
        }
175

    
176

    
177
        private bool _defaultProxy;
178

    
179
        public bool DefaultProxy
180
        {
181
            get { return _defaultProxy; }
182
            set
183
            {
184
                _defaultProxy = value;
185
                NotifyOfPropertyChange(() => DefaultProxy);
186
            }
187
        }
188

    
189

    
190
        private bool _manualProxy;
191

    
192
        public bool ManualProxy
193
        {
194
            get { return _manualProxy; }
195
            set
196
            {
197
                _manualProxy = value;
198
                NotifyOfPropertyChange(() => ManualProxy);
199
            }
200
        }
201
        #endregion
202

    
203

    
204
        public int StartupDelay
205
        {
206
            get { return (int) Settings.StartupDelay.TotalMinutes; }
207
            set
208
            {
209
                if (value<0)
210
                    throw new ArgumentOutOfRangeException("value",Resources.PreferencesViewModel_StartupDelay_Greater_or_equal_to_0);
211
                Settings.StartupDelay = TimeSpan.FromMinutes(value);
212
                NotifyOfPropertyChange(()=>StartupDelay);
213
            }
214
        }
215
       
216
        #region Commands
217
        
218
        public bool CanSelectiveSyncFolders
219
        {
220
            get { return CurrentAccount != null; }
221
        }
222

    
223
        public void SelectiveSyncFolders()
224
        {
225
            //var monitor = Shell.Monitors[CurrentAccount.AccountKey];
226
            
227

    
228
            var model = new SelectiveSynchViewModel(/*monitor,*/_events,CurrentAccount.Account,CurrentAccount.ApiKey);
229
            if (_windowManager.ShowDialog(model) == true)
230
            {
231
                
232
            }
233
        }
234

    
235
       /* private bool _networkTracing;
236
        public bool NetworkTracing
237
        {
238
            get { return _networkTracing; }
239
            set
240
            {
241
                _networkTracing = value;
242
                
243
            }
244
        }*/
245

    
246
        public void RefreshApiKey()
247
        {
248
            //_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 });
249
            if (CurrentAccount == null)
250
                return;
251
            try
252
            {
253

    
254
                var name = CurrentAccount.AccountName;
255

    
256
                var loginUri = new Uri(new Uri(CurrentAccount.ServerUrl), "login");
257
                var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString(),name);
258
                if (credentials==null)
259
                    return;
260
                //The server will return credentials for a different account, not just the current account
261
                //We need to find the correct account first
262
                var account = Accounts.First(act => act.AccountName == credentials.UserName && act.ServerUrl == CurrentAccount.ServerUrl);
263
                account.ApiKey = credentials.Password;                
264
                account.IsExpired = false;
265
                Settings.Save();
266
                TaskEx.Delay(10000).ContinueWith(_ =>Shell.MonitorAccount(account.Account));
267
                NotifyOfPropertyChange(() => Accounts);
268
            }
269
            catch (AggregateException exc)
270
            {
271
                string message = String.Format("API Key retrieval failed");
272
                Log.Error(message, exc.InnerException);
273
                _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
274
            }
275
            catch (Exception exc)
276
            {
277
                string message = String.Format("API Key retrieval failed");
278
                Log.Error(message, exc);
279
                _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
280
            }
281

    
282
        }
283

    
284
    
285
        public void OpenLogPath()
286
        {
287
            Shell.OpenLogPath();
288
        }
289

    
290
        public void OpenLogConsole()
291
        {
292
            var logView=IoC.Get<LogConsole.LogConsoleViewModel>();            
293
            _windowManager.ShowWindow(logView);
294
        }
295

    
296
        public void SaveChanges()
297
        {
298
            DoSave();
299
            TryClose(true);
300
        }
301

    
302
        public void RejectChanges()
303
        {
304
            Settings.Reload();
305
            TryClose(false);
306
        }
307

    
308
        public void ApplyChanges()
309
        {
310
            DoSave();
311
        }
312

    
313
        private void DoSave()
314
        {
315
            //SetStartupMode();            
316

    
317
            //Ensure we save the settings changes first
318
            foreach (var account in _accountsToRemove)
319
            {
320
                Settings.Accounts.Remove(account);
321
            }
322

    
323
            foreach (var account in _accountsToAdd)
324
            {
325
                Settings.Accounts.Add(account);    
326
            }
327

    
328
            Settings.Save();
329

    
330

    
331
            try
332
            {
333
                foreach (var account in _accountsToRemove)
334
                {
335
                    Shell.RemoveMonitor(account.ServerUrl, account.AccountName);
336
                    Shell.RemoveAccountFromDatabase(account);
337
                }
338

    
339
                foreach (var account in Settings.Accounts)
340
                {
341
                    Shell.MonitorAccount(account);
342
                }
343
            }                
344
            finally
345
            {
346
                _accountsToRemove.Clear();
347
                _accountsToAdd.Clear();
348
            }
349

    
350
            NotifyOfPropertyChange(()=>Settings);
351

    
352
            if (IgnoreCertificateErrors)
353
                ServicePointManager.ServerCertificateValidationCallback= (sender,certificate,chain,errors)=> true;
354
            else
355
            {
356
                ServicePointManager.ServerCertificateValidationCallback = null;
357
            }
358
        }
359

    
360
     /*   public void ChangePithosFolder()
361
        {
362
            var browser = new FolderBrowserDialog();
363
            browser.SelectedPath = Settings.PithosPath;
364
            var result = browser.ShowDialog((IWin32Window)GetView());
365
            if (result == DialogResult.OK)
366
            {
367
                var newPath = browser.SelectedPath;
368
                var accountName = CurrentAccount.AccountName;
369
                var monitor = Shell.Monitors[accountName];
370
                monitor.Stop();
371
                
372
                Shell.Monitors.Remove(accountName);
373

    
374
                Directory.Move(Settings.PithosPath, newPath);
375
                Settings.PithosPath = newPath;
376
                Settings.Save();
377

    
378
                Shell.MonitorAccount(CurrentAccount);
379

    
380
                NotifyOfPropertyChange(() => Settings);                
381
            }
382
        }
383
*/
384

    
385

    
386
        readonly List<AccountSettings> _accountsToAdd=new List<AccountSettings>();
387
       public void AddAccount()
388
       {
389
           var wizard = new AddAccountViewModel();
390
           if (_windowManager.ShowDialog(wizard) == true)
391
           {
392
               string selectedPath = wizard.AccountPath;
393
               var initialRootPath = wizard.ShouldCreateOkeanosFolder?
394
                   Path.Combine(selectedPath, "Okeanos")
395
                   :selectedPath;
396
               var actualRootPath= initialRootPath;
397
               if (wizard.ShouldCreateOkeanosFolder)
398
               {
399
                   int attempt = 1;
400
                   while (Directory.Exists(actualRootPath) || File.Exists(actualRootPath))
401
                   {
402
                       actualRootPath = String.Format("{0} {1}", initialRootPath, attempt++);
403
                   }
404
               }
405

    
406

    
407

    
408
               var account = Accounts.FirstOrDefault(act => act.AccountName == wizard.AccountName && act.ServerUrl == wizard.CurrentServer);
409
               if (account != null)
410
               {
411
                   if (MessageBox.Show("The account you specified already exists. Do you want to update it?", "The account exists") == MessageBoxResult.Yes)
412
                   {
413
                       account.ApiKey = wizard.Token;
414
                       account.IsExpired = false;
415
                       CurrentAccount = account;
416
                   }
417
               }
418
               else
419
               {
420
                   var newAccount = new AccountSettings
421
                                        {
422
                                            AccountName = wizard.AccountName,
423
                                            ServerUrl = wizard.CurrentServer,
424
                                            ApiKey = wizard.Token,
425
                                            RootPath = actualRootPath,
426
                                            IsActive = wizard.IsAccountActive,
427
                                        };
428

    
429

    
430
                   var client = new CloudFilesClient(newAccount.AccountName, newAccount.ApiKey)
431
                                    {
432
                                        AuthenticationUrl = newAccount.ServerUrl, UsePithos = true
433
                                    };
434
                   client.Authenticate();
435

    
436

    
437
                   var dirs = (from container in client.ListContainers(newAccount.AccountName)
438
                               from dir in client.ListObjects(newAccount.AccountName, container.Name)
439
                               where container.Name != "trash"
440
                               select dir)
441
                               .ToTree()
442
                               .Select(d=>d.Uri.ToString())
443
                               .ToArray();
444
                              
445
                   newAccount.SelectiveFolders.AddRange(dirs);
446

    
447
                   //TODO:Add the "pithos" container as a default selection                   
448

    
449
                   _accountsToAdd.Add(newAccount);
450
                   var accountVm = new AccountViewModel(newAccount);
451
                   (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVm);
452
                   CurrentAccount = accountVm;
453
               }
454
               NotifyOfPropertyChange(() => Accounts);
455
               NotifyOfPropertyChange(() => Settings);   
456
           }
457

    
458

    
459
            
460
       }
461

    
462
/*
463
        public void AddPithosAccount()
464
       {
465
            var credentials=PithosAccount.RetrieveCredentials(null);
466
            if (credentials == null)
467
                return;
468
            var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
469
            var accountVM = new AccountViewModel(account);
470
            if (account == null)
471
            {
472
                account=new AccountSettings{
473
                    AccountName=credentials.UserName,
474
                    ApiKey=credentials.Password
475
                };
476
                Settings.Accounts.Add(account);
477
                accountVM = new AccountViewModel(account);
478
                (Accounts as IProducerConsumerCollection<AccountViewModel>).TryAdd(accountVM);
479
            }
480
            else
481
            {
482
                account.ApiKey=credentials.Password;
483
            }
484
            //SelectedAccountIndex= Settings.Accounts.IndexOf(account);
485
            CurrentAccount = accountVM;
486
            NotifyOfPropertyChange(() => Accounts);
487
            NotifyOfPropertyChange(()=>Settings);                       
488
       }
489
*/
490

    
491

    
492
        readonly List<AccountSettings> _accountsToRemove = new List<AccountSettings>();
493
        public void RemoveAccount()
494
        {
495
            Accounts.TryRemove(CurrentAccount);
496
            _accountsToRemove.Add(CurrentAccount.Account);
497

    
498
            CurrentAccount = null;
499
            NotifyOfPropertyChange(() => Accounts);
500

    
501
            
502
            //NotifyOfPropertyChange("Settings.Accounts");
503
        }
504

    
505
        public bool CanRemoveAccount
506
        {
507
            get { return (CurrentAccount != null); }
508
        }
509

    
510
        public bool CanClearAccountCache
511
        {
512
            get { return (CurrentAccount != null); }
513
        }
514

    
515
        public void ClearAccountCache()
516
        {
517
            if (MessageBoxResult.Yes == MessageBox.Show("You are about to delete all partially downloaded files from the account's cache.\n" +
518
                            " You will have to download all partially downloaded data again\n" +
519
                            "This change can not be undone\n\n" +
520
            "Do you wish to delete all partially downloaded data?", "Warning! Clearing account cache",
521
                            MessageBoxButton.YesNo,MessageBoxImage.Question,MessageBoxResult.No))
522
            {
523

    
524
                var cachePath = Path.Combine(CurrentAccount.RootPath, FolderConstants.CacheFolder);
525
                var dir = new DirectoryInfo(cachePath);
526
                //The file may not exist if we just created the account
527
                if (!dir.Exists)
528
                    return;
529
                dir.EnumerateFiles().Apply(file=>file.Delete());
530
                dir.EnumerateDirectories().Apply(folder => folder.Delete(true));
531
            }
532
        }
533

    
534

    
535
        public bool ExtensionsActivated
536
        {
537
            get { return Settings.ExtensionsActivated; }
538
            set
539
            {
540
                if (Settings.ExtensionsActivated == value)
541
                    return;
542

    
543
                Settings.ExtensionsActivated = value;
544

    
545
/*
546
                if (value)
547
                    _extensionController.RegisterExtensions();
548
                else
549
                {
550
                    _extensionController.UnregisterExtensions();
551
                }
552
*/
553
                NotifyOfPropertyChange(() => ExtensionsActivated);
554
            }
555
        }
556

    
557
        public bool DebugLoggingEnabled
558
        {
559
            get { return Settings.DebugLoggingEnabled; }
560
            set { 
561
                Settings.DebugLoggingEnabled = value;
562
                NotifyOfPropertyChange(()=>DebugLoggingEnabled);
563
            }
564
        }
565

    
566
        public bool IgnoreCertificateErrors
567
        {
568
            get { return Settings.IgnoreCertificateErrors; }
569
            set {
570
                Settings.IgnoreCertificateErrors = value;
571
                NotifyOfPropertyChange(() => IgnoreCertificateErrors);
572
            }
573
        }
574
       
575
        #endregion
576

    
577
       /* private int _selectedAccountIndex;
578
        public int SelectedAccountIndex
579
        {
580
            get { return _selectedAccountIndex; }
581
            set
582
            {
583
                //var accountCount=Settings.Accounts.Count;
584
                //if (accountCount == 0)
585
                //    return;
586
                //if (0 <= value && value < accountCount)
587
                //    _selectedAccountIndex = value;
588
                //else
589
                //    _selectedAccountIndex = 0;
590
                _selectedAccountIndex = value;
591
                NotifyOfPropertyChange(() => CurrentAccount);
592
                NotifyOfPropertyChange(() => CanRemoveAccount);
593
                NotifyOfPropertyChange(()=>SelectedAccountIndex);
594
            }
595
        }*/
596

    
597
        private AccountViewModel _currentAccount;
598
        private readonly IWindowManager _windowManager;
599
        private readonly string _shortcutPath;
600

    
601

    
602
        
603
        public AccountViewModel CurrentAccount
604
        {
605
            get { return _currentAccount; }
606
            set
607
            {
608
                _currentAccount = value;
609
                NotifyOfPropertyChange(()=>CurrentAccount);
610
                NotifyOfPropertyChange(() => CanRemoveAccount);
611
                NotifyOfPropertyChange(() => CanSelectiveSyncFolders);
612
                NotifyOfPropertyChange(() => CanMoveAccountFolder);
613
                NotifyOfPropertyChange(() => CanClearAccountCache);
614
            }
615
        }
616

    
617
/*
618
        public AccountSettings CurrentAccount
619
        {
620
            get {
621
                if (0 <= SelectedAccountIndex && SelectedAccountIndex < Settings.Accounts.Count)                    
622
                    return Settings.Accounts[SelectedAccountIndex];
623
                return null;
624
            }
625

    
626
        }
627
*/
628

    
629

    
630
        public bool CanMoveAccountFolder
631
        {
632
            get { return CurrentAccount != null; }
633
        }
634

    
635
    public void MoveAccountFolder()
636
    {
637

    
638
        using (var dlg = new FolderBrowserDialog())
639
        {
640
            var currentFolder = CurrentAccount.RootPath;
641
            dlg.SelectedPath = currentFolder;
642
            //Ask the user to select a folder
643
            //Note: We need a parent window here, which we retrieve with GetView            
644
            var view = (Window)GetView();            
645
            if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view)))
646
                return;            
647

    
648
            var newPath= dlg.SelectedPath;                
649
            //Find the account's monitor and stop it
650
            PithosMonitor monitor;
651
            if (Shell.Monitors.TryGetValue(CurrentAccount.AccountKey, out monitor))
652
            {
653
                monitor.Stop();
654

    
655

    
656
                var oldPath = monitor.RootPath;
657
                //The old directory may not exist eg. if we create an account for the first time
658
                if (Directory.Exists(oldPath))
659
                {
660
                    //If it does, do the move
661

    
662
                    //Now Create all of the directories
663
                    foreach (string dirPath in Directory.EnumerateDirectories(oldPath, "*",
664
                                                           SearchOption.AllDirectories))
665
                        Directory.CreateDirectory(dirPath.Replace(oldPath, newPath));
666

    
667
                    //Copy all the files
668
                    foreach (string newFilePath in Directory.EnumerateFiles(oldPath, "*.*",
669
                                                                            SearchOption.AllDirectories))
670
                        File.Copy(newFilePath, newFilePath.Replace(oldPath, newPath));
671

    
672
                    Log.InfoFormat("Deleting account folder {0}",oldPath);
673
                    Directory.Delete(oldPath, true);
674

    
675
                    //We also need to change the path of the existing file states
676
                    monitor.MoveFileStates(oldPath, newPath);
677
                }
678
            }
679
            //Replace the old rootpath with the new
680
            CurrentAccount.RootPath = newPath;
681
            //TODO: This will save all settings changes. Too coarse grained, need to fix at a later date
682
            Settings.Save();            
683
            //And start the monitor on the new RootPath            
684
            if (monitor != null)
685
            {
686
                monitor.RootPath = newPath;
687
                if (CurrentAccount.IsActive)
688
                    monitor.Start();
689
            }
690
            else
691
                Shell.MonitorAccount(CurrentAccount.Account);
692
            //Finally, notify that the Settings, CurrentAccount have changed
693
            NotifyOfPropertyChange(() => CurrentAccount);
694
            NotifyOfPropertyChange(() => Settings);
695

    
696
        }
697
    }
698
    }
699
}