Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / PithosMonitor.cs @ 70f12b36

History | View | Annotate | Download (18.9 kB)

1
using System;
2
using System.Collections.Concurrent;
3
using System.Collections.Generic;
4
using System.ComponentModel.Composition;
5
using System.Diagnostics;
6
using System.Diagnostics.Contracts;
7
using System.IO;
8
using System.Linq;
9
using System.Net;
10
using System.Net.NetworkInformation;
11
using System.Security.Cryptography;
12
using System.ServiceModel.Description;
13
using System.Text;
14
using System.Threading;
15
using System.Threading.Tasks;
16
using Castle.ActiveRecord.Queries;
17
using Microsoft.WindowsAPICodePack.Net;
18
using Pithos.Core.Agents;
19
using Pithos.Interfaces;
20
using System.ServiceModel;
21
using Pithos.Network;
22
using log4net;
23

    
24
namespace Pithos.Core
25
{
26
    [Export(typeof(PithosMonitor))]
27
    public class PithosMonitor:IDisposable
28
    {
29
        private int _blockSize;
30
        private string _blockHash;
31

    
32
        [Import]
33
        public IPithosSettings Settings{get;set;}
34

    
35
        private IStatusKeeper _statusKeeper;
36

    
37
        [Import]
38
        public IStatusKeeper StatusKeeper
39
        {
40
            get { return _statusKeeper; }
41
            set
42
            {
43
                _statusKeeper = value;
44
                FileAgent.StatusKeeper = value;
45
            }
46
        }
47

    
48

    
49
        private IPithosWorkflow _workflow;
50

    
51
        [Import]
52
        public IPithosWorkflow Workflow
53
        {
54
            get { return _workflow; }
55
            set
56
            {
57
                _workflow = value;
58
                FileAgent.Workflow = value;
59
            }
60
        }
61

    
62
        public ICloudClient CloudClient { get; set; }
63

    
64
        public IStatusNotification StatusNotification { get; set; }
65

    
66
        //[Import]
67
        public FileAgent FileAgent { get; private set; }
68

    
69
        private WorkflowAgent _workflowAgent;
70

    
71
        [Import]
72
        public WorkflowAgent WorkflowAgent
73
        {
74
            get { return _workflowAgent; }
75
            set
76
            {
77
                _workflowAgent = value;
78
                FileAgent.WorkflowAgent = value;
79
            }
80
        }
81
        
82
        [Import]
83
        public NetworkAgent NetworkAgent { get; set; }        
84

    
85
        public string UserName { get; set; }
86
        private string _apiKey;
87
        public string ApiKey
88
        {
89
            get { return _apiKey; }
90
            set
91
            {
92
                _apiKey = value;
93
                if (_accountInfo != null)
94
                    _accountInfo.Token = value;
95
            }
96
        }
97

    
98
        private AccountInfo _accountInfo;
99
        
100
       
101

    
102

    
103
        private static readonly ILog Log = LogManager.GetLogger(typeof(PithosMonitor));
104

    
105

    
106
        public bool Pause
107
        {
108
            get { return FileAgent.Pause; }
109
            set
110
            {
111
                FileAgent.Pause = value;
112
                if (value)
113
                {
114
                    StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused);
115
                    StatusNotification.NotifyChange("Paused");
116
                }
117
                else
118
                {
119
                    StatusKeeper.SetPithosStatus(PithosStatus.InSynch);
120
                    StatusNotification.NotifyChange("Synchronizing");
121
                }
122
            }
123
        }
124

    
125
        private string _rootPath;
126
        public string RootPath
127
        {
128
            get { return _rootPath; }
129
            set 
130
            {
131
                _rootPath = String.IsNullOrWhiteSpace(value) 
132
                    ? String.Empty 
133
                    : value.ToLower();
134
            }
135
        }
136

    
137

    
138
        CancellationTokenSource _cancellationSource;
139

    
140
        public PithosMonitor()
141
        {
142
            FileAgent = new FileAgent();
143

    
144
        }
145
        private bool _started;
146

    
147
        public void Start()
148
        {            
149
            if (String.IsNullOrWhiteSpace(ApiKey))
150
                throw new InvalidOperationException("The ApiKey is empty");
151
            if (String.IsNullOrWhiteSpace(UserName))
152
                throw new InvalidOperationException("The UserName is empty");
153
            if (String.IsNullOrWhiteSpace(AuthenticationUrl))
154
                throw new InvalidOperationException("The Authentication url is empty");
155
            Contract.EndContractBlock();
156

    
157
            //If the account doesn't have a valid path, don't start monitoring but don't throw either
158
            if (String.IsNullOrWhiteSpace(RootPath))
159
                //TODO; Warn user?
160
                return;
161

    
162
            WorkflowAgent.StatusNotification = StatusNotification;
163

    
164
            StatusNotification.NotifyChange("Starting");
165
            if (_started)
166
            {
167
                if (!_cancellationSource.IsCancellationRequested)
168
                    return;
169
            }
170
            _cancellationSource = new CancellationTokenSource();
171
            
172

    
173
            CloudClient=new CloudFilesClient(UserName,ApiKey);
174
            var proxy = ProxyFromSettings();            
175
            CloudClient.Proxy = proxy;
176
            CloudClient.UsePithos = true;
177
            CloudClient.AuthenticationUrl = this.AuthenticationUrl;            
178

    
179
            _accountInfo = CloudClient.Authenticate();            
180
            _accountInfo.SiteUri = AuthenticationUrl;
181
            _accountInfo.AccountPath = RootPath;
182

    
183

    
184
            var pithosFolder = Path.Combine(RootPath, FolderConstants.PithosContainer);
185
            if (!Directory.Exists(pithosFolder))
186
                Directory.CreateDirectory(pithosFolder);
187
            //Create the cache folder and ensure it is hidden
188
            CreateHiddenFolder(RootPath, FolderConstants.CacheFolder);
189

    
190
            var policy=CloudClient.GetAccountPolicies(_accountInfo);
191

    
192
            StatusNotification.NotifyAccount(policy);
193
            EnsurePithosContainers();
194
            
195
            StatusKeeper.BlockHash = _blockHash;
196
            StatusKeeper.BlockSize = _blockSize;
197
            
198
            StatusKeeper.StartProcessing(_cancellationSource.Token);
199
            IndexLocalFiles();
200
            StartWatcherAgent();
201

    
202
            StartNetworkAgent();
203
            
204
            WorkflowAgent.RestartInterruptedFiles(_accountInfo);
205
            _started = true;
206
        }
207

    
208
        private void EnsurePithosContainers()
209
        {
210

    
211
            //Create the two default containers if they are missing
212
            var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
213
            foreach (var container in pithosContainers)
214
            {                
215
                var info=CloudClient.GetContainerInfo(this.UserName, container);
216
                if (info == ContainerInfo.Empty)
217
                {
218
                    CloudClient.CreateContainer(this.UserName, container);
219
                    info = CloudClient.GetContainerInfo(this.UserName, container);
220
                }
221
                _blockSize = info.BlockSize;
222
                _blockHash = info.BlockHash;
223
                _accountInfo.BlockSize = _blockSize;
224
                _accountInfo.BlockHash = _blockHash;
225
            }
226
        }
227

    
228
        public string AuthenticationUrl { get; set; }
229

    
230
        private WebProxy ProxyFromSettings()
231
        {            
232
            if (Settings.UseManualProxy)
233
            {
234
                var proxy = new WebProxy(Settings.ProxyServer, Settings.ProxyPort);
235
                //If the proxy requires specific authentication settings, use them
236
                if (Settings.ProxyAuthentication)
237
                {
238
                    proxy.Credentials=new NetworkCredential(Settings.ProxyUsername,Settings.ProxyPassword,Settings.ProxyDomain);
239
                }
240
                    //Otherwise, if there are generic authentication settings, use them
241
                if (!String.IsNullOrWhiteSpace(CredentialCache.DefaultNetworkCredentials.UserName))
242
                {
243
                    proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
244
                }
245
                return proxy;
246
            }
247
            return null;
248
        }
249

    
250
        private void IndexLocalFiles()
251
        {
252
            StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
253
            using (log4net.ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
254
            {
255
                Log.Info("START");
256
                try
257
                {
258
                    var cachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
259
                    var directory = new DirectoryInfo(RootPath);
260
                    var files =
261
                        from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
262
                        where !file.FullName.StartsWith(cachePath, StringComparison.InvariantCultureIgnoreCase) &&
263
                              !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
264
                        select file;
265
                    StatusKeeper.ProcessExistingFiles(files);
266

    
267
                }
268
                catch (Exception exc)
269
                {
270
                    Log.Error("[ERROR]", exc);
271
                }
272
                finally
273
                {
274
                    Log.Info("[END]");
275
                }
276
            }
277
        }
278

    
279
        
280
  
281

    
282

    
283
       /* private void StartWorkflowAgent()
284
        {
285
            WorkflowAgent.StatusNotification = StatusNotification;
286

    
287
/*            //On Vista and up we can check for a network connection
288
            bool connected=Environment.OSVersion.Version.Major < 6 || NetworkListManager.IsConnectedToInternet;
289
            //If we are not connected retry later
290
            if (!connected)
291
            {
292
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
293
                return;
294
            }#1#
295

    
296
            try
297
            {
298
                WorkflowAgent.Start();                
299
            }
300
            catch (Exception)
301
            {
302
                //Faild to authenticate due to network or account error
303
                //Retry after a while
304
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
305
            }
306
        }*/
307

    
308
        internal class LocalFileComparer:EqualityComparer<CloudAction>
309
        {
310
            public override bool Equals(CloudAction x, CloudAction y)
311
            {
312
                if (x.Action != y.Action)
313
                    return false;
314
                if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
315
                    return false;
316
                if (x.CloudFile != null && y.CloudFile != null )
317
                {
318
                    if (x.CloudFile.Hash == null & y.CloudFile.Hash != null)
319
                        return false;
320
                    if (x.CloudFile.Hash != null & y.CloudFile.Hash == null)
321
                        return false;
322
                    if (x.CloudFile.Hash == null & y.CloudFile.Hash == null)
323
                        return (x.CloudFile.Name == y.CloudFile.Name);
324
                    if (!x.CloudFile.Hash.Equals(y.CloudFile.Hash))
325
                        return false;
326
                }
327
                if (x.CloudFile == null ^ y.CloudFile == null ||
328
                    x.LocalFile == null ^ y.LocalFile == null)
329
                    return false;
330
                return true;
331
            }
332

    
333
            public override int GetHashCode(CloudAction obj)
334
            {
335
                var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
336
                var hash2 = (obj.CloudFile == null) ? int.MaxValue : (obj.CloudFile.Hash ?? obj.CloudFile.Name??"").GetHashCode();
337
                var hash3 = obj.Action.GetHashCode();
338
                return hash1 ^ hash2 & hash3;
339
            }
340
        }        
341

    
342
        private Timer timer;
343

    
344
        private void StartNetworkAgent()
345
        {
346

    
347
            NetworkAgent.AddAccount(_accountInfo);
348

    
349
            NetworkAgent.StatusNotification = StatusNotification;
350
                        
351
            NetworkAgent.Start();
352

    
353
            NetworkAgent.PollRemoteFiles();
354
        }
355

    
356
        //Make sure a hidden cache folder exists to store partial downloads
357
        private static string CreateHiddenFolder(string rootPath, string folderName)
358
        {
359
            if (String.IsNullOrWhiteSpace(rootPath))
360
                throw new ArgumentNullException("rootPath");
361
            if (!Path.IsPathRooted(rootPath))
362
                throw new ArgumentException("rootPath");
363
            if (String.IsNullOrWhiteSpace(folderName))
364
                throw new ArgumentNullException("folderName");
365
            Contract.EndContractBlock();
366

    
367
            var folder = Path.Combine(rootPath, folderName);
368
            if (!Directory.Exists(folder))
369
            {
370
                var info = Directory.CreateDirectory(folder);
371
                info.Attributes |= FileAttributes.Hidden;
372

    
373
                Log.InfoFormat("Created cache Folder: {0}", folder);
374
            }
375
            else
376
            {
377
                var info = new DirectoryInfo(folder);
378
                if ((info.Attributes & FileAttributes.Hidden) == 0)
379
                {
380
                    info.Attributes |= FileAttributes.Hidden;
381
                    Log.InfoFormat("Reset cache folder to hidden: {0}", folder);
382
                }                                
383
            }
384
            return folder;
385
        }
386

    
387
       
388

    
389

    
390
        private void StartWatcherAgent()
391
        {
392
            AgentLocator<FileAgent>.Register(FileAgent,RootPath);
393

    
394
            FileAgent.StatusKeeper = StatusKeeper;
395
            FileAgent.Workflow = Workflow;
396
            FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
397
            FileAgent.Start(_accountInfo, RootPath);
398
        }
399

    
400
        public void Stop()
401
        {
402
            AgentLocator<FileAgent>.Remove(RootPath);
403

    
404
            if (FileAgent!=null)
405
                FileAgent.Stop();
406
            FileAgent = null;
407
            if (timer != null)
408
                timer.Dispose();
409
            timer = null;            
410
        }
411

    
412

    
413
        ~PithosMonitor()
414
        {
415
            Dispose(false);
416
        }
417

    
418
        public void Dispose()
419
        {
420
            Dispose(true);
421
            GC.SuppressFinalize(this);
422
        }
423

    
424
        protected virtual void Dispose(bool disposing)
425
        {
426
            if (disposing)
427
            {
428
                Stop();
429
            }
430
        }
431

    
432

    
433
        public void MoveFileStates(string oldPath, string newPath)
434
        {
435
            if (String.IsNullOrWhiteSpace(oldPath))
436
                throw new ArgumentNullException("oldPath");
437
            if (!Path.IsPathRooted(oldPath))
438
                throw new ArgumentException("oldPath must be an absolute path","oldPath");
439
            if (string.IsNullOrWhiteSpace(newPath))
440
                throw new ArgumentNullException("newPath");
441
            if (!Path.IsPathRooted(newPath))
442
                throw new ArgumentException("newPath must be an absolute path","newPath");
443
            Contract.EndContractBlock();
444

    
445
            StatusKeeper.ChangeRoots(oldPath, newPath);
446
        }
447

    
448
        public void AddSelectivePaths(string[] added)
449
        {
450
           /* FileAgent.SelectivePaths.AddRange(added);
451
            NetworkAgent.SyncPaths(added);*/
452
        }
453

    
454
        public void RemoveSelectivePaths(string[] removed)
455
        {
456
            FileAgent.SelectivePaths.RemoveAll(removed.Contains);
457
            foreach (var removedPath in removed.Where(Directory.Exists))
458
            {
459
                Directory.Delete(removedPath,true);
460
            }
461
        }
462

    
463
        public IEnumerable<string> GetRootFolders()
464
        {
465
            var dirs = from container in CloudClient.ListContainers(UserName)
466
                       from dir in CloudClient.ListObjects(UserName, container.Name, "")
467
                       select dir.Name;
468
            return dirs;
469
        }
470

    
471
        public ObjectInfo GetObjectInfo(string filePath)
472
        {
473
            if (String.IsNullOrWhiteSpace(filePath))
474
                throw new ArgumentNullException("filePath");
475
            Contract.EndContractBlock();
476

    
477
            var file=new FileInfo(filePath);
478
            string relativeUrl;//=file.AsRelativeUrlTo(this.RootPath);
479
            var relativePath = file.AsRelativeTo(RootPath);
480
            
481
            string accountName,container;
482
            
483
            var parts=relativePath.Split('\\');
484

    
485
            var accountInfo = _accountInfo;
486
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
487
            {                
488
                accountName = parts[1];
489
                container = parts[2];
490
                relativeUrl = String.Join("/", parts.Splice(3));
491
                //Create the root URL for the target account
492
                var oldName = UserName;
493
                var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
494
                var nameIndex=absoluteUri.IndexOf(oldName);
495
                var root=absoluteUri.Substring(0, nameIndex);
496

    
497
                accountInfo = new AccountInfo
498
                {
499
                    UserName = accountName,
500
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
501
                    StorageUri = new Uri(root + accountName),
502
                    BlockHash=accountInfo.BlockHash,
503
                    BlockSize=accountInfo.BlockSize,
504
                    Token=accountInfo.Token
505
                };
506
            }
507
            else
508
            {
509
                accountName = this.UserName;
510
                container = parts[0];
511
                relativeUrl = String.Join("/", parts.Splice(1));
512
            }
513
            
514
            var client = new CloudFilesClient(accountInfo);
515
            var objectInfo=client.GetObjectInfo(accountName, container, relativeUrl);
516
            return objectInfo;
517
        }
518
        
519
        public Task<ContainerInfo> GetContainerInfo(string filePath)
520
        {
521
            if (String.IsNullOrWhiteSpace(filePath))
522
                throw new ArgumentNullException("filePath");
523
            Contract.EndContractBlock();
524

    
525
            var file=new FileInfo(filePath);
526
            var relativePath = file.AsRelativeTo(RootPath);
527
            
528
            string accountName,container;
529
            
530
            var parts=relativePath.Split('\\');
531

    
532
            var accountInfo = _accountInfo;
533
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
534
            {                
535
                accountName = parts[1];
536
                container = parts[2];                
537
                //Create the root URL for the target account
538
                var oldName = UserName;
539
                var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
540
                var nameIndex=absoluteUri.IndexOf(oldName);
541
                var root=absoluteUri.Substring(0, nameIndex);
542

    
543
                accountInfo = new AccountInfo
544
                {
545
                    UserName = accountName,
546
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
547
                    StorageUri = new Uri(root + accountName),
548
                    BlockHash=accountInfo.BlockHash,
549
                    BlockSize=accountInfo.BlockSize,
550
                    Token=accountInfo.Token
551
                };
552
            }
553
            else
554
            {
555
                accountName = UserName;
556
                container = parts[0];                
557
            }
558

    
559
            return Task.Factory.StartNew(() =>
560
            {
561
                var client = new CloudFilesClient(accountInfo);
562
                var containerInfo = client.GetContainerInfo(accountName, container);
563
                return containerInfo;
564
            });
565
        }
566
    }
567
}