2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.ComponentModel.Composition;
5 using System.Diagnostics;
6 using System.Diagnostics.Contracts;
9 using System.Net.NetworkInformation;
10 using System.Security.Cryptography;
11 using System.ServiceModel.Description;
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;
25 [Export(typeof(PithosMonitor))]
26 public class PithosMonitor:IDisposable
28 private const string PithosContainer = "pithos";
29 private const string TrashContainer = "trash";
31 private const string FragmentsFolder = "fragments";
33 private int _blockSize;
34 private string _blockHash;
37 public IPithosSettings Settings{get;set;}
40 public IStatusKeeper StatusKeeper { get; set; }
43 public IPithosWorkflow Workflow { get; set; }
46 public ICloudClient CloudClient { get; set; }
48 public IStatusNotification StatusNotification { get; set; }
51 public FileAgent FileAgent { get; set; }
54 public WorkflowAgent WorkflowAgent { get; set; }
57 public NetworkAgent NetworkAgent { get; set; }
60 public string UserName { get; set; }
61 public string ApiKey { get; set; }
63 private ServiceHost _statusService { get; set; }
65 private static readonly ILog Log = LogManager.GetLogger(typeof(PithosMonitor));
70 get { return FileAgent.Pause; }
73 FileAgent.Pause = value;
76 StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused);
77 StatusNotification.NotifyChange("Paused");
81 StatusKeeper.SetPithosStatus(PithosStatus.InSynch);
82 StatusNotification.NotifyChange("Synchronizing");
87 public string RootPath { get; set; }
90 CancellationTokenSource _cancellationSource;
93 private bool _isInitialized;
97 StatusNotification.NotifyChange("Starting");
100 if (!_cancellationSource.IsCancellationRequested)
103 _cancellationSource = new CancellationTokenSource();
105 var proxyUri = ProxyFromSettings();
106 CloudClient.Proxy = proxyUri;
107 CloudClient.UsePithos = this.UsePithos;
109 EnsurePithosContainers();
111 StatusKeeper.BlockHash = _blockHash;
112 StatusKeeper.BlockSize = _blockSize;
114 StatusKeeper.StartProcessing(_cancellationSource.Token);
115 IndexLocalFiles(RootPath);
116 StartWatcherAgent(RootPath);
117 StartStatusService();
118 StartWorkflowAgent();
119 WorkflowAgent.RestartInterruptedFiles();
120 _isInitialized = true;
123 private void EnsurePithosContainers()
125 CloudClient.UsePithos = this.UsePithos;
126 CloudClient.AuthenticationUrl = this.AuthenticationUrl;
127 CloudClient.Authenticate(UserName, ApiKey);
129 //Create the two default containers if they are missing
130 var pithosContainers = new List<string>{ TrashContainer,PithosContainer};
131 foreach (var container in pithosContainers)
133 var info=CloudClient.GetContainerInfo(this.UserName, container);
134 if (info == ContainerInfo.Empty)
136 CloudClient.CreateContainer(this.UserName, container);
137 info = CloudClient.GetContainerInfo(this.UserName, container);
139 _blockSize = info.BlockSize;
140 _blockHash = info.BlockHash;
144 //Create folders for any other containers
145 var allContainers = CloudClient.ListContainers();
146 pithosContainers.AddRange(new[]{"shared","others"});
148 var extraContainers = from container in allContainers
149 where !pithosContainers.Contains(container.Name.ToLower())
152 foreach (var container in extraContainers)
154 var containerPath = Path.Combine(this.RootPath, container.Name);
155 if (!Directory.Exists(containerPath))
156 Directory.CreateDirectory(containerPath);
164 public string AuthenticationUrl { get; set; }
166 private Uri ProxyFromSettings()
168 if (Settings.UseManualProxy)
170 var proxyUri = new UriBuilder
172 Host = Settings.ProxyServer,
173 Port = Settings.ProxyPort
175 if (Settings.ProxyAuthentication)
177 proxyUri.UserName = Settings.ProxyUsername;
178 proxyUri.Password = Settings.ProxyPassword;
185 private void IndexLocalFiles(string path)
187 StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
188 using (log4net.ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
193 var fragmentsPath = Path.Combine(RootPath, FragmentsFolder);
194 var directory = new DirectoryInfo(path);
196 from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
197 where !file.FullName.StartsWith(fragmentsPath, StringComparison.InvariantCultureIgnoreCase) &&
198 !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
200 StatusKeeper.ProcessExistingFiles(files);
203 catch (Exception exc)
205 Log.Error("[ERROR]", exc);
215 private void StartStatusService()
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);
221 var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
223 _statusService.AddServiceEndpoint(typeof(IStatusService), binding, "net.pipe://localhost/pithos/statuscache");
224 _statusService.AddServiceEndpoint(typeof (ISettingsService), binding, "net.pipe://localhost/pithos/settings");
227 //// Add a mex endpoint
228 var smb = new ServiceMetadataBehavior
230 HttpGetEnabled = true,
231 HttpGetUrl = new Uri("http://localhost:30000/pithos/mex")
233 _statusService.Description.Behaviors.Add(smb);
236 _statusService.Open();
239 private void StopStatusService()
241 if (_statusService == null)
244 if (_statusService.State == CommunicationState.Faulted)
245 _statusService.Abort();
246 else if (_statusService.State != CommunicationState.Closed)
247 _statusService.Close();
248 _statusService = null;
253 private void StartWorkflowAgent()
256 bool connected = NetworkListManager.IsConnectedToInternet;
257 //If we are not connected retry later
260 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
266 CloudClient.UsePithos = this.UsePithos;
267 CloudClient.AuthenticationUrl = this.AuthenticationUrl;
268 CloudClient.Authenticate(UserName, ApiKey);
270 StartNetworkAgent(RootPath);
272 WorkflowAgent.StatusNotification = StatusNotification;
273 WorkflowAgent.FragmentsPath = Path.Combine(RootPath, FragmentsFolder);
274 WorkflowAgent.Start();
278 //Faild to authenticate due to network or account error
279 //Retry after a while
280 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
284 public bool UsePithos { get; set; }
288 internal class LocalFileComparer:EqualityComparer<CloudAction>
290 public override bool Equals(CloudAction x, CloudAction y)
292 if (x.Action != y.Action)
294 if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
296 if (x.CloudFile != null && y.CloudFile != null )
298 if (x.CloudFile.Hash == null & y.CloudFile.Hash != null)
300 if (x.CloudFile.Hash != null & y.CloudFile.Hash == null)
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))
307 if (x.CloudFile == null ^ y.CloudFile == null ||
308 x.LocalFile == null ^ y.LocalFile == null)
313 public override int GetHashCode(CloudAction obj)
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;
324 private void StartNetworkAgent(string accountPath)
326 NetworkAgent.StatusNotification = StatusNotification;
328 NetworkAgent.Start(PithosContainer, TrashContainer,_blockSize,_blockHash);
330 NetworkAgent.ProcessRemoteFiles(accountPath);
333 //Make sure a hidden fragments folder exists to store partial downloads
334 private static string CreateHiddenFolder(string rootPath, string folderName)
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();
344 var folder = Path.Combine(rootPath, folderName);
345 if (!Directory.Exists(folder))
347 var info = Directory.CreateDirectory(folder);
348 info.Attributes |= FileAttributes.Hidden;
350 Log.InfoFormat("Created Fragments Folder: {0}", folder);
358 private void StartWatcherAgent(string path)
360 FileAgent.StatusKeeper = StatusKeeper;
361 FileAgent.Workflow = Workflow;
362 FileAgent.FragmentsPath = Path.Combine(RootPath, FragmentsFolder);
363 FileAgent.Start(path);
381 public void Dispose()
384 GC.SuppressFinalize(this);
387 protected virtual void Dispose(bool disposing)
396 public void MoveFileStates(string oldPath, string newPath)
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();
408 StatusKeeper.ChangeRoots(oldPath, newPath);
412 public interface IStatusNotification
414 void NotifyChange(string status,TraceLevel level=TraceLevel.Info);
415 void NotifyChangedFile(string filePath);