Statistics
| Branch: | Revision:

root / trunk / Pithos.Client.WPF / ShellViewModel.cs @ c28a075a

History | View | Annotate | Download (16.4 kB)

1
using System.Collections.Concurrent;
2
using System.ComponentModel.Composition;
3
using System.Diagnostics;
4
using System.Diagnostics.Contracts;
5
using System.IO;
6
using System.Net;
7
using System.Runtime.InteropServices;
8
using System.ServiceModel;
9
using System.ServiceModel.Description;
10
using System.Threading.Tasks;
11
using System.Windows;
12
using Caliburn.Micro;
13
using Hardcodet.Wpf.TaskbarNotification;
14
using Pithos.Client.WPF.Configuration;
15
using Pithos.Client.WPF.Properties;
16
using Pithos.Core;
17
using Pithos.Interfaces;
18
using System;
19
using System.Collections.Generic;
20
using System.Linq;
21
using System.Text;
22
using StatusService = Pithos.Client.WPF.Services.StatusService;
23

    
24
namespace Pithos.Client.WPF {
25
    using System.ComponentModel.Composition;
26

    
27
    [Export(typeof(IShell))]
28
    public class ShellViewModel : ViewAware, IStatusNotification, IShell, IHandle<Notification>
29
    {
30
       
31
        private IStatusChecker _statusChecker;
32
        private IEventAggregator _events;
33

    
34
        public PithosSettings Settings { get; private set; }
35

    
36
        public IScreen Parent { get; set; }
37

    
38

    
39
        private Dictionary<string, PithosMonitor> _monitors = new Dictionary<string, PithosMonitor>();
40
        public Dictionary<string, PithosMonitor> Monitors
41
        {
42
            get { return _monitors; }
43
        }
44

    
45
        private ServiceHost _statusService { get; set; }
46

    
47
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger("Pithos");
48

    
49
        [ImportingConstructor]
50
        public ShellViewModel(IWindowManager windowManager, IEventAggregator events, IStatusChecker statusChecker, PithosSettings settings)
51
        {
52
            _windowManager = windowManager;
53
            OpenPithosFolderCommand = new PithosCommand(OpenPithosFolder);
54
            _statusChecker = statusChecker;
55
            _events = events;            
56
            Settings = settings;
57
                                   
58
            UsageMessage = "Using 15% of 50 GB";
59
            StatusMessage = "In Synch";
60

    
61
            foreach (var account in settings.Accounts)
62
            {
63

    
64
                MonitorAccount(account);
65
            }
66

    
67
            StartStatusService();
68
        }
69
        
70

    
71
        
72
        public void MonitorAccount(AccountSettings account)
73
        {
74
            PithosMonitor monitor = null;
75
            var accountName = account.AccountName;
76

    
77
            if (_monitors.TryGetValue(accountName,out monitor))
78
            {
79
                //If the account is active
80
                if (account.IsActive)                    
81
                    //Start the monitor. It's OK to start an already started monitor,
82
                    //it will just ignore the call
83
                    monitor.Start();
84
                else
85
                {
86
                    //If the account is inactive
87
                    //Stop and remove the monitor
88
                    RemoveMonitor(accountName);
89
                }
90
                return;
91
            }
92

    
93
            //PithosMonitor uses MEF so we need to resolve it
94
            monitor = new PithosMonitor
95
                          {
96
                              UserName = accountName,
97
                              ApiKey = account.ApiKey,
98
                              UsePithos = account.UsePithos,
99
                              StatusNotification = this,
100
                              RootPath=account.RootPath                             
101
                          };          
102
            IoC.BuildUp(monitor);
103

    
104
            var appSettings = Properties.Settings.Default;
105
            monitor.AuthenticationUrl = account.UsePithos
106
                                            ? appSettings.PithosAuthenticationUrl
107
                                            : appSettings.CloudfilesAuthenticationUrl;
108

    
109
            _monitors[accountName] = monitor;
110

    
111
            if (account.IsActive)
112
            {                
113
                //Don't start a monitor if it doesn't have an account and ApiKey
114
                if (String.IsNullOrWhiteSpace(monitor.UserName) || String.IsNullOrWhiteSpace(monitor.ApiKey))
115
                    return;
116
                StartMonitor(monitor);
117
            }
118
        }
119

    
120

    
121
        protected override void OnViewLoaded(object view)
122
        {
123
            var window = (Window)view;
124
            window.Hide();
125
            UpdateStatus();
126
            base.OnViewLoaded(view);
127
        }
128

    
129

    
130
        #region Status Properties
131

    
132
        private string _statusMessage;
133
        public string StatusMessage
134
        {
135
            get { return _statusMessage; }
136
            set
137
            {
138
                _statusMessage = value;
139
                NotifyOfPropertyChange(() => StatusMessage);
140
            }
141
        }
142

    
143
        private string _usageMessage;
144
        public string UsageMessage
145
        {
146
            get { return _usageMessage; }
147
            set
148
            {
149
                _usageMessage = value;
150
                NotifyOfPropertyChange(() => UsageMessage);
151
            }
152
        }
153

    
154

    
155
        private string _pauseSyncCaption="Pause Syncing";
156
        public string PauseSyncCaption
157
        {
158
            get { return _pauseSyncCaption; }
159
            set
160
            {
161
                _pauseSyncCaption = value;
162
                NotifyOfPropertyChange(() => PauseSyncCaption);
163
            }
164
        }
165

    
166
        private readonly ObservableConcurrentCollection<FileEntry> _recentFiles = new ObservableConcurrentCollection<FileEntry>();
167
        public ObservableConcurrentCollection<FileEntry> RecentFiles
168
        {
169
            get { return _recentFiles; }
170
        }
171

    
172

    
173
        private string _statusIcon="Images/Tray.ico";
174
        public string StatusIcon
175
        {
176
            get { return _statusIcon; }
177
            set
178
            {
179
                _statusIcon = value;
180
                NotifyOfPropertyChange(() => StatusIcon);
181
            }
182
        }
183

    
184
        #endregion
185

    
186
        #region Commands
187

    
188
        public void ShowPreferences()
189
        {
190
            Settings.Reload();
191
            var preferences = new PreferencesViewModel(_events, this,Settings);            
192
            _windowManager.ShowDialog(preferences);
193
        }
194

    
195

    
196
        public PithosCommand OpenPithosFolderCommand { get; private set; }
197

    
198
        public void OpenPithosFolder()
199
        {
200
            Process.Start(Settings.PithosPath);
201
        }
202

    
203
        public void GoToSite()
204
        {
205
            var activeAccount=Settings.Accounts.FirstOrDefault(account => account.IsActive);
206
            if (activeAccount == null)
207
                return;            
208

    
209
            var site = String.Format("http://{0}/ui/?token={1}&user={2}",
210
                Properties.Settings.Default.PithosSite,activeAccount.ApiKey,
211
                activeAccount.AccountName);
212
            Process.Start(site);
213
        }
214

    
215

    
216
        public void ToggleSynching()
217
        {
218
            bool isPaused=false;
219
            foreach (var pair in Monitors)
220
            {
221
                var monitor = pair.Value;
222
                monitor.Pause = !monitor.Pause;
223
                isPaused = monitor.Pause;
224
            }
225

    
226
            PauseSyncCaption = isPaused ? "Resume syncing" : "Pause syncing";
227
            var iconKey = isPaused? "TraySyncPaused" : "TrayInSynch";
228
            StatusIcon = String.Format(@"Images/{0}.ico", iconKey);
229
        }
230

    
231
        public void ExitPithos()
232
        {
233
            foreach (var pair in Monitors)
234
            {
235
                var monitor = pair.Value;
236
                monitor.Stop();
237
            }
238

    
239
            ((Window)GetView()).Close();
240
        }
241
        #endregion
242

    
243

    
244
        private Dictionary<PithosStatus, StatusInfo> iconNames = new List<StatusInfo>
245
            {
246
                new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"),
247
                new StatusInfo(PithosStatus.Syncing, "Syncing Files", "TraySynching"),
248
                new StatusInfo(PithosStatus.SyncPaused, "Sync Paused", "TraySyncPaused")
249
            }.ToDictionary(s => s.Status);
250

    
251
        readonly IWindowManager _windowManager;
252

    
253

    
254
        public void UpdateStatus()
255
        {
256
            var pithosStatus = _statusChecker.GetPithosStatus();
257

    
258
            if (iconNames.ContainsKey(pithosStatus))
259
            {
260
                var info = iconNames[pithosStatus];
261
                StatusIcon = String.Format(@"Images/{0}.ico", info.IconName);
262
                StatusMessage = String.Format("Pithos 1.0\r\n{0}", info.StatusText);
263
            }
264

    
265
            var tv=this.GetView();
266
            _events.Publish(new Notification { Title = "Start", Message = "Start Monitoring", Level = TraceLevel.Info});
267
        }
268

    
269

    
270
       
271
        private Task StartMonitor(PithosMonitor monitor,int retries=0)
272
        {
273
            return Task.Factory.StartNew(() =>
274
            {
275
                using (log4net.ThreadContext.Stacks["Monitor"].Push("Start"))
276
                {
277
                    try
278
                    {
279
                        Log.InfoFormat("Start Monitoring {0}", monitor.UserName);
280

    
281
                        monitor.Start();
282
                    }
283
                    catch (WebException exc)
284
                    {
285
                        if (AbandonRetry(monitor, retries))
286
                            return;
287

    
288
                        if (IsUnauthorized(exc))
289
                        {
290
                            var message = String.Format("API Key Expired for {0}. Starting Renewal",monitor.UserName);                            
291
                            Log.Error(message,exc);
292
                            TryAuthorize(monitor,retries);
293
                        }
294
                        else
295
                        {
296
                            TryLater(monitor, exc,retries);
297
                        }
298
                    }
299
                    catch (Exception exc)
300
                    {
301
                        if (AbandonRetry(monitor, retries)) 
302
                            return;
303

    
304
                        TryLater(monitor,exc,retries);
305
                    }
306
                }
307
            });
308
        }
309

    
310
        private bool AbandonRetry(PithosMonitor monitor, int retries)
311
        {
312
            if (retries > 1)
313
            {
314
                var message = String.Format("Monitoring of account {0} has failed too many times. Will not retry",
315
                                            monitor.UserName);
316
                _events.Publish(new Notification
317
                                    {Title = "Account monitoring failed", Message = message, Level = TraceLevel.Error});
318
                return true;
319
            }
320
            return false;
321
        }
322

    
323

    
324
        private Task TryAuthorize(PithosMonitor monitor,int retries)
325
        {
326
            _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 });
327

    
328
            var authorize= PithosAccount.RetrieveCredentialsAsync(Settings.PithosSite);
329

    
330
            return authorize.ContinueWith(t =>
331
            {
332
                if (t.IsFaulted)
333
                {                    
334
                    string message = String.Format("API Key retrieval for {0} failed", monitor.UserName);
335
                    Log.Error(message,t.Exception.InnerException);
336
                    _events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
337
                    return;
338
                }
339
                var credentials = t.Result;
340
                var account =Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
341
                account.ApiKey = credentials.Password;
342
                monitor.ApiKey = credentials.Password;
343
                Task.Factory.StartNewDelayed(10000, () => StartMonitor(monitor,retries+1));
344
            });
345
        }
