X-Git-Url: https://code.grnet.gr/git/pithos-ms-client/blobdiff_plain/3c43ec9b31676dfbeb6f413a93bd64488d6e0e28..0f369429e94e6c8128658b8f45447abbb0b0a5ca:/trunk/Pithos.Core/PithosMonitor.cs diff --git a/trunk/Pithos.Core/PithosMonitor.cs b/trunk/Pithos.Core/PithosMonitor.cs index 27755f3..7ca5a30 100644 --- a/trunk/Pithos.Core/PithosMonitor.cs +++ b/trunk/Pithos.Core/PithosMonitor.cs @@ -1,718 +1,577 @@ +#region +/* ----------------------------------------------------------------------- + * + * + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + * + * ----------------------------------------------------------------------- + */ +#endregion using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Diagnostics; using System.Diagnostics.Contracts; using System.IO; using System.Linq; -using System.Net.NetworkInformation; -using System.Security.Cryptography; -using System.ServiceModel.Description; -using System.Text; +using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Castle.ActiveRecord.Queries; -using Microsoft.WindowsAPICodePack.Net; +using Pithos.Core.Agents; using Pithos.Interfaces; -using System.ServiceModel; +using Pithos.Network; +using log4net; namespace Pithos.Core { [Export(typeof(PithosMonitor))] public class PithosMonitor:IDisposable { - private const string PithosContainer = "pithos"; - private const string TrashContainer = "trash"; + private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - [Import] - public IPithosSettings Settings{get;set;} - - [Import] - public IStatusKeeper StatusKeeper { get; set; } + private int _blockSize; + private string _blockHash; [Import] - public IPithosWorkflow Workflow { get; set; } + public IPithosSettings Settings{get;set;} - [Import] - public ICloudClient CloudClient { get; set; } + private IStatusKeeper _statusKeeper; [Import] - public ICloudClient CloudListeningClient { get; set; } - - public string UserName { get; set; } - public string ApiKey { get; set; } - - private ServiceHost _statusService { get; set; } - - private FileSystemWatcher _watcher; - - public bool Pause + public IStatusKeeper StatusKeeper { - get { return _watcher == null || !_watcher.EnableRaisingEvents; } + get { return _statusKeeper; } set { - if (_watcher!=null) - _watcher.EnableRaisingEvents = !value; - if (value) - { - StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused); - } - else - { - StatusKeeper.SetPithosStatus(PithosStatus.InSynch); - } + _statusKeeper = value; + FileAgent.StatusKeeper = value; } } - public string RootPath { get; set; } - - - CancellationTokenSource _cancellationSource; - - readonly BlockingCollection _fileEvents = new BlockingCollection(); - readonly BlockingCollection _uploadEvents = new BlockingCollection(); - + private IPithosWorkflow _workflow; - public void Start() + [Import] + public IPithosWorkflow Workflow { - - if (_cancellationSource != null) + get { return _workflow; } + set { - if (!_cancellationSource.IsCancellationRequested) - return; + _workflow = value; + FileAgent.Workflow = value; } - _cancellationSource = new CancellationTokenSource(); - - var proxyUri = ProxyFromSettings(); - CloudClient.Proxy = proxyUri; - CloudClient.UsePithos = this.UsePithos; - EnsurePithosContainers(); - StatusKeeper.StartProcessing(_cancellationSource.Token); - IndexLocalFiles(RootPath); - StartMonitoringFiles(RootPath); + } - StartStatusService(); + public ICloudClient CloudClient { get; set; } - StartNetwork(); - } + public IStatusNotification StatusNotification { get; set; } - private void EnsurePithosContainers() - { - CloudClient.UsePithos = this.UsePithos; - CloudClient.Authenticate(UserName, ApiKey); + //[Import] + public FileAgent FileAgent { get; private set; } - var pithosContainers = new[] {PithosContainer, TrashContainer}; - foreach (var container in pithosContainers) - { - if (!CloudClient.ContainerExists(container)) - CloudClient.CreateContainer(container); - } - } + private WorkflowAgent _workflowAgent; - private Uri ProxyFromSettings() - { - if (Settings.UseManualProxy) + [Import] + public WorkflowAgent WorkflowAgent + { + get { return _workflowAgent; } + set { - var proxyUri = new UriBuilder - { - Host = Settings.ProxyServer, - Port = Settings.ProxyPort - }; - if (Settings.ProxyAuthentication) - { - proxyUri.UserName = Settings.ProxyUsername; - proxyUri.Password = Settings.ProxyPassword; - } - return proxyUri.Uri; + _workflowAgent = value; + FileAgent.WorkflowAgent = value; } - return null; } + + [Import] + public NetworkAgent NetworkAgent { get; set; } + [Import] + public PollAgent PollAgent { get; set; } - private void IndexLocalFiles(string path) + public string UserName { get; set; } + private string _apiKey; + public string ApiKey { - Trace.TraceInformation("[START] Inxed Local"); - try - { - var files = - from filePath in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).AsParallel() - select filePath; - StatusKeeper.StoreUnversionedFiles(files); - - RestartInterruptedFiles(); - } - catch (Exception exc) - { - Trace.TraceError("[ERROR] Index Local - {0}", exc); - } - finally + get { return _apiKey; } + set { - Trace.TraceInformation("[END] Inxed Local"); + _apiKey = value; + if (_accountInfo != null) + _accountInfo.Token = value; } } - private void RestartInterruptedFiles() - { - var interruptedStates = new[] { FileOverlayStatus.Unversioned, FileOverlayStatus.Modified }; - var filesQuery = from state in FileState.Queryable - where interruptedStates.Contains(state.OverlayStatus) - select new WorkflowState - { - Path = state.FilePath.ToLower(), - FileName = Path.GetFileName(state.FilePath).ToLower(), - Status=state.OverlayStatus==FileOverlayStatus.Unversioned? - FileStatus.Created: - FileStatus.Modified, - TriggeringChange = state.OverlayStatus==FileOverlayStatus.Unversioned? - WatcherChangeTypes.Created: - WatcherChangeTypes.Changed - }; - _uploadEvents.AddFromEnumerable(filesQuery,false); - - } - - private void StartStatusService() - { - // Create a ServiceHost for the CalculatorService type and provide the base address. - var baseAddress = new Uri("net.pipe://localhost/pithos"); - _statusService = new ServiceHost(typeof(StatusService), baseAddress); - - var binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None); - - _statusService.AddServiceEndpoint(typeof(IStatusService), binding, "net.pipe://localhost/pithos/statuscache"); - _statusService.AddServiceEndpoint(typeof (ISettingsService), binding, "net.pipe://localhost/pithos/settings"); - - - //// Add a mex endpoint - var smb = new ServiceMetadataBehavior - { - HttpGetEnabled = true, - HttpGetUrl = new Uri("http://localhost:30000/pithos/mex") - }; - _statusService.Description.Behaviors.Add(smb); + private AccountInfo _accountInfo; - _statusService.Open(); - } - - private void StopStatusService() - { - if (_statusService == null) - return; - if (_statusService.State == CommunicationState.Faulted) - _statusService.Abort(); - else if (_statusService.State != CommunicationState.Closed) - _statusService.Close(); - _statusService = null; - } - private void StartNetwork() + public bool Pause { - - bool connected = NetworkListManager.IsConnectedToInternet; - //If we are not connected retry later - if (!connected) + get { return FileAgent.Pause; } + set { - Task.Factory.StartNewDelayed(10000, StartNetwork); - return; + FileAgent.Pause = value; } + } - try - { - CloudClient.UsePithos = this.UsePithos; - CloudClient.Authenticate(UserName, ApiKey); - - StartListening(RootPath); - StartSending(); - } - catch (Exception) + private string _rootPath; + public string RootPath + { + get { return _rootPath; } + set { - //Faild to authenticate due to network or account error - //Retry after a while - Task.Factory.StartNewDelayed(10000, StartNetwork); + _rootPath = String.IsNullOrWhiteSpace(value) + ? String.Empty + : value.ToLower(); } } - public bool UsePithos { get; set; } - internal enum CloudActionType + CancellationTokenSource _cancellationSource; + + public PithosMonitor() { - Upload=0, - Download, - UploadUnconditional, - DownloadUnconditional, - DeleteLocal, - DeleteCloud + FileAgent = new FileAgent(); } + private bool _started; - internal class ListenerAction - { - public CloudActionType Action { get; set; } - public FileInfo LocalFile { get; set; } - public ObjectInfo CloudFile { get; set; } + public void Start() + { + if (String.IsNullOrWhiteSpace(ApiKey)) + throw new InvalidOperationException("The ApiKey is empty"); + if (String.IsNullOrWhiteSpace(UserName)) + throw new InvalidOperationException("The UserName is empty"); + if (String.IsNullOrWhiteSpace(AuthenticationUrl)) + throw new InvalidOperationException("The Authentication url is empty"); + Contract.EndContractBlock(); + + //If the account doesn't have a valid path, don't start monitoring but don't throw either + if (String.IsNullOrWhiteSpace(RootPath)) + //TODO; Warn user? + return; - public Lazy LocalHash { get; set; } + WorkflowAgent.StatusNotification = StatusNotification; - public ListenerAction(CloudActionType action, FileInfo localFile, ObjectInfo cloudFile) + StatusNotification.NotifyChange("Starting"); + if (_started) { - Action = action; - LocalFile = localFile; - CloudFile = cloudFile; - LocalHash=new Lazy(()=>Signature.CalculateHash(LocalFile.FullName),LazyThreadSafetyMode.ExecutionAndPublication); + if (!_cancellationSource.IsCancellationRequested) + return; } - - } + _cancellationSource = new CancellationTokenSource(); - internal class LocalFileComparer:EqualityComparer - { - public override bool Equals(ListenerAction x, ListenerAction y) + lock (this) { - if (x.Action != y.Action) - return false; - if (x.LocalFile != null && y.LocalFile != null && !x.LocalFile.FullName.Equals(y.LocalFile.FullName)) - return false; - if (x.CloudFile != null && y.CloudFile != null && !x.CloudFile.Hash.Equals(y.CloudFile.Hash)) - return false; - if (x.CloudFile == null ^ y.CloudFile == null || - x.LocalFile == null ^ y.LocalFile == null) - return false; - return true; + CloudClient = new CloudFilesClient(UserName, ApiKey) + {UsePithos = true, AuthenticationUrl = AuthenticationUrl}; + _accountInfo = CloudClient.Authenticate(); } + _accountInfo.SiteUri = AuthenticationUrl; + _accountInfo.AccountPath = RootPath; - public override int GetHashCode(ListenerAction obj) - { - var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode(); - var hash2 = (obj.CloudFile == null) ? int.MaxValue : obj.CloudFile.Hash.GetHashCode(); - var hash3 = obj.Action.GetHashCode(); - return hash1 ^ hash2 & hash3; - } - } - private BlockingCollection _networkActions=new BlockingCollection(); + var pithosFolder = Path.Combine(RootPath, FolderConstants.PithosContainer); + if (!Directory.Exists(pithosFolder)) + Directory.CreateDirectory(pithosFolder); + //Create the cache folder and ensure it is hidden + CreateHiddenFolder(RootPath, FolderConstants.CacheFolder); - private Timer timer; + var policy=CloudClient.GetAccountPolicies(_accountInfo); - private void StartListening(string accountPath) - { + StatusNotification.NotifyAccount(policy); + EnsurePithosContainers(); + + StatusKeeper.BlockHash = _blockHash; + StatusKeeper.BlockSize = _blockSize; - ProcessRemoteFiles(accountPath); + StatusKeeper.StartProcessing(_cancellationSource.Token); + IndexLocalFiles(); + //Extract the URIs from the string collection + var settings = Settings.Accounts.First(s => s.AccountKey == _accountInfo.AccountKey ); + var selectiveUrls=settings.SelectiveFolders.Cast().Select(url => new Uri(url)).ToArray(); - Task.Factory.StartNew(ProcessListenerActions); - - } + SetSelectivePaths(selectiveUrls,null,null); + + StartWatcherAgent(); - private Task ProcessRemoteFiles(string accountPath) - { - Trace.TraceInformation("[LISTENER] Scheduled"); - return Task.Factory.StartNewDelayed(10000) - .ContinueWith(t=>CloudClient.ListObjects(PithosContainer)) - .ContinueWith(task => - { - Trace.TraceInformation("[LISTENER] Start Processing"); - - var remoteObjects = task.Result; -/* - if (remoteObjects.Count == 0) - return; -*/ - - var pithosDir = new DirectoryInfo(accountPath); - - var remoteFiles = from info in remoteObjects - select info.Name.ToLower(); - - var onlyLocal = from localFile in pithosDir.EnumerateFiles() - where !remoteFiles.Contains(localFile.Name.ToLower()) - select new ListenerAction(CloudActionType.UploadUnconditional, localFile,null); - - - - var localNames = from info in pithosDir.EnumerateFiles() - select info.Name.ToLower(); - - var onlyRemote = from upFile in remoteObjects - where !localNames.Contains(upFile.Name.ToLower()) - select new ListenerAction(CloudActionType.DownloadUnconditional,null,upFile); - - - var commonObjects = from upFile in remoteObjects - join localFile in pithosDir.EnumerateFiles() - on upFile.Name.ToLower() equals localFile.Name.ToLower() - select new ListenerAction(CloudActionType.Download, localFile, upFile); - - var uniques = - onlyLocal.Union(onlyRemote).Union(commonObjects) - .Except(_networkActions,new LocalFileComparer()); - - _networkActions.AddFromEnumerable(uniques, false); - - Trace.TraceInformation("[LISTENER] End Processing"); - - } - ).ContinueWith(t=> - { - if (t.IsFaulted) - { - Trace.TraceError("[LISTENER] Exception: {0}",t.Exception); - } - else - { - Trace.TraceInformation("[LISTENER] Finished"); - } - ProcessRemoteFiles(accountPath); - }); + StartNetworkAgent(); + + WorkflowAgent.RestartInterruptedFiles(_accountInfo); + _started = true; } - private void ProcessListenerActions() + private void EnsurePithosContainers() { - foreach(var action in _networkActions.GetConsumingEnumerable()) - { - Trace.TraceInformation("[ACTION] Start Processing {0}:{1}->{2}",action.Action,action.LocalFile,action.CloudFile.Name); - var localFile = action.LocalFile; - var cloudFile = action.CloudFile; - var downloadPath = (cloudFile == null)? String.Empty - : Path.Combine(RootPath,cloudFile.Name); - try - { - switch (action.Action) - { - case CloudActionType.UploadUnconditional: - - UploadCloudFile(localFile.Name,localFile.Length,localFile.FullName,action.LocalHash.Value); - break; - case CloudActionType.DownloadUnconditional: - DownloadCloudFile(PithosContainer,cloudFile.Name,downloadPath); - break; - case CloudActionType.Download: - if (File.Exists(downloadPath)) - { - if (cloudFile.Hash !=action.LocalHash.Value) - { - var lastLocalTime =localFile.LastWriteTime; - var lastUpTime =cloudFile.Last_Modified; - if (lastUpTime <=lastLocalTime) - { - //Files in conflict - StatusKeeper.SetFileOverlayStatus(downloadPath,FileOverlayStatus.Conflict); - } - else - DownloadCloudFile(PithosContainer,action.CloudFile.Name,downloadPath); - } - } - else - DownloadCloudFile(PithosContainer,action.CloudFile.Name,downloadPath); - break; - } - Trace.TraceInformation("[ACTION] End Processing {0}:{1}->{2}", action.Action, action.LocalFile, action.CloudFile.Name); - } - catch (Exception exc) - { - Trace.TraceError("[REQUEUE] {0} : {1} -> {2} due to exception\r\n{3}", - action.Action, action.LocalFile,action.CloudFile,exc); - _networkActions.Add(action); + //Create the two default containers if they are missing + var pithosContainers = new List{ FolderConstants.TrashContainer,FolderConstants.PithosContainer}; + foreach (var container in pithosContainers) + { + var info=CloudClient.GetContainerInfo(UserName, container); + if (info == ContainerInfo.Empty) + { + CloudClient.CreateContainer(UserName, container); + info = CloudClient.GetContainerInfo(UserName, container); } + _blockSize = info.BlockSize; + _blockHash = info.BlockHash; + _accountInfo.BlockSize = _blockSize; + _accountInfo.BlockHash = _blockHash; } } - - - private void StartMonitoringFiles(string path) - { - _watcher = new FileSystemWatcher(path); - _watcher.Changed += OnFileEvent; - _watcher.Created += OnFileEvent; - _watcher.Deleted += OnFileEvent; - _watcher.Renamed += OnRenameEvent; - _watcher.EnableRaisingEvents = true; - - Task.Factory.StartNew(ProcesFileEvents,_cancellationSource.Token); - } + public string AuthenticationUrl { get; set; } - private void ProcesFileEvents() + private void IndexLocalFiles() { - foreach (var state in _fileEvents.GetConsumingEnumerable()) + using (ThreadContext.Stacks["Operation"].Push("Indexing local files")) { + try { - var networkState=StatusKeeper.GetNetworkState(state.Path); - //Skip if the file is already being downloaded or uploaded and - //the change is create or modify - if (networkState != NetworkState.None && - ( - state.TriggeringChange==WatcherChangeTypes.Created || - state.TriggeringChange==WatcherChangeTypes.Changed - )) - continue; - UpdateFileStatus(state); - UpdateOverlayStatus(state); - UpdateFileChecksum(state); - _uploadEvents.Add(state); + //StatusNotification.NotifyChange("Indexing Local Files"); + Log.Info("Start local indexing"); + StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,"Indexing Local Files"); + + var cachePath = Path.Combine(RootPath, FolderConstants.CacheFolder); + var directory = new DirectoryInfo(RootPath); + var files = + from file in directory.EnumerateFiles("*", SearchOption.AllDirectories) + where !file.FullName.StartsWith(cachePath, StringComparison.InvariantCultureIgnoreCase) && + !file.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase) + select file; + StatusKeeper.ProcessExistingFiles(files); + } - catch (OperationCanceledException exc) + catch (Exception exc) { - Trace.TraceError("[ERROR] File Event Processing:\r{0}", exc); - throw; + Log.Error("[ERROR]", exc); } - catch (Exception exc) + finally { - Trace.TraceError("[ERROR] File Event Processing:\r{0}",exc); + Log.Info("[END]"); } + StatusNotification.SetPithosStatus(PithosStatus.LocalComplete,"Indexing Completed"); } } - private void StartSending() - { - Task.Factory.StartNew(() => - { - foreach (var state in _uploadEvents.GetConsumingEnumerable()) - { - try - { - SynchToCloud(state); - } - catch (OperationCanceledException) - { - throw; - } - catch(Exception ex) - { - Trace.TraceError("[ERROR] Synch for {0}:\r{1}",state.FileName,ex); - } - } - - },_cancellationSource.Token); - } + + - private WorkflowState SynchToCloud(WorkflowState state) + /* private void StartWorkflowAgent() { - if (state.Skip) - return state; - string path = state.Path; - string fileName = Path.GetFileName(path); + WorkflowAgent.StatusNotification = StatusNotification; + +/* //On Vista and up we can check for a network connection + bool connected=Environment.OSVersion.Version.Major < 6 || NetworkListManager.IsConnectedToInternet; + //If we are not connected retry later + if (!connected) + { + Task.Factory.StartNewDelayed(10000, StartWorkflowAgent); + return; + }#1# - switch(state.Status) + try { - case FileStatus.Created: - case FileStatus.Modified: - var info = new FileInfo(path); - long fileSize = info.Length; - UploadCloudFile(fileName, fileSize, path,state.Hash); - break; - case FileStatus.Deleted: - DeleteCloudFile(fileName); - break; - case FileStatus.Renamed: - RenameCloudFile(state); - break; + WorkflowAgent.Start(); } - return state; - } + catch (Exception) + { + //Faild to authenticate due to network or account error + //Retry after a while + Task.Factory.StartNewDelayed(10000, StartWorkflowAgent); + } + }*/ + - private void RenameCloudFile(WorkflowState state) + private void StartNetworkAgent() { - this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified); + NetworkAgent.StatusNotification = StatusNotification; + //TODO: The Network and Poll agents are not account specific + //They should be moved outside PithosMonitor + NetworkAgent.Start(); + PollAgent.AddAccount(_accountInfo); - CloudClient.MoveObject(PithosContainer, state.OldFileName,PithosContainer, state.FileName); + PollAgent.StatusNotification = StatusNotification; - this.StatusKeeper.SetFileStatus(state.Path, FileStatus.Unchanged); - this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal); - Workflow.RaiseChangeNotification(state.Path); + PollAgent.PollRemoteFiles(); } - private void DeleteCloudFile(string fileName) + //Make sure a hidden cache folder exists to store partial downloads + private static void CreateHiddenFolder(string rootPath, string folderName) { - Contract.Requires(!Path.IsPathRooted(fileName)); + if (String.IsNullOrWhiteSpace(rootPath)) + throw new ArgumentNullException("rootPath"); + if (!Path.IsPathRooted(rootPath)) + throw new ArgumentException("rootPath"); + if (String.IsNullOrWhiteSpace(folderName)) + throw new ArgumentNullException("folderName"); + Contract.EndContractBlock(); - this.StatusKeeper.SetFileOverlayStatus(fileName, FileOverlayStatus.Modified); + var folder = Path.Combine(rootPath, folderName); + if (!Directory.Exists(folder)) + { + var info = Directory.CreateDirectory(folder); + info.Attributes |= FileAttributes.Hidden; - CloudClient.MoveObject(PithosContainer,fileName,TrashContainer,fileName); - this.StatusKeeper.ClearFileStatus(fileName); - this.StatusKeeper.RemoveFileOverlayStatus(fileName); + Log.InfoFormat("Created cache Folder: {0}", folder); + } + else + { + var info = new DirectoryInfo(folder); + if ((info.Attributes & FileAttributes.Hidden) == 0) + { + info.Attributes |= FileAttributes.Hidden; + Log.InfoFormat("Reset cache folder to hidden: {0}", folder); + } + } } - private void DownloadCloudFile(string container, string fileName, string localPath) - { - StatusKeeper.SetNetworkState(localPath,NetworkState.Downloading); - CloudClient.GetObject(container, fileName, localPath) - .ContinueWith(t=> - CloudClient.GetObjectInfo(container,fileName)) - .ContinueWith(t=> - StatusKeeper.StoreInfo(fileName,t.Result)) - .ContinueWith(t=> - StatusKeeper.SetNetworkState(localPath,NetworkState.None)) - .Wait(); - } + - private void UploadCloudFile(string fileName, long fileSize, string path,string hash) + + private void StartWatcherAgent() { - Contract.Requires(!Path.IsPathRooted(fileName)); + if (Log.IsDebugEnabled) + Log.DebugFormat("Start Folder Monitoring [{0}]",RootPath); - StatusKeeper.SetNetworkState(fileName,NetworkState.Uploading); + AgentLocator.Register(FileAgent,RootPath); - //Even if GetObjectInfo times out, we can proceed with the upload - var info = CloudClient.GetObjectInfo(PithosContainer, fileName); - Task.Factory.StartNew(() => - { - if (hash != info.Hash) - { - Task.Factory.StartNew(() => - this.StatusKeeper.SetFileOverlayStatus(path, FileOverlayStatus.Modified)) - .ContinueWith(t => - CloudClient.PutObject(PithosContainer, fileName, path, hash)); - } - else - { - this.StatusKeeper.StoreInfo(path,info); - } - } - ) - .ContinueWith(t => - this.StatusKeeper.SetFileState(path, FileStatus.Unchanged, FileOverlayStatus.Normal)) - .ContinueWith(t=> - this.StatusKeeper.SetNetworkState(path,NetworkState.None)) - .Wait(); - Workflow.RaiseChangeNotification(path); + FileAgent.IdleTimeout = Settings.FileIdleTimeout; + FileAgent.StatusKeeper = StatusKeeper; + FileAgent.StatusNotification = StatusNotification; + FileAgent.Workflow = Workflow; + FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder); + FileAgent.Start(_accountInfo, RootPath); } - private Dictionary _statusDict = new Dictionary + public void Stop() { - {WatcherChangeTypes.Created,FileStatus.Created}, - {WatcherChangeTypes.Changed,FileStatus.Modified}, - {WatcherChangeTypes.Deleted,FileStatus.Deleted}, - {WatcherChangeTypes.Renamed,FileStatus.Renamed} - }; + AgentLocator.Remove(RootPath); - private WorkflowState UpdateFileStatus(WorkflowState state) - { - string path = state.Path; - FileStatus status = _statusDict[state.TriggeringChange]; - var oldStatus = Workflow.StatusKeeper.GetFileStatus(path); - if (status == oldStatus) - { - state.Status = status; - state.Skip = true; - return state; - } - if (state.Status == FileStatus.Renamed) - Workflow.ClearFileStatus(path); + if (FileAgent!=null) + FileAgent.Stop(); + FileAgent = null; + } - state.Status = Workflow.SetFileStatus(path, status); - return state; + + ~PithosMonitor() + { + Dispose(false); } - private WorkflowState UpdateOverlayStatus(WorkflowState state) - { - if (state.Skip) - return state; + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - switch (state.Status) + protected virtual void Dispose(bool disposing) + { + if (disposing) { - case FileStatus.Created: - case FileStatus.Modified: - this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified); - break; - case FileStatus.Deleted: - this.StatusKeeper.RemoveFileOverlayStatus(state.Path); - break; - case FileStatus.Renamed: - this.StatusKeeper.RemoveFileOverlayStatus(state.OldPath); - this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified); - break; - case FileStatus.Unchanged: - this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal); - break; + Stop(); } - - if (state.Status==FileStatus.Deleted) - Workflow.RaiseChangeNotification(Path.GetDirectoryName(state.Path)); - else - Workflow.RaiseChangeNotification(state.Path); - return state; } - private WorkflowState UpdateFileChecksum(WorkflowState state) + public void MoveFileStates(string oldPath, string newPath) { - if (state.Skip) - return state; - - if (state.Status == FileStatus.Deleted) - return state; - - string path = state.Path; - string hash = Signature.CalculateHash(path); - - StatusKeeper.UpdateFileChecksum(path, hash); + if (String.IsNullOrWhiteSpace(oldPath)) + throw new ArgumentNullException("oldPath"); + if (!Path.IsPathRooted(oldPath)) + throw new ArgumentException("oldPath must be an absolute path","oldPath"); + if (string.IsNullOrWhiteSpace(newPath)) + throw new ArgumentNullException("newPath"); + if (!Path.IsPathRooted(newPath)) + throw new ArgumentException("newPath must be an absolute path","newPath"); + Contract.EndContractBlock(); - state.Hash = hash; - return state; + StatusKeeper.ChangeRoots(oldPath, newPath); } - - - private FileSystemEventArgs CalculateSignature(FileSystemEventArgs arg) + public void SetSelectivePaths(Uri[] uris,Uri[] added, Uri[] removed) { - Debug.WriteLine(String.Format("{0} {1} {2}", arg.ChangeType, arg.Name, arg.FullPath), "INFO"); - return arg; - } + //Convert the uris to paths + var selectivePaths = UrisToFilePaths(uris); + + FileAgent.SelectivePaths=selectivePaths; + WorkflowAgent.SelectivePaths = selectivePaths; + PollAgent.SetSyncUris(_accountInfo.AccountKey,uris); + + var removedPaths = UrisToFilePaths(removed); + UnversionSelectivePaths(removedPaths); - void OnFileEvent(object sender, FileSystemEventArgs e) - { - _fileEvents.Add(new WorkflowState{Path=e.FullPath,FileName = e.Name,TriggeringChange=e.ChangeType}); } - void OnRenameEvent(object sender, RenamedEventArgs e) + /// + /// Mark all unselected paths as Unversioned + /// + /// + private void UnversionSelectivePaths(List removed) { - _fileEvents.Add(new WorkflowState { OldPath=e.OldFullPath,OldFileName=e.OldName, - Path = e.FullPath, FileName = e.Name, TriggeringChange = e.ChangeType }); - } + if (removed == null) + return; - public void Stop() - { - if (_watcher != null) - { - _watcher.Changed -= OnFileEvent; - _watcher.Created -= OnFileEvent; - _watcher.Deleted -= OnFileEvent; - _watcher.Renamed -= OnRenameEvent; - _watcher.Dispose(); - } - _watcher = null; - _fileEvents.CompleteAdding(); - if (timer != null) - timer.Dispose(); - timer = null; - StopStatusService(); + //Ensure we remove any file state below the deleted folders + FileState.UnversionPaths(removed); } - ~PithosMonitor() + /// + /// Return a list of absolute filepaths from a list of Uris + /// + /// + /// + private List UrisToFilePaths(IEnumerable uris) { - Dispose(false); - } + if (uris == null) + return new List(); - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); + var own = (from uri in uris + where uri.ToString().StartsWith(_accountInfo.StorageUri.ToString()) + let relativePath = _accountInfo.StorageUri.MakeRelativeUri(uri).RelativeUriToFilePath() + //Trim the account name + select Path.Combine(RootPath, relativePath.After(_accountInfo.UserName + '\\'))).ToList(); + var others= (from uri in uris + where !uri.ToString().StartsWith(_accountInfo.StorageUri.ToString()) + let relativePath = _accountInfo.StorageUri.MakeRelativeUri(uri).RelativeUriToFilePath() + //Trim the account name + select Path.Combine(RootPath,"others-shared", relativePath)).ToList(); + return own.Union(others).ToList(); } - protected virtual void Dispose(bool disposing) + + public ObjectInfo GetObjectInfo(string filePath) { - if (disposing) + if (String.IsNullOrWhiteSpace(filePath)) + throw new ArgumentNullException("filePath"); + Contract.EndContractBlock(); + + var file=new FileInfo(filePath); + string relativeUrl;//=file.AsRelativeUrlTo(this.RootPath); + var relativePath = file.AsRelativeTo(RootPath); + + string accountName,container; + + var parts=relativePath.Split('\\'); + + var accountInfo = _accountInfo; + if (relativePath.StartsWith(FolderConstants.OthersFolder)) + { + accountName = parts[1]; + container = parts[2]; + relativeUrl = String.Join("/", parts.Splice(3)); + //Create the root URL for the target account + var oldName = UserName; + var absoluteUri = _accountInfo.StorageUri.AbsoluteUri; + var nameIndex=absoluteUri.IndexOf(oldName, StringComparison.Ordinal); + var root=absoluteUri.Substring(0, nameIndex); + + accountInfo = new AccountInfo + { + UserName = accountName, + AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]), + StorageUri = new Uri(root + accountName), + BlockHash=accountInfo.BlockHash, + BlockSize=accountInfo.BlockSize, + Token=accountInfo.Token + }; + } + else { - Stop(); + accountName = UserName; + container = parts[0]; + relativeUrl = String.Join("/", parts.Splice(1)); } + + var client = new CloudFilesClient(accountInfo); + var objectInfo=client.GetObjectInfo(accountName, container, relativeUrl); + return objectInfo; } + + public Task GetContainerInfo(string filePath) + { + if (String.IsNullOrWhiteSpace(filePath)) + throw new ArgumentNullException("filePath"); + Contract.EndContractBlock(); + var file=new FileInfo(filePath); + var relativePath = file.AsRelativeTo(RootPath); + + string accountName,container; + + var parts=relativePath.Split('\\'); + + var accountInfo = _accountInfo; + if (relativePath.StartsWith(FolderConstants.OthersFolder)) + { + accountName = parts[1]; + container = parts[2]; + //Create the root URL for the target account + var oldName = UserName; + var absoluteUri = _accountInfo.StorageUri.AbsoluteUri; + var nameIndex=absoluteUri.IndexOf(oldName, StringComparison.Ordinal); + var root=absoluteUri.Substring(0, nameIndex); + + accountInfo = new AccountInfo + { + UserName = accountName, + AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]), + StorageUri = new Uri(root + accountName), + BlockHash=accountInfo.BlockHash, + BlockSize=accountInfo.BlockSize, + Token=accountInfo.Token + }; + } + else + { + accountName = UserName; + container = parts[0]; + } + return Task.Factory.StartNew(() => + { + var client = new CloudFilesClient(accountInfo); + var containerInfo = client.GetContainerInfo(accountName, container); + return containerInfo; + }); + } } }