Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / PithosMonitor.cs @ 4b58c004

History | View | Annotate | Download (22.9 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

    
84

    
85
        private IPithosWorkflow _workflow;
86

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

    
98
        public ICloudClient CloudClient { get; set; }
99

    
100
        public IStatusNotification StatusNotification { get; set; }
101

    
102
        //[Import]
103
        public FileAgent FileAgent { get; private set; }
104

    
105
/*
106
        private WorkflowAgent _workflowAgent;
107

    
108
        [Import]
109
        public WorkflowAgent WorkflowAgent
110
        {
111
            get { return _workflowAgent; }
112
            set
113
            {
114
                _workflowAgent = value;
115
                //FileAgent.WorkflowAgent = value;
116
            }
117
        }
118
*/
119
        
120
        [Import]
121
        public NetworkAgent NetworkAgent { get; set; }
122

    
123
        private PollAgent _pollAgent;
124

    
125
        [Import]
126
        public PollAgent PollAgent
127
        {
128
            get { return _pollAgent; }
129
            set
130
            {
131
                _pollAgent = value;
132
                FileAgent.PollAgent = value;
133
            }
134
        }
135

    
136
        private Selectives _selectives;
137

    
138
        [Import]
139
        public Selectives Selectives
140
        {
141
            get { return _selectives; }
142
            set
143
            {
144
                _selectives = value;
145
                FileAgent.Selectives = value;
146
            }
147
        }
148

    
149
        public string UserName { get; set; }
150
        private string _apiKey;
151
        public string ApiKey
152
        {
153
            get { return _apiKey; }
154
            set
155
            {
156
                _apiKey = value;
157
                if (_accountInfo != null)
158
                    _accountInfo.Token = value;
159
            }
160
        }
161

    
162
        private AccountInfo _accountInfo;
163

    
164
        public AccountInfo Account
165
        {
166
            get { return _accountInfo; }
167
        }
168

    
169

    
170

    
171

    
172

    
173
        public bool Pause { get; set; }       
174
        /*public bool Pause
175
        {
176
            get { return FileAgent.Pause; }
177
            set
178
            {
179
                FileAgent.Pause = value;
180
            }
181
        }*/
182

    
183
        private string _rootPath;
184
        public string RootPath
185
        {
186
            get { return _rootPath; }
187
            set 
188
            {
189
                _rootPath = String.IsNullOrWhiteSpace(value) 
190
                    ? String.Empty 
191
                    : value.ToLower();
192
            }
193
        }
194

    
195

    
196
        CancellationTokenSource _cancellationSource;
197

    
198
        public PithosMonitor()
199
        {
200
            FileAgent = new FileAgent();            
201
        }
202
        private bool _started;
203

    
204
        public async Task Start()
205
        {            
206
            if (String.IsNullOrWhiteSpace(ApiKey))
207
                throw new InvalidOperationException("The ApiKey is empty");
208
            if (String.IsNullOrWhiteSpace(UserName))
209
                throw new InvalidOperationException("The UserName is empty");
210
            if (String.IsNullOrWhiteSpace(AuthenticationUrl))
211
                throw new InvalidOperationException("The Authentication url is empty");
212
            Contract.EndContractBlock();
213

    
214
            //If the account doesn't have a valid path, don't start monitoring but don't throw either
215
            if (String.IsNullOrWhiteSpace(RootPath))
216
                //TODO; Warn user?
217
                return;
218

    
219
            //WorkflowAgent.StatusNotification = StatusNotification;
220

    
221
            StatusNotification.NotifyChange("Starting");
222
            if (_started)
223
            {
224
                if (!_cancellationSource.IsCancellationRequested)
225
                    return;
226
            }
227
            _cancellationSource = new CancellationTokenSource();
228

    
229
            lock (this)
230
            {
231
                CloudClient = new CloudFilesClient(UserName, ApiKey)
232
                                  {UsePithos = true, AuthenticationUrl = AuthenticationUrl};
233
                _accountInfo = CloudClient.Authenticate();
234
            }
235
            _accountInfo.SiteUri = AuthenticationUrl;
236
            _accountInfo.AccountPath = RootPath;
237

    
238

    
239
            var pithosFolder = Path.Combine(RootPath, FolderConstants.PithosContainer);
240
            if (!Directory.Exists(pithosFolder))
241
                Directory.CreateDirectory(pithosFolder);
242
            //Create the cache folder and ensure it is hidden
243
            CreateHiddenFolder(RootPath, FolderConstants.CacheFolder);
244

    
245
            var policy=CloudClient.GetAccountPolicies(_accountInfo);
246

    
247
            StatusNotification.NotifyAccount(policy);
248
            EnsurePithosContainers();
249
            
250
            StatusKeeper.BlockHash = _blockHash;
251
            StatusKeeper.BlockSize = _blockSize;
252
            
253
            StatusKeeper.StartProcessing(_cancellationSource.Token);
254

    
255
            LoadSelectivePaths();
256

    
257
            await IndexLocalFiles();
258

    
259
            StartWatcherAgent();
260

    
261
            StartNetworkAgent();
262
            
263
            //WorkflowAgent.RestartInterruptedFiles(_accountInfo);
264
            _started = true;
265
        }
266

    
267
        private void LoadSelectivePaths()
268
        {
269
            var settings = Settings.Accounts.First(s => s.AccountKey == _accountInfo.AccountKey);
270

    
271
            var selectiveUrls = settings.SelectiveFolders.Cast<string>()
272
                .Select(url => new Uri(url, UriKind.RelativeOrAbsolute))
273
                .Where(uri => uri.IsAbsoluteUri).ToArray();
274

    
275
            SetSelectivePaths(selectiveUrls, null, null);
276
            CleanupUnselectedStates();
277
        }
278

    
279
        private void EnsurePithosContainers()
280
        {
281

    
282
            //Create the two default containers if they are missing
283
            var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
284
            foreach (var container in pithosContainers)
285
            {                
286
                var info=CloudClient.GetContainerInfo(UserName, container);
287
                if (info == ContainerInfo.Empty)
288
                {
289
                    CloudClient.CreateContainer(UserName, container);
290
                    info = CloudClient.GetContainerInfo(UserName, container);
291
                }
292
                _blockSize = info.BlockSize;
293
                _blockHash = info.BlockHash;
294
                _accountInfo.BlockSize = _blockSize;
295
                _accountInfo.BlockHash = _blockHash;
296
            }
297
        }
298

    
299
        public string AuthenticationUrl { get; set; }
300

    
301
        private Task IndexLocalFiles()
302
        {            
303
            return TaskEx.Run(() =>
304
                           {
305
                               using (ThreadContext.Stacks["Operation"].Push("Indexing local files"))
306
                               {
307

    
308
                                   try
309
                                   {
310
                                       //StatusNotification.NotifyChange("Indexing Local Files");
311
                                       Log.Info("Start local indexing");
312
                                       StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,
313
                                                                          "Indexing Local Files");
314

    
315
                                       var cachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
316
                                       var directory = new DirectoryInfo(RootPath);
317
                                       var files =
318
                                           from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
319
                                           where
320
                                               !file.FullName.StartsWith(cachePath,
321
                                                                         StringComparison.InvariantCultureIgnoreCase) &&
322
                                               !file.Extension.Equals("ignore",
323
                                                                      StringComparison.InvariantCultureIgnoreCase)
324
                                           select file;
325
                                       StatusKeeper.ProcessExistingFiles(files);
326

    
327
                                   }
328
                                   catch (Exception exc)
329
                                   {
330
                                       Log.Error("[ERROR]", exc);
331
                                   }
332
                                   finally
333
                                   {
334
                                       Log.Info("[END]");
335
                                   }
336
                                   StatusNotification.SetPithosStatus(PithosStatus.LocalComplete, "Indexing Completed");
337
                               }
338
                           });
339
        }
