Fix for erroneous storage of Hashes and Checksum
authorpkanavos <pkanavos@gmail.com>
Mon, 24 Sep 2012 17:18:39 +0000 (20:18 +0300)
committerpkanavos <pkanavos@gmail.com>
Mon, 24 Sep 2012 17:18:39 +0000 (20:18 +0300)
trunk/Pithos.Core/Agents/PollAgent.cs
trunk/Pithos.Core/Agents/StatusAgent.cs
trunk/Pithos.Core/IStatusKeeper.cs
trunk/Pithos.Network/ByteArrayContentWithProgress.cs [new file with mode: 0644]
trunk/Pithos.Network/DownloadArgs.cs [new file with mode: 0644]
trunk/Pithos.Network/RestHttpClient.cs [new file with mode: 0644]
trunk/Pithos.Network/UploadArgs.cs [new file with mode: 0644]

index 1aa9025..240040a 100644 (file)
@@ -1141,7 +1141,8 @@ namespace Pithos.Core.Agents
                                                     new StatusNotification(String.Format("Hashing {0} of {1}", d, file.Name))));\r
             var merkle = Signature.CalculateTreeHash(file, StatusKeeper.BlockSize, \r
                 StatusKeeper.BlockHash,CancellationToken, progress);\r
                                                     new StatusNotification(String.Format("Hashing {0} of {1}", d, file.Name))));\r
             var merkle = Signature.CalculateTreeHash(file, StatusKeeper.BlockSize, \r
                 StatusKeeper.BlockHash,CancellationToken, progress);\r
-            StatusKeeper.UpdateFileTreeHash(file.FullName, merkle);\r
+            \r
+            StatusKeeper.UpdateFileHashes(file.FullName, merkle);\r
             return merkle;\r
         }\r
 \r
             return merkle;\r
         }\r
 \r
index d48053e..16360ad 100644 (file)
@@ -772,16 +772,6 @@ namespace Pithos.Core.Agents
         }\r
 \r
 \r
         }\r
 \r
 \r
-        public void UpdateFileChecksum(string path, string etag, TreeHash treeHash)\r
-        {\r
-            if (String.IsNullOrWhiteSpace(path))\r
-                throw new ArgumentNullException("path");\r
-            if (!Path.IsPathRooted(path))\r
-                throw new ArgumentException("The path must be rooted", "path");            \r
-            Contract.EndContractBlock();\r
-\r
-            UpdateChecksum(path, etag,treeHash);\r
-        }\r
 \r
 \r
         public void CleanupOrphanStates()\r
 \r
 \r
         public void CleanupOrphanStates()\r
@@ -908,29 +898,7 @@ namespace Pithos.Core.Agents
             return null;\r
         }\r
 \r
             return null;\r
         }\r
 \r
-        //TODO: Must separate between UpdateChecksum and UpdateFileTreeHash\r
-        public  void UpdateChecksum(string absolutePath, string etag, TreeHash treeHash)\r
-        {\r
-            if (string.IsNullOrWhiteSpace(absolutePath))\r
-                throw new ArgumentNullException("absolutePath");\r
-            Contract.EndContractBlock();\r
-\r
-            var hashes = treeHash.ToJson();\r
-            var topHash = treeHash.TopHash.ToHashString();\r
-\r
-            ExecuteWithRetry((session, instance) =>\r
-            {\r
-                const string hqlUpdate = "update FileState set Checksum= :checksum,Hashes=:hashes,ETag=:etag where FilePath = :path ";\r
-                var updatedEntities = session.CreateQuery(hqlUpdate)\r
-                    .SetString("path", absolutePath)\r
-                    .SetString("checksum", topHash)\r
-                    .SetString("hashes", hashes)\r
-                    .SetString("etag", etag)\r
-                    .ExecuteUpdate();\r
-                return updatedEntities;\r
-            }, null);\r
-\r
-        }\r
+        //TODO: Must separate between UpdateChecksum and UpdateFileHashes\r
 \r
         public  void ChangeRootPath(string oldPath, string newPath)\r
         {\r
 \r
         public  void ChangeRootPath(string oldPath, string newPath)\r
         {\r
@@ -1032,8 +1000,36 @@ namespace Pithos.Core.Agents
             }\r
         }\r
 \r
             }\r
         }\r
 \r
