Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / PithosMonitor.cs @ 2341c603

History | View | Annotate | Download (20 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
        private WorkflowAgent _workflowAgent;
106

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

    
123
        private Selectives _selectives;
124

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

    
136
        public string UserName { get; set; }
137
        private string _apiKey;
138
        public string ApiKey
139
        {
140
            get { return _apiKey; }
141
            set
142
            {
143
                _apiKey = value;
144
                if (_accountInfo != null)
145
                    _accountInfo.Token = value;
146
            }
147
        }
148

    
149
        private AccountInfo _accountInfo;
150

    
151

    
152

    
153

    
154

    
155

    
156
        public bool Pause
157
        {
158
            get { return FileAgent.Pause; }
159
            set
160
            {
161
                FileAgent.Pause = value;
162
            }
163
        }
164

    
165
        private string _rootPath;
166
        public string RootPath
167
        {
168
            get { return _rootPath; }
169
            set 
170
            {
171
                _rootPath = String.IsNullOrWhiteSpace(value) 
172
                    ? String.Empty 
173
                    : value.ToLower();
174
            }
175
        }
176

    
177

    
178
        CancellationTokenSource _cancellationSource;
179

    
180
        public PithosMonitor()
181
        {
182
            FileAgent = new FileAgent();
183
        }
184
        private bool _started;
185

    
186
        public void Start()
187
        {            
188
            if (String.IsNullOrWhiteSpace(ApiKey))
189
                throw new InvalidOperationException("The ApiKey is empty");
190
            if (String.IsNullOrWhiteSpace(UserName))
191
                throw new InvalidOperationException("The UserName is empty");
192
            if (String.IsNullOrWhiteSpace(AuthenticationUrl))
193
                throw new InvalidOperationException("The Authentication url is empty");
194
            Contract.EndContractBlock();
195

    
196
            //If the account doesn't have a valid path, don't start monitoring but don't throw either
197
            if (String.IsNullOrWhiteSpace(RootPath))
198
                //TODO; Warn user?
199
                return;
200

    
201
            WorkflowAgent.StatusNotification = StatusNotification;
202

    
203
            StatusNotification.NotifyChange("Starting");
204
            if (_started)
205
            {
206
                if (!_cancellationSource.IsCancellationRequested)
207
                    return;
208
            }
209
            _cancellationSource = new CancellationTokenSource();
210

    
211
            lock (this)
212
            {
213
                CloudClient = new CloudFilesClient(UserName, ApiKey)
214
                                  {UsePithos = true, AuthenticationUrl = AuthenticationUrl};
215
                _accountInfo = CloudClient.Authenticate();
216
            }
217
            _accountInfo.SiteUri = AuthenticationUrl;
218
            _accountInfo.AccountPath = RootPath;
219

    
220

    
221
            var pithosFolder = Path.Combine(RootPath, FolderConstants.PithosContainer);
222
            if (!Directory.Exists(pithosFolder))
223
                Directory.CreateDirectory(pithosFolder);
224
            //Create the cache folder and ensure it is hidden
225
            CreateHiddenFolder(RootPath, FolderConstants.CacheFolder);
226

    
227
            var policy=CloudClient.GetAccountPolicies(_accountInfo);
228

    
229
            StatusNotification.NotifyAccount(policy);
230
            EnsurePithosContainers();
231
            
232
            StatusKeeper.BlockHash = _blockHash;
233
            StatusKeeper.BlockSize = _blockSize;
234
            
235
            StatusKeeper.StartProcessing(_cancellationSource.Token);
236
            IndexLocalFiles();
237
            //Extract the URIs from the string collection
238
            var settings = Settings.Accounts.First(s => s.AccountKey == _accountInfo.AccountKey );
239
            var selectiveUrls=settings.SelectiveFolders.Cast<string>().Select(url => new Uri(url)).ToArray();
240

    
241
            SetSelectivePaths(selectiveUrls,null,null);
242
            
243
            StartWatcherAgent();
244

    
245
            StartNetworkAgent();
246
            
247
            WorkflowAgent.RestartInterruptedFiles(_accountInfo);
248
            _started = true;
249
        }
250

    
251
        private void EnsurePithosContainers()
252
        {
253

    
254
            //Create the two default containers if they are missing
255
            var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
256
            foreach (var container in pithosContainers)
257
            {                
258
                var info=CloudClient.GetContainerInfo(UserName, container);
259
                if (info == ContainerInfo.Empty)
260
                {
261
                    CloudClient.CreateContainer(UserName, container);
262
                    info = CloudClient.GetContainerInfo(UserName, container);
263
                }
264
                _blockSize = info.BlockSize;
265
                _blockHash = info.BlockHash;
266
                _accountInfo.BlockSize = _blockSize;
267
                _accountInfo.BlockHash = _blockHash;
268
            }
269
        }
270

    
271
        public string AuthenticationUrl { get; set; }
272

    
273
        private void IndexLocalFiles()
274
        {
275
            using (ThreadContext.Stacks["Operation"].Push("Indexing local files"))
276
            {
277
                
278
                try
279
                {
280
                    //StatusNotification.NotifyChange("Indexing Local Files");
281
                    Log.Info("Start local indexing");
282
                    StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,"Indexing Local Files");                    
283

    
284
                    var cachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
285
                    var directory = new DirectoryInfo(RootPath);
286
                    var files =
287
                        from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
288
                        where !file.FullName.StartsWith(cachePath, StringComparison.InvariantCultureIgnoreCase) &&
289
                              !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
290
                        select file;
291
                    StatusKeeper.ProcessExistingFiles(files);
292

    
293
                }
294
                catch (Exception exc)
295
                {
296
                    Log.Error("[ERROR]", exc);
297
                }
298
                finally
299
                {
300
                    Log.Info("[END]");
301
                }
302
                StatusNotification.SetPithosStatus(PithosStatus.LocalComplete,"Indexing Completed");
303
            }
304
        }