340

    
341
        
342
  
343

    
344

    
345
       /* private void StartWorkflowAgent()
346
        {
347
            WorkflowAgent.StatusNotification = StatusNotification;
348

    
349
/*            //On Vista and up we can check for a network connection
350
            bool connected=Environment.OSVersion.Version.Major < 6 || NetworkListManager.IsConnectedToInternet;
351
            //If we are not connected retry later
352
            if (!connected)
353
            {
354
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
355
                return;
356
            }#1#
357

    
358
            try
359
            {
360
                WorkflowAgent.Start();                
361
            }
362
            catch (Exception)
363
            {
364
                //Faild to authenticate due to network or account error
365
                //Retry after a while
366
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
367
            }
368
        }*/
369

    
370

    
371
        private void StartNetworkAgent()
372
        {
373
            NetworkAgent.StatusNotification = StatusNotification;
374

    
375
            //TODO: The Network and Poll agents are not account specific
376
            //They should be moved outside PithosMonitor
377
/*
378
            NetworkAgent.Start();
379
*/
380

    
381
            PollAgent.AddAccount(_accountInfo);
382

    
383
            PollAgent.StatusNotification = StatusNotification;
384

    
385
            TaskEx.Run(()=> PollAgent.PollRemoteFiles());
386
        }
387

    
388
        //Make sure a hidden cache folder exists to store partial downloads
