Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / PithosMonitor.cs @ c92e02f3

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.NetworkInformation;
10
using System.Security.Cryptography;
11
using System.ServiceModel.Description;
12
using System.Text;
13
using System.Threading;
14
using System.Threading.Tasks;
15
using Castle.ActiveRecord.Queries;
16
using Microsoft.WindowsAPICodePack.Net;
17
using Pithos.Core.Agents;
18
using Pithos.Interfaces;
19
using System.ServiceModel;
20
using Pithos.Network;
21
using log4net;
22

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

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

    
34
        [Import]
35
        public IStatusKeeper StatusKeeper { get; set; }
36

    
37
        [Import]
38
        public IPithosWorkflow Workflow { get; set; }
39

    
40
        public ICloudClient CloudClient { get; set; }
41

    
42
        public IStatusNotification StatusNotification { get; set; }
43

    
44
        [Import]
45
        public FileAgent FileAgent { get; set; }
46
        
47
        [Import]
48
        public WorkflowAgent WorkflowAgent { get; set; }
49
        
50
        [Import]
51
        public NetworkAgent NetworkAgent { get; set; }        
52

    
53
        public string UserName { get; set; }
54
        private string _apiKey;
55
        public string ApiKey
56
        {
57
            get { return _apiKey; }
58
            set
59
            {
60
                _apiKey = value;
61
                if (_accountInfo != null)
62
                    _accountInfo.Token = value;
63
            }
64
        }
65

    
66
        private AccountInfo _accountInfo;
67

    
68

    
69
        private static readonly ILog Log = LogManager.GetLogger(typeof(PithosMonitor));
70

    
71

    
72
        public bool Pause
73
        {
74
            get { return FileAgent.Pause; }
75
            set
76
            {
77
                FileAgent.Pause = value;
78
                if (value)
79
                {
80
                    StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused);
81
                    StatusNotification.NotifyChange("Paused");
82
                }
83
                else
84
                {
85
                    StatusKeeper.SetPithosStatus(PithosStatus.InSynch);
86
                    StatusNotification.NotifyChange("Synchronizing");
87
                }
88
            }
89
        }
90

    
91
        private string _rootPath;
92
        public string RootPath
93
        {
94
            get { return _rootPath; }
95
            set 
96
            {
97
                _rootPath = String.IsNullOrWhiteSpace(value) 
98
                    ? String.Empty 
99
                    : value.ToLower();
100
            }
101
        }
102

    
103

    
104
        CancellationTokenSource _cancellationSource;
105

    
106

    
107
        private bool _started;
108

    
109
        public void Start()
110
        {            
111
            if (String.IsNullOrWhiteSpace(ApiKey))
112
                throw new InvalidOperationException("The ApiKey is empty");
113
            if (String.IsNullOrWhiteSpace(UserName))
114
                throw new InvalidOperationException("The UserName is empty");
115
            if (String.IsNullOrWhiteSpace(AuthenticationUrl))
116
                throw new InvalidOperationException("The Authentication url is empty");
117
            Contract.EndContractBlock();
118

    
119
            //If the account doesn't have a valid path, don't start monitoring but don't throw either
120
            if (String.IsNullOrWhiteSpace(RootPath))
121
                //TODO; Warn user?
122
                return;
123

    
124
            StatusNotification.NotifyChange("Starting");
125
            if (_started)
126
            {
127
                if (!_cancellationSource.IsCancellationRequested)
128
                    return;
129
            }
130
            _cancellationSource = new CancellationTokenSource();
131
            
132

    
133
            CloudClient=new CloudFilesClient(UserName,ApiKey);
134
            var proxyUri = ProxyFromSettings();            
135
            CloudClient.Proxy = proxyUri;
136
            CloudClient.UsePithos = this.UsePithos;
137
            CloudClient.AuthenticationUrl = this.AuthenticationUrl;            
138

    
139
            _accountInfo = CloudClient.Authenticate();
140
            _accountInfo.AccountPath = RootPath;
141

    
142

    
143
            var pithosFolder = Path.Combine(RootPath, FolderConstants.PithosContainer);
144
            if (!Directory.Exists(pithosFolder))
145
                Directory.CreateDirectory(pithosFolder);
146
            //Create the cache folder and ensure it is hidden
147
            CreateHiddenFolder(RootPath, FolderConstants.CacheFolder);
148

    
149
            var policy=CloudClient.GetAccountPolicies(_accountInfo);
150

    
151
            StatusNotification.NotifyAccount(policy);
152
            EnsurePithosContainers();
153
            
154
            StatusKeeper.BlockHash = _blockHash;
155
            StatusKeeper.BlockSize = _blockSize;
156
            
157
            StatusKeeper.StartProcessing(_cancellationSource.Token);
158
            IndexLocalFiles();
159
            StartWatcherAgent();
160

    
161
            StartNetworkAgent();
162

    
163
            StartWorkflowAgent();
164
            WorkflowAgent.RestartInterruptedFiles(_accountInfo);
165
            _started = true;
166
        }