305

    
306
        
307
  
308

    
309

    
310
       /* private void StartWorkflowAgent()
311
        {
312
            WorkflowAgent.StatusNotification = StatusNotification;
313

    
314
/*            //On Vista and up we can check for a network connection
315
            bool connected=Environment.OSVersion.Version.Major < 6 || NetworkListManager.IsConnectedToInternet;
316
            //If we are not connected retry later
317
            if (!connected)
318
            {
319
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
320
                return;
321
            }#1#
322

    
323
            try
324
            {
325
                WorkflowAgent.Start();                
326
            }
327
            catch (Exception)
328
            {
329
                //Faild to authenticate due to network or account error
330
                //Retry after a while
331
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
332
            }
333
        }*/
334

    
335

    
336
        private void StartNetworkAgent()
337
        {
338
            NetworkAgent.StatusNotification = StatusNotification;
339

    
340
            //TODO: The Network and Poll agents are not account specific
341
            //They should be moved outside PithosMonitor
342
            NetworkAgent.Start();
343

    
344
            PollAgent.AddAccount(_accountInfo);
345

    
346
            PollAgent.StatusNotification = StatusNotification;
347

    
348
            PollAgent.PollRemoteFiles();
349
        }
350

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

    
362
            var folder = Path.Combine(rootPath, folderName);
363
            if (!Directory.Exists(folder))
364
            {
365
                var info = Directory.CreateDirectory(folder);
366
                info.Attributes |= FileAttributes.Hidden;
367

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

    
381
       
382

    
383

    
384
        private void StartWatcherAgent()
385
        {
386
            if (Log.IsDebugEnabled)
387
                Log.DebugFormat("Start Folder Monitoring [{0}]",RootPath);
388

    
389
            AgentLocator<FileAgent>.Register(FileAgent,RootPath);
390
            
391
            FileAgent.IdleTimeout = Settings.FileIdleTimeout;
392
            FileAgent.StatusKeeper = StatusKeeper;
393
            FileAgent.StatusNotification = StatusNotification;
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
        }
407

    
408

    
409
        ~PithosMonitor()
410
        {
411
            Dispose(false);
412
        }
413

    
414
        public void Dispose()
415
        {
416
            Dispose(true);
417
            GC.SuppressFinalize(this);
418
        }
419

    
420
        protected virtual void Dispose(bool disposing)
421
        {
422
            if (disposing)
423
            {
424
                Stop();
425
            }
426
        }
427

    
428

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

    
441
            StatusKeeper.ChangeRoots(oldPath, newPath);
442
        }
443

    
444
        public void SetSelectivePaths(Uri[] uris,Uri[] added, Uri[] removed)
445
        {
446
            //Convert the uris to paths
447
            var selectivePaths = UrisToFilePaths(uris);
448
            
449
            var selectiveUri = uris.ToList();
450
            this.Selectives.SetSelectedUris(_accountInfo,selectiveUri);
451

    
452
            var removedPaths = UrisToFilePaths(removed);
453
            UnversionSelectivePaths(removedPaths);
454

    
455
        }