346

    
347
        private static bool IsUnauthorized(WebException exc)
348
        {
349
            if (exc==null)
350
                throw new ArgumentNullException("exc");
351
            Contract.EndContractBlock();
352

    
353
            var response = exc.Response as HttpWebResponse;
354
            if (response == null)
355
                return false;
356
            return (response.StatusCode == HttpStatusCode.Unauthorized);
357
        }
358

    
359
        private void TryLater(PithosMonitor monitor, Exception exc,int retries)
360
        {
361
            var message = String.Format("An exception occured. Can't start monitoring\nWill retry in 10 seconds");
362
            Task.Factory.StartNewDelayed(10000, () => StartMonitor(monitor,retries+1));
363
            _events.Publish(new Notification
364
                                {Title = "Error", Message = message, Level = TraceLevel.Error});
365
            Log.Error(message, exc);
366
        }
367

    
368

    
369
        public void NotifyChange(string status, TraceLevel level=TraceLevel.Info)
370
        {
371
            this.StatusMessage = status;
372
            
373
            _events.Publish(new Notification { Title = "Pithos", Message = status, Level = level });
374
        }
375

    
376
        public void NotifyChangedFile(string filePath)
377
        {
378
            var entry = new FileEntry {FullPath=filePath};
379
            IProducerConsumerCollection<FileEntry> files=this.RecentFiles;
380
            FileEntry popped;
381
            while (files.Count > 5)
382
                files.TryTake(out popped);
383
            files.TryAdd(entry);
384
        }
