Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / PithosMonitor.cs @ 79f92570

History | View | Annotate | Download (20.7 kB)

1
#region
2
/* -----------------------------------------------------------------------
3
 * <copyright file="PithosMonitor.cs" company="GRNet">
4
 * 
5
 * Copyright 2011-2012 GRNET S.A. All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or
8
 * without modification, are permitted provided that the following
9
 * conditions are met:
10
 *
11
 *   1. Redistributions of source code must retain the above
12
 *      copyright notice, this list of conditions and the following
13
 *      disclaimer.
14
 *
15
 *   2. Redistributions in binary form must reproduce the above
16
 *      copyright notice, this list of conditions and the following
17
 *      disclaimer in the documentation and/or other materials
18
 *      provided with the distribution.
19
 *
20
 *
21
 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
 * POSSIBILITY OF SUCH DAMAGE.
33
 *
34
 * The views and conclusions contained in the software and
35
 * documentation are those of the authors and should not be
36
 * interpreted as representing official policies, either expressed
37
 * or implied, of GRNET S.A.
38
 * </copyright>
39
 * -----------------------------------------------------------------------
40
 */
41
#endregion
42
using System;
43
using System.Collections.Generic;
44
using System.ComponentModel.Composition;
45
using System.Diagnostics.Contracts;
46
using System.IO;
47
using System.Linq;
48
using System.Reflection;
49
using System.Threading;
50
using System.Threading.Tasks;
51
using Pithos.Core.Agents;
52
using Pithos.Interfaces;
53
using Pithos.Network;
54
using log4net;
55

    
56
namespace Pithos.Core
57
{
58
    [Export(typeof(PithosMonitor))]
59
    public class PithosMonitor:IDisposable
60
    {
61
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
62

    
63
        private int _blockSize;
64
        private string _blockHash;
65

    
66
        [Import]
67
        public IPithosSettings Settings{get;set;}
68

    
69
        private IStatusKeeper _statusKeeper;
70

    
71
        [Import]
72
        public IStatusKeeper StatusKeeper
73
        {
74
            get { return _statusKeeper; }
75
            set
76
            {
77
                _statusKeeper = value;
78
                FileAgent.StatusKeeper = value;
79
            }
80
        }
81

    
82

    
83
        private IPithosWorkflow _workflow;
84

    
85
        [Import]
86
        public IPithosWorkflow Workflow
87
        {
88
            get { return _workflow; }
89
            set
90
            {
91
                _workflow = value;
92
                FileAgent.Workflow = value;
93
            }
94
        }
95

    
96
        public ICloudClient CloudClient { get; set; }
97

    
98
        public IStatusNotification StatusNotification { get; set; }
99

    
100
        //[Import]
101
        public FileAgent FileAgent { get; private set; }
102

    
103
        private WorkflowAgent _workflowAgent;
104

    
105
        [Import]
106
        public WorkflowAgent WorkflowAgent
107
        {
108
            get { return _workflowAgent; }
109
            set
110
            {
111
                _workflowAgent = value;
112
                FileAgent.WorkflowAgent = value;
113
            }
114
        }
115
        
116
        [Import]
117
        public NetworkAgent NetworkAgent { get; set; }
118
        [Import]
119
        public PollAgent PollAgent { get; set; }       
120

    
121
        public string UserName { get; set; }
122
        private string _apiKey;
123
        public string ApiKey
124
        {
125
            get { return _apiKey; }
126
            set
127
            {
128
                _apiKey = value;
129
                if (_accountInfo != null)
130
                    _accountInfo.Token = value;
131
            }
132
        }
133

    
134
        private AccountInfo _accountInfo;
135

    
136

    
137

    
138

    
139

    
140

    
141
        public bool Pause
142
        {
143
            get { return FileAgent.Pause; }
144
            set
145
            {
146
                FileAgent.Pause = value;
147
            }
148
        }
149

    
150
        private string _rootPath;
151
        public string RootPath
152
        {
153
            get { return _rootPath; }
154
            set 
155
            {
156
                _rootPath = String.IsNullOrWhiteSpace(value) 
157
                    ? String.Empty 
158
                    : value.ToLower();
159
            }
160
        }
161

    
162

    
163
        CancellationTokenSource _cancellationSource;
164

    
165
        public PithosMonitor()
166
        {
167
            FileAgent = new FileAgent(5000);
168

    
169
        }
170
        private bool _started;
171

    
172
        public void Start()
173
        {            
174
            if (String.IsNullOrWhiteSpace(ApiKey))
175
                throw new InvalidOperationException("The ApiKey is empty");
176
            if (String.IsNullOrWhiteSpace(UserName))
177
                throw new InvalidOperationException("The UserName is empty");
178
            if (String.IsNullOrWhiteSpace(AuthenticationUrl))
179
                throw new InvalidOperationException("The Authentication url is empty");
180
            Contract.EndContractBlock();
181

    
182
            //If the account doesn't have a valid path, don't start monitoring but don't throw either
183
            if (String.IsNullOrWhiteSpace(RootPath))
184
                //TODO; Warn user?
185
                return;
186

    
187
            WorkflowAgent.StatusNotification = StatusNotification;
188

    
189
            StatusNotification.NotifyChange("Starting");
190
            if (_started)
191
            {
192
                if (!_cancellationSource.IsCancellationRequested)
193
                    return;
194
            }
195
            _cancellationSource = new CancellationTokenSource();
196

    
197

    
198
            CloudClient = new CloudFilesClient(UserName, ApiKey)
199
                              {UsePithos = true, AuthenticationUrl = AuthenticationUrl};
200

    
201

    
202
            _accountInfo = CloudClient.Authenticate();            
203
            _accountInfo.SiteUri = AuthenticationUrl;
204
            _accountInfo.AccountPath = RootPath;
205

    
206

    
207
            var pithosFolder = Path.Combine(RootPath, FolderConstants.PithosContainer);
208
            if (!Directory.Exists(pithosFolder))
209
                Directory.CreateDirectory(pithosFolder);
210
            //Create the cache folder and ensure it is hidden
211
            CreateHiddenFolder(RootPath, FolderConstants.CacheFolder);
212

    
213
            var policy=CloudClient.GetAccountPolicies(_accountInfo);
214

    
215
            StatusNotification.NotifyAccount(policy);
216
            EnsurePithosContainers();
217
            
218
            StatusKeeper.BlockHash = _blockHash;
219
            StatusKeeper.BlockSize = _blockSize;
220
            
221
            StatusKeeper.StartProcessing(_cancellationSource.Token);
222
            IndexLocalFiles();
223
            //Extract the URIs from the string collection
224
            var settings = Settings.Accounts.First(s => s.AccountName == _accountInfo.UserName);
225
            var selectiveUrls=settings.SelectiveFolders.Cast<string>().Select(url => new Uri(url)).ToArray();
226

    
227
            SetSelectivePaths(selectiveUrls,null,null);
228
            
229
            StartWatcherAgent();
230

    
231
            StartNetworkAgent();
232
            
233
            WorkflowAgent.RestartInterruptedFiles(_accountInfo);
234
            _started = true;
235
        }
236

    
237
        private void EnsurePithosContainers()
238
        {
239

    
240
            //Create the two default containers if they are missing
241
            var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
242
            foreach (var container in pithosContainers)
243
            {                
244
                var info=CloudClient.GetContainerInfo(UserName, container);
245
                if (info == ContainerInfo.Empty)
246
                {
247
                    CloudClient.CreateContainer(UserName, container);
248
                    info = CloudClient.GetContainerInfo(UserName, container);
249
                }
250
                _blockSize = info.BlockSize;
251
                _blockHash = info.BlockHash;
252
                _accountInfo.BlockSize = _blockSize;
253
                _accountInfo.BlockHash = _blockHash;
254
            }
255
        }
256

    
257
        public string AuthenticationUrl { get; set; }
258

    
259
        private void IndexLocalFiles()
260
        {
261
            using (ThreadContext.Stacks["Operation"].Push("Indexing local files"))
262
            {
263
                
264
                try
265
                {
266
                    //StatusNotification.NotifyChange("Indexing Local Files");
267
                    Log.Info("Start local indexing");
268
                    StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,"Indexing Local Files");                    
269

    
270
                    var cachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
271
                    var directory = new DirectoryInfo(RootPath);
272
                    var files =
273
                        from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
274
                        where !file.FullName.StartsWith(cachePath, StringComparison.InvariantCultureIgnoreCase) &&
275
                              !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
276
                        select file;
277
                    StatusKeeper.ProcessExistingFiles(files);
278

    
279
                }
280
                catch (Exception exc)
281
                {
282
                    Log.Error("[ERROR]", exc);
283
                }
284
                finally
285
                {
286
                    Log.Info("[END]");
287
                }
288
                StatusNotification.SetPithosStatus(PithosStatus.LocalComplete,"Indexing Completed");
289
            }
290
        }
291

    
292
        
293
  
294

    
295

    
296
       /* private void StartWorkflowAgent()
297
        {
298
            WorkflowAgent.StatusNotification = StatusNotification;
299

    
300
/*            //On Vista and up we can check for a network connection
301
            bool connected=Environment.OSVersion.Version.Major < 6 || NetworkListManager.IsConnectedToInternet;
302
            //If we are not connected retry later
303
            if (!connected)
304
            {
305
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
306
                return;
307
            }#1#
308

    
309
            try
310
            {
311
                WorkflowAgent.Start();                
312
            }
313
            catch (Exception)
314
            {
315
                //Faild to authenticate due to network or account error
316
                //Retry after a while
317
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
318
            }
319
        }*/
320

    
321
        internal class LocalFileComparer:EqualityComparer<CloudAction>
322
        {
323
            public override bool Equals(CloudAction x, CloudAction y)
324
            {
325
                if (x.Action != y.Action)
326
                    return false;
327
                if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
328
                    return false;
329
                if (x.CloudFile != null && y.CloudFile != null )
330
                {
331
                    if (x.CloudFile.Hash == null & y.CloudFile.Hash != null)
332
                        return false;
333
                    if (x.CloudFile.Hash != null & y.CloudFile.Hash == null)
334
                        return false;
335
                    if (x.CloudFile.Hash == null & y.CloudFile.Hash == null)
336
                        return (x.CloudFile.Name == y.CloudFile.Name);
337
                    if (!x.CloudFile.Hash.Equals(y.CloudFile.Hash))
338
                        return false;
339
                }
340
                if (x.CloudFile == null ^ y.CloudFile == null ||
341
                    x.LocalFile == null ^ y.LocalFile == null)
342
                    return false;
343
                return true;
344
            }
345

    
346
            public override int GetHashCode(CloudAction obj)
347
            {
348
                if (obj == null)
349
                    return 0;
350
                var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
351
                var hash2 = (obj.CloudFile == null) ? int.MaxValue : (obj.CloudFile.Hash ?? obj.CloudFile.Name??"").GetHashCode();
352
                var hash3 = obj.Action.GetHashCode();
353
                return hash1 ^ hash2 & hash3;
354
            }
355
        }        
356

    
357

    
358
        private void StartNetworkAgent()
359
        {
360
            NetworkAgent.StatusNotification = StatusNotification;
361

    
362
            //TODO: The Network and Poll agents are not account specific
363
            //They should be moved outside PithosMonitor
364
            NetworkAgent.Start();
365

    
366
            PollAgent.AddAccount(_accountInfo);
367

    
368
            PollAgent.StatusNotification = StatusNotification;
369

    
370
            PollAgent.PollRemoteFiles();
371
        }
372

    
373
        //Make sure a hidden cache folder exists to store partial downloads
374
        private static void CreateHiddenFolder(string rootPath, string folderName)
375
        {
376
            if (String.IsNullOrWhiteSpace(rootPath))
377
                throw new ArgumentNullException("rootPath");
378
            if (!Path.IsPathRooted(rootPath))
379
                throw new ArgumentException("rootPath");
380
            if (String.IsNullOrWhiteSpace(folderName))
381
                throw new ArgumentNullException("folderName");
382
            Contract.EndContractBlock();
383

    
384
            var folder = Path.Combine(rootPath, folderName);
385
            if (!Directory.Exists(folder))
386
            {
387
                var info = Directory.CreateDirectory(folder);
388
                info.Attributes |= FileAttributes.Hidden;
389

    
390
                Log.InfoFormat("Created cache Folder: {0}", folder);
391
            }
392
            else
393
            {
394
                var info = new DirectoryInfo(folder);
395
                if ((info.Attributes & FileAttributes.Hidden) == 0)
396
                {
397
                    info.Attributes |= FileAttributes.Hidden;
398
                    Log.InfoFormat("Reset cache folder to hidden: {0}", folder);
399
                }                                
400
            }
401
        }
402

    
403
       
404

    
405

    
406
        private void StartWatcherAgent()
407
        {
408
            if (Log.IsDebugEnabled)
409
                Log.DebugFormat("Start Folder Monitoring [{0}]",RootPath);
410

    
411
            AgentLocator<FileAgent>.Register(FileAgent,RootPath);
412

    
413
            FileAgent.StatusKeeper = StatusKeeper;
414
            FileAgent.StatusNotification = StatusNotification;
415
            FileAgent.Workflow = Workflow;
416
            FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
417
            FileAgent.Start(_accountInfo, RootPath);
418
        }
419

    
420
        public void Stop()
421
        {
422
            AgentLocator<FileAgent>.Remove(RootPath);
423

    
424
            if (FileAgent!=null)
425
                FileAgent.Stop();
426
            FileAgent = null;
427
        }
428

    
429

    
430
        ~PithosMonitor()
431
        {
432
            Dispose(false);
433
        }
434

    
435
        public void Dispose()
436
        {
437
            Dispose(true);
438
            GC.SuppressFinalize(this);
439
        }
440

    
441
        protected virtual void Dispose(bool disposing)
442
        {
443
            if (disposing)
444
            {
445
                Stop();
446
            }
447
        }
448

    
449

    
450
        public void MoveFileStates(string oldPath, string newPath)
451
        {
452
            if (String.IsNullOrWhiteSpace(oldPath))
453
                throw new ArgumentNullException("oldPath");
454
            if (!Path.IsPathRooted(oldPath))
455
                throw new ArgumentException("oldPath must be an absolute path","oldPath");
456
            if (string.IsNullOrWhiteSpace(newPath))
457
                throw new ArgumentNullException("newPath");
458
            if (!Path.IsPathRooted(newPath))
459
                throw new ArgumentException("newPath must be an absolute path","newPath");
460
            Contract.EndContractBlock();
461

    
462
            StatusKeeper.ChangeRoots(oldPath, newPath);
463
        }
464

    
465
        public void SetSelectivePaths(Uri[] uris,Uri[] added, Uri[] removed)
466
        {
467
            //Convert the uris to paths
468
            var selectivePaths = UrisToFilePaths(uris);
469
            
470
            FileAgent.SelectivePaths=selectivePaths;
471
            PollAgent.SetSyncUris(uris);
472
            
473
            var removedPaths = UrisToFilePaths(removed);
474
            UnversionSelectivePaths(removedPaths);
475

    
476
        }
477

    
478
        /// <summary>
479
        /// Mark all unselected paths as Unversioned
480
        /// </summary>
481
        /// <param name="removed"></param>
482
        private void UnversionSelectivePaths(List<string> removed)
483
        {
484
            if (removed == null)
485
                return;
486

    
487
            //Ensure we remove any file state below the deleted folders
488
            FileState.UnversionPaths(removed);
489
        }
490

    
491

    
492
        /// <summary>
493
        /// Return a list of absolute filepaths from a list of Uris
494
        /// </summary>
495
        /// <param name="uris"></param>
496
        /// <returns></returns>
497
        private List<string> UrisToFilePaths(IEnumerable<Uri> uris)
498
        {
499
            if (uris == null)
500
                return new List<string>();
501

    
502
            return (from uri in uris
503
                    let relativePath = _accountInfo.StorageUri
504
                        .MakeRelativeUri(uri)
505
                        .RelativeUriToFilePath()
506
                        //Trim the account name
507
                    select Path.Combine(RootPath, relativePath.After(_accountInfo.UserName + '\\'))).ToList();            
508
        }
509

    
510

    
511
        public ObjectInfo GetObjectInfo(string filePath)
512
        {
513
            if (String.IsNullOrWhiteSpace(filePath))
514
                throw new ArgumentNullException("filePath");
515
            Contract.EndContractBlock();
516

    
517
            var file=new FileInfo(filePath);
518
            string relativeUrl;//=file.AsRelativeUrlTo(this.RootPath);
519
            var relativePath = file.AsRelativeTo(RootPath);
520
            
521
            string accountName,container;
522
            
523
            var parts=relativePath.Split('\\');
524

    
525
            var accountInfo = _accountInfo;
526
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
527
            {                
528
                accountName = parts[1];
529
                container = parts[2];
530
                relativeUrl = String.Join("/", parts.Splice(3));
531
                //Create the root URL for the target account
532
                var oldName = UserName;
533
                var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
534
                var nameIndex=absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
535
                var root=absoluteUri.Substring(0, nameIndex);
536

    
537
                accountInfo = new AccountInfo
538
                {
539
                    UserName = accountName,
540
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
541
                    StorageUri = new Uri(root + accountName),
542
                    BlockHash=accountInfo.BlockHash,
543
                    BlockSize=accountInfo.BlockSize,
544
                    Token=accountInfo.Token
545
                };
546
            }
547
            else
548
            {
549
                accountName = UserName;
550
                container = parts[0];
551
                relativeUrl = String.Join("/", parts.Splice(1));
552
            }
553
            
554
            var client = new CloudFilesClient(accountInfo);
555
            var objectInfo=client.GetObjectInfo(accountName, container, relativeUrl);
556
            return objectInfo;
557
        }
558
        
559
        public Task<ContainerInfo> GetContainerInfo(string filePath)
560
        {
561
            if (String.IsNullOrWhiteSpace(filePath))
562
                throw new ArgumentNullException("filePath");
563
            Contract.EndContractBlock();
564

    
565
            var file=new FileInfo(filePath);
566
            var relativePath = file.AsRelativeTo(RootPath);
567
            
568
            string accountName,container;
569
            
570
            var parts=relativePath.Split('\\');
571

    
572
            var accountInfo = _accountInfo;
573
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
574
            {                
575
                accountName = parts[1];
576
                container = parts[2];                
577
                //Create the root URL for the target account
578
                var oldName = UserName;
579
                var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
580
                var nameIndex=absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
581
                var root=absoluteUri.Substring(0, nameIndex);
582

    
583
                accountInfo = new AccountInfo
584
                {
585
                    UserName = accountName,
586
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
587
                    StorageUri = new Uri(root + accountName),
588
                    BlockHash=accountInfo.BlockHash,
589
                    BlockSize=accountInfo.BlockSize,
590
                    Token=accountInfo.Token
591
                };
592
            }
593
            else
594
            {
595
                accountName = UserName;
596
                container = parts[0];                
597
            }
598

    
599
            return Task.Factory.StartNew(() =>
600
            {
601
                var client = new CloudFilesClient(accountInfo);
602
                var containerInfo = client.GetContainerInfo(accountName, container);
603
                return containerInfo;
604
            });
605
        }
606
    }
607
}