Statistics
| Branch: | Revision:

root / trunk / Pithos.Core / PithosMonitor.cs @ 5120f3cb

History | View | Annotate | Download (13.8 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 const string PithosContainer = "pithos";
29
        private const string TrashContainer = "trash";
30

    
31
        private const string FragmentsFolder = "fragments";
32

    
33
        private int _blockSize;
34
        private string _blockHash;
35

    
36
        [Import]
37
        public IPithosSettings Settings{get;set;}
38

    
39
        [Import]
40
        public IStatusKeeper StatusKeeper { get; set; }
41

    
42
        [Import]
43
        public IPithosWorkflow Workflow { get; set; }
44

    
45
        [Import]
46
        public ICloudClient CloudClient { get; set; }
47

    
48
        public IStatusNotification StatusNotification { get; set; }
49

    
50
        [Import]
51
        public FileAgent FileAgent { get; set; }
52
        
53
        [Import]
54
        public WorkflowAgent WorkflowAgent { get; set; }
55
        
56
        [Import]
57
        public NetworkAgent NetworkAgent { get; set; }
58

    
59

    
60
        public string UserName { get; set; }
61
        public string ApiKey { get; set; }
62

    
63
        private ServiceHost _statusService { get; set; }
64

    
65
        private static readonly ILog Log = LogManager.GetLogger(typeof(PithosMonitor));
66

    
67

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

    
87
        public string RootPath { get; set; }
88

    
89

    
90
        CancellationTokenSource _cancellationSource;
91

    
92

    
93
        private bool _isInitialized;
94

    
95
        public void Start()
96
        {
97
            StatusNotification.NotifyChange("Starting");
98
            if (_isInitialized)
99
            {
100
                if (!_cancellationSource.IsCancellationRequested)
101
                    return;
102
            }
103
            _cancellationSource = new CancellationTokenSource();
104

    
105
            var proxyUri = ProxyFromSettings();            
106
            CloudClient.Proxy = proxyUri;
107
            CloudClient.UsePithos = this.UsePithos;
108
            
109
            EnsurePithosContainers();
110
            
111
            StatusKeeper.BlockHash = _blockHash;
112
            StatusKeeper.BlockSize = _blockSize;
113
            
114
            StatusKeeper.StartProcessing(_cancellationSource.Token);
115
            IndexLocalFiles(RootPath);
116
            StartWatcherAgent(RootPath);
117
            StartStatusService();
118
            StartWorkflowAgent();
119
            WorkflowAgent.RestartInterruptedFiles();
120
            _isInitialized = true;
121
        }
122

    
123
        private void EnsurePithosContainers()
124
        {
125
            CloudClient.UsePithos = this.UsePithos;
126
            CloudClient.AuthenticationUrl = this.AuthenticationUrl;
127
            CloudClient.Authenticate(UserName, ApiKey);
128

    
129
            //Create the two default containers if they are missing
130
            var pithosContainers = new List<string>{ TrashContainer,PithosContainer};
131
            foreach (var container in pithosContainers)
132
            {                
133
                var info=CloudClient.GetContainerInfo(this.UserName, container);
134
                if (info == ContainerInfo.Empty)
135
                {
136
                    CloudClient.CreateContainer(this.UserName, container);
137
                    info = CloudClient.GetContainerInfo(this.UserName, container);
138
                }
139
                _blockSize = info.BlockSize;
140
                _blockHash = info.BlockHash;
141
            }
142

    
143
/*
144
            //Create folders for any other containers
145
            var allContainers = CloudClient.ListContainers();            
146
            pithosContainers.AddRange(new[]{"shared","others"});
147

    
148
            var extraContainers = from container in allContainers
149
                                  where !pithosContainers.Contains(container.Name.ToLower())
150
                                  select container;
151
            
152
            foreach (var container in extraContainers)
153
            {
154
                var containerPath = Path.Combine(this.RootPath, container.Name);
155
                if (!Directory.Exists(containerPath))
156
                    Directory.CreateDirectory(containerPath);
157
            }
158
*/
159

    
160

    
161

    
162
        }
163

    
164
        public string AuthenticationUrl { get; set; }
165

    
166
        private Uri ProxyFromSettings()
167
        {            
168
            if (Settings.UseManualProxy)
169
            {
170
                var proxyUri = new UriBuilder
171
                                   {
172
                                       Host = Settings.ProxyServer, 
173
                                       Port = Settings.ProxyPort
174
                                   };
175
                if (Settings.ProxyAuthentication)
176
                {
177
                    proxyUri.UserName = Settings.ProxyUsername;
178
                    proxyUri.Password = Settings.ProxyPassword;
179
                }
180
                return proxyUri.Uri;
181
            }
182
            return null;
183
        }
184

    
185
        private void IndexLocalFiles(string path)
186
        {
187
            StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
188
            using (log4net.ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
189
            {
190
                Log.Info("START");
191
                try
192
                {
193
                    var fragmentsPath = Path.Combine(RootPath, FragmentsFolder);
194
                    var directory = new DirectoryInfo(path);
195
                    var files =
196
                        from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
197
                        where !file.FullName.StartsWith(fragmentsPath, StringComparison.InvariantCultureIgnoreCase) &&
198
                              !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
199
                        select file;
200
                    StatusKeeper.ProcessExistingFiles(files);
201

    
202
                }
203
                catch (Exception exc)
204
                {
205
                    Log.Error("[ERROR]", exc);
206
                }
207
                finally
208
                {
209
                    Log.Info("[END]");
210
                }
211
            }
212
        }
213

    
214
        
215
        private void StartStatusService()
216
        {
217
            // Create a ServiceHost for the CalculatorService type and provide the base address.
218
            var baseAddress = new Uri("net.pipe://localhost/pithos");
219
            _statusService = new ServiceHost(typeof(StatusService), baseAddress);
220
            
221
            var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
222
            
223
            _statusService.AddServiceEndpoint(typeof(IStatusService), binding, "net.pipe://localhost/pithos/statuscache");
224
            _statusService.AddServiceEndpoint(typeof (ISettingsService), binding, "net.pipe://localhost/pithos/settings");
225

    
226

    
227
            //// Add a mex endpoint
228
            var smb = new ServiceMetadataBehavior
229
                          {
230
                              HttpGetEnabled = true, 
231
                              HttpGetUrl = new Uri("http://localhost:30000/pithos/mex")
232
                          };
233
            _statusService.Description.Behaviors.Add(smb);
234

    
235

    
236
            _statusService.Open();
237
        }
238

    
239
        private void StopStatusService()
240
        {
241
            if (_statusService == null)
242
                return;
243

    
244
            if (_statusService.State == CommunicationState.Faulted)
245
                _statusService.Abort();
246
            else if (_statusService.State != CommunicationState.Closed)
247
                _statusService.Close();
248
            _statusService = null;
249

    
250
        }
251

    
252

    
253
        private void StartWorkflowAgent()
254
        {
255

    
256
            bool connected = NetworkListManager.IsConnectedToInternet;
257
            //If we are not connected retry later
258
            if (!connected)
259
            {
260
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
261
                return;
262
            }
263

    
264
            try
265
            {
266
                CloudClient.UsePithos = this.UsePithos;
267
                CloudClient.AuthenticationUrl = this.AuthenticationUrl;
268
                CloudClient.Authenticate(UserName, ApiKey);
269

    
270
                StartNetworkAgent(RootPath);
271

    
272
                WorkflowAgent.StatusNotification = StatusNotification;
273
                WorkflowAgent.FragmentsPath = Path.Combine(RootPath, FragmentsFolder);
274
                WorkflowAgent.Start();                
275
            }
276
            catch (Exception)
277
            {
278
                //Faild to authenticate due to network or account error
279
                //Retry after a while
280
                Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
281
            }
282
        }
283

    
284
        public bool UsePithos { get; set; }
285

    
286

    
287

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

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

    
322
        private Timer timer;
323

    
324
        private void StartNetworkAgent(string accountPath)
325
        {
326
            NetworkAgent.StatusNotification = StatusNotification;
327

    
328
            NetworkAgent.Start(PithosContainer, TrashContainer,_blockSize,_blockHash);
329

    
330
            NetworkAgent.ProcessRemoteFiles(accountPath);
331
        }
332

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

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

    
350
                Log.InfoFormat("Created Fragments Folder: {0}", folder);
351
            }
352
            return folder;
353
        }
354

    
355
       
356

    
357

    
358
        private void StartWatcherAgent(string path)
359
        {
360
            FileAgent.StatusKeeper = StatusKeeper;
361
            FileAgent.Workflow = Workflow;
362
            FileAgent.FragmentsPath = Path.Combine(RootPath, FragmentsFolder);
363
            FileAgent.Start(path);
364
        }
365

    
366
        public void Stop()
367
        {            
368
            FileAgent.Stop();
369
            if (timer != null)
370
                timer.Dispose();
371
            timer = null;
372
            StopStatusService();
373
        }
374

    
375

    
376
        ~PithosMonitor()
377
        {
378
            Dispose(false);
379
        }
380

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

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

    
395

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

    
408
            StatusKeeper.ChangeRoots(oldPath, newPath);
409
        }
410
    }
411

    
412
    public interface IStatusNotification
413
    {        
414
        void NotifyChange(string status,TraceLevel level=TraceLevel.Info);
415
        void NotifyChangedFile(string filePath);
416
    }
417
}