Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / PithosMonitor.cs @ 9c6d3193

History | View | Annotate | Download (19.3 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
            StatusNotification.NotifyChange("Starting");
163
            if (_started)
164
            {
165
                if (!_cancellationSource.IsCancellationRequested)
166
                    return;
167
            }
168
            _cancellationSource = new CancellationTokenSource();
169
            
170

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

    
177
            _accountInfo = CloudClient.Authenticate();            
178
            _accountInfo.SiteUri = AuthenticationUrl;
179
            _accountInfo.AccountPath = RootPath;
180

    
181

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

    
188
            var policy=CloudClient.GetAccountPolicies(_accountInfo);
189

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

    
200
            StartNetworkAgent();
201

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

    
207
        private void EnsurePithosContainers()
208
        {
209

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

    
227
        public string AuthenticationUrl { get; set; }
228

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

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

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

    
278
        
279
  
280

    
281

    
282
        private void StartWorkflowAgent()
283
        {
284
            WorkflowAgent.StatusNotification = StatusNotification;
285

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

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

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

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

    
341
        private Timer timer;
342

    
343
        private void StartNetworkAgent()
344
        {
345

    
346
            NetworkAgent.AddAccount(_accountInfo);
347

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

    
352
            NetworkAgent.PollRemoteFiles();
353
        }
354

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

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

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

    
386
       
387

    
388

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

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

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

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

    
411

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

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

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

    
431

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

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

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

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

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

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

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

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

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

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

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

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

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

    
567

    
568
    public interface IStatusNotification
569
    {        
570
        void NotifyChange(string status,TraceLevel level=TraceLevel.Info);
571
        void NotifyChangedFile(string filePath);
572
        void NotifyAccount(AccountInfo policy);
573
        void NotifyConflicts(IEnumerable<FileSystemInfo> conflictFiles, string message);
574
        void NotifyForFiles(IEnumerable<FileSystemInfo> files, string message,TraceLevel level);
575
    }
576
}