Merge branch 'master' of https://code.grnet.gr/git/pithos-ms-client
[pithos-ms-client] / trunk / Pithos.Core / PithosMonitor.cs
index d0a302e..7ca5a30 100644 (file)
@@ -1,22 +1,55 @@
+#region
+/* -----------------------------------------------------------------------
+ * <copyright file="PithosMonitor.cs" company="GRNet">
+ * 
+ * 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.
+ * </copyright>
+ * -----------------------------------------------------------------------
+ */
+#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;
 
@@ -25,30 +58,65 @@ namespace Pithos.Core
     [Export(typeof(PithosMonitor))]
     public class PithosMonitor:IDisposable
     {
+        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
         private int _blockSize;
         private string _blockHash;
 
         [Import]
         public IPithosSettings Settings{get;set;}
 
+        private IStatusKeeper _statusKeeper;
+
         [Import]
-        public IStatusKeeper StatusKeeper { get; set; }
+        public IStatusKeeper StatusKeeper
+        {
+            get { return _statusKeeper; }
+            set
+            {
+                _statusKeeper = value;
+                FileAgent.StatusKeeper = value;
+            }
+        }
+
+
+        private IPithosWorkflow _workflow;
 
         [Import]
-        public IPithosWorkflow Workflow { get; set; }
+        public IPithosWorkflow Workflow
+        {
+            get { return _workflow; }
+            set
+            {
+                _workflow = value;
+                FileAgent.Workflow = value;
+            }
+        }
 
         public ICloudClient CloudClient { get; set; }
 
         public IStatusNotification StatusNotification { get; set; }
 
+        //[Import]
+        public FileAgent FileAgent { get; private set; }
+
+        private WorkflowAgent _workflowAgent;
+
         [Import]
-        public FileAgent FileAgent { get; set; }
+        public WorkflowAgent WorkflowAgent
+        {
+            get { return _workflowAgent; }
+            set
+            {
+                _workflowAgent = value;
+                FileAgent.WorkflowAgent = value;
+            }
+        }
         
         [Import]
-        public WorkflowAgent WorkflowAgent { get; set; }
-        
+        public NetworkAgent NetworkAgent { get; set; }
         [Import]
-        public NetworkAgent NetworkAgent { get; set; }        
+        public PollAgent PollAgent { get; set; }       
 
         public string UserName { get; set; }
         private string _apiKey;
@@ -66,7 +134,8 @@ namespace Pithos.Core
         private AccountInfo _accountInfo;
 
 
-        private static readonly ILog Log = LogManager.GetLogger(typeof(PithosMonitor));
+
+
 
 
         public bool Pause
@@ -75,16 +144,6 @@ namespace Pithos.Core
             set
             {
                 FileAgent.Pause = value;
-                if (value)
-                {
-                    StatusKeeper.SetPithosStatus(PithosStatus.SyncPaused);
-                    StatusNotification.NotifyChange("Paused");
-                }
-                else
-                {
-                    StatusKeeper.SetPithosStatus(PithosStatus.InSynch);
-                    StatusNotification.NotifyChange("Synchronizing");
-                }
             }
         }
 
@@ -103,7 +162,10 @@ namespace Pithos.Core
 
         CancellationTokenSource _cancellationSource;
 
-
+        public PithosMonitor()
+        {
+            FileAgent = new FileAgent();
+        }
         private bool _started;
 
         public void Start()
@@ -121,6 +183,8 @@ namespace Pithos.Core
                 //TODO; Warn user?
                 return;
 
+            WorkflowAgent.StatusNotification = StatusNotification;
+
             StatusNotification.NotifyChange("Starting");
             if (_started)
             {
@@ -128,15 +192,13 @@ namespace Pithos.Core
                     return;
             }
             _cancellationSource = new CancellationTokenSource();
-            
 
-            CloudClient=new CloudFilesClient(UserName,ApiKey);
-            var proxyUri = ProxyFromSettings();            
-            CloudClient.Proxy = proxyUri;
-            CloudClient.UsePithos = true;
-            CloudClient.AuthenticationUrl = this.AuthenticationUrl;            
-
-            _accountInfo = CloudClient.Authenticate();            
+            lock (this)
+            {
+                CloudClient = new CloudFilesClient(UserName, ApiKey)
+                                  {UsePithos = true, AuthenticationUrl = AuthenticationUrl};
+                _accountInfo = CloudClient.Authenticate();
+            }
             _accountInfo.SiteUri = AuthenticationUrl;
             _accountInfo.AccountPath = RootPath;
 
