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 int _blockSize;
29 private string _blockHash;
32 public IPithosSettings Settings{get;set;}
35 public IStatusKeeper StatusKeeper { get; set; }
38 public IPithosWorkflow Workflow { get; set; }
40 public ICloudClient CloudClient { get; set; }
42 public IStatusNotification StatusNotification { get; set; }
45 public FileAgent FileAgent { get; set; }
48 public WorkflowAgent WorkflowAgent { get; set; }
51 public NetworkAgent NetworkAgent { get; set; }
53 public string UserName { get; set; }
54 public string ApiKey { get; set; }
56 private AccountInfo _accountInfo;
59 private static readonly ILog Log = LogManager.GetLogger(typeof(PithosMonitor));
64 get { return FileAgent.Pause; }
67 FileAgent.Pause = value;
70 StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused);
71 StatusNotification.NotifyChange("Paused");
75 StatusKeeper.SetPithosStatus(PithosStatus.InSynch);
76 StatusNotification.NotifyChange("Synchronizing");
81 private string _rootPath;
82 public string RootPath
84 get { return _rootPath; }
87 _rootPath = String.IsNullOrWhiteSpace(value)
94 CancellationTokenSource _cancellationSource;
97 private bool _started;
101 if (String.IsNullOrWhiteSpace(ApiKey))
102 throw new InvalidOperationException("The ApiKey is empty");
103 if (String.IsNullOrWhiteSpace(UserName))
104 throw new InvalidOperationException("The UserName is empty");
105 if (String.IsNullOrWhiteSpace(AuthenticationUrl))
106 throw new InvalidOperationException("The Authentication url is empty");
107 Contract.EndContractBlock();
109 StatusNotification.NotifyChange("Starting");
112 if (!_cancellationSource.IsCancellationRequested)
115 _cancellationSource = new CancellationTokenSource();
118 CloudClient=new CloudFilesClient(UserName,ApiKey);
119 var proxyUri = ProxyFromSettings();
120 CloudClient.Proxy = proxyUri;
121 CloudClient.UsePithos = this.UsePithos;
122 CloudClient.AuthenticationUrl = this.AuthenticationUrl;
124 _accountInfo = CloudClient.Authenticate();
125 _accountInfo.AccountPath = RootPath;
128 var pithosFolder = Path.Combine(RootPath, FolderConstants.PithosContainer);
129 if (!Directory.Exists(pithosFolder))
130 Directory.CreateDirectory(pithosFolder);
131 //Create the cache folder and ensure it is hidden
132 CreateHiddenFolder(RootPath, FolderConstants.CacheFolder);
134 var policy=CloudClient.GetAccountPolicies(_accountInfo);
136 StatusNotification.NotifyAccount(policy);
137 EnsurePithosContainers();
139 StatusKeeper.BlockHash = _blockHash;
140 StatusKeeper.BlockSize = _blockSize;
142 StatusKeeper.StartProcessing(_cancellationSource.Token);
148 StartWorkflowAgent();
149 WorkflowAgent.RestartInterruptedFiles(_accountInfo);
152 private void EnsurePithosContainers()
155 //Create the two default containers if they are missing
156 var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
157 foreach (var container in pithosContainers)
159 var info=CloudClient.GetContainerInfo(this.UserName, container);
160 if (info == ContainerInfo.Empty)
162 CloudClient.CreateContainer(this.UserName, container);
163 info = CloudClient.GetContainerInfo(this.UserName, container);
165 _blockSize = info.BlockSize;
166 _blockHash = info.BlockHash;
167 _accountInfo.BlockSize = _blockSize;
168 _accountInfo.BlockHash = _blockHash;
172 public string AuthenticationUrl { get; set; }
174 private Uri ProxyFromSettings()
176 if (Settings.UseManualProxy)
178 var proxyUri = new UriBuilder
180 Host = Settings.ProxyServer,
181 Port = Settings.ProxyPort
183 if (Settings.ProxyAuthentication)
185 proxyUri.UserName = Settings.ProxyUsername;
186 proxyUri.Password = Settings.ProxyPassword;
193 private void IndexLocalFiles()
195 StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
196 using (log4net.ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
201 var cachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
202 var directory = new DirectoryInfo(RootPath);
204 from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
205 where !file.FullName.StartsWith(cachePath, StringComparison.InvariantCultureIgnoreCase) &&
206 !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
208 StatusKeeper.ProcessExistingFiles(files);
211 catch (Exception exc)
213 Log.Error("[ERROR]", exc);
226 private void StartWorkflowAgent()
229 bool connected = NetworkListManager.IsConnectedToInternet;
230 //If we are not connected retry later
233 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
239 WorkflowAgent.StatusNotification = StatusNotification;
240 WorkflowAgent.Start();
244 //Faild to authenticate due to network or account error
245 //Retry after a while
246 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
250 public bool UsePithos { get; set; }
253 internal class LocalFileComparer:EqualityComparer<CloudAction>
255 public override bool Equals(CloudAction x, CloudAction y)
257 if (x.Action != y.Action)
259 if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
261 if (x.CloudFile != null && y.CloudFile != null )
263 if (x.CloudFile.Hash == null & y.CloudFile.Hash != null)
265 if (x.CloudFile.Hash != null & y.CloudFile.Hash == null)
267 if (x.CloudFile.Hash == null & y.CloudFile.Hash == null)
268 return (x.CloudFile.Name == y.CloudFile.Name);
269 if (!x.CloudFile.Hash.Equals(y.CloudFile.Hash))
272 if (x.CloudFile == null ^ y.CloudFile == null ||
273 x.LocalFile == null ^ y.LocalFile == null)
278 public override int GetHashCode(CloudAction obj)
280 var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
281 var hash2 = (obj.CloudFile == null) ? int.MaxValue : (obj.CloudFile.Hash ?? obj.CloudFile.Name??"").GetHashCode();
282 var hash3 = obj.Action.GetHashCode();
283 return hash1 ^ hash2 & hash3;
289 private void StartNetworkAgent()
292 NetworkAgent.AddAccount(_accountInfo);
294 NetworkAgent.StatusNotification = StatusNotification;
296 NetworkAgent.Start();
298 NetworkAgent.ProcessRemoteFiles();
301 //Make sure a hidden cache folder exists to store partial downloads
302 private static string CreateHiddenFolder(string rootPath, string folderName)
304 if (String.IsNullOrWhiteSpace(rootPath))
305 throw new ArgumentNullException("rootPath");
306 if (!Path.IsPathRooted(rootPath))
307 throw new ArgumentException("rootPath");
308 if (String.IsNullOrWhiteSpace(folderName))
309 throw new ArgumentNullException("folderName");
310 Contract.EndContractBlock();
312 var folder = Path.Combine(rootPath, folderName);
313 if (!Directory.Exists(folder))
315 var info = Directory.CreateDirectory(folder);
316 info.Attributes |= FileAttributes.Hidden;
318 Log.InfoFormat("Created cache Folder: {0}", folder);
322 var info = new DirectoryInfo(folder);
323 if ((info.Attributes & FileAttributes.Hidden) == 0)
325 info.Attributes |= FileAttributes.Hidden;
326 Log.InfoFormat("Reset cache folder to hidden: {0}", folder);
335 private void StartWatcherAgent()
337 AgentLocator<FileAgent>.Register(FileAgent,RootPath);
339 FileAgent.StatusKeeper = StatusKeeper;
340 FileAgent.Workflow = Workflow;
341 FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
342 FileAgent.Start(_accountInfo, RootPath);
347 AgentLocator<FileAgent>.Remove(RootPath);
363 public void Dispose()
366 GC.SuppressFinalize(this);
369 protected virtual void Dispose(bool disposing)
378 public void MoveFileStates(string oldPath, string newPath)
380 if (String.IsNullOrWhiteSpace(oldPath))
381 throw new ArgumentNullException("oldPath");
382 if (!Path.IsPathRooted(oldPath))
383 throw new ArgumentException("oldPath must be an absolute path","oldPath");
384 if (string.IsNullOrWhiteSpace(newPath))
385 throw new ArgumentNullException("newPath");
386 if (!Path.IsPathRooted(newPath))
387 throw new ArgumentException("newPath must be an absolute path","newPath");
388 Contract.EndContractBlock();
390 StatusKeeper.ChangeRoots(oldPath, newPath);
393 public void AddSelectivePaths(string[] added)
395 /* FileAgent.SelectivePaths.AddRange(added);
396 NetworkAgent.SyncPaths(added);*/
399 public void RemoveSelectivePaths(string[] removed)
401 FileAgent.SelectivePaths.RemoveAll(removed.Contains);
402 foreach (var removedPath in removed.Where(Directory.Exists))
404 Directory.Delete(removedPath,true);
408 public IEnumerable<string> GetRootFolders()
410 var dirs = from container in CloudClient.ListContainers(UserName)
411 from dir in CloudClient.ListObjects(UserName, container.Name, "")
418 public interface IStatusNotification
420 void NotifyChange(string status,TraceLevel level=TraceLevel.Info);
421 void NotifyChangedFile(string filePath);
422 void NotifyAccount(AccountInfo policy);