PutHashMap converted to HttpClient
[pithos-ms-client] / trunk / Pithos.Network / CloudFilesClient.cs
index bec46b7..f482038 100644 (file)
@@ -66,6 +66,7 @@ namespace Pithos.Network
     [Export(typeof(ICloudClient))]\r
     public class CloudFilesClient:ICloudClient\r
     {\r
+        private const string TOKEN_HEADER = "X-Auth-Token";\r
         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
 \r
         //CloudFilesClient uses *_baseClient* internally to communicate with the server\r
@@ -73,6 +74,7 @@ namespace Pithos.Network
         private RestClient _baseClient;\r
 \r
         private HttpClient _baseHttpClient;\r
+        private HttpClient _baseHttpClientNoTimeout;\r
         \r
 \r
         //During authentication the client provides a UserName \r
@@ -94,7 +96,7 @@ namespace Pithos.Network
             set\r
             {\r
                 _token = value;\r
-                _baseClient.Headers["X-Auth-Token"] = value;\r
+                _baseClient.Headers[TOKEN_HEADER] = value;\r
             }\r
         }\r
 \r
@@ -190,7 +192,14 @@ namespace Pithos.Network
                 BaseAddress = StorageUrl,\r
                 Timeout = TimeSpan.FromSeconds(30)\r
             };\r
-            _baseHttpClient.DefaultRequestHeaders.Add("X-Auth-Token", Token);\r
+            _baseHttpClient.DefaultRequestHeaders.Add(TOKEN_HEADER, Token);\r
+\r
+            _baseHttpClientNoTimeout = new HttpClient(httpClientHandler)\r
+            {\r
+                BaseAddress = StorageUrl,\r
+                Timeout = TimeSpan.FromMilliseconds(-1)\r
+            };\r
+            _baseHttpClientNoTimeout.DefaultRequestHeaders.Add(TOKEN_HEADER, Token);\r
 \r
 \r
         }\r
@@ -252,7 +261,7 @@ namespace Pithos.Network
                     if (String.IsNullOrWhiteSpace(storageUrl))\r
                         throw new InvalidOperationException("Failed to obtain storage url");\r
 \r
-                    token = response.Headers.GetValues("X-Auth-Token").First();\r
+                    token = response.Headers.GetValues(TOKEN_HEADER).First();\r
                     if (String.IsNullOrWhiteSpace(token))\r
                         throw new InvalidOperationException("Failed to obtain token url");\r
 \r
@@ -284,7 +293,14 @@ namespace Pithos.Network
                     BaseAddress = StorageUrl,\r
                     Timeout = TimeSpan.FromSeconds(30)\r
                 };\r
-                _baseHttpClient.DefaultRequestHeaders.Add("X-Auth-Token", token);\r
+                _baseHttpClient.DefaultRequestHeaders.Add(TOKEN_HEADER, token);\r
+\r
+                _baseHttpClientNoTimeout = new HttpClient(httpClientHandler)\r
+                {\r
+                    BaseAddress = StorageUrl,\r
+                    Timeout = TimeSpan.FromMilliseconds(-1)\r
+                };\r
+                _baseHttpClientNoTimeout.DefaultRequestHeaders.Add(TOKEN_HEADER, token);\r
 \r
                 /* var keys = authClient.ResponseHeaders.AllKeys.AsQueryable();\r
                 groups = (from key in keys\r
@@ -1396,6 +1412,80 @@ namespace Pithos.Network
 \r
         }\r
 \r
+        public async Task<IList<string>> PutHashMap(string account, Uri container, Uri objectName, TreeHash hash)\r
+        {\r
+            if (container == null)\r
+                throw new ArgumentNullException("container", "The container property can't be empty");\r
+            if (container.IsAbsoluteUri)\r
+                throw new ArgumentException("The container must be relative","container");\r
+            if (objectName == null)\r
+                throw new ArgumentNullException("objectName", "The objectName property can't be empty");\r
+            if (objectName.IsAbsoluteUri)\r
+                throw new ArgumentException("The objectName must be relative","objectName");\r
+            if (hash == null)\r
+                throw new ArgumentNullException("hash");\r
+            if (String.IsNullOrWhiteSpace(Token))\r
+                throw new InvalidOperationException("Invalid Token");\r
+            if (StorageUrl == null)\r
+                throw new InvalidOperationException("Invalid Storage Url");\r
+            Contract.EndContractBlock();\r
+\r
+            \r
+\r
+            //The container and objectName are relative names. They are joined with the client's\r
+            //BaseAddress to create the object's absolute address\r
+\r
+            var targetUri = GetTargetUri(account).Combine(container).Combine(objectName);\r
+  \r
+\r
+            var uri = new Uri(String.Format("{0}?format=json&hashmap",targetUri),UriKind.Absolute);\r
+\r
+            \r
+            //Send the tree hash as Json to the server            \r
+            var jsonHash = hash.ToJson();\r
+            if (Log.IsDebugEnabled)\r
+                Log.DebugFormat("Hashes:\r\n{0}", jsonHash);\r
+\r
+            var message = new HttpRequestMessage(HttpMethod.Put, uri)\r
+            {\r
+                Content = new StringContent(jsonHash)\r
+            };\r
+            message.Headers.Add("ETag",hash.TopHash.ToHashString());\r
+            \r
+            //Don't use a timeout because putting the hashmap may be a long process\r
+\r
+            using(var response=await _baseHttpClientNoTimeout.SendAsyncWithRetries(message,3))\r
+            {\r
+                var empty = (IList<string>)new List<string>();\r
+                \r
+                switch (response.StatusCode)\r
+                {\r
+                    case HttpStatusCode.Created:\r
+                        //The server will respond either with 201-created if all blocks were already on the server\r
+                        return empty;\r
+                    case HttpStatusCode.Conflict:\r
+                        //or with a 409-conflict and return the list of missing parts\r
+                        using (var stream = await response.Content.ReadAsStreamAsync())\r
+                        using(var reader=stream.GetLoggedReader(Log))\r
+                        {                            \r
+                            var serializer = new JsonSerializer();                            \r
+                            serializer.Error += (sender, args) => Log.ErrorFormat("Deserialization error at [{0}] [{1}]", args.ErrorContext.Error, args.ErrorContext.Member);\r
+                            var hashes = (List<string>)serializer.Deserialize(reader, typeof(List<string>));\r
+                            return hashes;\r
+                        }                        \r
+                    default:\r
+                        //All other cases are unexpected\r
+                        //Ensure that failure codes raise exceptions\r
+                        response.EnsureSuccessStatusCode();\r
+                        //And log any other codes as warngings, but continute processing\r
+                        Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",response.StatusCode,response.ReasonPhrase);\r
+                        return empty;\r
+                }\r
+            }\r
+\r
+        }\r
+\r
+/*\r
         public Task<IList<string>> PutHashMap(string account, Uri container, Uri objectName, TreeHash hash)\r
         {\r
             if (container == null)\r
@@ -1487,6 +1577,7 @@ namespace Pithos.Network
 \r
         }\r
 \r
+*/\r
 \r
         public async Task<byte[]> GetBlock(string account, Uri container, Uri relativeUrl, long start, long? end, CancellationToken cancellationToken)\r
         {\r