Added UI for SelectiveSynch
[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 int _blockSize;
29         private string _blockHash;
30
31         [Import]
32         public IPithosSettings Settings{get;set;}
33
34         [Import]
35         public IStatusKeeper StatusKeeper { get; set; }
36
37         [Import]
38         public IPithosWorkflow Workflow { get; set; }
39
40         public ICloudClient CloudClient { get; set; }
41
42         public IStatusNotification StatusNotification { get; set; }
43
44         [Import]
45         public FileAgent FileAgent { get; set; }
46         
47         [Import]
48         public WorkflowAgent WorkflowAgent { get; set; }
49         
50         [Import]
51         public NetworkAgent NetworkAgent { get; set; }        
52
53         public string UserName { get; set; }
54         public string ApiKey { get; set; }
55
56         private Network.AccountInfo _accountInfo;
57
58
59         private static readonly ILog Log = LogManager.GetLogger(typeof(PithosMonitor));
60
61
62         public bool Pause
63         {
64             get { return FileAgent.Pause; }
65             set
66             {
67                 FileAgent.Pause = value;
68                 if (value)
69                 {
70                     StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused);
71                     StatusNotification.NotifyChange("Paused");
72                 }
73                 else
74                 {
75                     StatusKeeper.SetPithosStatus(PithosStatus.InSynch);
76                     StatusNotification.NotifyChange("Synchronizing");
77                 }
78             }
79         }
80
81         private string _rootPath;
82         public string RootPath
83         {
84             get { return _rootPath; }
85             set
86             {
87                 _rootPath = value.ToLower();
88             }
89         }
90
91
92         CancellationTokenSource _cancellationSource;
93
94
95         private bool _isInitialized;
96
97         public void Start()
98         {            
99             if (String.IsNullOrWhiteSpace(ApiKey))
100                 throw new InvalidOperationException("The ApiKey is empty");
101             if (String.IsNullOrWhiteSpace(UserName))
102                 throw new InvalidOperationException("The UserName is empty");
103             if (String.IsNullOrWhiteSpace(AuthenticationUrl))
104                 throw new InvalidOperationException("The Authentication url is empty");
105             Contract.EndContractBlock();
106
107             StatusNotification.NotifyChange("Starting");
108             if (_isInitialized)
109             {
110                 if (!_cancellationSource.IsCancellationRequested)
111                     return;
112             }
113             _cancellationSource = new CancellationTokenSource();
114
115             CloudClient=new CloudFilesClient(UserName,ApiKey);
116             var proxyUri = ProxyFromSettings();            
117             CloudClient.Proxy = proxyUri;
118             CloudClient.UsePithos = this.UsePithos;
119             CloudClient.AuthenticationUrl = this.AuthenticationUrl;            
120
121             _accountInfo = CloudClient.Authenticate();
122             _accountInfo.AccountPath = RootPath;
123
124
125             EnsurePithosContainers();
126             
127             StatusKeeper.BlockHash = _blockHash;
128             StatusKeeper.BlockSize = _blockSize;
129             
130             StatusKeeper.StartProcessing(_cancellationSource.Token);
131             IndexLocalFiles();
132             StartWatcherAgent();
133
134             StartNetworkAgent();
135
136             StartWorkflowAgent();
137             WorkflowAgent.RestartInterruptedFiles(_accountInfo);
138             _isInitialized = true;
139         }
140
141         private void EnsurePithosContainers()
142         {
143
144             //Create the two default containers if they are missing
145             var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
146             foreach (var container in pithosContainers)
147             {                
148                 var info=CloudClient.GetContainerInfo(this.UserName, container);
149                 if (info == ContainerInfo.Empty)
150                 {
151                     CloudClient.CreateContainer(this.UserName, container);
152                     info = CloudClient.GetContainerInfo(this.UserName, container);
153                 }
154                 _blockSize = info.BlockSize;
155                 _blockHash = info.BlockHash;
156                 _accountInfo.BlockSize = _blockSize;
157                 _accountInfo.BlockHash = _blockHash;
158             }
159         }
160
161         public string AuthenticationUrl { get; set; }
162
163         private Uri ProxyFromSettings()
164         {            
165             if (Settings.UseManualProxy)
166             {
167                 var proxyUri = new UriBuilder
168                                    {
169                                        Host = Settings.ProxyServer, 
170                                        Port = Settings.ProxyPort
171                                    };
172                 if (Settings.ProxyAuthentication)
173                 {
174                     proxyUri.UserName = Settings.ProxyUsername;
175                     proxyUri.Password = Settings.ProxyPassword;
176                 }
177                 return proxyUri.Uri;
178             }
179             return null;
180         }
181
182         private void IndexLocalFiles()
183         {
184             StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
185             using (log4net.ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
186             {
187                 Log.Info("START");
188                 try
189                 {
190                     var fragmentsPath = Path.Combine(RootPath, FolderConstants.FragmentsFolder);
191                     var directory = new DirectoryInfo(RootPath);
192                     var files =
193                         from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
194                         where !file.FullName.StartsWith(fragmentsPath, StringComparison.InvariantCultureIgnoreCase) &&
195                               !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
196                         select file;
197                     StatusKeeper.ProcessExistingFiles(files);
198
199                 }
200                 catch (Exception exc)
201                 {
202                     Log.Error("[ERROR]", exc);
203                 }
204                 finally
205                 {
206                     Log.Info("[END]");
207                 }
208             }
209         }
210
211         
212   
213
214
215         private void StartWorkflowAgent()
216         {
217
218             bool connected = NetworkListManager.IsConnectedToInternet;
219             //If we are not connected retry later
220             if (!connected)
221             {
222                 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
223                 return;
224             }
225
226             try
227             {
228                 WorkflowAgent.StatusNotification = StatusNotification;
229                 WorkflowAgent.Start();                
230             }
231             catch (Exception)
232             {
233                 //Faild to authenticate due to network or account error
234                 //Retry after a while
235                 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
236             }
237         }
238
239         public bool UsePithos { get; set; }
240
241
242         internal class LocalFileComparer:EqualityComparer<CloudAction>
243         {
244             public override bool Equals(CloudAction x, CloudAction y)
245             {
246                 if (x.Action != y.Action)
247                     return false;
248                 if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
249                     return false;
250                 if (x.CloudFile != null && y.CloudFile != null )
251                 {
252                     if (x.CloudFile.Hash == null & y.CloudFile.Hash != null)
253                         return false;
254                     if (x.CloudFile.Hash != null & y.CloudFile.Hash == null)
255                         return false;
256                     if (x.CloudFile.Hash == null & y.CloudFile.Hash == null)
257                         return (x.CloudFile.Name == y.CloudFile.Name);
258                     if (!x.CloudFile.Hash.Equals(y.CloudFile.Hash))
259                         return false;
260                 }
261                 if (x.CloudFile == null ^ y.CloudFile == null ||
262                     x.LocalFile == null ^ y.LocalFile == null)
263                     return false;
264                 return true;
265             }
266
267             public override int GetHashCode(CloudAction obj)
268             {
269                 var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
270                 var hash2 = (obj.CloudFile == null) ? int.MaxValue : (obj.CloudFile.Hash ?? obj.CloudFile.Name??"").GetHashCode();
271                 var hash3 = obj.Action.GetHashCode();
272                 return hash1 ^ hash2 & hash3;
273             }
274         }        
275
276         private Timer timer;
277
278         private void StartNetworkAgent()
279         {
280
281             NetworkAgent.AddAccount(_accountInfo);
282
283             NetworkAgent.StatusNotification = StatusNotification;
284                         
285             NetworkAgent.Start();
286
287             NetworkAgent.ProcessRemoteFiles();
288         }
289
290         //Make sure a hidden fragments folder exists to store partial downloads
291         private static string CreateHiddenFolder(string rootPath, string folderName)
292         {
293             if (String.IsNullOrWhiteSpace(rootPath))
294                 throw new ArgumentNullException("rootPath");
295             if (!Path.IsPathRooted(rootPath))
296                 throw new ArgumentException("rootPath");
297             if (String.IsNullOrWhiteSpace(folderName))
298                 throw new ArgumentNullException("folderName");
299             Contract.EndContractBlock();
300
301             var folder = Path.Combine(rootPath, folderName);
302             if (!Directory.Exists(folder))
303             {
304                 var info = Directory.CreateDirectory(folder);
305                 info.Attributes |= FileAttributes.Hidden;
306
307                 Log.InfoFormat("Created Fragments Folder: {0}", folder);
308             }
309             return folder;
310         }
311
312        
313
314
315         private void StartWatcherAgent()
316         {
317             AgentLocator<FileAgent>.Register(FileAgent,RootPath);
318
319             FileAgent.StatusKeeper = StatusKeeper;
320             FileAgent.Workflow = Workflow;
321             FileAgent.FragmentsPath = Path.Combine(RootPath, FolderConstants.FragmentsFolder);
322             FileAgent.Start(_accountInfo, RootPath);
323         }
324
325         public void Stop()
326         {
327             AgentLocator<FileAgent>.Remove(RootPath);
328
329             if (FileAgent!=null)
330                 FileAgent.Stop();
331             FileAgent = null;
332             if (timer != null)
333                 timer.Dispose();
334             timer = null;            
335         }
336
337
338         ~PithosMonitor()
339         {
340             Dispose(false);
341         }
342
343         public void Dispose()
344         {
345             Dispose(true);
346             GC.SuppressFinalize(this);
347         }
348
349         protected virtual void Dispose(bool disposing)
350         {
351             if (disposing)
352             {
353                 Stop();
354             }
355         }
356
357
358         public void MoveFileStates(string oldPath, string newPath)
359         {
360             if (String.IsNullOrWhiteSpace(oldPath))
361                 throw new ArgumentNullException("oldPath");
362             if (!Path.IsPathRooted(oldPath))
363                 throw new ArgumentException("oldPath must be an absolute path","oldPath");
364             if (string.IsNullOrWhiteSpace(newPath))
365                 throw new ArgumentNullException("newPath");
366             if (!Path.IsPathRooted(newPath))
367                 throw new ArgumentException("newPath must be an absolute path","newPath");
368             Contract.EndContractBlock();
369
370             StatusKeeper.ChangeRoots(oldPath, newPath);
371         }
372
373         public void AddSelectivePaths(string[] added)
374         {
375            /* FileAgent.SelectivePaths.AddRange(added);
376             NetworkAgent.SyncPaths(added);*/
377         }
378
379         public void RemoveSelectivePaths(string[] removed)
380         {
381             FileAgent.SelectivePaths.RemoveAll(removed.Contains);
382             foreach (var removedPath in removed.Where(Directory.Exists))
383             {
384                 Directory.Delete(removedPath,true);
385             }
386         }
387     }
388
389     public interface IStatusNotification
390     {        
391         void NotifyChange(string status,TraceLevel level=TraceLevel.Info);
392         void NotifyChangedFile(string filePath);
393     }
394 }