Several CloudFilesClient.cs methods converted to use HttpClient
[pithos-ms-client] / trunk / Pithos.Network / CloudFilesClient.cs
1 #region\r
2 /* -----------------------------------------------------------------------\r
3  * <copyright file="CloudFilesClient.cs" company="GRNet">\r
4  * \r
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
6  *\r
7  * Redistribution and use in source and binary forms, with or\r
8  * without modification, are permitted provided that the following\r
9  * conditions are met:\r
10  *\r
11  *   1. Redistributions of source code must retain the above\r
12  *      copyright notice, this list of conditions and the following\r
13  *      disclaimer.\r
14  *\r
15  *   2. Redistributions in binary form must reproduce the above\r
16  *      copyright notice, this list of conditions and the following\r
17  *      disclaimer in the documentation and/or other materials\r
18  *      provided with the distribution.\r
19  *\r
20  *\r
21  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
22  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
32  * POSSIBILITY OF SUCH DAMAGE.\r
33  *\r
34  * The views and conclusions contained in the software and\r
35  * documentation are those of the authors and should not be\r
36  * interpreted as representing official policies, either expressed\r
37  * or implied, of GRNET S.A.\r
38  * </copyright>\r
39  * -----------------------------------------------------------------------\r
40  */\r
41 #endregion\r
42 \r
43 // **CloudFilesClient** provides a simple client interface to CloudFiles and Pithos\r
44 //\r
45 // The class provides methods to upload/download files, delete files, manage containers\r
46 \r
47 \r
48 using System;\r
49 using System.Collections.Generic;\r
50 using System.Collections.Specialized;\r
51 using System.ComponentModel.Composition;\r
52 using System.Diagnostics;\r
53 using System.Diagnostics.Contracts;\r
54 using System.Linq;\r
55 using System.Net;\r
56 using System.Net.Http;\r
57 using System.Reflection;\r
58 using System.Threading;\r
59 using System.Threading.Tasks;\r
60 using Newtonsoft.Json;\r
61 using Pithos.Interfaces;\r
62 using log4net;\r
63 \r
64 namespace Pithos.Network\r
65 {\r
66     [Export(typeof(ICloudClient))]\r
67     public class CloudFilesClient:ICloudClient\r
68     {\r
69         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
70 \r
71         //CloudFilesClient uses *_baseClient* internally to communicate with the server\r
72         //RestClient provides a REST-friendly interface over the standard WebClient.\r
73         private RestClient _baseClient;\r
74 \r
75         private HttpClient _baseHttpClient;\r
76         \r
77 \r
78         //During authentication the client provides a UserName \r
79         public string UserName { get; set; }\r
80         \r
81         //and and ApiKey to the server\r
82         public string ApiKey { get; set; }\r
83         \r
84         //And receives an authentication Token. This token must be provided in ALL other operations,\r
85         //in the X-Auth-Token header\r
86         private string _token;\r
87         private readonly string _emptyGuid = Guid.Empty.ToString();\r
88         private readonly Uri _emptyUri = new Uri("",UriKind.Relative);\r
89 \r
90 \r
91         public string Token\r
92         {\r
93             get { return _token; }\r
94             set\r
95             {\r
96                 _token = value;\r
97                 _baseClient.Headers["X-Auth-Token"] = value;\r
98             }\r
99         }\r
100 \r
101         //The client also receives a StorageUrl after authentication. All subsequent operations must\r
102         //use this url\r
103         public Uri StorageUrl { get; set; }\r
104 \r
105 \r
106         public Uri RootAddressUri { get; set; }\r
107 \r
108        /* private WebProxy _proxy;\r
109         public WebProxy Proxy\r
110         {\r
111             get { return _proxy; }\r
112             set\r
113             {\r
114                 _proxy = value;\r
115                 if (_baseClient != null)\r
116                     _baseClient.Proxy = value;                \r
117             }\r
118         }\r
119 */\r
120 \r
121         /* private Uri _proxy;\r
122         public Uri Proxy\r
123         {\r
124             get { return _proxy; }\r
125             set\r
126             {\r
127                 _proxy = value;\r
128                 if (_baseClient != null)\r
129                     _baseClient.Proxy = new WebProxy(value);                \r
130             }\r
131         }*/\r
132 \r
133         public double DownloadPercentLimit { get; set; }\r
134         public double UploadPercentLimit { get; set; }\r
135 \r
136         public string AuthenticationUrl { get; set; }\r
137 \r
138  \r
139         public string VersionPath\r
140         {\r
141             get { return UsePithos ? "v1" : "v1.0"; }\r
142         }\r
143 \r
144         public bool UsePithos { get; set; }\r
145 \r
146 \r
147 \r
148         public CloudFilesClient(string userName, string apiKey)\r
149         {\r
150             UserName = userName;\r
151             ApiKey = apiKey;\r
152         }\r
153 \r
154         public CloudFilesClient(AccountInfo accountInfo)\r
155         {\r
156             if (accountInfo==null)\r
157                 throw new ArgumentNullException("accountInfo");\r
158             Contract.Ensures(!String.IsNullOrWhiteSpace(Token));\r
159             Contract.Ensures(StorageUrl != null);\r
160             Contract.Ensures(_baseClient != null);\r
161             Contract.Ensures(RootAddressUri != null);\r
162             Contract.EndContractBlock();          \r
163 \r
164             _baseClient = new RestClient\r
165             {\r
166                 BaseAddress = accountInfo.StorageUri.ToString(),\r
167                 Timeout = 30000,\r
168                 Retries = 3,\r
169             };\r
170             StorageUrl = accountInfo.StorageUri;\r
171             Token = accountInfo.Token;\r
172             UserName = accountInfo.UserName;\r
173 \r
174             //Get the root address (StorageUrl without the account)\r
175             var storageUrl = StorageUrl.AbsoluteUri;\r
176             var usernameIndex = storageUrl.LastIndexOf(UserName);\r
177             var rootUrl = storageUrl.Substring(0, usernameIndex);\r
178             RootAddressUri = new Uri(rootUrl);\r
179 \r
180             var httpClientHandler = new HttpClientHandler\r
181             {\r
182                 AllowAutoRedirect = true,\r
183                 AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,\r
184                 UseCookies = true,\r
185             };\r
186 \r
187 \r
188             _baseHttpClient = new HttpClient(httpClientHandler)\r
189             {\r
190                 BaseAddress = StorageUrl,\r
191                 Timeout = TimeSpan.FromSeconds(30)\r
192             };\r
193             _baseHttpClient.DefaultRequestHeaders.Add("X-Auth-Token", Token);\r
194 \r
195 \r
196         }\r
197 \r
198 \r
199         private static void AssertStatusOK(HttpResponseMessage response, string message)\r
200         {\r
201             var statusCode = response.StatusCode;\r
202             if (statusCode >= HttpStatusCode.BadRequest)\r
203                 throw new WebException(String.Format("{0} with code {1} - {2}", message, statusCode, response.ReasonPhrase));\r
204         }\r
205 \r
206         public AccountInfo Authenticate()\r
207         {\r
208             if (String.IsNullOrWhiteSpace(UserName))\r
209                 throw new InvalidOperationException("UserName is empty");\r
210             if (String.IsNullOrWhiteSpace(ApiKey))\r
211                 throw new InvalidOperationException("ApiKey is empty");\r
212             if (String.IsNullOrWhiteSpace(AuthenticationUrl))\r
213                 throw new InvalidOperationException("AuthenticationUrl is empty");\r
214             Contract.Ensures(!String.IsNullOrWhiteSpace(Token));\r
215             Contract.Ensures(StorageUrl != null);\r
216             Contract.Ensures(_baseClient != null);\r
217             Contract.Ensures(RootAddressUri != null);\r
218             Contract.EndContractBlock();\r
219 \r
220 \r
221             Log.InfoFormat("[AUTHENTICATE] Start for {0}", UserName);\r
222 \r
223             var groups = new List<Group>();\r
224 \r
225             var httpClientHandler = new HttpClientHandler\r
226             {\r
227                 AllowAutoRedirect = true,\r
228                 AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,\r
229                 UseCookies = true,                                \r
230             };\r
231 \r
232             using (var authClient = new HttpClient(httpClientHandler){ BaseAddress = new Uri(AuthenticationUrl),Timeout=TimeSpan.FromSeconds(30) })\r
233             //using (var authClient = new RestClient{BaseAddress=AuthenticationUrl})\r
234             {                \r
235                /* if (Proxy != null)\r
236                     authClient.Proxy = Proxy;*/\r
237 \r
238                 //Contract.Assume(authClient.DefaultRequestHeaders != null);\r
239 \r
240                 authClient.DefaultRequestHeaders.Add("X-Auth-User", UserName);\r
241                 authClient.DefaultRequestHeaders.Add("X-Auth-Key", ApiKey);\r
242 \r
243                 //Func<Task<HttpResponseMessage>> call = () => ;\r
244                 string storageUrl;\r
245                 string token;\r
246                 \r
247                 using (var response = authClient.GetAsyncWithRetries(new Uri(VersionPath, UriKind.Relative),3).Result) // .DownloadStringWithRetryRelative(new Uri(VersionPath, UriKind.Relative), 3);                    \r
248                 {\r
249                     AssertStatusOK(response,"Authentication failed");\r
250                 \r
251                     storageUrl = response.Headers.GetValues("X-Storage-Url").First();\r
252                     if (String.IsNullOrWhiteSpace(storageUrl))\r
253                         throw new InvalidOperationException("Failed to obtain storage url");\r
254 \r
255                     token = response.Headers.GetValues("X-Auth-Token").First();\r
256                     if (String.IsNullOrWhiteSpace(token))\r
257                         throw new InvalidOperationException("Failed to obtain token url");\r
258 \r
259                 }\r
260 \r
261 \r
262                 _baseClient = new RestClient\r
263                 {\r
264                     BaseAddress = storageUrl,\r
265                     Timeout = 30000,\r
266                     Retries = 3,                    \r
267                     //Proxy=Proxy\r
268                 };\r
269 \r
270                 StorageUrl = new Uri(storageUrl);\r
271                 Token = token;\r
272 \r
273                 \r
274 \r
275                                                                \r
276                 //Get the root address (StorageUrl without the account)\r
277                 var usernameIndex=storageUrl.LastIndexOf(UserName);\r
278                 var rootUrl = storageUrl.Substring(0, usernameIndex);\r
279                 RootAddressUri = new Uri(rootUrl);\r
280                 \r
281 \r
282                 _baseHttpClient = new HttpClient(httpClientHandler)\r
283                 {\r
284                     BaseAddress = StorageUrl,\r
285                     Timeout = TimeSpan.FromSeconds(30)\r
286                 };\r
287                 _baseHttpClient.DefaultRequestHeaders.Add("X-Auth-Token", token);\r
288 \r
289                 /* var keys = authClient.ResponseHeaders.AllKeys.AsQueryable();\r
290                 groups = (from key in keys\r
291                             where key.StartsWith("X-Account-Group-")\r
292                             let name = key.Substring(16)\r
293                             select new Group(name, authClient.ResponseHeaders[key]))\r
294                         .ToList();\r
295                     \r
296 */\r
297             }\r
298 \r
299             Log.InfoFormat("[AUTHENTICATE] End for {0}", UserName);\r
300             Debug.Assert(_baseClient!=null);\r
301 \r
302             return new AccountInfo {StorageUri = StorageUrl, Token = Token, UserName = UserName,Groups=groups};            \r
303 \r
304         }\r
305 \r
306         private static void TraceStart(string method, Uri actualAddress)\r
307         {\r
308             Log.InfoFormat("[{0}] {1} {2}", method, DateTime.Now, actualAddress);\r
309         }\r
310 \r
311         private async Task<string> GetString(Uri targetUri, string errorMessage,DateTime? since=null)\r
312         {\r
313             TraceStart("GET",targetUri);\r
314             var request = new HttpRequestMessage(HttpMethod.Get, targetUri);            \r
315             if (since.HasValue)\r
316             {\r
317                 request.Headers.IfModifiedSince = since.Value;\r
318             }\r
319             //Func<Task<HttpResponseMessage>> call = () => _baseHttpClient.SendAsync(request);\r
320             using (var response = await _baseHttpClient.SendAsyncWithRetries(request,3))\r
321             {\r
322                 AssertStatusOK(response, errorMessage);\r
323 \r
324                 if (response.StatusCode == HttpStatusCode.NoContent)\r
325                     return String.Empty;\r
326 \r
327                 var content = await response.Content.ReadAsStringAsync();\r
328                 return content;\r
329             }\r
330         }\r
331 \r
332         public IList<ContainerInfo> ListContainers(string account)\r
333         {\r
334 \r
335             var targetUrl = GetTargetUrl(account);\r
336             var targetUri=new Uri(String.Format("{0}?format=json",targetUrl));\r
337             var result = GetString(targetUri, "List Containers failed").Result;\r
338             if (String.IsNullOrWhiteSpace(result))\r
339                 return new List<ContainerInfo>();\r
340             var infos = JsonConvert.DeserializeObject<IList<ContainerInfo>>(result);                \r
341             foreach (var info in infos)\r
342             {\r
343                 info.Account = account;\r
344             }\r
345             return infos;\r
346            /* using (var client = new RestClient(_baseClient))\r
347             {\r
348                 if (!String.IsNullOrWhiteSpace(account))\r
349                     client.BaseAddress = GetAccountUrl(account);\r
350                 \r
351                 client.Parameters.Clear();\r
352                 client.Parameters.Add("format", "json");\r
353                 var content = client.DownloadStringWithRetryRelative(_emptyUri, 3);\r
354                 client.AssertStatusOK("List Containers failed");\r
355 \r
356                 if (client.StatusCode == HttpStatusCode.NoContent)\r
357                     return new List<ContainerInfo>();\r
358                 var infos = JsonConvert.DeserializeObject<IList<ContainerInfo>>(content);\r
359                 \r
360                 foreach (var info in infos)\r
361                 {\r
362                     info.Account = account;\r
363                 }\r
364                 return infos;\r
365             }   */       \r
366 \r
367         }\r
368 \r
369         private string GetAccountUrl(string account)\r
370         {\r
371             return RootAddressUri.Combine(account).AbsoluteUri;\r
372         }\r
373 \r
374         public IList<ShareAccountInfo> ListSharingAccounts(DateTime? since=null)\r
375         {\r
376             using (ThreadContext.Stacks["Share"].Push("List Accounts"))\r
377             {\r
378                 if (Log.IsDebugEnabled) Log.DebugFormat("START");\r
379 \r
380                 var targetUri = new Uri(String.Format("{0}?format=json", RootAddressUri), UriKind.Absolute);\r
381                 var content=GetString(targetUri, "ListSharingAccounts failed", since).Result;\r
382 \r
383                 //If the result is empty, return an empty list,\r
384                 var infos = String.IsNullOrWhiteSpace(content)\r
385                                 ? new List<ShareAccountInfo>()\r
386                             //Otherwise deserialize the account list into a list of ShareAccountInfos\r
387                                 : JsonConvert.DeserializeObject<IList<ShareAccountInfo>>(content);\r
388 \r
389                 Log.DebugFormat("END");\r
390                 return infos;\r
391 /*\r
392                 using (var client = new RestClient(_baseClient))\r
393                 {\r
394                     client.Parameters.Clear();\r
395                     client.Parameters.Add("format", "json");\r
396                     client.IfModifiedSince = since;\r
397 \r
398                     //Extract the username from the base address\r
399                     client.BaseAddress = RootAddressUri.AbsoluteUri; \r
400 \r
401                     var content = client.DownloadStringWithRetryRelative(_emptyUri, 3);\r
402 \r
403                     client.AssertStatusOK("ListSharingAccounts failed");\r
404 \r
405                     //If the result is empty, return an empty list,\r
406                     var infos = String.IsNullOrWhiteSpace(content)\r
407                                     ? new List<ShareAccountInfo>()\r
408                                 //Otherwise deserialize the account list into a list of ShareAccountInfos\r
409                                     : JsonConvert.DeserializeObject<IList<ShareAccountInfo>>(content);\r
410 \r
411                     Log.DebugFormat("END");\r
412                     return infos;\r
413                 }\r
414 */\r
415             }\r
416         }\r
417 \r
418 \r
419         /// <summary>\r
420         /// Request listing of all objects in a container modified since a specific time.\r
421         /// If the *since* value is missing, return all objects\r
422         /// </summary>\r
423         /// <param name="knownContainers">Use the since variable only for the containers listed in knownContainers. Unknown containers are considered new\r
424         /// and should be polled anyway\r
425         /// </param>\r
426         /// <param name="since"></param>\r
427         /// <returns></returns>\r
428         public IList<ObjectInfo> ListSharedObjects(HashSet<string> knownContainers,DateTime? since = null )\r
429         {\r
430 \r
431             using (ThreadContext.Stacks["Share"].Push("List Objects"))\r
432             {\r
433                 if (Log.IsDebugEnabled) Log.DebugFormat("START");\r
434                 //'since' is not used here because we need to have ListObjects return a NoChange result\r
435                 //for all shared accounts,containers\r
436 \r
437                 Func<ContainerInfo, string> getKey = c => String.Format("{0}\\{1}", c.Account, c.Name);\r
438 \r
439                 var accounts = ListSharingAccounts();\r
440                 var containers = (from account in accounts\r
441                                  let conts = ListContainers(account.name)\r
442                                  from container in conts\r
443                                  select container).ToList();                \r
444                 var items = from container in containers \r
445                             let actualSince=knownContainers.Contains(getKey(container))?since:null\r
446                             select ListObjects(container.Account , container.Name,  actualSince);\r
447                 var objects=items.SelectMany(r=> r).ToList();\r
448 \r
449                 //For each object\r
450                 //Check parents recursively up to (but not including) the container.\r
451                 //If parents are missing, add them to the list\r
452                 //Need function to calculate all parent URLs\r
453                 objects = AddMissingParents(objects);\r
454                 \r
455                 //Store any new containers\r
456                 foreach (var container in containers)\r
457                 {\r
458                     knownContainers.Add(getKey(container));\r
459                 }\r
460 \r
461 \r
462 \r
463                 if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
464                 return objects;\r
465             }\r
466         }\r
467 \r
468         private List<ObjectInfo> AddMissingParents(List<ObjectInfo> objects)\r
469         {\r
470             //TODO: Remove short-circuit when we decide to use Missing Parents functionality\r
471             //return objects;\r
472 \r
473             var existingUris = objects.ToDictionary(o => o.Uri, o => o);\r
474             foreach (var objectInfo in objects)\r
475             {\r
476                 //Can be null when retrieving objects to show in selective sync\r
477                 if (objectInfo.Name == null)\r
478                     continue;\r
479 \r
480                 //No need to unescape here, the parts will be used to create new ObjectInfos\r
481                 var parts = objectInfo.Name.ToString().Split(new[]{'/'},StringSplitOptions.RemoveEmptyEntries);\r
482                 //If there is no parent, skip\r
483                 if (parts.Length == 1)\r
484                     continue;\r
485                 var baseParts = new[]\r
486                                   {\r
487                                       objectInfo.Uri.Host, objectInfo.Uri.Segments[1].TrimEnd('/'),objectInfo.Account,objectInfo.Container.ToString()\r
488                                   };\r
489                 for (var partIdx = 0; partIdx < parts.Length - 1; partIdx++)\r
490                 {\r
491                     var nameparts = parts.Range(0, partIdx).ToArray();\r
492                     var parentName= String.Join("/", nameparts);\r
493 \r
494                     var parentParts = baseParts.Concat(nameparts);\r
495                     var parentUrl = objectInfo.Uri.Scheme+ "://" + String.Join("/", parentParts);\r
496                     \r
497                     var parentUri = new Uri(parentUrl, UriKind.Absolute);\r
498 \r
499                     ObjectInfo existingInfo;\r
500                     if (!existingUris.TryGetValue(parentUri,out existingInfo))\r
501                     {\r
502                         var h = parentUrl.GetHashCode();\r
503                         var reverse = new string(parentUrl.Reverse().ToArray());\r
504                         var rh = reverse.GetHashCode();\r
505                         var b1 = BitConverter.GetBytes(h);\r
506                         var b2 = BitConverter.GetBytes(rh);\r
507                         var g = new Guid(0,0,0,b1.Concat(b2).ToArray());\r
508                         \r
509 \r
510                         existingUris[parentUri] = new ObjectInfo\r
511                                                       {\r
512                                                           Account = objectInfo.Account,\r
513                                                           Container = objectInfo.Container,\r
514                                                           Content_Type = ObjectInfo.CONTENT_TYPE_DIRECTORY,\r
515                                                           ETag = Signature.MERKLE_EMPTY,\r
516                                                           X_Object_Hash = Signature.MERKLE_EMPTY,\r
517                                                           Name=new Uri(parentName,UriKind.Relative),\r
518                                                           StorageUri=objectInfo.StorageUri,\r
519                                                           Bytes = 0,\r
520                                                           UUID=g.ToString(),                                                          \r
521                                                       };\r
522                     }\r
523                 }\r
524             }\r
525             return existingUris.Values.ToList();\r
526         }\r
527 \r
528         public void SetTags(ObjectInfo target,IDictionary<string,string> tags)\r
529         {\r
530             if (String.IsNullOrWhiteSpace(Token))\r
531                 throw new InvalidOperationException("The Token is not set");\r
532             if (StorageUrl == null)\r
533                 throw new InvalidOperationException("The StorageUrl is not set");\r
534             if (target == null)\r
535                 throw new ArgumentNullException("target");\r
536             Contract.EndContractBlock();\r
537 \r
538             using (ThreadContext.Stacks["Share"].Push("Share Object"))\r
539             {\r
540                 if (Log.IsDebugEnabled) Log.DebugFormat("START");\r
541 \r
542                 using (var client = new RestClient(_baseClient))\r
543                 {\r
544 \r
545                     client.BaseAddress = GetAccountUrl(target.Account);\r
546 \r
547                     client.Parameters.Clear();\r
548                     client.Parameters.Add("update", "");\r
549 \r
550                     foreach (var tag in tags)\r
551                     {\r
552                         var headerTag = String.Format("X-Object-Meta-{0}", tag.Key);\r
553                         client.Headers.Add(headerTag, tag.Value);\r
554                     }\r
555                     \r
556                     client.DownloadStringWithRetryRelative(target.Container, 3);\r
557 \r
558                     \r
559                     client.AssertStatusOK("SetTags failed");\r
560                     //If the status is NOT ACCEPTED we have a problem\r
561                     if (client.StatusCode != HttpStatusCode.Accepted)\r
562                     {\r
563                         Log.Error("Failed to set tags");\r
564                         throw new Exception("Failed to set tags");\r
565                     }\r
566 \r
567                     if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
568                 }\r
569             }\r
570 \r
571 \r
572         }\r
573 \r
574         public void ShareObject(string account, Uri container, Uri objectName, string shareTo, bool read, bool write)\r
575         {\r
576             if (String.IsNullOrWhiteSpace(Token))\r
577                 throw new InvalidOperationException("The Token is not set");\r
578             if (StorageUrl==null)\r
579                 throw new InvalidOperationException("The StorageUrl is not set");\r
580             if (container==null)\r
581                 throw new ArgumentNullException("container");\r
582             if (container.IsAbsoluteUri)\r
583                 throw new ArgumentException("container");\r
584             if (objectName==null)\r
585                 throw new ArgumentNullException("objectName");\r
586             if (objectName.IsAbsoluteUri)\r
587                 throw new ArgumentException("objectName");\r
588             if (String.IsNullOrWhiteSpace(account))\r
589                 throw new ArgumentNullException("account");\r
590             if (String.IsNullOrWhiteSpace(shareTo))\r
591                 throw new ArgumentNullException("shareTo");\r
592             Contract.EndContractBlock();\r
593 \r
594             using (ThreadContext.Stacks["Share"].Push("Share Object"))\r
595             {\r
596                 if (Log.IsDebugEnabled) Log.DebugFormat("START");\r
597                 \r
598                 using (var client = new RestClient(_baseClient))\r
599                 {\r
600 \r
601                     client.BaseAddress = GetAccountUrl(account);\r
602 \r
603                     client.Parameters.Clear();\r
604                     client.Parameters.Add("format", "json");\r
605 \r
606                     string permission = "";\r
607                     if (write)\r
608                         permission = String.Format("write={0}", shareTo);\r
609                     else if (read)\r
610                         permission = String.Format("read={0}", shareTo);\r
611                     client.Headers.Add("X-Object-Sharing", permission);\r
612 \r
613                     var content = client.DownloadStringWithRetryRelative(container, 3);\r
614 \r
615                     client.AssertStatusOK("ShareObject failed");\r
616 \r
617                     //If the result is empty, return an empty list,\r
618                     var infos = String.IsNullOrWhiteSpace(content)\r
619                                     ? new List<ObjectInfo>()\r
620                                 //Otherwise deserialize the object list into a list of ObjectInfos\r
621                                     : JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);\r
622 \r
623                     if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
624                 }\r
625             }\r
626 \r
627 \r
628         }\r
629 \r
630         public AccountInfo GetAccountPolicies(AccountInfo accountInfo)\r
631         {\r
632             if (accountInfo==null)\r
633                 throw new ArgumentNullException("accountInfo");\r
634             Contract.EndContractBlock();\r
635 \r
636             using (ThreadContext.Stacks["Account"].Push("GetPolicies"))\r
637             {\r
638                 if (Log.IsDebugEnabled) Log.DebugFormat("START");\r
639 \r
640                 if (_baseClient == null)\r
641                 {\r
642                     _baseClient = new RestClient\r
643                     {\r
644                         BaseAddress = accountInfo.StorageUri.ToString(),\r
645                         Timeout = 30000,\r
646                         Retries = 3,\r
647                     };\r
648                 }\r
649 \r
650                 using (var client = new RestClient(_baseClient))\r
651                 {\r
652                     if (!String.IsNullOrWhiteSpace(accountInfo.UserName))\r
653                         client.BaseAddress = GetAccountUrl(accountInfo.UserName);\r
654 \r
655                     client.Parameters.Clear();\r
656                     client.Parameters.Add("format", "json");                    \r
657                     client.Head(_emptyUri, 3);\r
658 \r
659                     var quotaValue=client.ResponseHeaders["X-Account-Policy-Quota"];\r
660                     var bytesValue= client.ResponseHeaders["X-Account-Bytes-Used"];\r
661 \r
662                     long quota, bytes;\r
663                     if (long.TryParse(quotaValue, out quota))\r
664                         accountInfo.Quota = quota;\r
665                     if (long.TryParse(bytesValue, out bytes))\r
666                         accountInfo.BytesUsed = bytes;\r
667                     \r
668                     return accountInfo;\r
669 \r
670                 }\r
671 \r
672             }\r
673         }\r
674 \r
675         public void UpdateMetadata(ObjectInfo objectInfo)\r
676         {\r
677             if (objectInfo == null)\r
678                 throw new ArgumentNullException("objectInfo");\r
679             Contract.EndContractBlock();\r
680 \r
681             using (ThreadContext.Stacks["Objects"].Push("UpdateMetadata"))\r
682             {\r
683                 if (Log.IsDebugEnabled) Log.DebugFormat("START");\r
684 \r
685 \r
686                 using(var client=new RestClient(_baseClient))\r
687                 {\r
688 \r
689                     client.BaseAddress = GetAccountUrl(objectInfo.Account);\r
690                     \r
691                     client.Parameters.Clear();\r
692                     \r
693 \r
694                     //Set Tags\r
695                     foreach (var tag in objectInfo.Tags)\r
696                     {\r
697                         var headerTag = String.Format("X-Object-Meta-{0}", tag.Key);\r
698                         client.Headers.Add(headerTag, tag.Value);\r
699                     }\r
700 \r
701                     //Set Permissions\r
702 \r
703                     var permissions=objectInfo.GetPermissionString();\r
704                     client.SetNonEmptyHeaderValue("X-Object-Sharing",permissions);\r
705 \r
706                     client.SetNonEmptyHeaderValue("Content-Disposition",objectInfo.ContendDisposition);\r
707                     client.SetNonEmptyHeaderValue("Content-Encoding",objectInfo.ContentEncoding);\r
708                     client.SetNonEmptyHeaderValue("X-Object-Manifest",objectInfo.Manifest);\r
709                     var isPublic = objectInfo.IsPublic.ToString().ToLower();\r
710                     client.Headers.Add("X-Object-Public", isPublic);\r
711 \r
712 \r
713                     /*var uriBuilder = client.GetAddressBuilder(objectInfo.Container, objectInfo.Name);\r
714                     uriBuilder.Query = "update=";\r
715                     var uri = uriBuilder.Uri.MakeRelativeUri(this.RootAddressUri);*/\r
716                     var address = String.Format("{0}/{1}?update=",objectInfo.Container, objectInfo.Name);\r
717                     client.PostWithRetry(new Uri(address,UriKind.Relative),"application/xml");\r
718                     \r
719                     //client.UploadValues(uri,new NameValueCollection());\r
720 \r
721 \r
722                     client.AssertStatusOK("UpdateMetadata failed");\r
723                     //If the status is NOT ACCEPTED or OK we have a problem\r
724                     if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK))\r
725                     {\r
726                         Log.Error("Failed to update metadata");\r
727                         throw new Exception("Failed to update metadata");\r
728                     }\r
729 \r
730                     if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
731                 }\r
732             }\r
733 \r
734         }\r
735 \r
736         public void UpdateMetadata(ContainerInfo containerInfo)\r
737         {\r
738             if (containerInfo == null)\r
739                 throw new ArgumentNullException("containerInfo");\r
740             Contract.EndContractBlock();\r
741 \r
742             using (ThreadContext.Stacks["Containers"].Push("UpdateMetadata"))\r
743             {\r
744                 if (Log.IsDebugEnabled) Log.DebugFormat("START");\r
745 \r
746 \r
747                 using(var client=new RestClient(_baseClient))\r
748                 {\r
749 \r
750                     client.BaseAddress = GetAccountUrl(containerInfo.Account);\r
751                     \r
752                     client.Parameters.Clear();\r
753                     \r
754 \r
755                     //Set Tags\r
756                     foreach (var tag in containerInfo.Tags)\r
757                     {\r
758                         var headerTag = String.Format("X-Container-Meta-{0}", tag.Key);\r
759                         client.Headers.Add(headerTag, tag.Value);\r
760                     }\r
761 \r
762                     \r
763                     //Set Policies\r
764                     foreach (var policy in containerInfo.Policies)\r
765                     {\r
766                         var headerPolicy = String.Format("X-Container-Policy-{0}", policy.Key);\r
767                         client.Headers.Add(headerPolicy, policy.Value);\r
768                     }\r
769 \r
770 \r
771                     var uriBuilder = client.GetAddressBuilder(containerInfo.Name,_emptyUri);\r
772                     var uri = uriBuilder.Uri;\r
773 \r
774                     client.UploadValues(uri,new NameValueCollection());\r
775 \r
776 \r
777                     client.AssertStatusOK("UpdateMetadata failed");\r
778                     //If the status is NOT ACCEPTED or OK we have a problem\r
779                     if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK))\r
780                     {\r
781                         Log.Error("Failed to update metadata");\r
782                         throw new Exception("Failed to update metadata");\r
783                     }\r
784 \r
785                     if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
786                 }\r
787             }\r
788 \r
789         }\r
790 \r
791        \r
792 \r
793 \r
794         public IList<ObjectInfo> ListObjects(string account, Uri container, DateTime? since = null)\r
795         {\r
796             if (container==null)\r
797                 throw new ArgumentNullException("container");\r
798             if (container.IsAbsoluteUri)\r
799                 throw new ArgumentException("container");\r
800             Contract.EndContractBlock();\r
801 \r
802             using (ThreadContext.Stacks["Objects"].Push("List"))\r
803             {\r
804                 /*if (Log.IsDebugEnabled) Log.DebugFormat("START");\r
805 \r
806                 var targetUrl = String.IsNullOrWhiteSpace(account)\r
807                     ? _baseHttpClient.BaseAddress.ToString()\r
808                     : GetAccountUrl(account);\r
809                 var targetUri = new Uri(String.Format("{0}?format=json", targetUrl));\r
810                 var result = GetString(targetUri, "ListObjects failed",since).Result;\r
811 \r
812                 if (String.IsNullOrWhiteSpace(result))\r
813                     return new[]{new NoModificationInfo(account,container)};\r
814                 //If the result is empty, return an empty list,\r
815                 var infos = String.IsNullOrWhiteSpace(result)\r
816                                 ? new List<ObjectInfo>()\r
817                             //Otherwise deserialize the object list into a list of ObjectInfos\r
818                                 : JsonConvert.DeserializeObject<IList<ObjectInfo>>(result);\r
819 \r
820                 foreach (var info in infos)\r
821                 {\r
822                     info.Container = container;\r
823                     info.Account = account;\r
824                     info.StorageUri = StorageUrl;\r
825                 }\r
826                 if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
827                 return infos;*/\r
828 \r
829                 var containerUri = GetTargetUri(account).Combine(container);\r
830                 var targetUri = new Uri(String.Format("{0}?format=json", containerUri), UriKind.Absolute);\r
831                 var content = GetString(targetUri, "ListObjects failed", since).Result;\r
832 \r
833                 //304 will result in an empty string. Empty containers return an empty json array\r
834                 if (String.IsNullOrWhiteSpace(content))\r
835                      return new[] {new NoModificationInfo(account, container)};\r
836 \r
837                  //If the result is empty, return an empty list,\r
838                  var infos = String.IsNullOrWhiteSpace(content)\r
839                                  ? new List<ObjectInfo>()\r
840                              //Otherwise deserialize the object list into a list of ObjectInfos\r
841                                  : JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);\r
842 \r
843                  foreach (var info in infos)\r
844                  {\r
845                      info.Container = container;\r
846                      info.Account = account;\r
847                      info.StorageUri = StorageUrl;\r
848                  }\r
849                  if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
850                  return infos;\r
851 /*\r
852              using (var client = new RestClient(_baseClient))\r
853              {\r
854                  if (!String.IsNullOrWhiteSpace(account))\r
855                      client.BaseAddress = GetAccountUrl(account);\r
856 \r
857                  client.Parameters.Clear();\r
858                  client.Parameters.Add("format", "json");\r
859                  client.IfModifiedSince = since;\r
860                  var content = client.DownloadStringWithRetryRelative(container, 3);\r
861 \r
862                  client.AssertStatusOK("ListObjects failed");\r
863 \r
864                  if (client.StatusCode == HttpStatusCode.NotModified)\r
865                      return new[] {new NoModificationInfo(account, container)};\r
866                  //If the result is empty, return an empty list,\r
867                  var infos = String.IsNullOrWhiteSpace(content)\r
868                                  ? new List<ObjectInfo>()\r
869                              //Otherwise deserialize the object list into a list of ObjectInfos\r
870                                  : JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);\r
871 \r
872                  foreach (var info in infos)\r
873                  {\r
874                      info.Container = container;\r
875                      info.Account = account;\r
876                      info.StorageUri = StorageUrl;\r
877                  }\r
878                  if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
879                  return infos;\r
880              }\r
881 */\r
882             }\r
883         }\r
884 \r
885         public IList<ObjectInfo> ListObjects(string account, Uri container, Uri folder, DateTime? since = null)\r
886         {\r
887             if (container==null)\r
888                 throw new ArgumentNullException("container");\r
889             if (container.IsAbsoluteUri)\r
890                 throw new ArgumentException("container");\r
891 /*\r
892             if (String.IsNullOrWhiteSpace(folder))\r
893                 throw new ArgumentNullException("folder");\r
894 */\r
895             Contract.EndContractBlock();\r
896 \r
897             using (ThreadContext.Stacks["Objects"].Push("List"))\r
898             {\r
899                 if (Log.IsDebugEnabled) Log.DebugFormat("START");\r
900 \r
901                 var containerUri = GetTargetUri(account).Combine(container);\r
902                 var targetUri = new Uri(String.Format("{0}?format=json&path={1}", containerUri,folder), UriKind.Absolute);\r
903                 var content = GetString(targetUri, "ListObjects failed", since).Result;                \r
904 \r
905                 //304 will result in an empty string. Empty containers return an empty json array\r
906                 if (String.IsNullOrWhiteSpace(content))\r
907                     return new[] { new NoModificationInfo(account, container) };\r
908 \r
909 \r
910                 var infos = JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);\r
911                 foreach (var info in infos)\r
912                 {\r
913                     info.Account = account;\r
914                     if (info.Container == null)\r
915                         info.Container = container;\r
916                     info.StorageUri = StorageUrl;\r
917                 }\r
918                 if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
919                 return infos;\r
920 /*\r
921                 using (var client = new RestClient(_baseClient))\r
922                 {\r
923                     if (!String.IsNullOrWhiteSpace(account))\r
924                         client.BaseAddress = GetAccountUrl(account);\r
925 \r
926                     client.Parameters.Clear();\r
927                     client.Parameters.Add("format", "json");\r
928                     client.Parameters.Add("path", folder.ToString());\r
929                     client.IfModifiedSince = since;\r
930                     var content = client.DownloadStringWithRetryRelative(container, 3);\r
931                     client.AssertStatusOK("ListObjects failed");\r
932 \r
933                     if (client.StatusCode==HttpStatusCode.NotModified)\r
934                         return new[]{new NoModificationInfo(account,container,folder)};\r
935 \r
936                     var infos = JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);\r
937                     foreach (var info in infos)\r
938                     {\r
939                         info.Account = account;\r
940                         if (info.Container == null)\r
941                             info.Container = container;\r
942                         info.StorageUri = StorageUrl;\r
943                     }\r
944                     if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
945                     return infos;\r
946                 }\r
947 */\r
948             }\r
949         }\r
950 \r
951  \r
952         public bool ContainerExists(string account, Uri container)\r
953         {\r
954             if (container==null)\r
955                 throw new ArgumentNullException("container", "The container property can't be empty");\r
956             if (container.IsAbsoluteUri)\r
957                 throw new ArgumentException( "The container must be relative","container");\r
958             Contract.EndContractBlock();\r
959 \r
960             using (ThreadContext.Stacks["Containters"].Push("Exists"))\r
961             {\r
962                 if (Log.IsDebugEnabled) Log.DebugFormat("START");\r
963 \r
964                 var targetUri = GetTargetUri(account).Combine(container);\r
965 \r
966                 using (var response = _baseHttpClient.HeadAsyncWithRetries(targetUri, 3).Result)\r
967                 {\r
968 \r
969                     bool result;\r
970                     switch (response.StatusCode)\r
971                     {\r
972                         case HttpStatusCode.OK:\r
973                         case HttpStatusCode.NoContent:\r
974                             result = true;\r
975                             break;\r
976                         case HttpStatusCode.NotFound:\r
977                             result = false;\r
978                             break;\r
979                         default:\r
980                             throw CreateWebException("ContainerExists", response.StatusCode);\r
981                     }\r
982                     if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
983 \r
984                     return result;\r
985                 }\r
986 /*\r
987                 using (var client = new RestClient(_baseClient))\r
988                 {\r
989                     if (!String.IsNullOrWhiteSpace(account))\r
990                         client.BaseAddress = GetAccountUrl(account);\r
991 \r
992                     client.Parameters.Clear();\r
993                     client.Head(container, 3);\r
994                                         \r
995                     bool result;\r
996                     switch (client.StatusCode)\r
997                     {\r
998                         case HttpStatusCode.OK:\r
999                         case HttpStatusCode.NoContent:\r
1000                             result=true;\r
1001                             break;\r
1002                         case HttpStatusCode.NotFound:\r
1003                             result=false;\r
1004                             break;\r
1005                         default:\r
1006                             throw CreateWebException("ContainerExists", client.StatusCode);\r
1007                     }\r
1008                     if (Log.IsDebugEnabled) Log.DebugFormat("END");\r
1009 \r
1010                     return result;\r
1011                 }\r
1012 */\r
1013                 \r
1014             }\r
1015         }\r
1016 \r
1017         private Uri GetTargetUri(string account)\r
1018         {\r
1019             return new Uri(GetTargetUrl(account),UriKind.Absolute);\r
1020         }\r
1021 \r
1022         private string GetTargetUrl(string account)\r
1023         {\r
1024             return String.IsNullOrWhiteSpace(account)\r
1025                        ? _baseHttpClient.BaseAddress.ToString()\r
1026                        : GetAccountUrl(account);\r
1027         }\r
1028 \r
1029         public bool ObjectExists(string account, Uri container, Uri objectName)\r
1030         {\r
1031             if (container == null)\r
1032                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1033             if (container.IsAbsoluteUri)\r
1034                 throw new ArgumentException("The container must be relative","container");\r
1035             if (objectName == null)\r
1036                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");\r
1037             if (objectName.IsAbsoluteUri)\r
1038                 throw new ArgumentException("The objectName must be relative","objectName");\r
1039             Contract.EndContractBlock();\r
1040 \r
1041                 var targetUri=GetTargetUri(account).Combine(container).Combine(objectName);\r
1042 \r
1043             using (var response = _baseHttpClient.HeadAsyncWithRetries(targetUri, 3).Result)\r
1044             {\r
1045                 switch (response.StatusCode)\r
1046                 {\r
1047                     case HttpStatusCode.OK:\r
1048                     case HttpStatusCode.NoContent:\r
1049                         return true;\r
1050                     case HttpStatusCode.NotFound:\r
1051                         return false;\r
1052                     default:\r
1053                         throw CreateWebException("ObjectExists", response.StatusCode);\r
1054                 }\r
1055             }\r
1056 \r
1057 /*\r
1058             using (var client = new RestClient(_baseClient))\r
1059             {\r
1060                 if (!String.IsNullOrWhiteSpace(account))\r
1061                     client.BaseAddress = GetAccountUrl(account);\r
1062 \r
1063                 client.Parameters.Clear();\r
1064                 client.Head(container.Combine(objectName), 3);\r
1065 \r
1066                 switch (client.StatusCode)\r
1067                 {\r
1068                     case HttpStatusCode.OK:\r
1069                     case HttpStatusCode.NoContent:\r
1070                         return true;\r
1071                     case HttpStatusCode.NotFound:\r
1072                         return false;\r
1073                     default:\r
1074                         throw CreateWebException("ObjectExists", client.StatusCode);\r
1075                 }\r
1076             }\r
1077 */\r
1078 \r
1079         }\r
1080 \r
1081         public ObjectInfo GetObjectInfo(string account, Uri container, Uri objectName)\r
1082         {\r
1083             if (container == null)\r
1084                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1085             if (container.IsAbsoluteUri)\r
1086                 throw new ArgumentException("The container must be relative","container");\r
1087             if (objectName == null)\r
1088                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");\r
1089             if (objectName.IsAbsoluteUri)\r
1090                 throw new ArgumentException("The objectName must be relative","objectName");\r
1091             Contract.EndContractBlock();\r
1092 \r
1093             using (ThreadContext.Stacks["Objects"].Push("GetObjectInfo"))\r
1094             {                \r
1095 \r
1096                 using (var client = new RestClient(_baseClient))\r
1097                 {\r
1098                     if (!String.IsNullOrWhiteSpace(account))\r
1099                         client.BaseAddress = GetAccountUrl(account);\r
1100                     try\r
1101                     {\r
1102                         client.Parameters.Clear();\r
1103 \r
1104                         client.Head(container.Combine(objectName), 3);\r
1105 \r
1106                         if (client.TimedOut)\r
1107                             return ObjectInfo.Empty;\r
1108 \r
1109                         switch (client.StatusCode)\r
1110                         {\r
1111                             case HttpStatusCode.OK:\r
1112                             case HttpStatusCode.NoContent:\r
1113                                 var keys = client.ResponseHeaders.AllKeys.AsQueryable();\r
1114                                 var tags = client.GetMeta("X-Object-Meta-");\r
1115                                 var extensions = (from key in keys\r
1116                                                   where key.StartsWith("X-Object-") && !key.StartsWith("X-Object-Meta-")\r
1117                                                   select new {Name = key, Value = client.ResponseHeaders[key]})\r
1118                                     .ToDictionary(t => t.Name, t => t.Value);\r
1119 \r
1120                                 var permissions=client.GetHeaderValue("X-Object-Sharing", true);\r
1121                                 \r
1122                                 \r
1123                                 var info = new ObjectInfo\r
1124                                                {\r
1125                                                    Account = account,\r
1126                                                    Container = container,\r
1127                                                    Name = objectName,\r
1128                                                    ETag = client.GetHeaderValue("ETag"),\r
1129                                                    UUID=client.GetHeaderValue("X-Object-UUID"),\r
1130                                                    X_Object_Hash = client.GetHeaderValue("X-Object-Hash"),\r
1131                                                    Content_Type = client.GetHeaderValue("Content-Type"),\r
1132                                                    Bytes = Convert.ToInt64(client.GetHeaderValue("Content-Length",true)),\r
1133                                                    Tags = tags,\r
1134                                                    Last_Modified = client.LastModified,\r
1135                                                    Extensions = extensions,\r
1136                                                    ContentEncoding=client.GetHeaderValue("Content-Encoding",true),\r
1137                                                    ContendDisposition = client.GetHeaderValue("Content-Disposition",true),\r
1138                                                    Manifest=client.GetHeaderValue("X-Object-Manifest",true),\r
1139                                                    PublicUrl=client.GetHeaderValue("X-Object-Public",true),  \r
1140                                                    StorageUri=StorageUrl,\r
1141                                                };\r
1142                                 info.SetPermissions(permissions);\r
1143                                 return info;\r
1144                             case HttpStatusCode.NotFound:\r
1145                                 return ObjectInfo.Empty;\r
1146                             default:\r
1147                                 throw new WebException(\r
1148                                     String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}",\r
1149                                                   objectName, client.StatusCode));\r
1150                         }\r
1151 \r
1152                     }\r
1153                     catch (RetryException)\r
1154                     {\r
1155                         Log.WarnFormat("[RETRY FAIL] GetObjectInfo for {0} failed.",objectName);\r
1156                         return ObjectInfo.Empty;\r
1157                     }\r
1158                     catch (WebException e)\r
1159                     {\r
1160                         Log.Error(\r
1161                             String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}",\r
1162                                           objectName, client.StatusCode), e);\r
1163                         throw;\r
1164                     }\r
1165                 }                \r
1166             }\r
1167 \r
1168         }\r
1169 \r
1170         \r
1171 \r
1172         public void CreateFolder(string account, Uri container, Uri folder)\r
1173         {\r
1174             if (container == null)\r
1175                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1176             if (container.IsAbsoluteUri)\r
1177                 throw new ArgumentException("The container must be relative","container");\r
1178             if (folder == null)\r
1179                 throw new ArgumentNullException("folder", "The objectName property can't be empty");\r
1180             if (folder.IsAbsoluteUri)\r
1181                 throw new ArgumentException("The objectName must be relative","folder");\r
1182             Contract.EndContractBlock();\r
1183 \r
1184             var folderUri=container.Combine(folder);            \r
1185             var targetUri = GetTargetUri(account).Combine(folderUri);\r
1186             var message = new HttpRequestMessage(HttpMethod.Put, targetUri);\r
1187             \r
1188             message.Headers.Add("Content-Type", ObjectInfo.CONTENT_TYPE_DIRECTORY);\r
1189             message.Headers.Add("Content-Length", "0");\r
1190             using (var response = _baseHttpClient.SendAsyncWithRetries(message, 3).Result)\r
1191             {\r
1192                 if (response.StatusCode != HttpStatusCode.Created && response.StatusCode != HttpStatusCode.Accepted)\r
1193                     throw CreateWebException("CreateFolder", response.StatusCode);\r
1194             }\r
1195 /*\r
1196             using (var client = new RestClient(_baseClient))\r
1197             {\r
1198                 if (!String.IsNullOrWhiteSpace(account))\r
1199                     client.BaseAddress = GetAccountUrl(account);\r
1200 \r
1201                 client.Parameters.Clear();\r
1202                 client.Headers.Add("Content-Type", ObjectInfo.CONTENT_TYPE_DIRECTORY);\r
1203                 client.Headers.Add("Content-Length", "0");\r
1204                 client.PutWithRetry(folderUri, 3);\r
1205 \r
1206                 if (client.StatusCode != HttpStatusCode.Created && client.StatusCode != HttpStatusCode.Accepted)\r
1207                     throw CreateWebException("CreateFolder", client.StatusCode);\r
1208             }\r
1209 */\r
1210         }\r
1211 \r
1212      \r
1213 \r
1214         public ContainerInfo GetContainerInfo(string account, Uri container)\r
1215         {\r
1216             if (container == null)\r
1217                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1218             if (container.IsAbsoluteUri)\r
1219                 throw new ArgumentException("The container must be relative","container");\r
1220             Contract.EndContractBlock();\r
1221 \r
1222             using (var client = new RestClient(_baseClient))\r
1223             {\r
1224                 if (!String.IsNullOrWhiteSpace(account))\r
1225                     client.BaseAddress = GetAccountUrl(account);                \r
1226 \r
1227                 client.Head(container);\r
1228                 switch (client.StatusCode)\r
1229                 {\r
1230                     case HttpStatusCode.OK:\r
1231                     case HttpStatusCode.NoContent:\r
1232                         var tags = client.GetMeta("X-Container-Meta-");\r
1233                         var policies = client.GetMeta("X-Container-Policy-");\r
1234 \r
1235                         var containerInfo = new ContainerInfo\r
1236                                                 {\r
1237                                                     Account=account,\r
1238                                                     Name = container,\r
1239                                                     StorageUrl=StorageUrl.ToString(),\r
1240                                                     Count =\r
1241                                                         long.Parse(client.GetHeaderValue("X-Container-Object-Count")),\r
1242                                                     Bytes = long.Parse(client.GetHeaderValue("X-Container-Bytes-Used")),\r
1243                                                     BlockHash = client.GetHeaderValue("X-Container-Block-Hash"),\r
1244                                                     BlockSize=int.Parse(client.GetHeaderValue("X-Container-Block-Size")),\r
1245                                                     Last_Modified=client.LastModified,\r
1246                                                     Tags=tags,\r
1247                                                     Policies=policies\r
1248                                                 };\r
1249                         \r
1250 \r
1251                         return containerInfo;\r
1252                     case HttpStatusCode.NotFound:\r
1253                         return ContainerInfo.Empty;\r
1254                     default:\r
1255                         throw CreateWebException("GetContainerInfo", client.StatusCode);\r
1256                 }\r
1257             }\r
1258         }\r
1259 \r
1260         public void CreateContainer(string account, Uri container)\r
1261         {\r
1262             if (container == null)\r
1263                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1264             if (container.IsAbsoluteUri)\r
1265                 throw new ArgumentException("The container must be relative","container");\r
1266             Contract.EndContractBlock();\r
1267 \r
1268             var targetUri=GetTargetUri(account).Combine(container);\r
1269             var message = new HttpRequestMessage(HttpMethod.Put, targetUri);\r
1270             message.Headers.Add("Content-Length", "0");\r
1271             using (var response = _baseHttpClient.SendAsyncWithRetries(message, 3).Result)\r
1272             {            \r
1273                 var expectedCodes = new[] {HttpStatusCode.Created, HttpStatusCode.Accepted, HttpStatusCode.OK};\r
1274                 if (!expectedCodes.Contains(response.StatusCode))\r
1275                     throw CreateWebException("CreateContainer", response.StatusCode);\r
1276             }\r
1277 /*\r
1278             using (var client = new RestClient(_baseClient))\r
1279             {\r
1280                 if (!String.IsNullOrWhiteSpace(account))\r
1281                     client.BaseAddress = GetAccountUrl(account);\r
1282 \r
1283                 client.PutWithRetry(container, 3);\r
1284                 var expectedCodes = new[] {HttpStatusCode.Created, HttpStatusCode.Accepted, HttpStatusCode.OK};\r
1285                 if (!expectedCodes.Contains(client.StatusCode))\r
1286                     throw CreateWebException("CreateContainer", client.StatusCode);\r
1287             }\r
1288 */\r
1289         }\r
1290 \r
1291         public void DeleteContainer(string account, Uri container)\r
1292         {\r
1293             if (container == null)\r
1294                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1295             if (container.IsAbsoluteUri)\r
1296                 throw new ArgumentException("The container must be relative","container");\r
1297             Contract.EndContractBlock();\r
1298 \r
1299             var targetUri = GetTargetUri(account).Combine(container);\r
1300             var message = new HttpRequestMessage(HttpMethod.Delete, targetUri);\r
1301             using (var response = _baseHttpClient.SendAsyncWithRetries(message, 3).Result)\r
1302             {\r
1303                 var expectedCodes = new[] { HttpStatusCode.NotFound, HttpStatusCode.NoContent };\r
1304                 if (!expectedCodes.Contains(response.StatusCode))\r
1305                     throw CreateWebException("DeleteContainer", response.StatusCode);\r
1306             }\r
1307 /*\r
1308             using (var client = new RestClient(_baseClient))\r
1309             {\r
1310                 if (!String.IsNullOrWhiteSpace(account))\r
1311                     client.BaseAddress = GetAccountUrl(account);\r
1312 \r
1313                 client.DeleteWithRetry(container, 3);\r
1314                 var expectedCodes = new[] {HttpStatusCode.NotFound, HttpStatusCode.NoContent};\r
1315                 if (!expectedCodes.Contains(client.StatusCode))\r
1316                     throw CreateWebException("DeleteContainer", client.StatusCode);\r
1317             }\r
1318 */\r
1319 \r
1320         }\r
1321 \r
1322         /// <summary>\r
1323         /// \r
1324         /// </summary>\r
1325         /// <param name="account"></param>\r
1326         /// <param name="container"></param>\r
1327         /// <param name="objectName"></param>\r
1328         /// <param name="fileName"></param>\r
1329         /// <param name="cancellationToken"> </param>\r
1330         /// <returns></returns>\r
1331         /// <remarks>This method should have no timeout or a very long one</remarks>\r
1332         //Asynchronously download the object specified by *objectName* in a specific *container* to \r
1333         // a local file\r
1334         public async Task GetObject(string account, Uri container, Uri objectName, string fileName,CancellationToken cancellationToken)\r
1335         {\r
1336             if (container == null)\r
1337                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1338             if (container.IsAbsoluteUri)\r
1339                 throw new ArgumentException("The container must be relative","container");\r
1340             if (objectName == null)\r
1341                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");\r
1342             if (objectName.IsAbsoluteUri)\r
1343                 throw new ArgumentException("The objectName must be relative","objectName");\r
1344             Contract.EndContractBlock();\r
1345                         \r
1346 \r
1347             try\r
1348             {\r
1349                 //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient\r
1350                 //object to avoid concurrency errors.\r
1351                 //\r
1352                 //Download operations take a long time therefore they have no timeout.\r
1353                 using(var client = new RestClient(_baseClient) { Timeout = 0 })\r
1354                 {\r
1355                     if (!String.IsNullOrWhiteSpace(account))\r
1356                         client.BaseAddress = GetAccountUrl(account);\r
1357 \r
1358                     //The container and objectName are relative names. They are joined with the client's\r
1359                     //BaseAddress to create the object's absolute address\r
1360                     var builder = client.GetAddressBuilder(container, objectName);\r
1361                     var uri = builder.Uri;\r
1362 \r
1363                     //Download progress is reported to the Trace log\r
1364                     Log.InfoFormat("[GET] START {0}", objectName);\r
1365                     /*client.DownloadProgressChanged += (sender, args) =>\r
1366                                                       Log.InfoFormat("[GET PROGRESS] {0} {1}% {2} of {3}",\r
1367                                                                      fileName, args.ProgressPercentage,\r
1368                                                                      args.BytesReceived,\r
1369                                                                      args.TotalBytesToReceive);*/\r
1370                     var progress = new Progress<DownloadProgressChangedEventArgs>(args =>\r
1371                                 {\r
1372                                     Log.InfoFormat("[GET PROGRESS] {0} {1}% {2} of {3}",\r
1373                                                    fileName, args.ProgressPercentage,\r
1374                                                    args.BytesReceived,\r
1375                                                    args.TotalBytesToReceive);\r
1376                                     if (DownloadProgressChanged!=null)\r
1377                                         DownloadProgressChanged(this, args);\r
1378                                 });\r
1379                     \r
1380                     //Start downloading the object asynchronously                    \r
1381                     await client.DownloadFileTaskAsync(uri, fileName, cancellationToken,progress).ConfigureAwait(false);\r
1382 \r
1383                     //Once the download completes\r
1384                     //Delete the local client object\r
1385                 }\r
1386                 //And report failure or completion\r
1387             }\r
1388             catch (Exception exc)\r
1389             {\r
1390                 Log.ErrorFormat("[GET] FAIL {0} with {1}", objectName, exc);\r
1391                 throw;\r
1392             }\r
1393 \r
1394             Log.InfoFormat("[GET] END {0}", objectName);                                             \r
1395 \r
1396 \r
1397         }\r
1398 \r
1399         public Task<IList<string>> PutHashMap(string account, Uri container, Uri objectName, TreeHash hash)\r
1400         {\r
1401             if (container == null)\r
1402                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1403             if (container.IsAbsoluteUri)\r
1404                 throw new ArgumentException("The container must be relative","container");\r
1405             if (objectName == null)\r
1406                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");\r
1407             if (objectName.IsAbsoluteUri)\r
1408                 throw new ArgumentException("The objectName must be relative","objectName");\r
1409             if (hash == null)\r
1410                 throw new ArgumentNullException("hash");\r
1411             if (String.IsNullOrWhiteSpace(Token))\r
1412                 throw new InvalidOperationException("Invalid Token");\r
1413             if (StorageUrl == null)\r
1414                 throw new InvalidOperationException("Invalid Storage Url");\r
1415             Contract.EndContractBlock();\r
1416 \r
1417             \r
1418 \r
1419             //Don't use a timeout because putting the hashmap may be a long process\r
1420             var client = new RestClient(_baseClient) { Timeout = 0 };           \r
1421             if (!String.IsNullOrWhiteSpace(account))\r
1422                 client.BaseAddress = GetAccountUrl(account);\r
1423 \r
1424             //The container and objectName are relative names. They are joined with the client's\r
1425             //BaseAddress to create the object's absolute address\r
1426             var builder = client.GetAddressBuilder(container, objectName);\r
1427             builder.Query = "format=json&hashmap";\r
1428             var uri = builder.Uri;\r
1429 \r
1430 \r
1431             //Send the tree hash as Json to the server            \r
1432             client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";\r
1433             var jsonHash = hash.ToJson();\r
1434                         \r
1435             client.Headers.Add("ETag",hash.TopHash.ToHashString());\r
1436             var uploadTask=client.UploadStringTask(uri, "PUT", jsonHash);\r
1437             if (Log.IsDebugEnabled)\r
1438                 Log.DebugFormat("Hashes:\r\n{0}", jsonHash);\r
1439             return uploadTask.ContinueWith(t =>\r
1440             {\r
1441 \r
1442                 var empty = (IList<string>)new List<string>();\r
1443                 \r
1444 \r
1445                 //The server will respond either with 201-created if all blocks were already on the server\r
1446                 if (client.StatusCode == HttpStatusCode.Created)                    \r
1447                 {\r
1448                     //in which case we return an empty hash list\r
1449                     return empty;\r
1450                 }\r
1451                 //or with a 409-conflict and return the list of missing parts\r
1452                 //A 409 will cause an exception so we need to check t.IsFaulted to avoid propagating the exception                \r
1453                 if (t.IsFaulted)\r
1454                 {\r
1455                     var ex = t.Exception.InnerException;\r
1456                     var we = ex as WebException;\r
1457                     var response = we.Response as HttpWebResponse;\r
1458                     if (response!=null && response.StatusCode==HttpStatusCode.Conflict)\r
1459                     {\r
1460                         //In case of 409 the missing parts will be in the response content                        \r
1461                         using (var stream = response.GetResponseStream())\r
1462                         using(var reader=stream.GetLoggedReader(Log))\r
1463                         {\r
1464                             //We used to have to cleanup the content before returning it because it contains\r
1465                             //error content after the list of hashes\r
1466                             //\r
1467                             //As of 30/1/2012, the result is a proper Json array so we don't need to read the content\r
1468                             //line by line\r
1469                             \r
1470                             var serializer = new JsonSerializer();                            \r
1471                             serializer.Error += (sender, args) => Log.ErrorFormat("Deserialization error at [{0}] [{1}]", args.ErrorContext.Error, args.ErrorContext.Member);\r
1472                             var hashes = (List<string>)serializer.Deserialize(reader, typeof(List<string>));\r
1473                             return hashes;\r
1474                         }                        \r
1475                     }                    \r
1476                     //Any other status code is unexpected and the exception should be rethrown\r
1477                     Log.LogError(response);\r
1478                     throw ex;\r
1479                     \r
1480                 }\r
1481 \r
1482                 //Any other status code is unexpected but there was no exception. We can probably continue processing\r
1483                 Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",client.StatusCode,client.StatusDescription);                    \r
1484                 \r
1485                 return empty;\r
1486             });\r
1487 \r
1488         }\r
1489 \r
1490 \r
1491         public async Task<byte[]> GetBlock(string account, Uri container, Uri relativeUrl, long start, long? end, CancellationToken cancellationToken)\r
1492         {\r
1493             if (String.IsNullOrWhiteSpace(Token))\r
1494                 throw new InvalidOperationException("Invalid Token");\r
1495             if (StorageUrl == null)\r
1496                 throw new InvalidOperationException("Invalid Storage Url");\r
1497             if (container == null)\r
1498                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1499             if (container.IsAbsoluteUri)\r
1500                 throw new ArgumentException("The container must be relative","container");\r
1501             if (relativeUrl == null)\r
1502                 throw new ArgumentNullException("relativeUrl");\r
1503             if (end.HasValue && end < 0)\r
1504                 throw new ArgumentOutOfRangeException("end");\r
1505             if (start < 0)\r
1506                 throw new ArgumentOutOfRangeException("start");\r
1507             Contract.EndContractBlock();\r
1508 \r
1509             //Don't use a timeout because putting the hashmap may be a long process\r
1510             using (var client = new RestClient(_baseClient) {Timeout = 0, RangeFrom = start, RangeTo = end})\r
1511             {\r
1512                 if (!String.IsNullOrWhiteSpace(account))\r
1513                     client.BaseAddress = GetAccountUrl(account);\r
1514 \r
1515                 var builder = client.GetAddressBuilder(container, relativeUrl);\r
1516                 var uri = builder.Uri;\r
1517 \r
1518 /*                client.DownloadProgressChanged += (sender, args) =>\r
1519                                                       {\r
1520                                                           Log.DebugFormat("[GET PROGRESS] {0} {1}% {2} of {3}",\r
1521                                                                           uri.Segments.Last(), args.ProgressPercentage,\r
1522                                                                           args.BytesReceived,\r
1523                                                                           args.TotalBytesToReceive);\r
1524                                                           DownloadProgressChanged(sender, args);\r
1525                                                       };*/\r
1526                 var progress = new Progress<DownloadProgressChangedEventArgs>(args =>\r
1527                 {\r
1528                     Log.DebugFormat("[GET PROGRESS] {0} {1}% {2} of {3}",\r
1529                                     uri.Segments.Last(), args.ProgressPercentage,\r
1530                                     args.BytesReceived,\r
1531                                     args.TotalBytesToReceive);\r
1532                     if (DownloadProgressChanged!=null)\r
1533                         DownloadProgressChanged(this, args);\r
1534                 });\r
1535 \r
1536 \r
1537                 var result = await client.DownloadDataTaskAsync(uri, cancellationToken,progress).ConfigureAwait(false);\r
1538                 return result;\r
1539             }\r
1540         }\r
1541 \r
1542         public event UploadProgressChangedEventHandler UploadProgressChanged;\r
1543         public event DownloadProgressChangedEventHandler DownloadProgressChanged;\r
1544 \r
1545         public async Task PostBlock(string account, Uri container, byte[] block, int offset, int count,CancellationToken token)\r
1546         {\r
1547             if (container == null)\r
1548                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1549             if (container.IsAbsoluteUri)\r
1550                 throw new ArgumentException("The container must be relative","container");\r
1551             if (block == null)\r
1552                 throw new ArgumentNullException("block");\r
1553             if (offset < 0 || offset >= block.Length)\r
1554                 throw new ArgumentOutOfRangeException("offset");\r
1555             if (count < 0 || count > block.Length)\r
1556                 throw new ArgumentOutOfRangeException("count");\r
1557             if (String.IsNullOrWhiteSpace(Token))\r
1558                 throw new InvalidOperationException("Invalid Token");\r
1559             if (StorageUrl == null)\r
1560                 throw new InvalidOperationException("Invalid Storage Url");                        \r
1561             Contract.EndContractBlock();\r
1562 \r
1563 \r
1564             try\r
1565             {\r
1566 \r
1567                 //Don't use a timeout because putting the hashmap may be a long process\r
1568                 using (var client = new RestClient(_baseClient) { Timeout = 0 })\r
1569                 {\r
1570                     if (!String.IsNullOrWhiteSpace(account))\r
1571                         client.BaseAddress = GetAccountUrl(account);\r
1572 \r
1573                     var builder = client.GetAddressBuilder(container, _emptyUri);\r
1574                     //We are doing an update\r
1575                     builder.Query = "update";\r
1576                     var uri = builder.Uri;\r
1577 \r
1578                     client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";\r
1579 \r
1580                     Log.InfoFormat("[BLOCK POST] START");\r
1581 \r
1582 /*\r
1583                     client.UploadProgressChanged += (sender, args) =>\r
1584                                                         {\r
1585                                                             Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}",\r
1586                                                                            args.ProgressPercentage, args.BytesSent,\r
1587                                                                            args.TotalBytesToSend);\r
1588                                                             UploadProgressChanged(sender, args);\r
1589                                                         };\r
1590 */\r
1591                     client.UploadFileCompleted += (sender, args) =>\r
1592                                                   Log.InfoFormat("[BLOCK POST PROGRESS] Completed ");\r
1593 \r
1594                     var progress=new Progress<UploadProgressChangedEventArgs>(args=>\r
1595                     {\r
1596                         Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}",\r
1597                                         args.ProgressPercentage, args.BytesSent,\r
1598                                         args.TotalBytesToSend);\r
1599                         if (UploadProgressChanged!=null)\r
1600                             UploadProgressChanged(this, args);                                                                                      \r
1601                     });\r
1602                     var buffer = new byte[count];\r
1603                     Buffer.BlockCopy(block, offset, buffer, 0, count);\r
1604                     //Send the block\r
1605                     await client.UploadDataTaskAsync(uri, "POST", buffer,token,progress).ConfigureAwait(false);\r
1606                     Log.InfoFormat("[BLOCK POST] END");\r
1607                 }\r
1608             }\r
1609             catch (TaskCanceledException )\r
1610             {\r
1611                 Log.Info("Aborting block");\r
1612                 throw;\r
1613             }\r
1614             catch (Exception exc)\r
1615             {\r
1616                 Log.ErrorFormat("[BLOCK POST] FAIL with \r{0}", exc);\r
1617                 throw;\r
1618             }\r
1619         }\r
1620 \r
1621 \r
1622         public async Task<TreeHash> GetHashMap(string account, Uri container, Uri objectName)\r
1623         {\r
1624             if (container == null)\r
1625                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1626             if (container.IsAbsoluteUri)\r
1627                 throw new ArgumentException("The container must be relative","container");\r
1628             if (objectName == null)\r
1629                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");\r
1630             if (objectName.IsAbsoluteUri)\r
1631                 throw new ArgumentException("The objectName must be relative","objectName");\r
1632             if (String.IsNullOrWhiteSpace(Token))\r
1633                 throw new InvalidOperationException("Invalid Token");\r
1634             if (StorageUrl == null)\r
1635                 throw new InvalidOperationException("Invalid Storage Url");\r
1636             Contract.EndContractBlock();\r
1637 \r
1638             try\r
1639             {\r
1640                 //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient\r
1641                 //object to avoid concurrency errors.\r
1642                 //\r
1643                 //Download operations take a long time therefore they have no timeout.\r
1644                 //TODO: Do they really? this is a hashmap operation, not a download\r
1645                 \r
1646                 //Start downloading the object asynchronously\r
1647                 using (var client = new RestClient(_baseClient) { Timeout = 0 })\r
1648                 {\r
1649                     if (!String.IsNullOrWhiteSpace(account))\r
1650                         client.BaseAddress = GetAccountUrl(account);\r
1651 \r
1652                     //The container and objectName are relative names. They are joined with the client's\r
1653                     //BaseAddress to create the object's absolute address\r
1654                     \r
1655                     var builder = client.GetAddressBuilder(container, objectName);\r
1656                     builder.Query = "format=json&hashmap";\r
1657                     var uri = builder.Uri;\r
1658 \r
1659 \r
1660                     var json = await client.DownloadStringTaskAsync(uri).ConfigureAwait(false);\r
1661                     var treeHash = TreeHash.Parse(json);\r
1662                     Log.InfoFormat("[GET HASH] END {0}", objectName);\r
1663                     return treeHash;\r
1664                 }\r
1665             }\r
1666             catch (Exception exc)\r
1667             {\r
1668                 Log.ErrorFormat("[GET HASH] END {0} with {1}", objectName, exc);\r
1669                 throw;\r
1670             }\r
1671 \r
1672         }\r
1673 \r
1674 \r
1675         /// <summary>\r
1676         /// \r
1677         /// </summary>\r
1678         /// <param name="account"></param>\r
1679         /// <param name="container"></param>\r
1680         /// <param name="objectName"></param>\r
1681         /// <param name="fileName"></param>\r
1682         /// <param name="hash">Optional hash value for the file. If no hash is provided, the method calculates a new hash</param>\r
1683         /// <param name="contentType"> </param>\r
1684         /// <remarks>>This method should have no timeout or a very long one</remarks>\r
1685         public async Task PutObject(string account, Uri container, Uri objectName, string fileName, string hash = Signature.MERKLE_EMPTY, string contentType = "application/octet-stream")\r
1686         {\r
1687             if (container == null)\r
1688                 throw new ArgumentNullException("container", "The container property can't be empty");\r
1689             if (container.IsAbsoluteUri)\r
1690                 throw new ArgumentException("The container must be relative","container");\r
1691             if (objectName == null)\r
1692                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");\r
1693             if (objectName.IsAbsoluteUri)\r
1694                 throw new ArgumentException("The objectName must be relative","objectName");\r
1695             if (String.IsNullOrWhiteSpace(fileName))\r
1696                 throw new ArgumentNullException("fileName", "The fileName property can't be empty");\r
1697 /*\r
1698             if (!File.Exists(fileName) && !Directory.Exists(fileName))\r
1699                 throw new FileNotFoundException("The file or directory does not exist",fileName);\r
1700 */\r
1701             \r
1702             try\r
1703             {\r
1704 \r
1705                 using (var client = new RestClient(_baseClient) { Timeout = 0 })\r
1706                 {\r
1707                     if (!String.IsNullOrWhiteSpace(account))\r
1708                         client.BaseAddress = GetAccountUrl(account);\r
1709 \r
1710                     var builder = client.GetAddressBuilder(container, objectName);\r
1711                     var uri = builder.Uri;\r
1712 \r
1713                     string etag = hash ;\r
1714 \r
1715                     client.Headers.Add("Content-Type", contentType);\r
1716                     if (contentType!=ObjectInfo.CONTENT_TYPE_DIRECTORY)\r
1717                         client.Headers.Add("ETag", etag);\r
1718 \r
1719 \r
1720                     Log.InfoFormat("[PUT] START {0}", objectName);\r
1721                     client.UploadProgressChanged += (sender, args) =>\r
1722                                                         {\r
1723                                                             using (ThreadContext.Stacks["PUT"].Push("Progress"))\r
1724                                                             {\r
1725                                                                 Log.InfoFormat("{0} {1}% {2} of {3}", fileName,\r
1726                                                                                args.ProgressPercentage,\r
1727                                                                                args.BytesSent, args.TotalBytesToSend);\r
1728                                                             }\r
1729                                                         };\r
1730 \r
1731                     client.UploadFileCompleted += (sender, args) =>\r
1732                                                       {\r
1733                                                           using (ThreadContext.Stacks["PUT"].Push("Progress"))\r
1734                                                           {\r
1735                                                               Log.InfoFormat("Completed {0}", fileName);\r
1736                                                           }\r
1737                                                       }; \r
1738                     \r
1739                     if (contentType==ObjectInfo.CONTENT_TYPE_DIRECTORY)\r
1740                         await client.UploadDataTaskAsync(uri, "PUT", new byte[0]).ConfigureAwait(false);\r
1741                     else\r
1742                         await client.UploadFileTaskAsync(uri, "PUT", fileName).ConfigureAwait(false);\r
1743                 }\r
1744 \r
1745                 Log.InfoFormat("[PUT] END {0}", objectName);\r
1746             }\r
1747             catch (Exception exc)\r
1748             {\r
1749                 Log.ErrorFormat("[PUT] END {0} with {1}", objectName, exc);\r
1750                 throw;\r
1751             }                \r
1752 \r
1753         }\r
1754        \r
1755         \r
1756 /*\r
1757         private static string CalculateHash(string fileName)\r
1758         {\r
1759             Contract.Requires(!String.IsNullOrWhiteSpace(fileName));\r
1760             Contract.EndContractBlock();\r
1761 \r
1762             string hash;\r
1763             using (var hasher = MD5.Create())\r
1764             using(var stream=File.OpenRead(fileName))\r
1765             {\r
1766                 var hashBuilder=new StringBuilder();\r
1767                 foreach (byte b in hasher.ComputeHash(stream))\r
1768                     hashBuilder.Append(b.ToString("x2").ToLower());\r
1769                 hash = hashBuilder.ToString();                \r
1770             }\r
1771             return hash;\r
1772         }\r
1773 */\r
1774 \r
1775         \r
1776         public void MoveObject(string account, Uri sourceContainer, Uri oldObjectName, Uri targetContainer, Uri newObjectName)\r
1777         {\r
1778             if (sourceContainer == null)\r
1779                 throw new ArgumentNullException("sourceContainer", "The sourceContainer property can't be empty");\r
1780             if (sourceContainer.IsAbsoluteUri)\r
1781                 throw new ArgumentException("The sourceContainer must be relative","sourceContainer");\r
1782             if (oldObjectName == null)\r
1783                 throw new ArgumentNullException("oldObjectName", "The oldObjectName property can't be empty");\r
1784             if (oldObjectName.IsAbsoluteUri)\r
1785                 throw new ArgumentException("The oldObjectName must be relative","oldObjectName");\r
1786             if (targetContainer == null)\r
1787                 throw new ArgumentNullException("targetContainer", "The targetContainer property can't be empty");\r
1788             if (targetContainer.IsAbsoluteUri)\r
1789                 throw new ArgumentException("The targetContainer must be relative","targetContainer");\r
1790             if (newObjectName == null)\r
1791                 throw new ArgumentNullException("newObjectName", "The newObjectName property can't be empty");\r
1792             if (newObjectName.IsAbsoluteUri)\r
1793                 throw new ArgumentException("The newObjectName must be relative","newObjectName");\r
1794             Contract.EndContractBlock();\r
1795 \r
1796             var baseUri = GetTargetUri(account);\r
1797             var targetUri = baseUri.Combine(targetContainer).Combine(newObjectName);\r
1798             var sourceUri = new Uri(String.Format("/{0}/{1}", sourceContainer, oldObjectName),UriKind.Relative);\r
1799 \r
1800             var message = new HttpRequestMessage(HttpMethod.Put, targetUri);\r
1801             message.Headers.Add("X-Move-From", sourceUri.ToString());\r
1802             using (var response = _baseHttpClient.SendAsyncWithRetries(message, 3).Result)\r
1803             {\r
1804                 var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created};\r
1805                 if (!expectedCodes.Contains(response.StatusCode))\r
1806                     throw CreateWebException("MoveObject", response.StatusCode);\r
1807             }\r
1808 /*\r
1809             using (var client = new RestClient(_baseClient))\r
1810             {\r
1811                 if (!String.IsNullOrWhiteSpace(account))\r
1812                     client.BaseAddress = GetAccountUrl(account);\r
1813 \r
1814                 client.Headers.Add("X-Move-From", sourceUri.ToString());\r
1815                 client.PutWithRetry(targetUri, 3);\r
1816 \r
1817                 var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created};\r
1818                 if (!expectedCodes.Contains(client.StatusCode))\r
1819                     throw CreateWebException("MoveObject", client.StatusCode);\r
1820             }\r
1821 */\r
1822         }\r
1823 \r
1824         public void DeleteObject(string account, Uri sourceContainer, Uri objectName, bool isDirectory)\r
1825         {\r
1826             if (sourceContainer == null)\r
1827                 throw new ArgumentNullException("sourceContainer", "The sourceContainer property can't be empty");\r
1828             if (sourceContainer.IsAbsoluteUri)\r
1829                 throw new ArgumentException("The sourceContainer must be relative","sourceContainer");\r
1830             if (objectName == null)\r
1831                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");\r
1832             if (objectName.IsAbsoluteUri)\r
1833                 throw new ArgumentException("The objectName must be relative","objectName");\r
1834             Contract.EndContractBlock();\r
1835 \r
1836             var targetUrl = FolderConstants.TrashContainer + "/" + objectName;\r
1837 /*\r
1838             if (isDirectory)\r
1839                 targetUrl = targetUrl + "?delimiter=/";\r
1840 */\r
1841 \r
1842             var sourceUrl = String.Format("/{0}/{1}", sourceContainer, objectName);\r
1843 \r
1844             using (var client = new RestClient(_baseClient))\r
1845             {\r
1846                 if (!String.IsNullOrWhiteSpace(account))\r
1847                     client.BaseAddress = GetAccountUrl(account);\r
1848 \r
1849                 client.Headers.Add("X-Move-From", sourceUrl);\r
1850                 client.AllowedStatusCodes.Add(HttpStatusCode.NotFound);\r
1851                 Log.InfoFormat("[TRASH] [{0}] to [{1}]",sourceUrl,targetUrl);\r
1852                 client.PutWithRetry(new Uri(targetUrl,UriKind.Relative), 3);\r
1853 \r
1854                 var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created,HttpStatusCode.NotFound};\r
1855                 if (!expectedCodes.Contains(client.StatusCode))\r
1856                     throw CreateWebException("DeleteObject", client.StatusCode);\r
1857             }\r
1858         }\r
1859 \r
1860       \r
1861         private static WebException CreateWebException(string operation, HttpStatusCode statusCode)\r
1862         {\r
1863             return new WebException(String.Format("{0} failed with unexpected status code {1}", operation, statusCode));\r
1864         }\r
1865 \r
1866 \r
1867 /*\r
1868         public IEnumerable<ObjectInfo> ListDirectories(ContainerInfo container)\r
1869         {\r
1870             var directories=this.ListObjects(container.Account, container.Name, "/");\r
1871         }\r
1872 */\r
1873 \r
1874         public bool CanUpload(string account, ObjectInfo cloudFile)\r
1875         {\r
1876             Contract.Requires(!String.IsNullOrWhiteSpace(account));\r
1877             Contract.Requires(cloudFile!=null);\r
1878 \r
1879             using (var client = new RestClient(_baseClient))\r
1880             {\r
1881                 if (!String.IsNullOrWhiteSpace(account))\r
1882                     client.BaseAddress = GetAccountUrl(account);\r
1883 \r
1884 \r
1885                 var parts = cloudFile.Name.ToString().Split('/');\r
1886                 var folder = String.Join("/", parts,0,parts.Length-1);\r
1887 \r
1888                 var fileName = String.Format("{0}/{1}.pithos.ignore", folder, Guid.NewGuid());\r
1889                 var fileUri=fileName.ToEscapedUri();                                            \r
1890 \r
1891                 client.Parameters.Clear();\r
1892                 try\r
1893                 {\r
1894                     var relativeUri = cloudFile.Container.Combine(fileUri);\r
1895                     client.PutWithRetry(relativeUri, 3, @"application/octet-stream");\r
1896 \r
1897                     var expectedCodes = new[] { HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created};\r
1898                     var result=(expectedCodes.Contains(client.StatusCode));\r
1899                     DeleteObject(account, cloudFile.Container, fileUri, cloudFile.IsDirectory);\r
1900                     return result;\r
1901                 }\r
1902                 catch\r
1903                 {\r
1904                     return false;\r
1905                 }\r
1906             }\r
1907         }\r
1908     }\r
1909 \r
1910     public class ShareAccountInfo\r
1911     {\r
1912         public DateTime? last_modified { get; set; }\r
1913         public string name { get; set; }\r
1914     }\r
1915 }\r