Added File/Container properties windows
[pithos-ms-client] / trunk / Pithos.Core / PithosMonitor.cs
index 155eb2b..fe8f57a 100644 (file)
@@ -18,17 +18,13 @@ 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 const string FragmentsFolder = "fragments";
-
         private int _blockSize;
         private string _blockHash;
 
@@ -41,12 +37,8 @@ namespace Pithos.Core
         [Import]
         public IPithosWorkflow Workflow { get; set; }
 
-        [Import]
         public ICloudClient CloudClient { get; set; }
 
-        [Import]
-        public ICloudClient CloudListeningClient { get; set; }
-
         public IStatusNotification StatusNotification { get; set; }
 
         [Import]
@@ -56,13 +48,15 @@ namespace Pithos.Core
         public WorkflowAgent WorkflowAgent { get; set; }
         
         [Import]
-        public NetworkAgent NetworkAgent { get; set; }
-
+        public NetworkAgent NetworkAgent { get; set; }        
 
         public string UserName { get; set; }
         public string ApiKey { get; set; }
 
-        private ServiceHost _statusService { get; set; }
+        private AccountInfo _accountInfo;
+
+
+        private static readonly ILog Log = LogManager.GetLogger(typeof(PithosMonitor));
 
 
         public bool Pause
@@ -84,68 +78,95 @@ namespace Pithos.Core
             }
         }
 
-        public string RootPath { get; set; }
+        private string _rootPath;
+        public string RootPath
+        {
+            get { return _rootPath; }
+            set 
+            {
+                _rootPath = String.IsNullOrWhiteSpace(value) 
+                    ? String.Empty 
+                    : value.ToLower();
+            }
+        }
 
 
         CancellationTokenSource _cancellationSource;
 
 
-        private bool _isInitialized;
+        private bool _started;
 
         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();
+
             StatusNotification.NotifyChange("Starting");
-            if (_isInitialized)
+            if (_started)
             {
                 if (!_cancellationSource.IsCancellationRequested)
                     return;
             }
             _cancellationSource = new CancellationTokenSource();
+            _started = true;
 
+            CloudClient=new CloudFilesClient(UserName,ApiKey);
             var proxyUri = ProxyFromSettings();            
             CloudClient.Proxy = proxyUri;
             CloudClient.UsePithos = this.UsePithos;
-            
+            CloudClient.AuthenticationUrl = this.AuthenticationUrl;            
+
+            _accountInfo = CloudClient.Authenticate();
+            _accountInfo.AccountPath = RootPath;
+
+
+            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);
+
+            var policy=CloudClient.GetAccountPolicies(_accountInfo);
+
+            StatusNotification.NotifyAccount(policy);
             EnsurePithosContainers();
             
             StatusKeeper.BlockHash = _blockHash;
             StatusKeeper.BlockSize = _blockSize;
             
             StatusKeeper.StartProcessing(_cancellationSource.Token);
-            IndexLocalFiles(RootPath);
-            StartWatcherAgent(RootPath);
-            StartStatusService();
+            IndexLocalFiles();
+            StartWatcherAgent();
+
+            StartNetworkAgent();
+
             StartWorkflowAgent();
-            WorkflowAgent.RestartInterruptedFiles();
-            _isInitialized = true;
+            WorkflowAgent.RestartInterruptedFiles(_accountInfo);            
         }
 
         private void EnsurePithosContainers()
         {
-            CloudClient.UsePithos = this.UsePithos;
-            CloudClient.AuthenticationUrl = this.AuthenticationUrl;
-            CloudClient.Authenticate(UserName, ApiKey);
 
-            var pithosContainers = new[] { TrashContainer,PithosContainer};
+            //Create the two default containers if they are missing
+            var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
             foreach (var container in pithosContainers)
-            {
-                var info=CloudClient.GetContainerInfo(container);
+            {                
+                var info=CloudClient.GetContainerInfo(this.UserName, container);
                 if (info == ContainerInfo.Empty)
                 {
-                    CloudClient.CreateContainer(container);
-                    info = CloudClient.GetContainerInfo(container);
+                    CloudClient.CreateContainer(this.UserName, container);
+                    info = CloudClient.GetContainerInfo(this.UserName, container);
                 }
                 _blockSize = info.BlockSize;
                 _blockHash = info.BlockHash;
+                _accountInfo.BlockSize = _blockSize;
+                _accountInfo.BlockHash = _blockHash;
             }
-
-            var allContainers= CloudClient.ListContainers();
-            var extraContainers = from container in allContainers
-                                  where !pithosContainers.Contains(container.Name.ToLower())
-                                      select container;
-
-
-
         }
 
         public string AuthenticationUrl { get; set; }
@@ -169,68 +190,37 @@ namespace Pithos.Core
             return null;
         }
 
