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;}
34 private IStatusKeeper _statusKeeper;
37 public IStatusKeeper StatusKeeper
39 get { return _statusKeeper; }
42 _statusKeeper = value;
43 FileAgent.StatusKeeper = value;
48 private IPithosWorkflow _workflow;
51 public IPithosWorkflow Workflow
53 get { return _workflow; }
57 FileAgent.Workflow = value;
61 public ICloudClient CloudClient { get; set; }
63 public IStatusNotification StatusNotification { get; set; }
66 public FileAgent FileAgent { get; private set; }
68 private WorkflowAgent _workflowAgent;
71 public WorkflowAgent WorkflowAgent
73 get { return _workflowAgent; }
76 _workflowAgent = value;
77 FileAgent.WorkflowAgent = value;
82 public NetworkAgent NetworkAgent { get; set; }
84 public string UserName { get; set; }
85 private string _apiKey;
88 get { return _apiKey; }
92 if (_accountInfo != null)
93 _accountInfo.Token = value;
97 private AccountInfo _accountInfo;
102 private static readonly ILog Log = LogManager.GetLogger(typeof(PithosMonitor));
107 get { return FileAgent.Pause; }
110 FileAgent.Pause = value;
113 StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused);
114 StatusNotification.NotifyChange("Paused");
118 StatusKeeper.SetPithosStatus(PithosStatus.InSynch);
119 StatusNotification.NotifyChange("Synchronizing");
124 private string _rootPath;
125 public string RootPath
127 get { return _rootPath; }
130 _rootPath = String.IsNullOrWhiteSpace(value)
137 CancellationTokenSource _cancellationSource;
139 public PithosMonitor()
141 FileAgent = new FileAgent();
144 private bool _started;
148 if (String.IsNullOrWhiteSpace(ApiKey))
149 throw new InvalidOperationException("The ApiKey is empty");
150 if (String.IsNullOrWhiteSpace(UserName))
151 throw new InvalidOperationException("The UserName is empty");
152 if (String.IsNullOrWhiteSpace(AuthenticationUrl))
153 throw new InvalidOperationException("The Authentication url is empty");
154 Contract.EndContractBlock();
156 //If the account doesn't have a valid path, don't start monitoring but don't throw either
157 if (String.IsNullOrWhiteSpace(RootPath))
161 StatusNotification.NotifyChange("Starting");
164 if (!_cancellationSource.IsCancellationRequested)
167 _cancellationSource = new CancellationTokenSource();
170 CloudClient=new CloudFilesClient(UserName,ApiKey);
171 var proxyUri = ProxyFromSettings();
172 CloudClient.Proxy = proxyUri;
173 CloudClient.UsePithos = true;
174 CloudClient.AuthenticationUrl = this.AuthenticationUrl;
176 _accountInfo = CloudClient.Authenticate();
177 _accountInfo.SiteUri = AuthenticationUrl;
178 _accountInfo.AccountPath = RootPath;
181 var pithosFolder = Path.Combine(RootPath, FolderConstants.PithosContainer);
182 if (!Directory.Exists(pithosFolder))
183 Directory.CreateDirectory(pithosFolder);
184 //Create the cache folder and ensure it is hidden
185 CreateHiddenFolder(RootPath, FolderConstants.CacheFolder);
187 var policy=CloudClient.GetAccountPolicies(_accountInfo);
189 StatusNotification.NotifyAccount(policy);
190 EnsurePithosContainers();
192 StatusKeeper.BlockHash = _blockHash;
193 StatusKeeper.BlockSize = _blockSize;
195 StatusKeeper.StartProcessing(_cancellationSource.Token);
201 StartWorkflowAgent();
202 WorkflowAgent.RestartInterruptedFiles(_accountInfo);
206 private void EnsurePithosContainers()
209 //Create the two default containers if they are missing
210 var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
211 foreach (var container in pithosContainers)
213 var info=CloudClient.GetContainerInfo(this.UserName, container);
214 if (info == ContainerInfo.Empty)
216 CloudClient.CreateContainer(this.UserName, container);
217 info = CloudClient.GetContainerInfo(this.UserName, container);
219 _blockSize = info.BlockSize;
220 _blockHash = info.BlockHash;
221 _accountInfo.BlockSize = _blockSize;
222 _accountInfo.BlockHash = _blockHash;
226 public string AuthenticationUrl { get; set; }
228 private Uri ProxyFromSettings()
230 if (Settings.UseManualProxy)
232 var proxyUri = new UriBuilder
234 Host = Settings.ProxyServer,
235 Port = Settings.ProxyPort
237 if (Settings.ProxyAuthentication)
239 proxyUri.UserName = Settings.ProxyUsername;
240 proxyUri.Password = Settings.ProxyPassword;
247 private void IndexLocalFiles()
249 StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
250 using (log4net.ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
255 var cachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
256 var directory = new DirectoryInfo(RootPath);
258 from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
259 where !file.FullName.StartsWith(cachePath, StringComparison.InvariantCultureIgnoreCase) &&
260 !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
262 StatusKeeper.ProcessExistingFiles(files);
265 catch (Exception exc)
267 Log.Error("[ERROR]", exc);
280 private void StartWorkflowAgent()
282 //On Vista and up we can check for a network connection
283 bool connected=Environment.OSVersion.Version.Major < 6 || NetworkListManager.IsConnectedToInternet;
284 //If we are not connected retry later
287 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
293 WorkflowAgent.StatusNotification = StatusNotification;
294 WorkflowAgent.Start();
298 //Faild to authenticate due to network or account error
299 //Retry after a while
300 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
304 internal class LocalFileComparer:EqualityComparer<CloudAction>
306 public override bool Equals(CloudAction x, CloudAction y)
308 if (x.Action != y.Action)
310 if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
312 if (x.CloudFile != null && y.CloudFile != null )
314 if (x.CloudFile.Hash == null & y.CloudFile.Hash != null)
316 if (x.CloudFile.Hash != null & y.CloudFile.Hash == null)
318 if (x.CloudFile.Hash == null & y.CloudFile.Hash == null)
319 return (x.CloudFile.Name == y.CloudFile.Name);
320 if (!x.CloudFile.Hash.Equals(y.CloudFile.Hash))
323 if (x.CloudFile == null ^ y.CloudFile == null ||
324 x.LocalFile == null ^ y.LocalFile == null)
329 public override int GetHashCode(CloudAction obj)
331 var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
332 var hash2 = (obj.CloudFile == null) ? int.MaxValue : (obj.CloudFile.Hash ?? obj.CloudFile.Name??"").GetHashCode();
333 var hash3 = obj.Action.GetHashCode();
334 return hash1 ^ hash2 & hash3;
340 private void StartNetworkAgent()
343 NetworkAgent.AddAccount(_accountInfo);
345 NetworkAgent.StatusNotification = StatusNotification;
347 NetworkAgent.Start();
349 NetworkAgent.PollRemoteFiles();
352 //Make sure a hidden cache folder exists to store partial downloads
353 private static string CreateHiddenFolder(string rootPath, string folderName)
355 if (String.IsNullOrWhiteSpace(rootPath))
356 throw new ArgumentNullException("rootPath");
357 if (!Path.IsPathRooted(rootPath))
358 throw new ArgumentException("rootPath");
359 if (String.IsNullOrWhiteSpace(folderName))
360 throw new ArgumentNullException("folderName");
361 Contract.EndContractBlock();
363 var folder = Path.Combine(rootPath, folderName);
364 if (!Directory.Exists(folder))
366 var info = Directory.CreateDirectory(folder);
367 info.Attributes |= FileAttributes.Hidden;
369 Log.InfoFormat("Created cache Folder: {0}", folder);
373 var info = new DirectoryInfo(folder);
374 if ((info.Attributes & FileAttributes.Hidden) == 0)
376 info.Attributes |= FileAttributes.Hidden;
377 Log.InfoFormat("Reset cache folder to hidden: {0}", folder);
386 private void StartWatcherAgent()
388 AgentLocator<FileAgent>.Register(FileAgent,RootPath);
390 FileAgent.StatusKeeper = StatusKeeper;
391 FileAgent.Workflow = Workflow;
392 FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
393 FileAgent.Start(_accountInfo, RootPath);
398 AgentLocator<FileAgent>.Remove(RootPath);
414 public void Dispose()
417 GC.SuppressFinalize(this);
420 protected virtual void Dispose(bool disposing)
429 public void MoveFileStates(string oldPath, string newPath)
431 if (String.IsNullOrWhiteSpace(oldPath))
432 throw new ArgumentNullException("oldPath");
433 if (!Path.IsPathRooted(oldPath))
434 throw new ArgumentException("oldPath must be an absolute path","oldPath");
435 if (string.IsNullOrWhiteSpace(newPath))
436 throw new ArgumentNullException("newPath");
437 if (!Path.IsPathRooted(newPath))
438 throw new ArgumentException("newPath must be an absolute path","newPath");
439 Contract.EndContractBlock();
441 StatusKeeper.ChangeRoots(oldPath, newPath);
444 public void AddSelectivePaths(string[] added)
446 /* FileAgent.SelectivePaths.AddRange(added);
447 NetworkAgent.SyncPaths(added);*/
450 public void RemoveSelectivePaths(string[] removed)
452 FileAgent.SelectivePaths.RemoveAll(removed.Contains);
453 foreach (var removedPath in removed.Where(Directory.Exists))
455 Directory.Delete(removedPath,true);
459 public IEnumerable<string> GetRootFolders()
461 var dirs = from container in CloudClient.ListContainers(UserName)
462 from dir in CloudClient.ListObjects(UserName, container.Name, "")
467 public ObjectInfo GetObjectInfo(string filePath)
469 if (String.IsNullOrWhiteSpace(filePath))
470 throw new ArgumentNullException("filePath");
471 Contract.EndContractBlock();
473 var file=new FileInfo(filePath);
474 string relativeUrl;//=file.AsRelativeUrlTo(this.RootPath);
475 var relativePath = file.AsRelativeTo(RootPath);
477 string accountName,container;
479 var parts=relativePath.Split('\\');
481 var accountInfo = _accountInfo;
482 if (relativePath.StartsWith(FolderConstants.OthersFolder))
484 accountName = parts[1];
485 container = parts[2];
486 relativeUrl = String.Join("/", parts.Splice(3));
487 //Create the root URL for the target account
488 var oldName = UserName;
489 var absoluteUri = _accountInfo.StorageUri.AbsoluteUri;
490 var nameIndex=absoluteUri.IndexOf(oldName);
491 var root=absoluteUri.Substring(0, nameIndex);
493 accountInfo = new AccountInfo
495 UserName = accountName,
496 AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
497 StorageUri = new Uri(root + accountName),
498 BlockHash=accountInfo.BlockHash,
499 BlockSize=accountInfo.BlockSize,
500 Token=accountInfo.Token
505 accountName = this.UserName;
506 container = parts[0];
507 relativeUrl = String.Join("/", parts.Splice(1));
510 var client = new CloudFilesClient(accountInfo);
511 var objectInfo=client.GetObjectInfo(accountName, container, relativeUrl);
515 public Task<ContainerInfo> GetContainerInfo(string filePath)
517 if (String.IsNullOrWhiteSpace(filePath))
518 throw new ArgumentNullException("filePath");
519 Contract.EndContractBlock();
521 var file=new FileInfo(filePath);
522 var relativePath = file.AsRelativeTo(RootPath);
524 string accountName,container;
526 var parts=relativePath.Split('\\');
528 var accountInfo = _accountInfo;
529 if (relativePath.StartsWith(FolderConstants.OthersFolder))
531 accountName = parts[1];
532 container = parts[2];
533 //Create the root URL for the target account
534 var oldName = UserName;
535 var absoluteUri = _accountInfo.StorageUri.AbsoluteUri;
536 var nameIndex=absoluteUri.IndexOf(oldName);
537 var root=absoluteUri.Substring(0, nameIndex);
539 accountInfo = new AccountInfo
541 UserName = accountName,
542 AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
543 StorageUri = new Uri(root + accountName),
544 BlockHash=accountInfo.BlockHash,
545 BlockSize=accountInfo.BlockSize,
546 Token=accountInfo.Token
551 accountName = UserName;
552 container = parts[0];
555 return Task.Factory.StartNew(() =>
557 var client = new CloudFilesClient(accountInfo);
558 var containerInfo = client.GetContainerInfo(accountName, container);
559 return containerInfo;
565 public interface IStatusNotification
567 void NotifyChange(string status,TraceLevel level=TraceLevel.Info);
568 void NotifyChangedFile(string filePath);
569 void NotifyAccount(AccountInfo policy);