@@ -157,11 +219,16 @@ namespace Pithos.Core
             
             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<string>().Select(url => new Uri(url)).ToArray();
+
+            SetSelectivePaths(selectiveUrls,null,null);
+            
             StartWatcherAgent();
 
             StartNetworkAgent();
-
-            StartWorkflowAgent();
+            
             WorkflowAgent.RestartInterruptedFiles(_accountInfo);
             _started = true;
         }
@@ -173,11 +240,11 @@ namespace Pithos.Core
             var pithosContainers = new List<string>{ FolderConstants.TrashContainer,FolderConstants.PithosContainer};
             foreach (var container in pithosContainers)
             {                
-                var info=CloudClient.GetContainerInfo(this.UserName, container);
+                var info=CloudClient.GetContainerInfo(UserName, container);
                 if (info == ContainerInfo.Empty)
                 {
-                    CloudClient.CreateContainer(this.UserName, container);
-                    info = CloudClient.GetContainerInfo(this.UserName, container);
+                    CloudClient.CreateContainer(UserName, container);
+                    info = CloudClient.GetContainerInfo(UserName, container);
                 }
                 _blockSize = info.BlockSize;
                 _blockHash = info.BlockHash;
@@ -188,33 +255,17 @@ namespace Pithos.Core
 
         public string AuthenticationUrl { get; set; }
 
-        private Uri ProxyFromSettings()
-        {            
-            if (Settings.UseManualProxy)
-            {
-                var proxyUri = new UriBuilder
-                                   {
-                                       Host = Settings.ProxyServer, 
-                                       Port = Settings.ProxyPort
-                                   };
-                if (Settings.ProxyAuthentication)
-                {
-                    proxyUri.UserName = Settings.ProxyUsername;
-                    proxyUri.Password = Settings.ProxyPassword;
-                }
-                return proxyUri.Uri;
-            }
-            return null;
-        }
-
         private void IndexLocalFiles()
         {
-            StatusNotification.NotifyChange("Indexing Local Files",TraceLevel.Info);
-            using (log4net.ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
+            using (ThreadContext.Stacks["Operation"].Push("Indexing local files"))
             {
-                Log.Info("START");
+                
                 try
                 {
+                    //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 =
@@ -233,6 +284,7 @@ namespace Pithos.Core
                 {
                     Log.Info("[END]");
                 }
+                StatusNotification.SetPithosStatus(PithosStatus.LocalComplete,"Indexing Completed");
             }
         }
 
@@ -240,20 +292,21 @@ namespace Pithos.Core
   
 
 
-        private void StartWorkflowAgent()
+       /* private void StartWorkflowAgent()
         {
+            WorkflowAgent.StatusNotification = StatusNotification;
 
-            bool connected = NetworkListManager.IsConnectedToInternet;
+/*            //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#
 
             try
             {
-                WorkflowAgent.StatusNotification = StatusNotification;
                 WorkflowAgent.Start();                
             }
             catch (Exception)
@@ -262,58 +315,26 @@ namespace Pithos.Core
                 //Retry after a while
                 Task.Factory.StartNewDelayed(10000, StartWorkflowAgent);
             }
-        }
-
-        internal class LocalFileComparer:EqualityComparer<CloudAction>
-        {
-            public override bool Equals(CloudAction x, CloudAction y)
-            {
-                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 )
-                {
-                    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;
-                return true;
-            }
+        }*/
 
-            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 ?? obj.CloudFile.Name??"").GetHashCode();
-                var hash3 = obj.Action.GetHashCode();
-                return hash1 ^ hash2 & hash3;
-            }
-        }        
-
-        private Timer timer;
 
         private void StartNetworkAgent()
         {
-
-            NetworkAgent.AddAccount(_accountInfo);
-
             NetworkAgent.StatusNotification = StatusNotification;
-                        
+
+            //TODO: The Network and Poll agents are not account specific
+            //They should be moved outside PithosMonitor
             NetworkAgent.Start();
 
-            NetworkAgent.ProcessRemoteFiles();
+            PollAgent.AddAccount(_accountInfo);
+
+            PollAgent.StatusNotification = StatusNotification;
+
+            PollAgent.PollRemoteFiles();
         }
 
         //Make sure a hidden cache folder exists to store partial downloads
