commit b5061ac83447e3fda486b9a81f12a37e340f6971
Author: Panagiotis Kanavos <pkanavos@gmail.com>
Date:   Sun Aug 21 13:24:47 2011 +0300

    -

diff --git a/trunk/Pithos.Network/CloudFilesClient.cs b/trunk/Pithos.Network/CloudFilesClient.cs
index 0dfb046..55f9344 100644
--- a/trunk/Pithos.Network/CloudFilesClient.cs
+++ b/trunk/Pithos.Network/CloudFilesClient.cs
@@ -8,6 +8,7 @@ using System.Linq;
 using System.Net;
 using System.Security.Cryptography;
 using System.Text;
+using System.Threading.Tasks;
 using Hammock;
 using Hammock.Caching;
 using Hammock.Retries;
@@ -28,11 +29,15 @@ namespace Pithos.Network
         private RestClient _client;
         private readonly TimeSpan _shortTimeout = TimeSpan.FromSeconds(10);
         private readonly int _retries = 5;
+        private RetryPolicy _retryPolicy;
         public string ApiKey { get; set; }
         public string UserName { get; set; }
         public Uri StorageUrl { get; set; }
         public string Token { get; set; }
         public Uri Proxy { get; set; }
+
+        public double DownloadPercentLimit { get; set; }
+        public double UploadPercentLimit { get; set; }
         
         public string AuthUrl
         {
@@ -48,6 +53,7 @@ namespace Pithos.Network
 
         public void Authenticate(string userName,string apiKey)
         {
+            Trace.TraceInformation("[AUTHENTICATE] Start for {0}",userName);
             if (String.IsNullOrWhiteSpace(userName))
                 throw new ArgumentNullException("userName","The userName property can't be empty");
             if (String.IsNullOrWhiteSpace(apiKey))
@@ -91,10 +97,11 @@ namespace Pithos.Network
             else
                 Token = "0000";
 
-            var retryPolicy = new RetryPolicy { RetryCount = _retries };
-            retryPolicy.RetryConditions.Add(new TimeoutRetryCondition());
+            _retryPolicy = new RetryPolicy { RetryCount = _retries };
+            _retryPolicy.RetryConditions.Add(new TimeoutRetryCondition());
 
-            _client = new RestClient { Authority = StorageUrl.AbsoluteUri, Path = UserName, Proxy = proxy, RetryPolicy = retryPolicy, };
+            _client = new RestClient { Authority = StorageUrl.AbsoluteUri, Path = UserName, Proxy = proxy };
+            _client.FileProgress += OnFileProgress;
             
             _client.AddHeader("X-Auth-Token", Token);
             if (UsePithos)
@@ -103,7 +110,12 @@ namespace Pithos.Network
                 _client.AddHeader("X-Auth-Key",ApiKey);                
             }
 
+            Trace.TraceInformation("[AUTHENTICATE] End for {0}", userName);
+        }
 
+        private void OnFileProgress(object sender, FileProgressEventArgs e)
+        {
+            Trace.TraceInformation("[PROGRESS] {0} {1:p} {2} of {3}",e.FileName,(double)e.BytesWritten/e.TotalBytes, e.BytesWritten,e.TotalBytes);            
         }
 
         public IList<ContainerInfo> ListContainers()
@@ -112,7 +124,7 @@ namespace Pithos.Network
             //appends a / unless a Path is specified.
             
             //Create a request with a complete path
-            var request = new RestRequest { Path = StorageUrl.ToString(), Timeout = _shortTimeout };
+            var request = new RestRequest { Path = StorageUrl.ToString(), RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
             request.AddParameter("format","json");
             //Create a client clone
             var client = new RestClient{Proxy=Proxy.ToString()};
@@ -139,12 +151,15 @@ namespace Pithos.Network
             if (String.IsNullOrWhiteSpace(container))
                 throw new ArgumentNullException("container", "The container property can't be empty");
 
-            var request = new RestRequest { Path = container, Timeout = _shortTimeout };
+            Trace.TraceInformation("[START] ListObjects");
+
+            var request = new RestRequest { Path = container, RetryPolicy = _retryPolicy, Timeout = TimeSpan.FromMinutes(1) };
             request.AddParameter("format", "json");
             var response = _client.Request(request);
             
             var infos = InfosFromContent(response);
 
+            Trace.TraceInformation("[END] ListObjects");
             return infos;
         }
 
@@ -155,13 +170,16 @@ namespace Pithos.Network
             if (String.IsNullOrWhiteSpace(container))
                 throw new ArgumentNullException("container", "The container property can't be empty");
 