389
        private static void CreateHiddenFolder(string rootPath, string folderName)
390
        {
391
            if (String.IsNullOrWhiteSpace(rootPath))
392
                throw new ArgumentNullException("rootPath");
393
            if (!Path.IsPathRooted(rootPath))
394
                throw new ArgumentException("rootPath");
395
            if (String.IsNullOrWhiteSpace(folderName))
396
                throw new ArgumentNullException("folderName");
397
            Contract.EndContractBlock();
398

    
399
            var folder = Path.Combine(rootPath, folderName);
400
            if (!Directory.Exists(folder))
401
            {
402
                var info = Directory.CreateDirectory(folder);
403
                info.Attributes |= FileAttributes.Hidden;
404

    
405
                Log.InfoFormat("Created cache Folder: {0}", folder);
406
            }
407
            else
408
            {
409
                var info = new DirectoryInfo(folder);
410
                if ((info.Attributes & FileAttributes.Hidden) == 0)
411
                {
412
                    info.Attributes |= FileAttributes.Hidden;
413
                    Log.InfoFormat("Reset cache folder to hidden: {0}", folder);
414
                }                                
415
            }
416
        }
417

    
418
       
419

    
420

    
421
        private void StartWatcherAgent()
422
        {
423
            if (Log.IsDebugEnabled)
424
                Log.DebugFormat("Start Folder Monitoring [{0}]",RootPath);
425

    
426
            AgentLocator<FileAgent>.Register(FileAgent,RootPath);
427
            
428
            FileAgent.IdleTimeout = Settings.FileIdleTimeout;
429
            FileAgent.StatusKeeper = StatusKeeper;
430
            FileAgent.StatusNotification = StatusNotification;
431
            FileAgent.Workflow = Workflow;
432
            FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
433
            //Ensure this starts in a different thread than the initiating UI thread
434
            TaskEx.Run(()=>FileAgent.Start(_accountInfo, RootPath));
435
        }
436

    
437
        public void Stop()
438
        {
439
/*
440
            AgentLocator<FileAgent>.Remove(RootPath);
441

    
442
            if (FileAgent!=null)
443
                FileAgent.Stop();
444
            FileAgent = null;
445
*/
446
        }
447

    
448

    
449
        ~PithosMonitor()
450
        {
451
            Dispose(false);
452
        }
453

    
454
        public void Dispose()
455
        {
456
            Dispose(true);
457
            GC.SuppressFinalize(this);
458
        }
459

    
460
        protected virtual void Dispose(bool disposing)
461
        {
462
            if (disposing)
463
            {
464
                Stop();
465
            }
466
        }
467

    
468

    
469
        public void MoveFileStates(string oldPath, string newPath)
470
        {
471
            if (String.IsNullOrWhiteSpace(oldPath))
472
                throw new ArgumentNullException("oldPath");
473
            if (!Path.IsPathRooted(oldPath))
474
                throw new ArgumentException("oldPath must be an absolute path","oldPath");
475
            if (string.IsNullOrWhiteSpace(newPath))
476
                throw new ArgumentNullException("newPath");
477
            if (!Path.IsPathRooted(newPath))
478
                throw new ArgumentException("newPath must be an absolute path","newPath");
479
            Contract.EndContractBlock();
480

    
481
            StatusKeeper.ChangeRoots(oldPath, newPath);
482
        }
483

    
484
        private void CleanupUnselectedStates()
485
        {
486
            //var settings = Settings.Accounts.First(s => s.AccountKey == _accountInfo.AccountKey);
487
            if (!Selectives.IsSelectiveEnabled(_accountInfo.AccountKey)) return;
488

    
489
            List<string> selectivePaths;
490
            if (Selectives.SelectivePaths.TryGetValue(_accountInfo.AccountKey, out selectivePaths))
491
            {
492
                var statePaths= FileState.Queryable.Select(state => state.FilePath).ToList();
493
                var removedPaths = statePaths
494
                    .Where(sp => !selectivePaths.Any(sp.IsAtOrBelow))
495
                    .ToList();
496

    
497
                UnversionSelectivePaths(removedPaths);
498
            }
499
            else
500
            {
501
                StatusKeeper.ClearFolderStatus(Account.AccountPath);
502
            }
503
        }
504

    
505
        public void SetSelectivePaths(Uri[] uris,Uri[] added, Uri[] removed)