-        private static string CreateHiddenFolder(string rootPath, string folderName)
+        private static void CreateHiddenFolder(string rootPath, string folderName)
         {
             if (String.IsNullOrWhiteSpace(rootPath))
                 throw new ArgumentNullException("rootPath");
@@ -340,7 +361,6 @@ namespace Pithos.Core
                     Log.InfoFormat("Reset cache folder to hidden: {0}", folder);
                 }                                
             }
-            return folder;
         }
 
        
@@ -348,9 +368,14 @@ namespace Pithos.Core
 
         private void StartWatcherAgent()
         {
-            AgentLocator<FileAgent>.Register(FileAgent,RootPath);
+            if (Log.IsDebugEnabled)
+                Log.DebugFormat("Start Folder Monitoring [{0}]",RootPath);
 
+            AgentLocator<FileAgent>.Register(FileAgent,RootPath);
+            
+            FileAgent.IdleTimeout = Settings.FileIdleTimeout;
             FileAgent.StatusKeeper = StatusKeeper;
+            FileAgent.StatusNotification = StatusNotification;
             FileAgent.Workflow = Workflow;
             FileAgent.CachePath = Path.Combine(RootPath, FolderConstants.CacheFolder);
             FileAgent.Start(_accountInfo, RootPath);
@@ -363,9 +388,6 @@ namespace Pithos.Core
             if (FileAgent!=null)
                 FileAgent.Stop();
             FileAgent = null;
-            if (timer != null)
-                timer.Dispose();
-            timer = null;            
         }
 
 
@@ -404,29 +426,58 @@ namespace Pithos.Core
             StatusKeeper.ChangeRoots(oldPath, newPath);
         }
 
-        public void AddSelectivePaths(string[] added)
+        public void SetSelectivePaths(Uri[] uris,Uri[] added, Uri[] removed)
         {
-           /* FileAgent.SelectivePaths.AddRange(added);
-            NetworkAgent.SyncPaths(added);*/
+            //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);
+
         }
 
-        public void RemoveSelectivePaths(string[] removed)
+        /// <summary>
+        /// Mark all unselected paths as Unversioned
+        /// </summary>
+        /// <param name="removed"></param>
+        private void UnversionSelectivePaths(List<string> removed)
         {
-            FileAgent.SelectivePaths.RemoveAll(removed.Contains);
-            foreach (var removedPath in removed.Where(Directory.Exists))
-            {
-                Directory.Delete(removedPath,true);
-            }
+            if (removed == null)
+                return;
+
+            //Ensure we remove any file state below the deleted folders
+            FileState.UnversionPaths(removed);
         }
 
-        public IEnumerable<string> GetRootFolders()
+
+        /// <summary>
+        /// Return a list of absolute filepaths from a list of Uris
+        /// </summary>
+        /// <param name="uris"></param>
+        /// <returns></returns>
+        private List<string> UrisToFilePaths(IEnumerable<Uri> uris)
         {
-            var dirs = from container in CloudClient.ListContainers(UserName)
-                       from dir in CloudClient.ListObjects(UserName, container.Name, "")
-                       select dir.Name;
-            return dirs;
+            if (uris == null)
+                return new List<string>();
+
+            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();            
         }
 
+
         public ObjectInfo GetObjectInfo(string filePath)
         {
             if (String.IsNullOrWhiteSpace(filePath))
@@ -450,7 +501,7 @@ namespace Pithos.Core
                 //Create the root URL for the target account
                 var oldName = UserName;
                 var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
-                var nameIndex=absoluteUri.IndexOf(oldName);
+                var nameIndex=absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
                 var root=absoluteUri.Substring(0, nameIndex);
 
                 accountInfo = new AccountInfo
@@ -465,7 +516,7 @@ namespace Pithos.Core
             }
             else
             {
-                accountName = this.UserName;
+                accountName = UserName;
                 container = parts[0];
                 relativeUrl = String.Join("/", parts.Splice(1));
             }
@@ -496,7 +547,7 @@ namespace Pithos.Core
                 //Create the root URL for the target account
                 var oldName = UserName;
                 var absoluteUri =  _accountInfo.StorageUri.AbsoluteUri;
-                var nameIndex=absoluteUri.IndexOf(oldName);
+                var nameIndex=absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
                 var root=absoluteUri.Substring(0, nameIndex);
 
                 accountInfo = new AccountInfo
@@ -523,12 +574,4 @@ namespace Pithos.Core
             });
         }
     }
-
-
-    public interface IStatusNotification
-    {        
-        void NotifyChange(string status,TraceLevel level=TraceLevel.Info);
-        void NotifyChangedFile(string filePath);
-        void NotifyAccount(AccountInfo policy);
-    }
 }