385

    
386

    
387
        public void Handle(Notification notification)
388
        {
389
            if (!Settings.ShowDesktopNotifications)
390
                return;
391
            BalloonIcon icon = BalloonIcon.None;
392
            switch (notification.Level)
393
            {
394
                case TraceLevel.Error:
395
                    icon = BalloonIcon.Error;
396
                    break;
397
                case TraceLevel.Info:
398
                case TraceLevel.Verbose:
399
                    icon = BalloonIcon.Info;
400
                    break;
401
                case TraceLevel.Warning:
402
                    icon = BalloonIcon.Warning;
403
                    break;
404
                default:
405
                    icon = BalloonIcon.None;
406
                    break;
407
            }
408

    
409
            var tv = (ShellView)this.GetView();
410
            tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
411
        }
412

    
413
        public void RemoveMonitor(string accountName)
414
        {
415
            if (String.IsNullOrWhiteSpace(accountName))
416
                return;
417

    
418
            PithosMonitor monitor;
419
            if (Monitors.TryGetValue(accountName, out monitor))
420
            {
421
                Monitors.Remove(accountName);
422
                monitor.Stop();
423
            }
424
        }
425

    
426
        public void RefreshOverlays()
427
        {
428
            foreach (var pair in Monitors)
429
            {
430
                var monitor = pair.Value;
431

    
432
                var path = monitor.RootPath;
433

    
434
                if (String.IsNullOrWhiteSpace(path))
435
                    continue;
436

    
437
                if (!Directory.Exists(path) && !File.Exists(path))
438
                    continue;
439

    
440
                IntPtr pathPointer = Marshal.StringToCoTaskMemAuto(path);
441

    
442
                try
443
                {
444
                    NativeMethods.SHChangeNotify(HChangeNotifyEventID.SHCNE_UPDATEITEM,
445
                                                 HChangeNotifyFlags.SHCNF_PATHW | HChangeNotifyFlags.SHCNF_FLUSHNOWAIT,
446
                                                 pathPointer, IntPtr.Zero);
447
                }
448
                finally
449
                {
450
                    Marshal.FreeHGlobal(pathPointer);
451
                }
452
            }
453
        }
