2 /* -----------------------------------------------------------------------
3 * <copyright file="PithosMonitor.cs" company="GRNet">
5 * Copyright 2011-2012 GRNET S.A. All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
11 * 1. Redistributions of source code must retain the above
12 * copyright notice, this list of conditions and the following
15 * 2. Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials
18 * provided with the distribution.
21 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
34 * The views and conclusions contained in the software and
35 * documentation are those of the authors and should not be
36 * interpreted as representing official policies, either expressed
37 * or implied, of GRNET S.A.
39 * -----------------------------------------------------------------------
43 using System.Collections.Generic;
44 using System.ComponentModel.Composition;
45 using System.Diagnostics.Contracts;
48 using System.Reflection;
49 using System.Threading;
50 using System.Threading.Tasks;
51 using Pithos.Core.Agents;
52 using Pithos.Interfaces;
58 [Export(typeof(PithosMonitor))]
59 public class PithosMonitor:IDisposable
61 private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
63 private int _blockSize;
64 private string _blockHash;
67 public IPithosSettings Settings{get;set;}
69 private IStatusKeeper _statusKeeper;
72 public IStatusKeeper StatusKeeper
74 get { return _statusKeeper; }
77 _statusKeeper = value;
78 FileAgent.StatusKeeper = value;
83 private IPithosWorkflow _workflow;
86 public IPithosWorkflow Workflow
88 get { return _workflow; }
92 FileAgent.Workflow = value;
96 public ICloudClient CloudClient { get; set; }
98 public IStatusNotification StatusNotification { get; set; }
101 public FileAgent FileAgent { get; private set; }
103 private WorkflowAgent _workflowAgent;
106 public WorkflowAgent WorkflowAgent
108 get { return _workflowAgent; }
111 _workflowAgent = value;
112 FileAgent.WorkflowAgent = value;
117 public NetworkAgent NetworkAgent { get; set; }
119 public PollAgent PollAgent { get; set; }
121 public string UserName { get; set; }
122 private string _apiKey;
125 get { return _apiKey; }
129 if (_accountInfo != null)
130 _accountInfo.Token = value;
134 private AccountInfo _accountInfo;
143 get { return FileAgent.Pause; }
146 FileAgent.Pause = value;
150 private string _rootPath;
151 public string RootPath
153 get { return _rootPath; }
156 _rootPath = String.IsNullOrWhiteSpace(value)
163 CancellationTokenSource _cancellationSource;
165 public PithosMonitor()
167 FileAgent = new FileAgent();
170 private bool _started;
174 if (String.IsNullOrWhiteSpace(ApiKey))
175 throw new InvalidOperationException("The ApiKey is empty");
176 if (String.IsNullOrWhiteSpace(UserName))
177 throw new InvalidOperationException("The UserName is empty");
178 if (String.IsNullOrWhiteSpace(AuthenticationUrl))
179 throw new InvalidOperationException("The Authentication url is empty");
180 Contract.EndContractBlock();
182 //If the account doesn't have a valid path, don't start monitoring but don't throw either
183 if (String.IsNullOrWhiteSpace(RootPath))
187 WorkflowAgent.StatusNotification = StatusNotification;
189 StatusNotification.NotifyChange("Starting");
192 if (!_cancellationSource.IsCancellationRequested)
195 _cancellationSource = new CancellationTokenSource();
198 CloudClient = new CloudFilesClient(UserName, ApiKey)
199 {UsePithos = true, AuthenticationUrl = AuthenticationUrl};
202 _accountInfo = CloudClient.Authenticate();
203 _accountInfo.SiteUri = AuthenticationUrl;
204 _accountInfo.AccountPath = RootPath;
207 var pithosFolder = Path.Combine(RootPath, FolderConstants.PithosContainer);
208 if (!Directory.Exists(pithosFolder))
209 Directory.CreateDirectory(pithosFolder);
210 //Create the cache folder and ensure it is hidden
211 CreateHiddenFolder(RootPath, FolderConstants.CacheFolder);
213 var policy=CloudClient.GetAccountPolicies(_accountInfo);
215 StatusNotification.NotifyAccount(policy);
216 EnsurePithosContainers();
218 StatusKeeper.BlockHash = _blockHash;
219 StatusKeeper.BlockSize = _blockSize;
221 StatusKeeper.StartProcessing(_cancellationSource.Token);
223 //Extract the URIs from the string collection
224 var settings = Settings.Accounts.First(s => s.AccountName == _accountInfo.UserName);
225 var selectiveUrls=settings.SelectiveFolders.Cast<string>().Select(url => new Uri(url)).ToArray();
227 SetSelectivePaths(selectiveUrls,null,null);
233 WorkflowAgent.RestartInterruptedFiles(_accountInfo);
237 private void EnsurePithosContainers()
240 //Create the two default containers if they are missing
241 var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
242 foreach (var container in pithosContainers)
244 var info=CloudClient.GetContainerInfo(UserName, container);
245 if (info == ContainerInfo.Empty)
247 CloudClient.CreateContainer(UserName, container);
248 info = CloudClient.GetContainerInfo(UserName, container);
250 _blockSize = info.BlockSize;
251 _blockHash = info.BlockHash;
252 _accountInfo.BlockSize = _blockSize;
253 _accountInfo.BlockHash = _blockHash;
257 public string AuthenticationUrl { get; set; }
259 private void IndexLocalFiles()
261 using (ThreadContext.Stacks["Operation"].Push("Indexing local files"))
266 //StatusNotification.NotifyChange("Indexing Local Files");
267 Log.Info("Start local indexing");
268 StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,"Indexing Local Files");
270 var cachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
271 var directory = new DirectoryInfo(RootPath);
273 from file in directory.EnumerateFiles("*", SearchOption.AllDirectories)
274 where !file.FullName.StartsWith(cachePath, StringComparison.InvariantCultureIgnoreCase) &&
275 !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)
277 StatusKeeper.ProcessExistingFiles(files);
280 catch (Exception exc)
282 Log.Error("[ERROR]", exc);
288 StatusNotification.SetPithosStatus(PithosStatus.LocalComplete,"Indexing Completed");
296 /* private void StartWorkflowAgent()
298 WorkflowAgent.StatusNotification = StatusNotification;
300 /* //On Vista and up we can check for a network connection
301 bool connected=Environment.OSVersion.Version.Major < 6 || NetworkListManager.IsConnectedToInternet;
302 //If we are not connected retry later
305 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
311 WorkflowAgent.Start();
315 //Faild to authenticate due to network or account error
316 //Retry after a while
317 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
321 internal class LocalFileComparer:EqualityComparer<CloudAction>
323 public override bool Equals(CloudAction x, CloudAction y)
325 if (x.Action != y.Action)
327 if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName))
329 if (x.CloudFile != null && y.CloudFile != null )
331 if (x.CloudFile.Hash == null & y.CloudFile.Hash != null)
333 if (x.CloudFile.Hash != null & y.CloudFile.Hash == null)
335 if (x.CloudFile.Hash == null & y.CloudFile.Hash == null)
336 return (x.CloudFile.Name == y.CloudFile.Name);
337 if (!x.CloudFile.Hash.Equals(y.CloudFile.Hash))
340 if (x.CloudFile == null ^ y.CloudFile == null ||
341 x.LocalFile == null ^ y.LocalFile == null)
346 public override int GetHashCode(CloudAction obj)
350 var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
351 var hash2 = (obj.CloudFile == null) ? int.MaxValue : (obj.CloudFile.Hash ?? obj.CloudFile.Name??"").GetHashCode();
352 var hash3 = obj.Action.GetHashCode();
353 return hash1 ^ hash2 & hash3;
358 private void StartNetworkAgent()
360 NetworkAgent.StatusNotification = StatusNotification;
362 //TODO: The Network and Poll agents are not account specific
363 //They should be moved outside PithosMonitor
364 NetworkAgent.Start();
366 PollAgent.AddAccount(_accountInfo);
368 PollAgent.StatusNotification = StatusNotification;
370 PollAgent.PollRemoteFiles();
373 //Make sure a hidden cache folder exists to store partial downloads
374 private static void CreateHiddenFolder(string rootPath, string folderName)
376 if (String.IsNullOrWhiteSpace(rootPath))
377 throw new ArgumentNullException("rootPath");
378 if (!Path.IsPathRooted(rootPath))
379 throw new ArgumentException("rootPath");
380 if (String.IsNullOrWhiteSpace(folderName))
381 throw new ArgumentNullException("folderName");
382 Contract.EndContractBlock();
384 var folder = Path.Combine(rootPath, folderName);
385 if (!Directory.Exists(folder))
387 var info = Directory.CreateDirectory(folder);
388 info.Attributes |= FileAttributes.Hidden;
390 Log.InfoFormat("Created cache Folder: {0}", folder);
394 var info = new DirectoryInfo(folder);
395 if ((info.Attributes & FileAttributes.Hidden) == 0)
397 info.Attributes |= FileAttributes.Hidden;
398 Log.InfoFormat("Reset cache folder to hidden: {0}", folder);
406 private void StartWatcherAgent()
408 if (Log.IsDebugEnabled)
409 Log.DebugFormat("Start Folder Monitoring [{0}]",RootPath);
411 AgentLocator<FileAgent>.Register(FileAgent,RootPath);
413 FileAgent.StatusKeeper = StatusKeeper;
414 FileAgent.StatusNotification = StatusNotification;
415 FileAgent.Workflow = Workflow;
416 FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
417 FileAgent.Start(_accountInfo, RootPath);
422 AgentLocator<FileAgent>.Remove(RootPath);
435 public void Dispose()
438 GC.SuppressFinalize(this);
441 protected virtual void Dispose(bool disposing)
450 public void MoveFileStates(string oldPath, string newPath)
452 if (String.IsNullOrWhiteSpace(oldPath))
453 throw new ArgumentNullException("oldPath");
454 if (!Path.IsPathRooted(oldPath))
455 throw new ArgumentException("oldPath must be an absolute path","oldPath");
456 if (string.IsNullOrWhiteSpace(newPath))
457 throw new ArgumentNullException("newPath");
458 if (!Path.IsPathRooted(newPath))
459 throw new ArgumentException("newPath must be an absolute path","newPath");
460 Contract.EndContractBlock();
462 StatusKeeper.ChangeRoots(oldPath, newPath);
465 public void SetSelectivePaths(Uri[] uris,Uri[] added, Uri[] removed)
467 //Convert the uris to paths
468 var selectivePaths = UrisToFilePaths(uris);
470 FileAgent.SelectivePaths=selectivePaths;
471 PollAgent.SetSyncUris(uris);
473 var removedPaths = UrisToFilePaths(removed);
474 UnversionSelectivePaths(removedPaths);
479 /// Mark all unselected paths as Unversioned
481 /// <param name="removed"></param>
482 private void UnversionSelectivePaths(List<string> removed)
487 //Ensure we remove any file state below the deleted folders
488 FileState.UnversionPaths(removed);
493 /// Return a list of absolute filepaths from a list of Uris
495 /// <param name="uris"></param>
496 /// <returns></returns>
497 private List<string> UrisToFilePaths(IEnumerable<Uri> uris)
500 return new List<string>();
502 return (from uri in uris
503 let relativePath = _accountInfo.StorageUri
504 .MakeRelativeUri(uri)
505 .RelativeUriToFilePath()
506 //Trim the account name
507 select Path.Combine(RootPath, relativePath.After(_accountInfo.UserName + '\\'))).ToList();
511 public ObjectInfo GetObjectInfo(string filePath)
513 if (String.IsNullOrWhiteSpace(filePath))
514 throw new ArgumentNullException("filePath");
515 Contract.EndContractBlock();
517 var file=new FileInfo(filePath);
518 string relativeUrl;//=file.AsRelativeUrlTo(this.RootPath);
519 var relativePath = file.AsRelativeTo(RootPath);
521 string accountName,container;
523 var parts=relativePath.Split('\\');
525 var accountInfo = _accountInfo;
526 if (relativePath.StartsWith(FolderConstants.OthersFolder))
528 accountName = parts[1];
529 container = parts[2];
530 relativeUrl = String.Join("/", parts.Splice(3));
531 //Create the root URL for the target account
532 var oldName = UserName;
533 var absoluteUri = _accountInfo.StorageUri.AbsoluteUri;
534 var nameIndex=absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
535 var root=absoluteUri.Substring(0, nameIndex);
537 accountInfo = new AccountInfo
539 UserName = accountName,
540 AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
541 StorageUri = new Uri(root + accountName),
542 BlockHash=accountInfo.BlockHash,
543 BlockSize=accountInfo.BlockSize,
544 Token=accountInfo.Token
549 accountName = UserName;
550 container = parts[0];
551 relativeUrl = String.Join("/", parts.Splice(1));
554 var client = new CloudFilesClient(accountInfo);
555 var objectInfo=client.GetObjectInfo(accountName, container, relativeUrl);
559 public Task<ContainerInfo> GetContainerInfo(string filePath)
561 if (String.IsNullOrWhiteSpace(filePath))
562 throw new ArgumentNullException("filePath");
563 Contract.EndContractBlock();
565 var file=new FileInfo(filePath);
566 var relativePath = file.AsRelativeTo(RootPath);
568 string accountName,container;
570 var parts=relativePath.Split('\\');
572 var accountInfo = _accountInfo;
573 if (relativePath.StartsWith(FolderConstants.OthersFolder))
575 accountName = parts[1];
576 container = parts[2];
577 //Create the root URL for the target account
578 var oldName = UserName;
579 var absoluteUri = _accountInfo.StorageUri.AbsoluteUri;
580 var nameIndex=absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
581 var root=absoluteUri.Substring(0, nameIndex);
583 accountInfo = new AccountInfo
585 UserName = accountName,
586 AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
587 StorageUri = new Uri(root + accountName),
588 BlockHash=accountInfo.BlockHash,
589 BlockSize=accountInfo.BlockSize,
590 Token=accountInfo.Token
595 accountName = UserName;
596 container = parts[0];
599 return Task.Factory.StartNew(() =>
601 var client = new CloudFilesClient(accountInfo);
602 var containerInfo = client.GetContainerInfo(accountName, container);
603 return containerInfo;