456

    
457
        /// <summary>
458
        /// Mark all unselected paths as Unversioned
459
        /// </summary>
460
        /// <param name="removed"></param>
461
        private void UnversionSelectivePaths(List<string> removed)
462
        {
463
            if (removed == null)
464
                return;
465

    
466
            //Ensure we remove any file state below the deleted folders
467
            FileState.UnversionPaths(removed);
468
        }
469

    
470

    
471
        /// <summary>
472
        /// Return a list of absolute filepaths from a list of Uris
473
        /// </summary>
474
        /// <param name="uris"></param>
475
        /// <returns></returns>
476
        private List<string> UrisToFilePaths(IEnumerable<Uri> uris)
477
        {
478
            if (uris == null)
479
                return new List<string>();
480

    
481
            var own = (from uri in uris
482
                       where uri.ToString().StartsWith(_accountInfo.StorageUri.ToString())
483
                                   let relativePath = _accountInfo.StorageUri.MakeRelativeUri(uri).RelativeUriToFilePath()
484
                                   //Trim the account name
485
                                   select Path.Combine(RootPath, relativePath.After(_accountInfo.UserName + '\\'))).ToList();
486
            var others= (from uri in uris
487
                         where !uri.ToString().StartsWith(_accountInfo.StorageUri.ToString())
488
                                   let relativePath = _accountInfo.StorageUri.MakeRelativeUri(uri).RelativeUriToFilePath()
489
                                   //Trim the account name
490
                                   select Path.Combine(RootPath,"others-shared", relativePath)).ToList();
491
            return own.Union(others).ToList();            
492
        }
493

    
494

    
495
        public ObjectInfo GetObjectInfo(string filePath)
496
        {
497
            if (String.IsNullOrWhiteSpace(filePath))
498
                throw new ArgumentNullException("filePath");
499
            Contract.EndContractBlock();
500

    
501
            var file=new FileInfo(filePath);
502
            string relativeUrl;//=file.AsRelativeUrlTo(this.RootPath);
503
            var relativePath = file.AsRelativeTo(RootPath);
504
            
505
            string accountName,container;
506
            
507
            var parts=relativePath.Split('\\');
508

    
509
            var accountInfo = _accountInfo;
510
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
511
            {                
512
                accountName = parts[1];
513
                container = parts[2];
514
                relativeUrl = String.Join("/", parts.Splice(3));
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, StringComparison.Ordinal);
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
                relativeUrl = String.Join("/", parts.Splice(1));
536
            }
537
            
538
            var client = new CloudFilesClient(accountInfo);
539
            var objectInfo=client.GetObjectInfo(accountName, container, relativeUrl);
540
            return objectInfo;
541
        }
542
        
543
        public Task<ContainerInfo> GetContainerInfo(string filePath)
544
        {
545
            if (String.IsNullOrWhiteSpace(filePath))
546
                throw new ArgumentNullException("filePath");
547
            Contract.EndContractBlock();
548

    
549
            var file=new FileInfo(filePath);
550
            var relativePath = file.AsRelativeTo(RootPath);
551
            
552
            string accountName,container;
553
            
554
            var parts=relativePath.Split('\\');
555

    
556
            var accountInfo = _accountInfo;
557
            if (relativePath.StartsWith(FolderConstants.OthersFolder))
558
            {                
559
                accountName = parts[1];
560
                container = parts[2];                
561
                //Create the root URL for the target account
562
                var oldName = UserName;
563
                var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
564
                var nameIndex=absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
565
                var root=absoluteUri.Substring(0, nameIndex);
566

    
567
                accountInfo = new AccountInfo
568
                {
569
                    UserName = accountName,
570
                    AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
571
                    StorageUri = new Uri(root + accountName),
572
                    BlockHash=accountInfo.BlockHash,
573
                    BlockSize=accountInfo.BlockSize,
574
                    Token=accountInfo.Token
575
                };
576
            }
577
            else
578
            {
579
                accountName = UserName;
580
                container = parts[0];                
581
            }
582

    
583
            return Task.Factory.StartNew(() =>
584
            {
585
                var client = new CloudFilesClient(accountInfo);
586
                var containerInfo = client.GetContainerInfo(accountName, container);
587
                return containerInfo;
588
            });
589
        }
590
    }
591
}