-        private void IndexLocalFiles(string path)
+        private void IndexLocalFiles()
         {
             StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
-            Trace.TraceInformation("[START] Index Local");
-            try
-            {
-                var fragmentsPath=Path.Combine(RootPath, FragmentsFolder);
-                var files =
-                    from filePath in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).AsParallel()
-                    where !filePath.StartsWith(fragmentsPath,StringComparison.InvariantCultureIgnoreCase) &&
-                            !filePath.EndsWith(".ignore",StringComparison.InvariantCultureIgnoreCase)
-                    select filePath.ToLower();
-                StatusKeeper.StoreUnversionedFiles(files);
-                
-            }
-            catch (Exception exc)
-            {
-                Trace.TraceError("[ERROR] Index Local - {0}", exc);
-            }
-            finally
+            using (log4net.ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
             {
-                Trace.TraceInformation("[END] Inxed Local");
+                Log.Info("START");
+                try
+                {
+                    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 (Exception exc)
+                {
+                    Log.Error("[ERROR]", exc);
+                }
+                finally
+                {
+                    Log.Info("[END]");
+                }
             }
         }
 
         
-        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);
-
-
-            _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 StartWorkflowAgent()
@@ -246,14 +236,7 @@ namespace Pithos.Core
 
             try
             {
-                CloudClient.UsePithos = this.UsePithos;
-                CloudClient.AuthenticationUrl = this.AuthenticationUrl;
-                CloudClient.Authenticate(UserName, ApiKey);
-
-                StartNetworkAgent(RootPath);
-
                 WorkflowAgent.StatusNotification = StatusNotification;
-                WorkflowAgent.FragmentsPath = Path.Combine(RootPath, FragmentsFolder);
                 WorkflowAgent.Start();                
             }
             catch (Exception)
@@ -267,7 +250,6 @@ namespace Pithos.Core
         public bool UsePithos { get; set; }
 
 
-
         internal class LocalFileComparer:EqualityComparer<CloudAction>
         {
             public override bool Equals(CloudAction x, CloudAction y)
@@ -276,8 +258,17 @@ namespace Pithos.Core
                     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 )
+                {
+                    if (x.CloudFile.Hash == null & y.CloudFile.Hash != null)
+                        return false;
+                    if (x.CloudFile.Hash != null & y.CloudFile.Hash == null)
+                        return false;
+                    if (x.CloudFile.Hash == null & y.CloudFile.Hash == null)
+                        return (x.CloudFile.Name == y.CloudFile.Name);
+                    if (!x.CloudFile.Hash.Equals(y.CloudFile.Hash))
+                        return false;
+                }
                 if (x.CloudFile == null ^ y.CloudFile == null ||
                     x.LocalFile == null ^ y.LocalFile == null)
                     return false;
@@ -287,7 +278,7 @@ namespace Pithos.Core
             public override int GetHashCode(CloudAction obj)
             {
                 var hash1 = (obj.LocalFile == null) ? int.MaxValue : obj.LocalFile.FullName.GetHashCode();
-                var hash2 = (obj.CloudFile == null) ? int.MaxValue : obj.CloudFile.Hash.GetHashCode();
+                var hash2 = (obj.CloudFile == null) ? int.MaxValue : (obj.CloudFile.Hash ?? obj.CloudFile.Name??"").GetHashCode();
                 var hash3 = obj.Action.GetHashCode();
                 return hash1 ^ hash2 & hash3;
             }