167

    
168
        private void EnsurePithosContainers()
169
        {
170

    
171
            //Create the two default containers if they are missing
172
            var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
173
            foreach (var container in pithosContainers)
174
            {                
175
                var info=CloudClient.GetContainerInfo(this.UserName, container);
176
                if (info == ContainerInfo.Empty)
177
                {
178
                    CloudClient.CreateContainer(this.UserName, container);
179
                    info = CloudClient.GetContainerInfo(this.UserName, container);
180
                }
181
                _blockSize = info.BlockSize;
182
                _blockHash = info.BlockHash;
183
                _accountInfo.BlockSize = _blockSize;
184
                _accountInfo.BlockHash = _blockHash;
185
            }
186
        }
187

    
188
        public string AuthenticationUrl { get; set; }
189

    
190
        private Uri ProxyFromSettings()
191
        {            
192
            if (Settings.UseManualProxy)
193
            {
194
                var proxyUri = new UriBuilder
195
                                   {
196
                                       Host = Settings.ProxyServer, 
197
                                       Port = Settings.ProxyPort
198
                                   };
199
                if (Settings.ProxyAuthentication)
200
                {
201
                    proxyUri.UserName = Settings.ProxyUsername;
202
                    proxyUri.Password = Settings.ProxyPassword;
203
                }
204
                return proxyUri.Uri;
205
            }
206
            return null;
207
        }
208

    
209
        private void IndexLocalFiles()