+        public void UpdateFileChecksum(string path, string etag, TreeHash treeHash)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(path))\r
+                throw new ArgumentNullException("path");\r
+            if (!Path.IsPathRooted(path))\r
+                throw new ArgumentException("The path must be rooted", "path");\r
+            Contract.EndContractBlock();\r
+\r
+            if (string.IsNullOrWhiteSpace(path))\r
+                throw new ArgumentNullException("absolutePath");\r
+            Contract.EndContractBlock();\r
+\r
+            var hashes = treeHash.ToJson();\r
+            var topHash = treeHash.TopHash.ToHashString();\r
 \r
 \r
-        public  void UpdateFileTreeHash(string absolutePath, TreeHash treeHash)\r
+            ExecuteWithRetry((session, instance) =>\r
+            {\r
+                const string hqlUpdate = "update FileState set Checksum= :checksum,Hashes=:hashes,ETag=:etag where FilePath = :path ";\r
+                var updatedEntities = session.CreateQuery(hqlUpdate)\r
+                    .SetString("path", path)\r
+                    .SetString("checksum", topHash)\r
+                    .SetString("hashes", hashes)\r
+                    .SetString("etag", etag)\r
+                    .ExecuteUpdate();\r
+                return updatedEntities;\r
+            }, null);\r
+        }\r
+\r
+        //Store only the hashes\r
+        public  void UpdateFileHashes(string absolutePath, TreeHash treeHash)\r
         {\r
             if (string.IsNullOrWhiteSpace(absolutePath))\r
                 throw new ArgumentNullException("absolutePath");\r
         {\r
             if (string.IsNullOrWhiteSpace(absolutePath))\r
                 throw new ArgumentNullException("absolutePath");\r
@@ -1045,10 +1041,13 @@ namespace Pithos.Core.Agents
             ExecuteWithRetry((session, instance) =>\r
             {\r
 \r
             ExecuteWithRetry((session, instance) =>\r
             {\r
 \r
+                const string hqlUpdate = "update FileState set Hashes=:hashes where FilePath = :path ";\r
+/*\r
                 const string hqlUpdate = "update FileState set Checksum= :checksum,Hashes=:hashes where FilePath = :path ";\r
                 const string hqlUpdate = "update FileState set Checksum= :checksum,Hashes=:hashes where FilePath = :path ";\r
+*/\r
                 var updatedEntities = session.CreateQuery(hqlUpdate)\r
                     .SetString("path", absolutePath)\r
                 var updatedEntities = session.CreateQuery(hqlUpdate)\r
                     .SetString("path", absolutePath)\r
-                    .SetString("checksum", topHash)\r
+                    //.SetString("checksum", topHash)\r
                     //                    .SetString("md5",treeHash.MD5)\r
                     .SetString("hashes", hashes)\r
                     .ExecuteUpdate();\r
                     //                    .SetString("md5",treeHash.MD5)\r
                     .SetString("hashes", hashes)\r
                     .ExecuteUpdate();\r
index f1a0114..f1997ac 100644 (file)
@@ -57,7 +57,7 @@ namespace Pithos.Core
     {\r
         void SetFileOverlayStatus(string path, FileOverlayStatus status);\r
         void UpdateFileChecksum(string path, string etag, TreeHash treeHash);\r
     {\r
         void SetFileOverlayStatus(string path, FileOverlayStatus status);\r
         void UpdateFileChecksum(string path, string etag, TreeHash treeHash);\r
-        void UpdateFileTreeHash(string path, TreeHash treeHash);\r
+        void UpdateFileHashes(string path, TreeHash treeHash);\r
         void SetFileStatus(string path, FileStatus status);\r
         FileStatus GetFileStatus(string path);\r
         void ClearFileStatus(string path);\r
         void SetFileStatus(string path, FileStatus status);\r
         FileStatus GetFileStatus(string path);\r
         void ClearFileStatus(string path);\r
@@ -119,7 +119,7 @@ namespace Pithos.Core
             Contract.Requires(Path.IsPathRooted(path));\r
         }\r
 \r
             Contract.Requires(Path.IsPathRooted(path));\r
         }\r
 \r
-        public void UpdateFileTreeHash(string path, TreeHash treeHash)\r
+        public void UpdateFileHashes(string path, TreeHash treeHash)\r
         {\r
             Contract.Requires(!String.IsNullOrWhiteSpace(path));\r
             Contract.Requires(treeHash!=null);\r
         {\r
             Contract.Requires(!String.IsNullOrWhiteSpace(path));\r
             Contract.Requires(treeHash!=null);\r
diff --git a/trunk/Pithos.Network/ByteArrayContentWithProgress.cs b/trunk/Pithos.Network/ByteArrayContentWithProgress.cs
new file mode 100644 (file)
index 0000000..01c85f8
--- /dev/null
@@ -0,0 +1,67 @@
+using System;\r
+using System.Collections.Generic;\r
+using System.IO;\r
+using System.Linq;\r
+using System.Net;\r
+using System.Net.Http;\r
+using System.Text;\r
+using System.Threading.Tasks;\r
+\r
+namespace Pithos.Network\r
+{\r
+    public class ByteArrayContentWithProgress : ByteArrayContent\r
+    {\r
+        private IProgress<UploadArgs> _progress;\r
+        private readonly byte[] _content;\r
+        private readonly int _offset;\r
+        private readonly int _count;\r
+\r
+\r
+        public ByteArrayContentWithProgress(byte[] content,IProgress<UploadArgs> progress )\r
+            : this(content,0,content.Length,progress)\r
+        {\r
+        }\r
+\r
+        /// <summary>\r
+        /// Initializes a new instance of the <see cref="T:System.Net.Http.ByteArrayContent"/> class.\r
+        /// </summary>\r
+        /// <param name="content">The content used to initialize the <see cref="T:System.Net.Http.ByteArrayContent"/>.</param><param name="offset">The offset, in bytes, in the <paramref name="content"/>  parameter used to initialize the <see cref="T:System.Net.Http.ByteArrayContent"/>.</param><param name="count">The number of bytes in the <paramref name="content"/> starting from the <paramref name="offset"/> parameter used to initialize the <see cref="T:System.Net.Http.ByteArrayContent"/>.</param><exception cref="T:System.ArgumentNullException">The <paramref name="content"/> parameter is null. </exception><exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="offset"/> parameter is less than zero.-or-The <paramref name="offset"/> parameter is greater than the length of content specified by the <paramref name="content"/> parameter.-or-The <paramref name="count "/> parameter is less than zero.-or-The <paramref name="count"/> parameter is greater than the length of content specified by the <paramref name="content"/> parameter - minus the <paramref name="offset"/> parameter.</exception>\r
+        public ByteArrayContentWithProgress(byte[] content, int offset, int count, IProgress<UploadArgs> progress)\r
+            : base(content, offset, count)\r
+        {\r
+            if (progress== null)\r
+                throw new ArgumentNullException("progress");\r
+            _progress = progress;\r
+            _content = content;\r
+            _offset = offset;\r
+            _count = count;\r
+        }\r
+\r
+        /// <summary>\r
+        /// Serialize and write the byte array provided in the constructor to an HTTP content stream as an asynchronous operation.\r
+        /// </summary>\r
+        /// \r
+        /// <returns>\r
+        /// Returns <see cref="T:System.Threading.Tasks.Task"/>. The task object representing the asynchronous operation.\r
+        /// </returns>\r
+        /// <param name="stream">The target stream.</param><param name="context">Information about the transport, like channel binding token. This parameter may be null.</param>\r
+        protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)\r
+        {\r
+            using(var mem=new MemoryStream(_content,_offset,_count,false))\r
+            {\r
+                long total = 0;\r
+                var buffer = new byte[65536];\r
+                int read;\r
+                while ((read = mem.Read(buffer, 0, buffer.Length)) != 0)\r
+                {\r
+                    total += read;\r
+                    var percentage = Convert.ToInt32(100*total/(double) _count);\r
+                    _progress.Report(new UploadArgs(percentage,null,total,_count,0,0));\r
+                    await stream.WriteAsync(buffer, 0, read).ConfigureAwait(false);\r
+                }\r
+            }\r
+            //await Task.Factory.FromAsync(stream.BeginWrite, stream.EndWrite, _content, _offset, _count, null);\r
+        }\r
+\r
+    }\r
+}\r
diff --git a/trunk/Pithos.Network/DownloadArgs.cs b/trunk/Pithos.Network/DownloadArgs.cs
new file mode 100644 (file)
index 0000000..4b09929
--- /dev/null
@@ -0,0 +1,35 @@
+using System;\r
+using System.ComponentModel;\r
+using System.Net;\r
+\r
+namespace Pithos.Network\r
+{\r
+    public class DownloadArgs: ProgressChangedEventArgs\r
+    {\r
+        public DownloadArgs(int progressPercentage, object userToken, long bytesReceived, long totalBytesToReceive) : \r
+            base(progressPercentage, userToken) \r
+        {\r
+            BytesReceived = bytesReceived; \r
+            TotalBytesToReceive = totalBytesToReceive;\r
+        }\r
+\r
+        public DownloadArgs(long bytesReceived, long totalBytesToReceive) :\r
+            this(Convert.ToInt32(100 * (bytesReceived/ (double)totalBytesToReceive)), null,bytesReceived,totalBytesToReceive) \r
+        {\r
+            BytesReceived = bytesReceived; \r
+            TotalBytesToReceive = totalBytesToReceive;\r
+        }\r
+\r
+       public  DownloadArgs(DownloadProgressChangedEventArgs args)\r
+            :base(args.ProgressPercentage,args.UserState)\r
+        {\r
+            BytesReceived = args.BytesReceived;\r
+            TotalBytesToReceive = args.TotalBytesToReceive;\r
+            \r
+        }\r
+\r
+        public long BytesReceived { get; private set; }\r
+\r
+        public long TotalBytesToReceive { get; private set; }\r
+    }\r
+}
\ No newline at end of file
diff --git a/trunk/Pithos.Network/RestHttpClient.cs b/trunk/Pithos.Network/RestHttpClient.cs
new file mode 100644 (file)
index 0000000..03ee871
--- /dev/null
@@ -0,0 +1,466 @@
+#region\r
+/* -----------------------------------------------------------------------\r
+ * <copyright file="RestClient.cs" company="GRNet">\r
+ * \r
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer in the documentation and/or other materials\r
+ *      provided with the distribution.\r
+ *\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * The views and conclusions contained in the software and\r
+ * documentation are those of the authors and should not be\r
+ * interpreted as representing official policies, either expressed\r
+ * or implied, of GRNET S.A.\r
+ * </copyright>\r
+ * -----------------------------------------------------------------------\r
+ */\r
+#endregion\r
+using System.Collections.Specialized;\r
+using System.Diagnostics;\r
+using System.Diagnostics.Contracts;\r
+using System.IO;\r
+using System.Net;\r
+using System.Net.Http;\r
+using System.Reflection;\r
+using System.Runtime.Serialization;\r
+using System.Threading;\r
+using System.Threading.Tasks;\r
+using log4net;\r
+\r
+\r
+namespace Pithos.Network\r
+{\r
+    using System;\r
+    using System.Collections.Generic;\r
+    using System.Linq;\r
+    using System.Text;\r
+\r
+    /// <summary>\r
+    /// TODO: Update summary.\r
+    /// </summary>\r
+    public class RestHttpClient:HttpClient\r
+    {\r
+        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
+\r
+        //public bool TimedOut { get; set; }\r
+\r
+        //public HttpStatusCode StatusCode { get; private set; }\r
+\r
+        public string StatusDescription { get; set; }\r
+\r
+        public long? RangeFrom { get; set; }\r
+        public long? RangeTo { get; set; }\r
+\r
+        public int Retries { get; set; }\r
+\r
+        private readonly Dictionary<string, string> _parameters=new Dictionary<string, string>();\r
+        public Dictionary<string, string> Parameters\r
+        {\r
+            get\r
+            {\r
+                Contract.Ensures(_parameters!=null);\r
+                return _parameters;\r
+            }            \r
+        }\r
+\r
+        public RestHttpClient(HttpMessageHandler handler)\r
+            :base(handler)\r
+        {}\r
+\r
+        public RestHttpClient():base(new HttpClientHandler\r
+                                         {\r
+                                             AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,\r
+                                             UseCookies=true\r
+                                         })\r
+        {            \r
+\r
+            //The maximum error response must be large because missing server hashes are return as a Conflivt (409) error response\r
+            //Any value above 2^21-1 will result in an empty response.\r
+            //-1 essentially ignores the maximum length\r
+            HttpWebRequest.DefaultMaximumErrorResponseLength = -1;               \r
+            //this.MaxResponseContentBufferSize = -1;\r
+        }\r
+\r
+       \r
+        \r
+\r
+\r
+\r
+        private readonly List<HttpStatusCode> _allowedStatusCodes=new List<HttpStatusCode>{HttpStatusCode.NotModified};        \r
+\r
+        public List<HttpStatusCode> AllowedStatusCodes\r
+        {\r
+            get\r
+            {\r
+                return _allowedStatusCodes;\r
+            }            \r
+        }\r
+\r
+        public DateTime LastModified { get; private set; }\r
+\r
+        private static string LogContent(WebResponse webResponse)\r
+        {\r
+            if (webResponse == null)\r
+                throw new ArgumentNullException("webResponse");\r
+            Contract.EndContractBlock();\r
+\r
+            //The response stream must be copied to avoid affecting other code by disposing of the \r
+            //original response stream.\r
+            var stream = webResponse.GetResponseStream();            \r
+            using(var memStream=new MemoryStream())\r
+            using (var reader = new StreamReader(memStream))\r
+            {\r
+                stream.CopyTo(memStream);                \r
+                string content = reader.ReadToEnd();\r
+\r
+                stream.Seek(0,SeekOrigin.Begin);\r
+                return content;\r
+            }\r
+        }\r
+\r
+        \r
+        public string GetStringWithRetry(Uri address, int retries = 0)\r
+        {\r
+            if (address == null)\r
+                throw new ArgumentNullException("address");\r
+\r
+            var actualRetries = (retries == 0) ? Retries : retries;\r
+            var task = GetAsync(address).WithRetries(Timeout,actualRetries)\r
+                .ContinueWith(async r =>\r
+                {\r
+                    var response = r.Result;\r
+                    if (response.StatusCode == HttpStatusCode.NoContent)\r
+                        return String.Empty;\r
+                    if (response.StatusCode == HttpStatusCode.NotFound)\r
+                        return null;\r
+                    return await response.Content.ReadAsStringAsync();\r
+                }).Unwrap();\r
+\r
+            var result = task.Result;\r
+            return result;\r
+        }\r
+\r
+\r
+/*\r
+        public string DownloadStringWithRetry(string address,int retries=0)\r
+        {\r
+            \r
+            if (address == null)\r
+                throw new ArgumentNullException("address");\r
+\r
+            var actualAddress = GetActualAddress(address);\r
+\r
+            TraceStart("GET",actualAddress);            \r
+            \r
+            var actualRetries = (retries == 0) ? Retries : retries;\r
+\r
+            \r
+            var task = Retry(GetAsync(actualAddress)).ContinueWith(async () =>\r
+            {\r
+                var response= await GetAsync(actualAddress);                \r
+\r
+                if (response.StatusCode== HttpStatusCode.NoContent)\r
+                    return String.Empty;\r
+                return await response.Content.ReadAsStringAsync();\r
+\r
+            }, actualRetries);\r
+\r
+            try\r
+            {\r
+                    var result = task.Result;\r
+                return result;\r
+\r
+            }\r
+            catch (AggregateException exc)\r
+            {\r
+                //If the task fails, propagate the original exception\r
+                if (exc.InnerException!=null)\r
+                    throw exc.InnerException;\r
+                throw;\r
+            }\r
+        }\r
+*/\r
+\r
+       /* public void Head(string address,int retries=0)\r
+        {\r
+            AllowedStatusCodes.Add(HttpStatusCode.NotFound);\r
+            RetryWithoutContent(address, retries, "HEAD");            \r
+        }\r
+\r
+        public void PutWithRetry(string address, int retries = 0, string contentType=null)\r
+        {\r
+            RetryWithoutContent(address, retries, "PUT",contentType);\r
+        }\r
+\r
+        public void PostWithRetry(string address,string contentType)\r
+        {            \r
+            RetryWithoutContent(address, 0, "POST",contentType);\r
+        }\r
+\r
+        public void DeleteWithRetry(string address,int retries=0)\r
+        {\r
+            RetryWithoutContent(address, retries, "DELETE");\r
+        }*/\r
+\r
+      /*  public string GetHeaderValue(string headerName,bool optional=false)\r
+        {\r
+            if (this.ResponseHeaders==null)\r
+                throw new InvalidOperationException("ResponseHeaders are null");\r
+            Contract.EndContractBlock();\r
+\r
+            var values=this.ResponseHeaders.GetValues(headerName);\r
+            if (values != null)\r
+                return values[0];\r
+\r
+            if (optional)            \r
+                return null;            \r
+            //A required header was not found\r
+            throw new WebException(String.Format("The {0}  header is missing", headerName));\r
+        }*/\r
+\r
+        public void SetNonEmptyHeaderValue(string headerName, string value)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(value))\r
+                return;\r
+            DefaultRequestHeaders.Add(headerName,value);\r
+        }\r
+\r
+     /*   private void RetryWithoutContent(string address, int retries, string method,string contentType=null)\r
+        {\r
+            if (address == null)\r
+                throw new ArgumentNullException("address");\r
+\r
+            var actualAddress = GetActualAddress(address);            \r
+            var actualRetries = (retries == 0) ? Retries : retries;\r
+\r
+            var task = Retry(() =>\r
+            {\r
+                var uriString = String.Join("/",BaseAddress ,actualAddress);\r
+                var uri = new Uri(uriString);\r
+                var request =  GetWebRequest(uri);\r
+                if (contentType!=null)\r
+                {\r
+                    request.ContentType = contentType;\r
+                    request.ContentLength = 0;\r
+                }\r
+                request.Method = method;\r
+                if (ResponseHeaders!=null)\r
+                    ResponseHeaders.Clear();\r
+\r
+                TraceStart(method, uriString);\r
+                if (method == "PUT")\r
+                    request.ContentLength = 0;\r
+\r
+                //Have to use try/finally instead of using here, because WebClient needs a valid WebResponse object\r
+                //in order to return response headers\r
+                var response = (HttpWebResponse)GetWebResponse(request);\r
+                try\r
+                {\r
+                    LastModified = response.LastModified;\r
+                    StatusCode = response.StatusCode;\r
+                    StatusDescription = response.StatusDescription;\r
+                }\r
+                finally\r
+                {\r
+                    response.Close();\r
+                }\r
+                \r
+\r
+                return 0;\r
+            }, actualRetries);\r
+\r
+            try\r
+            {\r
+                task.Wait();\r
+            }\r
+            catch (AggregateException ex)\r
+            {\r
+                var exc = ex.InnerException;\r
+                if (exc is RetryException)\r
+                {\r
+                    Log.ErrorFormat("[{0}] RETRY FAILED for {1} after {2} retries",method,address,retries);\r
+                }\r
+                else\r
+                {\r
+                    Log.ErrorFormat("[{0}] FAILED for {1} with \n{2}", method, address, exc);\r
+                }\r
+                throw exc;\r
+\r
+            }\r
+            catch(Exception ex)\r
+            {\r
+                Log.ErrorFormat("[{0}] FAILED for {1} with \n{2}", method, address, ex);\r
+                throw;\r
+            }\r
+        }\r
+     */   \r
+        private static void TraceStart(string method, string actualAddress)\r
+        {\r
+            Log.InfoFormat("[{0}] {1} {2}", method, DateTime.Now, actualAddress);\r
+        }\r
+\r
+        private string GetActualAddress(string address)\r
+        {\r
+            if (Parameters.Count == 0)\r
+                return address;\r
+            var addressBuilder=new StringBuilder(address);            \r
+\r
+            bool isFirst = true;\r
+            foreach (var parameter in Parameters)\r
+            {\r
+                if(isFirst)\r
+                    addressBuilder.AppendFormat("?{0}={1}", parameter.Key, parameter.Value);\r
+                else\r
+                    addressBuilder.AppendFormat("&{0}={1}", parameter.Key, parameter.Value);\r
+                isFirst = false;\r
+            }\r
+            return addressBuilder.ToString();\r
+        }\r
+\r
+\r
+      \r
+       /*public void AssertStatusOK(string message)\r
+        {\r
+            if (StatusCode >= HttpStatusCode.BadRequest)\r
+                throw new WebException(String.Format("{0} with code {1} - {2}", message, StatusCode, StatusDescription));\r
+        }*/\r
+\r
+\r
+        \r
+/*\r
+        private Task<T> Retry2<T>(Func<T> original, int retryCount, TaskCompletionSource<T> tcs = null)\r
+        {\r
+            if (original==null)\r
+                throw new ArgumentNullException("original");\r
+            Contract.EndContractBlock();\r
+\r
+            if (tcs == null)\r
+                tcs = new TaskCompletionSource<T>();\r
+            Task.Factory.StartNew(original).ContinueWith(_original =>\r
+                {\r
+                    if (!_original.IsFaulted)\r
+                        tcs.SetFromTask(_original);\r
+                    else \r
+                    {\r
+                        var e = _original.Exception.InnerException;\r
+                        var we = (e as WebException);\r
+                        if (we==null)\r
+                            tcs.SetException(e);\r
+                        else\r
+                        {\r
+                            var statusCode = GetStatusCode(we);\r
+\r
+                            //Return null for 404\r
+                            if (statusCode == HttpStatusCode.NotFound)\r
+                                tcs.SetResult(default(T));\r
+                            //Retry for timeouts and service unavailable\r
+                            else if (we.Status == WebExceptionStatus.Timeout ||\r
+                                (we.Status == WebExceptionStatus.ProtocolError && statusCode == HttpStatusCode.ServiceUnavailable))\r
+                            {\r
+                                TimedOut = true;\r
+                                if (retryCount == 0)\r
+                                {                                    \r
+                                    Log.ErrorFormat("[ERROR] Timed out too many times. \n{0}\n",e);\r
+                                    tcs.SetException(new RetryException("Timed out too many times.", e));                                    \r
+                                }\r
+                                else\r
+                                {\r
+                                    Log.ErrorFormat(\r
+                                        "[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout,\r
+                                        retryCount, e);\r
+                                    Retry(original, retryCount - 1, tcs);\r
+                                }\r
+                            }\r
+                            else\r
+                                tcs.SetException(e);\r
+                        }\r
+                    };\r
+                });\r
+            return tcs.Task;\r
+        }\r
+*/\r
+\r
+     /*   private HttpStatusCode GetStatusCode(WebException we)\r
+        {\r
+            if (we==null)\r
+                throw new ArgumentNullException("we");\r
+            var statusCode = HttpStatusCode.RequestTimeout;\r
+            if (we.Response != null)\r
+            {\r
+                statusCode = ((HttpWebResponse) we.Response).StatusCode;\r
+                this.StatusCode = statusCode;\r
+            }\r
+            return statusCode;\r
+        }*/\r
+\r
+        public UriBuilder GetAddressBuilder(string container, string objectName)\r
+        {\r
+            var builder = new UriBuilder(String.Join("/", BaseAddress, container, objectName));\r
+            return builder;\r
+        }\r
+\r
+      /*  public Dictionary<string, string> GetMeta(string metaPrefix)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(metaPrefix))\r
+                throw new ArgumentNullException("metaPrefix");\r
+            Contract.EndContractBlock();\r
+\r
+            var keys = ResponseHeaders.AllKeys.AsQueryable();\r
+            var dict = (from key in keys\r
+                        where key.StartsWith(metaPrefix)\r
+                        let name = key.Substring(metaPrefix.Length)\r
+                        select new { Name = name, Value = ResponseHeaders[key] })\r
+                        .ToDictionary(t => t.Name, t => t.Value);\r
+            return dict;\r
+        }\r
+\r
+\r
+        internal Task DownloadFileTaskAsync(Uri uri, string fileName, CancellationToken cancellationToken, IProgress<DownloadProgressChangedEventArgs> progress)\r
+        {\r
+            cancellationToken.Register(CancelAsync);\r
+            DownloadProgressChangedEventHandler onDownloadProgressChanged = (o, e) => progress.Report(e);\r
+            this.DownloadProgressChanged += onDownloadProgressChanged;\r
+            return this.DownloadFileTaskAsync(uri, fileName).ContinueWith(t=>\r
+                {\r
+                    this.DownloadProgressChanged -= onDownloadProgressChanged;\r
+                });\r
+        }\r
+\r
+        internal Task<byte[]> DownloadDataTaskAsync(Uri uri, CancellationToken cancellationToken, IProgress<DownloadProgressChangedEventArgs> progress)\r
+        {\r
+            cancellationToken.Register(CancelAsync);\r
+            DownloadProgressChangedEventHandler onDownloadProgressChanged = (o, e) => progress.Report(e);\r
+            this.DownloadProgressChanged += onDownloadProgressChanged;\r
+            return this.DownloadDataTaskAsync(uri).ContinueWith(t =>\r
+            {\r
+                this.DownloadProgressChanged -= onDownloadProgressChanged;\r
+                return t.Result;\r
+            });\r
+        }\r
+*/    }\r
+\r
+}\r
diff --git a/trunk/Pithos.Network/UploadArgs.cs b/trunk/Pithos.Network/UploadArgs.cs
new file mode 100644 (file)
index 0000000..7a82bed
--- /dev/null
@@ -0,0 +1,34 @@
+using System.ComponentModel;\r
+using System.Net;\r
+\r
+namespace Pithos.Network\r
+{\r
+    public class UploadArgs : ProgressChangedEventArgs\r
+    {\r
+        public UploadArgs(int progressPercentage, object userToken, long bytesSent, long totalBytesToSend, long bytesReceived, long totalBytesToReceive) :\r
+            base(progressPercentage, userToken) \r
+        {\r
+            BytesReceived = bytesReceived; \r
+            TotalBytesToReceive = totalBytesToReceive; \r
+            BytesSent = bytesSent;\r
+            TotalBytesToSend = totalBytesToSend; \r
+        }\r
+\r
+        public UploadArgs(UploadProgressChangedEventArgs args) :\r
+            base(args.ProgressPercentage, args.UserState) \r
+        {\r
+            BytesReceived = args.BytesReceived; \r
+            TotalBytesToReceive = args.TotalBytesToReceive; \r
+            BytesSent = args.BytesSent;\r
+            TotalBytesToSend = args.TotalBytesToSend; \r
+        }\r
+\r
+        public long BytesReceived { get; private set; }\r
+\r
+        public long TotalBytesToReceive { get; private set; }\r
+\r
+        public long BytesSent { get; private set; }\r
+\r
+        public long TotalBytesToSend { get; private set; }\r
+    }\r
+}
\ No newline at end of file