454

    
455
        private void StartStatusService()
456
        {
457
            // Create a ServiceHost for the CalculatorService type and provide the base address.
458
            var baseAddress = new Uri("net.pipe://localhost/pithos");
459
            _statusService = new ServiceHost(typeof(StatusService), baseAddress);
460

    
461
            var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
462

    
463
            _statusService.AddServiceEndpoint(typeof(IStatusService), binding, "net.pipe://localhost/pithos/statuscache");
464
            _statusService.AddServiceEndpoint(typeof(ISettingsService), binding, "net.pipe://localhost/pithos/settings");
465

    
466

    
467
            //// Add a mex endpoint
468
            var smb = new ServiceMetadataBehavior
469
            {
470
                HttpGetEnabled = true,
471
                HttpGetUrl = new Uri("http://localhost:30000/pithos/mex")
472
            };
473
            _statusService.Description.Behaviors.Add(smb);
474

    
475

    
476
            _statusService.Open();
477
        }
478

    
479
        private void StopStatusService()
480
        {
481
            if (_statusService == null)
482
                return;
483

    
484
            if (_statusService.State == CommunicationState.Faulted)
485
                _statusService.Abort();
486
            else if (_statusService.State != CommunicationState.Closed)
487
                _statusService.Close();
488
            _statusService = null;
489

    
490
        }
491
    }
492
}