-            var request = new RestRequest { Path = container, Timeout = _shortTimeout };
+            Trace.TraceInformation("[START] ListObjects");
+
+            var request = new RestRequest { Path = container,RetryPolicy = _retryPolicy, Timeout = TimeSpan.FromMinutes(1) };
             request.AddParameter("format", "json");
             request.AddParameter("path", folder);
             var response = _client.Request(request);
             
             var infos = InfosFromContent(response);
 
+            Trace.TraceInformation("[END] ListObjects");
             return infos;
         }
 
@@ -193,7 +211,7 @@ namespace Pithos.Network
             if (String.IsNullOrWhiteSpace(container))
                 throw new ArgumentNullException("container", "The container property can't be empty");
 
-            var request = new RestRequest { Path = container, Method = WebMethod.Head, Timeout = _shortTimeout };
+            var request = new RestRequest { Path = container, Method = WebMethod.Head, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
             var response = _client.Request(request);
 
             switch(response.StatusCode)
@@ -215,7 +233,7 @@ namespace Pithos.Network
                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
 
 
-            var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Head, Timeout = _shortTimeout };
+            var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Head,RetryPolicy = _retryPolicy, Timeout = _shortTimeout };
             var response = _client.Request(request);
 
             switch (response.StatusCode)
@@ -239,7 +257,7 @@ namespace Pithos.Network
                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
 
 
-            var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Head, Timeout = _shortTimeout };
+            var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Head, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
             var response = _client.Request(request);
 
             if (response.TimedOut)
@@ -252,7 +270,7 @@ namespace Pithos.Network
                     var keys = response.Headers.AllKeys.AsQueryable();
                     return new ObjectInfo
                                {
-                                   Name=objectName,
+                                   Name = objectName,
                                    Bytes = long.Parse(GetHeaderValue("Content-Length", response, keys)),
                                    Hash = GetHeaderValue("ETag", response, keys),
                                    Content_Type = GetHeaderValue("Content-Type", response, keys)
@@ -260,7 +278,15 @@ namespace Pithos.Network
                 case HttpStatusCode.NotFound:
                     return ObjectInfo.Empty;
                 default:
-                        throw new WebException(String.Format("GetObjectInfo failed with unexpected status code {0}", response.StatusCode));
+                    if (request.RetryState.RepeatCount > 0)
+                    {
+                        Trace.TraceWarning("[RETRY FAIL] GetObjectInfo for {0} failed after {1} retries",
+                                                      objectName, request.RetryState.RepeatCount);
+                        return ObjectInfo.Empty;
+                    }
+                    if (response.InnerException != null)
+                        throw new WebException(String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}", objectName, response.StatusCode), response.InnerException);
+                    throw new WebException(String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}", objectName, response.StatusCode));
             }
         }
 
@@ -272,7 +298,7 @@ namespace Pithos.Network
                 throw new ArgumentNullException("folder", "The folder property can't be empty");
 
             var folderUrl=String.Format("{0}/{1}",container,folder);
