Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / PithosMonitor.cs @ 34bdb91d

History | View | Annotate | Download (18 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
            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
            WorkflowAgent.RestartInterruptedFiles(_accountInfo);
203
            _started = true;
204
        }
205

    
206
        private void EnsurePithosContainers()
207
        {
208

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

    
226
        public string AuthenticationUrl { get; set; }
227

    
228
        private void IndexLocalFiles()
229
        {
230
            StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
231
            using (log4net.ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
232
            {
233
                Log.Info("START");
234
                try
235
                {
236
                    var cachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
237
                    var directory = new DirectoryInfo(RootPath);
238
                    var files =
239
                        from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
240
                        where !file.FullName.StartsWith(cachePath, StringComparison.InvariantCultureIgnoreCase) &&
241
                              !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
242
                        select file;
243
                    StatusKeeper.ProcessExistingFiles(files);
244

    
245
                }
246
                catch (Exception exc)
247
                {
248
                    Log.Error("[ERROR]", exc);
249
                }
250
                finally
251
                {
252
                    Log.Info("[END]");
253
                }
254
            }
255
        }
256

    
257
        
258
  
259

    
260

    
261
       /* private void StartWorkflowAgent()
262
        {
263
            WorkflowAgent.StatusNotification = StatusNotification;
264

    
265
/*            //On Vista and up we can check for a network connection
266
            bool connected=Environment.OSVersion.Version.Major < 6 || NetworkListManager.IsConnectedToInternet;
267
            //If we are not connected retry later
268
            if (!connected)
269
            {
270
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
271
                return;
272
            }#1#
273

    
274
            try
275
            {
276
                WorkflowAgent.Start();                
277
            }
278
            catch (Exception)
279
            {
280
                //Faild to authenticate due to network or account error
281
                //Retry after a while
282
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
283
            }
284
        }*/
285

    
286
        internal class LocalFileComparer:EqualityComparer<CloudAction>
287
        {
288
            public override bool Equals(CloudAction x, CloudAction y)
289
            {
290
                if (x.Action != y.Action)
291
                    return false;
292
                if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
293
                    return false;
294
                if (x.CloudFile != null && y.CloudFile != null )
295
                {
296
                    if (x.CloudFile.Hash == null & y.CloudFile.Hash != null)
297
                        return false;
298
                    if (x.CloudFile.Hash != null & y.CloudFile.Hash == null)
299
                        return false;
300
                    if (x.CloudFile.Hash == null & y.CloudFile.Hash == null)
301
                        return (x.CloudFile.Name == y.CloudFile.Name);
302
                    if (!x.CloudFile.Hash.Equals(y.CloudFile.Hash))
303
                        return false;
304
                }
305
                if (x.CloudFile == null ^ y.CloudFile == null ||
306
                    x.LocalFile == null ^ y.LocalFile == null)
307
                    return false;
308
                return true;
309
            }
310

    
311
            public override int GetHashCode(CloudAction obj)
312
            {
313
                var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
314
                var hash2 = (obj.CloudFile == null) ? int.MaxValue : (obj.CloudFile.Hash ?? obj.CloudFile.Name??"").GetHashCode();
315
                var hash3 = obj.Action.GetHashCode();
316
                return hash1 ^ hash2 & hash3;
317
            }
318
        }        
319

    
320
        private Timer timer;
321

    
322
        private void StartNetworkAgent()
323
        {
324

    
325
            NetworkAgent.AddAccount(_accountInfo);
326

    
327
            NetworkAgent.StatusNotification = StatusNotification;
328
                        
329
            NetworkAgent.Start();
330

    
331
            NetworkAgent.PollRemoteFiles();
332
        }
333

    
334
        //Make sure a hidden cache folder exists to store partial downloads
335
        private static string CreateHiddenFolder(string rootPath, string folderName)
336
        {
337
            if (String.IsNullOrWhiteSpace(rootPath))
338
                throw new ArgumentNullException("rootPath");
339
            if (!Path.IsPathRooted(rootPath))
340
                throw new ArgumentException("rootPath");
341
            if (String.IsNullOrWhiteSpace(folderName))
342
                throw new ArgumentNullException("folderName");
343
            Contract.EndContractBlock();
344

    
345
            var folder = Path.Combine(rootPath, folderName);
346
            if (!Directory.Exists(folder))
347
            {
348
                var info = Directory.CreateDirectory(folder);
349
                info.Attributes |= FileAttributes.Hidden;
350

    
351
                Log.InfoFormat("Created cache Folder: {0}", folder);
352
            }
353
            else
354
            {
355
                var info = new DirectoryInfo(folder);
356
                if ((info.Attributes & FileAttributes.Hidden) == 0)
357
                {
358
                    info.Attributes |= FileAttributes.Hidden;
359
                    Log.InfoFormat("Reset cache folder to hidden: {0}", folder);
360
                }                                
361
            }
362
            return folder;
363
        }
364

    
365
       
366

    
367

    
368
        private void StartWatcherAgent()
369
        {
370
            AgentLocator<FileAgent>.Register(FileAgent,RootPath);
371

    
372
            FileAgent.StatusKeeper = StatusKeeper;
373
            FileAgent.Workflow = Workflow;
374
            FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
375
            FileAgent.Start(_accountInfo, RootPath);
376
        }
377

    
378
        public void Stop()
379
        {
380
            AgentLocator<FileAgent>.Remove(RootPath);
381

    
382
            if (FileAgent!=null)
383
                FileAgent.Stop();
384
            FileAgent = null;
385
            if (timer != null)
386
                timer.Dispose();
387
            timer = null;            
388
        }
389

    
390

    
391
        ~PithosMonitor()
392
        {
393
            Dispose(false);
394
        }
395

    
396
        public void Dispose()
397
        {
398
            Dispose(true);
399
            GC.SuppressFinalize(this);
400
        }
401

    
402
        protected virtual void Dispose(bool disposing)
403
        {
404
            if (disposing)
405
            {
406
                Stop();
407
            }
408
        }
409

    
410

    
411
        public void MoveFileStates(string oldPath, string newPath)
412
        {
413
            if (String.IsNullOrWhiteSpace(oldPath))
414
                throw new ArgumentNullException("oldPath");
415
            if (!Path.IsPathRooted(oldPath))
416
                throw new ArgumentException("oldPath must be an absolute path","oldPath");
417
            if (string.IsNullOrWhiteSpace(newPath))
418
                throw new ArgumentNullException("newPath");
419
            if (!Path.IsPathRooted(newPath))
420
                throw new ArgumentException("newPath must be an absolute path","newPath");
421
            Contract.EndContractBlock();
422

    
423
            StatusKeeper.ChangeRoots(oldPath, newPath);
424
        }
425

    
426
        public void AddSelectivePaths(string[] added)
427
        {
428
           /* FileAgent.SelectivePaths.AddRange(added);
429
            NetworkAgent.SyncPaths(added);*/
430
        }
431

    
432
        public void RemoveSelectivePaths(string[] removed)
433
        {
434
            FileAgent.SelectivePaths.RemoveAll(removed.Contains);
435
            foreach (var removedPath in removed.Where(Directory.Exists))
436
            {
437
                Directory.Delete(removedPath,true);
438
            }
439
        }
440

    
441
        public IEnumerable<string> GetRootFolders()
442
        {
443
            var dirs = from container in CloudClient.ListContainers(UserName)
444
                       from dir in CloudClient.ListObjects(UserName, container.Name, "")
445
                       select dir.Name;
446
            return dirs;
447
        }
448

    
449
        public ObjectInfo GetObjectInfo(string filePath)
450
        {
451
            if (String.IsNullOrWhiteSpace(filePath))
452
                throw new ArgumentNullException("filePath");
453
            Contract.EndContractBlock();
454

    
455
            var file=new FileInfo(filePath);
456
            string relativeUrl;//=file.AsRelativeUrlTo(this.RootPath);
457
            var relativePath = file.AsRelativeTo(RootPath);
458
            
459
            string accountName,container;
460
            
461
            var parts=relativePath.Split('\\');
462

    
463
            var accountInfo = _accountInfo;
464
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
465
            {                
466
                accountName = parts[1];
467
                container = parts[2];
468
                relativeUrl = String.Join("/", parts.Splice(3));
469
                //Create the root URL for the target account
470
                var oldName = UserName;
471
                var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
472
                var nameIndex=absoluteUri.IndexOf(oldName);
473
                var root=absoluteUri.Substring(0, nameIndex);
474

    
475
                accountInfo = new AccountInfo
476
                {
477
                    UserName = accountName,
478
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
479
                    StorageUri = new Uri(root + accountName),
480
                    BlockHash=accountInfo.BlockHash,
481
                    BlockSize=accountInfo.BlockSize,
482
                    Token=accountInfo.Token
483
                };
484
            }
485
            else
486
            {
487
                accountName = this.UserName;
488
                container = parts[0];
489
                relativeUrl = String.Join("/", parts.Splice(1));
490
            }
491
            
492
            var client = new CloudFilesClient(accountInfo);
493
            var objectInfo=client.GetObjectInfo(accountName, container, relativeUrl);
494
            return objectInfo;
495
        }
496
        
497
        public Task<ContainerInfo> GetContainerInfo(string filePath)
498
        {
499
            if (String.IsNullOrWhiteSpace(filePath))
500
                throw new ArgumentNullException("filePath");
501
            Contract.EndContractBlock();
502

    
503
            var file=new FileInfo(filePath);
504
            var relativePath = file.AsRelativeTo(RootPath);
505
            
506
            string accountName,container;
507
            
508
            var parts=relativePath.Split('\\');
509

    
510
            var accountInfo = _accountInfo;
511
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
512
            {                
513
                accountName = parts[1];
514
                container = parts[2];                
515
                //Create the root URL for the target account
516
                var oldName = UserName;
517
                var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
518
                var nameIndex=absoluteUri.IndexOf(oldName);
519
                var root=absoluteUri.Substring(0, nameIndex);
520

    
521
                accountInfo = new AccountInfo
522
                {
523
                    UserName = accountName,
524
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
525
                    StorageUri = new Uri(root + accountName),
526
                    BlockHash=accountInfo.BlockHash,
527
                    BlockSize=accountInfo.BlockSize,
528
                    Token=accountInfo.Token
529
                };
530
            }
531
            else
532
            {
533
                accountName = UserName;
534
                container = parts[0];                
535
            }
536

    
537
            return Task.Factory.StartNew(() =>
538
            {
539
                var client = new CloudFilesClient(accountInfo);
540
                var containerInfo = client.GetContainerInfo(accountName, container);
541
                return containerInfo;
542
            });
543
        }
544
    }
545
}