Logging changes, first changes to multi account support
[pithos-ms-client] / trunk / Pithos.Core / PithosMonitor.cs
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 }