210
        {
211
            StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
212
            using (log4net.ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
213
            {
214
                Log.Info("START");
215
                try
216
                {
217
                    var cachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
218
                    var directory = new DirectoryInfo(RootPath);
219
                    var files =
220
                        from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
221
                        where !file.FullName.StartsWith(cachePath, StringComparison.InvariantCultureIgnoreCase) &&
222
                              !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
223
                        select file;
224
                    StatusKeeper.ProcessExistingFiles(files);
225

    
226
                }
227
                catch (Exception exc)
228
                {
229
                    Log.Error("[ERROR]", exc);
230
                }
231
                finally
232
                {
233
                    Log.Info("[END]");
234
                }
235
            }
236
        }
237

    
238
        
239
  
240

    
241

    
242
        private void StartWorkflowAgent()
243
        {
244

    
245
            bool connected = NetworkListManager.IsConnectedToInternet;
246
            //If we are not connected retry later
247
            if (!connected)
248
            {
249
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
250
                return;
251
            }
252

    
253
            try
254
            {
255
                WorkflowAgent.StatusNotification = StatusNotification;
256
                WorkflowAgent.Start();                
257
            }
258
            catch (Exception)
259
            {
260
                //Faild to authenticate due to network or account error
261
                //Retry after a while
262
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
263
            }
264
        }
265

    
266
        public bool UsePithos { get; set; }
267

    
268

    
269
        internal class LocalFileComparer:EqualityComparer<CloudAction>
270
        {
271
            public override bool Equals(CloudAction x, CloudAction y)
272
            {
273
                if (x.Action != y.Action)
274
                    return false;
275
                if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
276
                    return false;
277
                if (x.CloudFile != null && y.CloudFile != null )
278
                {
279
                    if (x.CloudFile.Hash == null & y.CloudFile.Hash != null)
280
                        return false;
281
                    if (x.CloudFile.Hash != null & y.CloudFile.Hash == null)
282
                        return false;
283
                    if (x.CloudFile.Hash == null & y.CloudFile.Hash == null)
284
                        return (x.CloudFile.Name == y.CloudFile.Name);
285
                    if (!x.CloudFile.Hash.Equals(y.CloudFile.Hash))
286
                        return false;
287
                }
288
                if (x.CloudFile == null ^ y.CloudFile == null ||
289
                    x.LocalFile == null ^ y.LocalFile == null)
290
                    return false;
291
                return true;
292
            }
293

    
294
            public override int GetHashCode(CloudAction obj)
295
            {
296
                var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
297
                var hash2 = (obj.CloudFile == null) ? int.MaxValue : (obj.CloudFile.Hash ?? obj.CloudFile.Name??"").GetHashCode();
298
                var hash3 = obj.Action.GetHashCode();
299
                return hash1 ^ hash2 & hash3;
300
            }
301
        }        
302

    
303
        private Timer timer;
304

    
305
        private void StartNetworkAgent()
306
        {
307

    
308
            NetworkAgent.AddAccount(_accountInfo);
309

    
310
            NetworkAgent.StatusNotification = StatusNotification;
311
                        
312
            NetworkAgent.Start();
313

    
314
            NetworkAgent.ProcessRemoteFiles();
315
        }
316

    
317
        //Make sure a hidden cache folder exists to store partial downloads
318
        private static string CreateHiddenFolder(string rootPath, string folderName)
319
        {
320
            if (String.IsNullOrWhiteSpace(rootPath))
321
                throw new ArgumentNullException("rootPath");
322
            if (!Path.IsPathRooted(rootPath))
323
                throw new ArgumentException("rootPath");
324
            if (String.IsNullOrWhiteSpace(folderName))
325
                throw new ArgumentNullException("folderName");
326
            Contract.EndContractBlock();
327

    
328
            var folder = Path.Combine(rootPath, folderName);
329
            if (!Directory.Exists(folder))
330
            {
331
                var info = Directory.CreateDirectory(folder);
332
                info.Attributes |= FileAttributes.Hidden;
333

    
334
                Log.InfoFormat("Created cache Folder: {0}", folder);
335
            }
336
            else
337
            {
338
                var info = new DirectoryInfo(folder);
339
                if ((info.Attributes & FileAttributes.Hidden) == 0)
340
                {
341
                    info.Attributes |= FileAttributes.Hidden;
342
                    Log.InfoFormat("Reset cache folder to hidden: {0}", folder);
343
                }                                
344
            }
345
            return folder;
346
        }
347

    
348
       
349

    
350

    
351
        private void StartWatcherAgent()
352
        {
353
            AgentLocator<FileAgent>.Register(FileAgent,RootPath);
354

    
355
            FileAgent.StatusKeeper = StatusKeeper;
356
            FileAgent.Workflow = Workflow;
357
            FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
358
            FileAgent.Start(_accountInfo, RootPath);
359
        }
360

    
361
        public void Stop()
362
        {
363
            AgentLocator<FileAgent>.Remove(RootPath);
364

    
365
            if (FileAgent!=null)
366
                FileAgent.Stop();
367
            FileAgent = null;
368
            if (timer != null)
369
                timer.Dispose();
370
            timer = null;            
371
        }
372

    
373

    
374
        ~PithosMonitor()
375
        {
376
            Dispose(false);
377
        }
378

    
379
        public void Dispose()
380
        {
381
            Dispose(true);
382
            GC.SuppressFinalize(this);
383
        }
384

    
385
        protected virtual void Dispose(bool disposing)
386
        {
387
            if (disposing)
388
            {
389
                Stop();
390
            }
391
        }
392

    
393

    
394
        public void MoveFileStates(string oldPath, string newPath)
395
        {
396
            if (String.IsNullOrWhiteSpace(oldPath))
397
                throw new ArgumentNullException("oldPath");
398
            if (!Path.IsPathRooted(oldPath))
399
                throw new ArgumentException("oldPath must be an absolute path","oldPath");
400
            if (string.IsNullOrWhiteSpace(newPath))
401
                throw new ArgumentNullException("newPath");
402
            if (!Path.IsPathRooted(newPath))
403
                throw new ArgumentException("newPath must be an absolute path","newPath");
404
            Contract.EndContractBlock();
405

    
406
            StatusKeeper.ChangeRoots(oldPath, newPath);
407
        }
408

    
409
        public void AddSelectivePaths(string[] added)
410
        {
411
           /* FileAgent.SelectivePaths.AddRange(added);
412
            NetworkAgent.SyncPaths(added);*/
413
        }
414

    
415
        public void RemoveSelectivePaths(string[] removed)
416
        {
417
            FileAgent.SelectivePaths.RemoveAll(removed.Contains);
418
            foreach (var removedPath in removed.Where(Directory.Exists))
419
            {
420
                Directory.Delete(removedPath,true);
421
            }
422
        }
423

    
424
        public IEnumerable<string> GetRootFolders()
425
        {
426
            var dirs = from container in CloudClient.ListContainers(UserName)
427
                       from dir in CloudClient.ListObjects(UserName, container.Name, "")
428
                       select dir.Name;
429
            return dirs;
430
        }
431

    
432
        public ObjectInfo GetObjectInfo(string filePath)
433
        {
434
            if (String.IsNullOrWhiteSpace(filePath))
435
                throw new ArgumentNullException("filePath");
436
            Contract.EndContractBlock();
437

    
438
            var file=new FileInfo(filePath);
439
            string relativeUrl;//=file.AsRelativeUrlTo(this.RootPath);
440
            var relativePath = file.AsRelativeTo(RootPath);
441
            
442
            string accountName,container;
443
            
444
            var parts=relativePath.Split('\\');
445

    
446
            var accountInfo = _accountInfo;
447
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
448
            {                
449
                accountName = parts[1];
450
                container = parts[2];
451
                relativeUrl = String.Join("/", parts.Splice(3));
452
                //Create the root URL for the target account
453
                var oldName = UserName;
454
                var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
455
                var nameIndex=absoluteUri.IndexOf(oldName);
456
                var root=absoluteUri.Substring(0, nameIndex);
457

    
458
                accountInfo = new AccountInfo
459
                {
460
                    UserName = accountName,
461
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
462
                    StorageUri = new Uri(root + accountName),
463
                    BlockHash=accountInfo.BlockHash,
464
                    BlockSize=accountInfo.BlockSize,
465
                    Token=accountInfo.Token
466
                };
467
            }
468
            else
469
            {
470
                accountName = this.UserName;
471
                container = parts[0];
472
                relativeUrl = String.Join("/", parts.Splice(1));
473
            }
474
            
475
            var client = new CloudFilesClient(accountInfo);
476
            var objectInfo=client.GetObjectInfo(accountName, container, relativeUrl);
477
            return objectInfo;
478
        }
479
        
480
        public Task<ContainerInfo> GetContainerInfo(string filePath)
481
        {
482
            if (String.IsNullOrWhiteSpace(filePath))
483
                throw new ArgumentNullException("filePath");
484
            Contract.EndContractBlock();
485

    
486
            var file=new FileInfo(filePath);
487
            var relativePath = file.AsRelativeTo(RootPath);
488
            
489
            string accountName,container;
490
            
491
            var parts=relativePath.Split('\\');
492

    
493
            var accountInfo = _accountInfo;
494
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
495
            {                
496
                accountName = parts[1];
497
                container = parts[2];                
498
                //Create the root URL for the target account
499
                var oldName = UserName;
500
                var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
501
                var nameIndex=absoluteUri.IndexOf(oldName);
502
                var root=absoluteUri.Substring(0, nameIndex);
503

    
504
                accountInfo = new AccountInfo
505
                {
506
                    UserName = accountName,
507
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
508
                    StorageUri = new Uri(root + accountName),
509
                    BlockHash=accountInfo.BlockHash,
510
                    BlockSize=accountInfo.BlockSize,
511
                    Token=accountInfo.Token
512
                };
513
            }
514
            else
515
            {
516
                accountName = UserName;
517
                container = parts[0];                
518
            }
519

    
520
            return Task.Factory.StartNew(() =>
521
            {
522
                var client = new CloudFilesClient(accountInfo);
523
                var containerInfo = client.GetContainerInfo(accountName, container);
524
                return containerInfo;
525
            });
526
        }
527
    }
528

    
529

    
530
    public interface IStatusNotification
531
    {        
532
        void NotifyChange(string status,TraceLevel level=TraceLevel.Info);
533
        void NotifyChangedFile(string filePath);
534
        void NotifyAccount(AccountInfo policy);
535
    }
536
}