-            var request = new RestRequest { Path = folderUrl, Method = WebMethod.Put, Timeout = _shortTimeout };
+            var request = new RestRequest { Path = folderUrl, Method = WebMethod.Put, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
             request.AddHeader("Content-Type", @"application/directory");
             request.AddHeader("Content-Length", "0");
 
@@ -288,7 +314,7 @@ namespace Pithos.Network
             if (String.IsNullOrWhiteSpace(container))
                 throw new ArgumentNullException("container", "The container property can't be empty");
 
-            var request = new RestRequest { Path = container, Method = WebMethod.Head, Timeout = _shortTimeout };
+            var request = new RestRequest { Path = container, Method = WebMethod.Head, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
             var response = _client.Request(request);
 
             switch(response.StatusCode)
@@ -314,7 +340,7 @@ namespace Pithos.Network
             if (String.IsNullOrWhiteSpace(container))
                 throw new ArgumentNullException("container", "The container property can't be empty");
 
-            var request = new RestRequest { Path = container, Method = WebMethod.Put, Timeout = _shortTimeout };
+            var request = new RestRequest { Path = container, Method = WebMethod.Put, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
             
             var response = _client.Request(request);
                         
@@ -327,7 +353,7 @@ namespace Pithos.Network
             if (String.IsNullOrWhiteSpace(container))
                 throw new ArgumentNullException("container", "The container property can't be empty");
 
-            var request = new RestRequest { Path = container, Method = WebMethod.Delete, Timeout = _shortTimeout };
+            var request = new RestRequest { Path = container, Method = WebMethod.Delete, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
             var response = _client.Request(request);
 
             if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.NoContent)
@@ -352,6 +378,11 @@ namespace Pithos.Network
                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
 
             var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Get };
+/*
+            if (DownloadPercentLimit > 0)
+                request.TaskOptions = new TaskOptions<int> { RateLimitPercent = DownloadPercentLimit };
+*/
+            
             var response = _client.Request(request);
             
             if (response.StatusCode == HttpStatusCode.NotFound)
@@ -370,8 +401,9 @@ namespace Pithos.Network
         /// <param name="container"></param>
         /// <param name="objectName"></param>
         /// <param name="fileName"></param>
+        /// <param name="hash">Optional hash value for the file. If no hash is provided, the method calculates a new hash</param>
         /// <remarks>>This method should have no timeout or a very long one</remarks>
-        public void PutObject(string container, string objectName, string fileName)
+        public Task PutObject(string container, string objectName, string fileName, string hash = null)
         {
             if (String.IsNullOrWhiteSpace(container))
                 throw new ArgumentNullException("container", "The container property can't be empty");
@@ -386,21 +418,58 @@ namespace Pithos.Network
             string url = container + "/" + objectName;
 
             var request = new RestRequest {Path=url,Method=WebMethod.Put};           
-            request.TaskOptions=new TaskOptions<int>{RateLimitPercent=0.5};
             
-            string hash = CalculateHash(fileName);
-
-            request.AddPostContent(File.ReadAllBytes(fileName));
+/*
+            if(UploadPercentLimit>0)
+                request.TaskOptions=new TaskOptions<int>{RateLimitPercent=UploadPercentLimit};
+*/
+            Trace.TraceInformation("[PUT] START {0}",objectName);
+            string etag = hash??CalculateHash(fileName);
+            request.AddFile(fileName, fileName, fileName);            
+            //request.AddPostContent(File.ReadAllBytes(fileName));
             request.AddHeader("Content-Type","application/octet-stream");
-            request.AddHeader("ETag",hash);
-            var response=_client.Request(request);
-            _client.TaskOptions = new TaskOptions<int> {RateLimitPercent = 0.5};
-            if (response.StatusCode == HttpStatusCode.Created)
-                return;
-            if (response.StatusCode == HttpStatusCode.LengthRequired)
-                throw new InvalidOperationException();
-            else
-                throw new WebException(String.Format("GetObject failed with unexpected status code {0}", response.StatusCode));
+            request.AddHeader("ETag", etag);
+            //_client.TaskOptions = new TaskOptions<int> {RateLimitPercent = 0.5};
+            try
+            {
+
+                var response=_client.Request(request);
+                Trace.TraceInformation("[PUT] END {0}", objectName);
+
+                if (response.StatusCode == HttpStatusCode.Created)
+                    return Task.Factory.StartNew(()=>{});
+                if (response.StatusCode == HttpStatusCode.LengthRequired)
+                    throw new InvalidOperationException();
+                else
+                    throw new WebException(String.Format("GetObject failed with unexpected status code {0}",
+                            response.StatusCode));
+                /*            return Task.Factory.FromAsync(_client.BeginRequest(request),ar=>_client.EndRequest(ar))
+                                .ContinueWith(t=>
+                                {*/
+            }
+            catch (Exception exc)
+            {
+                Trace.TraceError("[PUT] END {0} with {1}", objectName, exc);
+                throw;
+            }                
+
+/*
+            var response = t.Result;
+                    if (t.IsFaulted)
+                        Trace.TraceError("[PUT] END {0} with {1}", objectName, t.Exception);
+                    else
+                    {
+                        Trace.TraceInformation("[PUT] END {0}",objectName);
+                    }
+*/
+                  /*  if (response.StatusCode == HttpStatusCode.Created)
+                        return;
+                    if (response.StatusCode == HttpStatusCode.LengthRequired)
+                        throw new InvalidOperationException();
+                    else
+                        throw new WebException(String.Format("GetObject failed with unexpected status code {0}",
+                                response.StatusCode));*/
+                /*});*/
         }
 
         private static string CalculateHash(string fileName)
@@ -424,7 +493,7 @@ namespace Pithos.Network
             if (String.IsNullOrWhiteSpace(objectName))
                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
 
-            var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Delete, Timeout=_shortTimeout };
+            var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Delete, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
             var response = _client.Request(request);
 
             if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.NoContent)