506
        {
507
            //Convert the uris to paths
508
            //var selectivePaths = UrisToFilePaths(uris);
509
            
510
            var selectiveUri = uris.ToList();
511
            this.Selectives.SetSelectedUris(_accountInfo,selectiveUri);
512

    
513
            var removedPaths = UrisToFilePaths(removed);
514
            UnversionSelectivePaths(removedPaths);
515

    
516
        }
517

    
518
        /// <summary>
519
        /// Mark all unselected paths as Unversioned
520
        /// </summary>
521
        /// <param name="removed"></param>
522
        private void UnversionSelectivePaths(List<string> removed)
523
        {
524
            if (removed == null)
525
                return;
526

    
527
            //Ensure we remove any file state below the deleted folders
528
            FileState.UnversionPaths(removed);
529
        }
530

    
531

    
532
        /// <summary>
533
        /// Return a list of absolute filepaths from a list of Uris
534
        /// </summary>
535
        /// <param name="uris"></param>
536
        /// <returns></returns>
537
        public List<string> UrisToFilePaths(IEnumerable<Uri> uris)
538
        {
539
            if (uris == null)
540
                return new List<string>();
541

    
542
            var own = (from uri in uris
543
                       where uri.ToString().StartsWith(_accountInfo.StorageUri.ToString())
544
                                   let relativePath = _accountInfo.StorageUri.MakeRelativeUri(uri).RelativeUriToFilePath()
545
                                   //Trim the account name
546
                                   select Path.Combine(RootPath, relativePath.After(_accountInfo.UserName + '\\'))).ToList();
547
            var others= (from uri in uris
548
                         where !uri.ToString().StartsWith(_accountInfo.StorageUri.ToString())
549
                                   let relativePath = _accountInfo.StorageUri.MakeRelativeUri(uri).RelativeUriToFilePath()
550
                                   //Trim the account name
551
                                   select Path.Combine(RootPath,"others-shared", relativePath)).ToList();
552
            return own.Union(others).ToList();            
553
        }
554

    
555

    
556
        public ObjectInfo GetObjectInfo(string filePath)
557
        {
558
            if (String.IsNullOrWhiteSpace(filePath))
559
                throw new ArgumentNullException("filePath");
560
            Contract.EndContractBlock();
561

    
562
            var file=new FileInfo(filePath);
563
            string relativeUrl;//=file.AsRelativeUrlTo(this.RootPath);
564
            var relativePath = file.AsRelativeTo(RootPath);
565
            
566
            string accountName,container;
567
            
568
            var parts=relativePath.Split('\\');
569

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

    
582
                accountInfo = new AccountInfo
583
                {
584
                    UserName = accountName,
585
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
586
                    StorageUri = new Uri(root + accountName),
587
                    BlockHash=accountInfo.BlockHash,
588
                    BlockSize=accountInfo.BlockSize,
589
                    Token=accountInfo.Token
590
                };
591
            }
592
            else
593
            {
594
                accountName = UserName;
595
                container = parts[0];
596
                relativeUrl = String.Join("/", parts.Splice(1));
597
            }
598
            
599
            var client = new CloudFilesClient(accountInfo);
600
            var objectInfo=client.GetObjectInfo(accountName, container, relativeUrl);
601
            return objectInfo;
602
        }
603
        
604
        public Task<ContainerInfo> GetContainerInfo(string filePath)
605
        {
606
            if (String.IsNullOrWhiteSpace(filePath))
607
                throw new ArgumentNullException("filePath");
608
            Contract.EndContractBlock();
609

    
610
            var file=new FileInfo(filePath);
611
            var relativePath = file.AsRelativeTo(RootPath);
612
            
613
            string accountName,container;
614
            
615
            var parts=relativePath.Split('\\');
616

    
617
            var accountInfo = _accountInfo;
618
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
619
            {                
620
                accountName = parts[1];
621
                container = parts[2];                
622
                //Create the root URL for the target account
623
                var oldName = UserName;
624
                var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
625
                var nameIndex=absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
626
                var root=absoluteUri.Substring(0, nameIndex);
627

    
628
                accountInfo = new AccountInfo
629
                {
630
                    UserName = accountName,
631
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
632
                    StorageUri = new Uri(root + accountName),
633
                    BlockHash=accountInfo.BlockHash,
634
                    BlockSize=accountInfo.BlockSize,
635
                    Token=accountInfo.Token
636
                };
637
            }
638
            else
639
            {
640
                accountName = UserName;
641
                container = parts[0];                
642
            }
643

    
644
            return Task.Factory.StartNew(() =>
645
            {
646
                var client = new CloudFilesClient(accountInfo);
647
                var containerInfo = client.GetContainerInfo(accountName, container);
648
                return containerInfo;
649
            });
650
        }
651
    }
652
}