@@ -295,16 +286,19 @@ namespace Pithos.Core
 
         private Timer timer;
 
-        private void StartNetworkAgent(string accountPath)
+        private void StartNetworkAgent()
         {
-            NetworkAgent.StatusNotification = StatusNotification;
 
-            NetworkAgent.Start(PithosContainer, TrashContainer,_blockSize,_blockHash);
+            NetworkAgent.AddAccount(_accountInfo);
 
-            NetworkAgent.ProcessRemoteFiles(accountPath);
+            NetworkAgent.StatusNotification = StatusNotification;
+                        
+            NetworkAgent.Start();
+
+            NetworkAgent.ProcessRemoteFiles();
         }
 
-        //Make sure a hidden fragments folder exists to store partial downloads
+        //Make sure a hidden cache folder exists to store partial downloads
         private static string CreateHiddenFolder(string rootPath, string folderName)
         {
             if (String.IsNullOrWhiteSpace(rootPath))
@@ -321,7 +315,16 @@ namespace Pithos.Core
                 var info = Directory.CreateDirectory(folder);
                 info.Attributes |= FileAttributes.Hidden;
 
-                Trace.TraceInformation("Created Fragments Folder: {0}", folder);
+                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);
+                }                                
             }
             return folder;
         }
@@ -329,21 +332,26 @@ namespace Pithos.Core
        
 
 
-        private void StartWatcherAgent(string path)
+        private void StartWatcherAgent()
         {
+            AgentLocator<FileAgent>.Register(FileAgent,RootPath);
+
             FileAgent.StatusKeeper = StatusKeeper;
             FileAgent.Workflow = Workflow;
-            FileAgent.FragmentsPath = Path.Combine(RootPath, FragmentsFolder);
-            FileAgent.Start(path);
+            FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
+            FileAgent.Start(_accountInfo, RootPath);
         }
 
         public void Stop()
-        {            
-            FileAgent.Stop();
+        {
+            AgentLocator<FileAgent>.Remove(RootPath);
+
+            if (FileAgent!=null)
+                FileAgent.Stop();
+            FileAgent = null;
             if (timer != null)
                 timer.Dispose();
-            timer = null;
-            StopStatusService();
+            timer = null;            
         }
 
 
@@ -367,11 +375,143 @@ namespace Pithos.Core
         }
 
 
+        public void MoveFileStates(string oldPath, string newPath)
+        {
+            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();
+
+            StatusKeeper.ChangeRoots(oldPath, newPath);
+        }
+
+        public void AddSelectivePaths(string[] added)
+        {
+           /* FileAgent.SelectivePaths.AddRange(added);
+            NetworkAgent.SyncPaths(added);*/
+        }
+
+        public void RemoveSelectivePaths(string[] removed)
+        {
+            FileAgent.SelectivePaths.RemoveAll(removed.Contains);
+            foreach (var removedPath in removed.Where(Directory.Exists))
+            {
+                Directory.Delete(removedPath,true);
+            }
+        }
+
+        public IEnumerable<string> GetRootFolders()
+        {
+            var dirs = from container in CloudClient.ListContainers(UserName)
+                       from dir in CloudClient.ListObjects(UserName, container.Name, "")
+                       select dir.Name;
+            return dirs;
+        }
+
+        public ObjectInfo GetObjectInfo(string filePath)
+        {
+            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);
+                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 = this.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 ContainerInfo 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);
+                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];                
+            }
+            
+            var client = new CloudFilesClient(accountInfo);
+            var containerInfo=client.GetContainerInfo(accountName, container);
+            return containerInfo;
+        }
     }
 
+
     public interface IStatusNotification
     {        
         void NotifyChange(string status,TraceLevel level=TraceLevel.Info);
         void NotifyChangedFile(string filePath);
+        void NotifyAccount(AccountInfo policy);
     }
 }