Modifications to enable Sync Pausing for all operations
[pithos-ms-client] / trunk / Pithos.Network / CloudFilesClient.cs
1 #region
2 /* -----------------------------------------------------------------------
3  * <copyright file="CloudFilesClient.cs" company="GRNet">
4  * 
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or
8  * without modification, are permitted provided that the following
9  * conditions are met:
10  *
11  *   1. Redistributions of source code must retain the above
12  *      copyright notice, this list of conditions and the following
13  *      disclaimer.
14  *
15  *   2. Redistributions in binary form must reproduce the above
16  *      copyright notice, this list of conditions and the following
17  *      disclaimer in the documentation and/or other materials
18  *      provided with the distribution.
19  *
20  *
21  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * The views and conclusions contained in the software and
35  * documentation are those of the authors and should not be
36  * interpreted as representing official policies, either expressed
37  * or implied, of GRNET S.A.
38  * </copyright>
39  * -----------------------------------------------------------------------
40  */
41 #endregion
42
43 // **CloudFilesClient** provides a simple client interface to CloudFiles and Pithos
44 //
45 // The class provides methods to upload/download files, delete files, manage containers
46
47
48 using System;
49 using System.Collections.Generic;
50 using System.Collections.Specialized;
51 using System.ComponentModel.Composition;
52 using System.Diagnostics;
53 using System.Diagnostics.Contracts;
54 using System.IO;
55 using System.Linq;
56 using System.Net;
57 using System.Reflection;
58 using System.Security.Cryptography;
59 using System.Text;
60 using System.Threading;
61 using System.Threading.Tasks;
62 using Newtonsoft.Json;
63 using Pithos.Interfaces;
64 using log4net;
65
66 namespace Pithos.Network
67 {
68     [Export(typeof(ICloudClient))]
69     public class CloudFilesClient:ICloudClient
70     {
71         private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
72
73         //CloudFilesClient uses *_baseClient* internally to communicate with the server
74         //RestClient provides a REST-friendly interface over the standard WebClient.
75         private RestClient _baseClient;
76         
77
78         //During authentication the client provides a UserName 
79         public string UserName { get; set; }
80         
81         //and and ApiKey to the server
82         public string ApiKey { get; set; }
83         
84         //And receives an authentication Token. This token must be provided in ALL other operations,
85         //in the X-Auth-Token header
86         private string _token;
87         public string Token
88         {
89             get { return _token; }
90             set
91             {
92                 _token = value;
93                 _baseClient.Headers["X-Auth-Token"] = value;
94             }
95         }
96
97         //The client also receives a StorageUrl after authentication. All subsequent operations must
98         //use this url
99         public Uri StorageUrl { get; set; }
100
101
102         protected Uri RootAddressUri { get; set; }
103
104        /* private WebProxy _proxy;
105         public WebProxy Proxy
106         {
107             get { return _proxy; }
108             set
109             {
110                 _proxy = value;
111                 if (_baseClient != null)
112                     _baseClient.Proxy = value;                
113             }
114         }
115 */
116
117         /* private Uri _proxy;
118         public Uri Proxy
119         {
120             get { return _proxy; }
121             set
122             {
123                 _proxy = value;
124                 if (_baseClient != null)
125                     _baseClient.Proxy = new WebProxy(value);                
126             }
127         }*/
128
129         public double DownloadPercentLimit { get; set; }
130         public double UploadPercentLimit { get; set; }
131
132         public string AuthenticationUrl { get; set; }
133
134  
135         public string VersionPath
136         {
137             get { return UsePithos ? "v1" : "v1.0"; }
138         }
139
140         public bool UsePithos { get; set; }
141
142
143
144         public CloudFilesClient(string userName, string apiKey)
145         {
146             UserName = userName;
147             ApiKey = apiKey;
148         }
149
150         public CloudFilesClient(AccountInfo accountInfo)
151         {
152             if (accountInfo==null)
153                 throw new ArgumentNullException("accountInfo");
154             Contract.Ensures(!String.IsNullOrWhiteSpace(Token));
155             Contract.Ensures(StorageUrl != null);
156             Contract.Ensures(_baseClient != null);
157             Contract.Ensures(RootAddressUri != null);
158             Contract.EndContractBlock();          
159
160             _baseClient = new RestClient
161             {
162                 BaseAddress = accountInfo.StorageUri.ToString(),
163                 Timeout = 10000,
164                 Retries = 3,
165             };
166             StorageUrl = accountInfo.StorageUri;
167             Token = accountInfo.Token;
168             UserName = accountInfo.UserName;
169
170             //Get the root address (StorageUrl without the account)
171             var storageUrl = StorageUrl.AbsoluteUri;
172             var usernameIndex = storageUrl.LastIndexOf(UserName);
173             var rootUrl = storageUrl.Substring(0, usernameIndex);
174             RootAddressUri = new Uri(rootUrl);
175         }
176
177
178         public AccountInfo Authenticate()
179         {
180             if (String.IsNullOrWhiteSpace(UserName))
181                 throw new InvalidOperationException("UserName is empty");
182             if (String.IsNullOrWhiteSpace(ApiKey))
183                 throw new InvalidOperationException("ApiKey is empty");
184             if (String.IsNullOrWhiteSpace(AuthenticationUrl))
185                 throw new InvalidOperationException("AuthenticationUrl is empty");
186             Contract.Ensures(!String.IsNullOrWhiteSpace(Token));
187             Contract.Ensures(StorageUrl != null);
188             Contract.Ensures(_baseClient != null);
189             Contract.Ensures(RootAddressUri != null);
190             Contract.EndContractBlock();
191
192
193             Log.InfoFormat("[AUTHENTICATE] Start for {0}", UserName);
194
195             var groups = new List<Group>();
196
197             using (var authClient = new RestClient{BaseAddress=AuthenticationUrl})
198             {                
199                /* if (Proxy != null)
200                     authClient.Proxy = Proxy;*/
201
202                 Contract.Assume(authClient.Headers!=null);
203
204                 authClient.Headers.Add("X-Auth-User", UserName);
205                 authClient.Headers.Add("X-Auth-Key", ApiKey);
206
207                 authClient.DownloadStringWithRetry(VersionPath, 3);
208
209                 authClient.AssertStatusOK("Authentication failed");
210
211                 var storageUrl = authClient.GetHeaderValue("X-Storage-Url");
212                 if (String.IsNullOrWhiteSpace(storageUrl))
213                     throw new InvalidOperationException("Failed to obtain storage url");
214                 
215                 _baseClient = new RestClient
216                 {
217                     BaseAddress = storageUrl,
218                     Timeout = 10000,
219                     Retries = 3,
220                     //Proxy=Proxy
221                 };
222
223                 StorageUrl = new Uri(storageUrl);
224                 
225                 //Get the root address (StorageUrl without the account)
226                 var usernameIndex=storageUrl.LastIndexOf(UserName);
227                 var rootUrl = storageUrl.Substring(0, usernameIndex);
228                 RootAddressUri = new Uri(rootUrl);
229                 
230                 var token = authClient.GetHeaderValue("X-Auth-Token");
231                 if (String.IsNullOrWhiteSpace(token))
232                     throw new InvalidOperationException("Failed to obtain token url");
233                 Token = token;
234
235                /* var keys = authClient.ResponseHeaders.AllKeys.AsQueryable();
236                 groups = (from key in keys
237                             where key.StartsWith("X-Account-Group-")
238                             let name = key.Substring(16)
239                             select new Group(name, authClient.ResponseHeaders[key]))
240                         .ToList();
241                     
242 */
243             }
244
245             Log.InfoFormat("[AUTHENTICATE] End for {0}", UserName);
246             Debug.Assert(_baseClient!=null);
247
248             return new AccountInfo {StorageUri = StorageUrl, Token = Token, UserName = UserName,Groups=groups};            
249
250         }
251
252
253
254         public IList<ContainerInfo> ListContainers(string account)
255         {
256             using (var client = new RestClient(_baseClient))
257             {
258                 if (!String.IsNullOrWhiteSpace(account))
259                     client.BaseAddress = GetAccountUrl(account);
260                 
261                 client.Parameters.Clear();
262                 client.Parameters.Add("format", "json");
263                 var content = client.DownloadStringWithRetry("", 3);
264                 client.AssertStatusOK("List Containers failed");
265
266                 if (client.StatusCode == HttpStatusCode.NoContent)
267                     return new List<ContainerInfo>();
268                 var infos = JsonConvert.DeserializeObject<IList<ContainerInfo>>(content);
269                 
270                 foreach (var info in infos)
271                 {
272                     info.Account = account;
273                 }
274                 return infos;
275             }
276
277         }
278
279         private string GetAccountUrl(string account)
280         {
281             return new Uri(RootAddressUri, new Uri(account,UriKind.Relative)).AbsoluteUri;
282         }
283
284         public IList<ShareAccountInfo> ListSharingAccounts(DateTime? since=null)
285         {
286             using (ThreadContext.Stacks["Share"].Push("List Accounts"))
287             {
288                 if (Log.IsDebugEnabled) Log.DebugFormat("START");
289
290                 using (var client = new RestClient(_baseClient))
291                 {
292                     client.Parameters.Clear();
293                     client.Parameters.Add("format", "json");
294                     client.IfModifiedSince = since;
295
296                     //Extract the username from the base address
297                     client.BaseAddress = RootAddressUri.AbsoluteUri;
298
299                     var content = client.DownloadStringWithRetry(@"", 3);
300
301                     client.AssertStatusOK("ListSharingAccounts failed");
302
303                     //If the result is empty, return an empty list,
304                     var infos = String.IsNullOrWhiteSpace(content)
305                                     ? new List<ShareAccountInfo>()
306                                 //Otherwise deserialize the account list into a list of ShareAccountInfos
307                                     : JsonConvert.DeserializeObject<IList<ShareAccountInfo>>(content);
308
309                     Log.DebugFormat("END");
310                     return infos;
311                 }
312             }
313         }
314
315         //Request listing of all objects in a container modified since a specific time.
316         //If the *since* value is missing, return all objects
317         public IList<ObjectInfo> ListSharedObjects(DateTime? since = null)
318         {
319
320             using (ThreadContext.Stacks["Share"].Push("List Objects"))
321             {
322                 if (Log.IsDebugEnabled) Log.DebugFormat("START");
323                 //'since' is not used here because we need to have ListObjects return a NoChange result
324                 //for all shared accounts,containers
325                 var accounts = ListSharingAccounts();
326                 var items = from account in accounts 
327                             let containers = ListContainers(account.name) 
328                             from container in containers 
329                             select ListObjects(account.name, container.Name,since);
330                 var objects=items.SelectMany(r=> r).ToList();
331 /*
332                 var objects = new List<ObjectInfo>();
333                 foreach (var containerObjects in items)
334                 {
335                     objects.AddRange(containerObjects);
336                 }
337 */
338                 if (Log.IsDebugEnabled) Log.DebugFormat("END");
339                 return objects;
340             }
341         }
342
343         public void SetTags(ObjectInfo target,IDictionary<string,string> tags)
344         {
345             if (String.IsNullOrWhiteSpace(Token))
346                 throw new InvalidOperationException("The Token is not set");
347             if (StorageUrl == null)
348                 throw new InvalidOperationException("The StorageUrl is not set");
349             if (target == null)
350                 throw new ArgumentNullException("target");
351             Contract.EndContractBlock();
352
353             using (ThreadContext.Stacks["Share"].Push("Share Object"))
354             {
355                 if (Log.IsDebugEnabled) Log.DebugFormat("START");
356
357                 using (var client = new RestClient(_baseClient))
358                 {
359
360                     client.BaseAddress = GetAccountUrl(target.Account);
361
362                     client.Parameters.Clear();
363                     client.Parameters.Add("update", "");
364
365                     foreach (var tag in tags)
366                     {
367                         var headerTag = String.Format("X-Object-Meta-{0}", tag.Key);
368                         client.Headers.Add(headerTag, tag.Value);
369                     }
370                     
371                     client.DownloadStringWithRetry(target.Container, 3);
372
373                     
374                     client.AssertStatusOK("SetTags failed");
375                     //If the status is NOT ACCEPTED we have a problem
376                     if (client.StatusCode != HttpStatusCode.Accepted)
377                     {
378                         Log.Error("Failed to set tags");
379                         throw new Exception("Failed to set tags");
380                     }
381
382                     if (Log.IsDebugEnabled) Log.DebugFormat("END");
383                 }
384             }
385
386
387         }
388
389         public void ShareObject(string account, string container, string objectName, string shareTo, bool read, bool write)
390         {
391             if (String.IsNullOrWhiteSpace(Token))
392                 throw new InvalidOperationException("The Token is not set");
393             if (StorageUrl==null)
394                 throw new InvalidOperationException("The StorageUrl is not set");
395             if (String.IsNullOrWhiteSpace(container))
396                 throw new ArgumentNullException("container");
397             if (String.IsNullOrWhiteSpace(objectName))
398                 throw new ArgumentNullException("objectName");
399             if (String.IsNullOrWhiteSpace(account))
400                 throw new ArgumentNullException("account");
401             if (String.IsNullOrWhiteSpace(shareTo))
402                 throw new ArgumentNullException("shareTo");
403             Contract.EndContractBlock();
404
405             using (ThreadContext.Stacks["Share"].Push("Share Object"))
406             {
407                 if (Log.IsDebugEnabled) Log.DebugFormat("START");
408                 
409                 using (var client = new RestClient(_baseClient))
410                 {
411
412                     client.BaseAddress = GetAccountUrl(account);
413
414                     client.Parameters.Clear();
415                     client.Parameters.Add("format", "json");
416
417                     string permission = "";
418                     if (write)
419                         permission = String.Format("write={0}", shareTo);
420                     else if (read)
421                         permission = String.Format("read={0}", shareTo);
422                     client.Headers.Add("X-Object-Sharing", permission);
423
424                     var content = client.DownloadStringWithRetry(container, 3);
425
426                     client.AssertStatusOK("ShareObject failed");
427
428                     //If the result is empty, return an empty list,
429                     var infos = String.IsNullOrWhiteSpace(content)
430                                     ? new List<ObjectInfo>()
431                                 //Otherwise deserialize the object list into a list of ObjectInfos
432                                     : JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
433
434                     if (Log.IsDebugEnabled) Log.DebugFormat("END");
435                 }
436             }
437
438
439         }
440
441         public AccountInfo GetAccountPolicies(AccountInfo accountInfo)
442         {
443             if (accountInfo==null)
444                 throw new ArgumentNullException("accountInfo");
445             Contract.EndContractBlock();
446
447             using (ThreadContext.Stacks["Account"].Push("GetPolicies"))
448             {
449                 if (Log.IsDebugEnabled) Log.DebugFormat("START");
450
451                 using (var client = new RestClient(_baseClient))
452                 {
453                     if (!String.IsNullOrWhiteSpace(accountInfo.UserName))
454                         client.BaseAddress = GetAccountUrl(accountInfo.UserName);
455
456                     client.Parameters.Clear();
457                     client.Parameters.Add("format", "json");                    
458                     client.Head(String.Empty, 3);
459
460                     var quotaValue=client.ResponseHeaders["X-Account-Policy-Quota"];
461                     var bytesValue= client.ResponseHeaders["X-Account-Bytes-Used"];
462
463                     long quota, bytes;
464                     if (long.TryParse(quotaValue, out quota))
465                         accountInfo.Quota = quota;
466                     if (long.TryParse(bytesValue, out bytes))
467                         accountInfo.BytesUsed = bytes;
468                     
469                     return accountInfo;
470
471                 }
472
473             }
474         }
475
476         public void UpdateMetadata(ObjectInfo objectInfo)
477         {
478             if (objectInfo == null)
479                 throw new ArgumentNullException("objectInfo");
480             Contract.EndContractBlock();
481
482             using (ThreadContext.Stacks["Objects"].Push("UpdateMetadata"))
483             {
484                 if (Log.IsDebugEnabled) Log.DebugFormat("START");
485
486
487                 using(var client=new RestClient(_baseClient))
488                 {
489
490                     client.BaseAddress = GetAccountUrl(objectInfo.Account);
491                     
492                     client.Parameters.Clear();
493                     
494
495                     //Set Tags
496                     foreach (var tag in objectInfo.Tags)
497                     {
498                         var headerTag = String.Format("X-Object-Meta-{0}", tag.Key);
499                         client.Headers.Add(headerTag, tag.Value);
500                     }
501
502                     //Set Permissions
503
504                     var permissions=objectInfo.GetPermissionString();
505                     client.SetNonEmptyHeaderValue("X-Object-Sharing",permissions);
506
507                     client.SetNonEmptyHeaderValue("Content-Disposition",objectInfo.ContendDisposition);
508                     client.SetNonEmptyHeaderValue("Content-Encoding",objectInfo.ContentEncoding);
509                     client.SetNonEmptyHeaderValue("X-Object-Manifest",objectInfo.Manifest);
510                     var isPublic = objectInfo.IsPublic.ToString().ToLower();
511                     client.Headers.Add("X-Object-Public", isPublic);
512
513
514                     /*var uriBuilder = client.GetAddressBuilder(objectInfo.Container, objectInfo.Name);
515                     uriBuilder.Query = "update=";
516                     var uri = uriBuilder.Uri.MakeRelativeUri(this.RootAddressUri);*/
517                     var address = String.Format("{0}/{1}?update=",objectInfo.Container, objectInfo.Name);
518                     client.PostWithRetry(address,"application/xml");
519                     
520                     //client.UploadValues(uri,new NameValueCollection());
521
522
523                     client.AssertStatusOK("UpdateMetadata failed");
524                     //If the status is NOT ACCEPTED or OK we have a problem
525                     if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK))
526                     {
527                         Log.Error("Failed to update metadata");
528                         throw new Exception("Failed to update metadata");
529                     }
530
531                     if (Log.IsDebugEnabled) Log.DebugFormat("END");
532                 }
533             }
534
535         }
536
537         public void UpdateMetadata(ContainerInfo containerInfo)
538         {
539             if (containerInfo == null)
540                 throw new ArgumentNullException("containerInfo");
541             Contract.EndContractBlock();
542
543             using (ThreadContext.Stacks["Containers"].Push("UpdateMetadata"))
544             {
545                 if (Log.IsDebugEnabled) Log.DebugFormat("START");
546
547
548                 using(var client=new RestClient(_baseClient))
549                 {
550
551                     client.BaseAddress = GetAccountUrl(containerInfo.Account);
552                     
553                     client.Parameters.Clear();
554                     
555
556                     //Set Tags
557                     foreach (var tag in containerInfo.Tags)
558                     {
559                         var headerTag = String.Format("X-Container-Meta-{0}", tag.Key);
560                         client.Headers.Add(headerTag, tag.Value);
561                     }
562
563                     
564                     //Set Policies
565                     foreach (var policy in containerInfo.Policies)
566                     {
567                         var headerPolicy = String.Format("X-Container-Policy-{0}", policy.Key);
568                         client.Headers.Add(headerPolicy, policy.Value);
569                     }
570
571
572                     var uriBuilder = client.GetAddressBuilder(containerInfo.Name,"");
573                     var uri = uriBuilder.Uri;
574
575                     client.UploadValues(uri,new NameValueCollection());
576
577
578                     client.AssertStatusOK("UpdateMetadata failed");
579                     //If the status is NOT ACCEPTED or OK we have a problem
580                     if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK))
581                     {
582                         Log.Error("Failed to update metadata");
583                         throw new Exception("Failed to update metadata");
584                     }
585
586                     if (Log.IsDebugEnabled) Log.DebugFormat("END");
587                 }
588             }
589
590         }
591
592
593         public IList<ObjectInfo> ListObjects(string account, string container, DateTime? since = null)
594         {
595             if (String.IsNullOrWhiteSpace(container))
596                 throw new ArgumentNullException("container");
597             Contract.EndContractBlock();
598
599             using (ThreadContext.Stacks["Objects"].Push("List"))
600             {
601                 if (Log.IsDebugEnabled) Log.DebugFormat("START");
602
603                 using (var client = new RestClient(_baseClient))
604                 {
605                     if (!String.IsNullOrWhiteSpace(account))
606                         client.BaseAddress = GetAccountUrl(account);
607
608                     client.Parameters.Clear();
609                     client.Parameters.Add("format", "json");
610                     client.IfModifiedSince = since;
611                     var content = client.DownloadStringWithRetry(container, 3);
612
613                     client.AssertStatusOK("ListObjects failed");
614
615                     if (client.StatusCode==HttpStatusCode.NotModified)
616                         return new[]{new NoModificationInfo(account,container)};
617                     //If the result is empty, return an empty list,
618                     var infos = String.IsNullOrWhiteSpace(content)
619                                     ? new List<ObjectInfo>()
620                                 //Otherwise deserialize the object list into a list of ObjectInfos
621                                     : JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
622
623                     foreach (var info in infos)
624                     {
625                         info.Container = container;
626                         info.Account = account;
627                         info.StorageUri = this.StorageUrl;
628                     }
629                     if (Log.IsDebugEnabled) Log.DebugFormat("END");
630                     return infos;
631                 }
632             }
633         }
634
635         public IList<ObjectInfo> ListObjects(string account, string container, string folder, DateTime? since = null)
636         {
637             if (String.IsNullOrWhiteSpace(container))
638                 throw new ArgumentNullException("container");
639 /*
640             if (String.IsNullOrWhiteSpace(folder))
641                 throw new ArgumentNullException("folder");
642 */
643             Contract.EndContractBlock();
644
645             using (ThreadContext.Stacks["Objects"].Push("List"))
646             {
647                 if (Log.IsDebugEnabled) Log.DebugFormat("START");
648
649                 using (var client = new RestClient(_baseClient))
650                 {
651                     if (!String.IsNullOrWhiteSpace(account))
652                         client.BaseAddress = GetAccountUrl(account);
653
654                     client.Parameters.Clear();
655                     client.Parameters.Add("format", "json");
656                     client.Parameters.Add("path", folder);
657                     client.IfModifiedSince = since;
658                     var content = client.DownloadStringWithRetry(container, 3);
659                     client.AssertStatusOK("ListObjects failed");
660
661                     if (client.StatusCode==HttpStatusCode.NotModified)
662                         return new[]{new NoModificationInfo(account,container,folder)};
663
664                     var infos = JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
665                     foreach (var info in infos)
666                     {
667                         info.Account = account;
668                         if (info.Container == null)
669                             info.Container = container;
670                         info.StorageUri = this.StorageUrl;
671                     }
672                     if (Log.IsDebugEnabled) Log.DebugFormat("END");
673                     return infos;
674                 }
675             }
676         }
677
678  
679         public bool ContainerExists(string account, string container)
680         {
681             if (String.IsNullOrWhiteSpace(container))
682                 throw new ArgumentNullException("container", "The container property can't be empty");
683             Contract.EndContractBlock();
684
685             using (ThreadContext.Stacks["Containters"].Push("Exists"))
686             {
687                 if (Log.IsDebugEnabled) Log.DebugFormat("START");
688
689                 using (var client = new RestClient(_baseClient))
690                 {
691                     if (!String.IsNullOrWhiteSpace(account))
692                         client.BaseAddress = GetAccountUrl(account);
693
694                     client.Parameters.Clear();
695                     client.Head(container, 3);
696                                         
697                     bool result;
698                     switch (client.StatusCode)
699                     {
700                         case HttpStatusCode.OK:
701                         case HttpStatusCode.NoContent:
702                             result=true;
703                             break;
704                         case HttpStatusCode.NotFound:
705                             result=false;
706                             break;
707                         default:
708                             throw CreateWebException("ContainerExists", client.StatusCode);
709                     }
710                     if (Log.IsDebugEnabled) Log.DebugFormat("END");
711
712                     return result;
713                 }
714                 
715             }
716         }
717
718         public bool ObjectExists(string account, string container, string objectName)
719         {
720             if (String.IsNullOrWhiteSpace(container))
721                 throw new ArgumentNullException("container", "The container property can't be empty");
722             if (String.IsNullOrWhiteSpace(objectName))
723                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
724             Contract.EndContractBlock();
725
726             using (var client = new RestClient(_baseClient))
727             {
728                 if (!String.IsNullOrWhiteSpace(account))
729                     client.BaseAddress = GetAccountUrl(account);
730
731                 client.Parameters.Clear();
732                 client.Head(container + "/" + objectName, 3);
733
734                 switch (client.StatusCode)
735                 {
736                     case HttpStatusCode.OK:
737                     case HttpStatusCode.NoContent:
738                         return true;
739                     case HttpStatusCode.NotFound:
740                         return false;
741                     default:
742                         throw CreateWebException("ObjectExists", client.StatusCode);
743                 }
744             }
745
746         }
747
748         public ObjectInfo GetObjectInfo(string account, string container, string objectName)
749         {
750             if (String.IsNullOrWhiteSpace(container))
751                 throw new ArgumentNullException("container", "The container property can't be empty");
752             if (String.IsNullOrWhiteSpace(objectName))
753                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
754             Contract.EndContractBlock();
755
756             using (ThreadContext.Stacks["Objects"].Push("GetObjectInfo"))
757             {                
758
759                 using (var client = new RestClient(_baseClient))
760                 {
761                     if (!String.IsNullOrWhiteSpace(account))
762                         client.BaseAddress = GetAccountUrl(account);
763                     try
764                     {
765                         client.Parameters.Clear();
766
767                         client.Head(container + "/" + objectName, 3);
768
769                         if (client.TimedOut)
770                             return ObjectInfo.Empty;
771
772                         switch (client.StatusCode)
773                         {
774                             case HttpStatusCode.OK:
775                             case HttpStatusCode.NoContent:
776                                 var keys = client.ResponseHeaders.AllKeys.AsQueryable();
777                                 var tags = client.GetMeta("X-Object-Meta-");
778                                 var extensions = (from key in keys
779                                                   where key.StartsWith("X-Object-") && !key.StartsWith("X-Object-Meta-")
780                                                   select new {Name = key, Value = client.ResponseHeaders[key]})
781                                     .ToDictionary(t => t.Name, t => t.Value);
782
783                                 var permissions=client.GetHeaderValue("X-Object-Sharing", true);
784                                 
785                                 
786                                 var info = new ObjectInfo
787                                                {
788                                                    Account = account,
789                                                    Container = container,
790                                                    Name = objectName,
791                                                    Hash = client.GetHeaderValue("ETag"),
792                                                    Content_Type = client.GetHeaderValue("Content-Type"),
793                                                    Bytes = Convert.ToInt64(client.GetHeaderValue("Content-Length",true)),
794                                                    Tags = tags,
795                                                    Last_Modified = client.LastModified,
796                                                    Extensions = extensions,
797                                                    ContentEncoding=client.GetHeaderValue("Content-Encoding",true),
798                                                    ContendDisposition = client.GetHeaderValue("Content-Disposition",true),
799                                                    Manifest=client.GetHeaderValue("X-Object-Manifest",true),
800                                                    PublicUrl=client.GetHeaderValue("X-Object-Public",true),  
801                                                    StorageUri=this.StorageUrl,
802                                                };
803                                 info.SetPermissions(permissions);
804                                 return info;
805                             case HttpStatusCode.NotFound:
806                                 return ObjectInfo.Empty;
807                             default:
808                                 throw new WebException(
809                                     String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}",
810                                                   objectName, client.StatusCode));
811                         }
812
813                     }
814                     catch (RetryException)
815                     {
816                         Log.WarnFormat("[RETRY FAIL] GetObjectInfo for {0} failed.",objectName);
817                         return ObjectInfo.Empty;
818                     }
819                     catch (WebException e)
820                     {
821                         Log.Error(
822                             String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}",
823                                           objectName, client.StatusCode), e);
824                         throw;
825                     }
826                 }                
827             }
828
829         }
830
831         public void CreateFolder(string account, string container, string folder)
832         {
833             if (String.IsNullOrWhiteSpace(container))
834                 throw new ArgumentNullException("container", "The container property can't be empty");
835             if (String.IsNullOrWhiteSpace(folder))
836                 throw new ArgumentNullException("folder", "The folder property can't be empty");
837             Contract.EndContractBlock();
838
839             var folderUrl=String.Format("{0}/{1}",container,folder);
840             using (var client = new RestClient(_baseClient))
841             {
842                 if (!String.IsNullOrWhiteSpace(account))
843                     client.BaseAddress = GetAccountUrl(account);
844
845                 client.Parameters.Clear();
846                 client.Headers.Add("Content-Type", @"application/directory");
847                 client.Headers.Add("Content-Length", "0");
848                 client.PutWithRetry(folderUrl, 3);
849
850                 if (client.StatusCode != HttpStatusCode.Created && client.StatusCode != HttpStatusCode.Accepted)
851                     throw CreateWebException("CreateFolder", client.StatusCode);
852             }
853         }
854
855      
856
857         public ContainerInfo GetContainerInfo(string account, string container)
858         {
859             if (String.IsNullOrWhiteSpace(container))
860                 throw new ArgumentNullException("container", "The container property can't be empty");
861             Contract.EndContractBlock();
862
863             using (var client = new RestClient(_baseClient))
864             {
865                 if (!String.IsNullOrWhiteSpace(account))
866                     client.BaseAddress = GetAccountUrl(account);                
867
868                 client.Head(container);
869                 switch (client.StatusCode)
870                 {
871                     case HttpStatusCode.OK:
872                     case HttpStatusCode.NoContent:
873                         var tags = client.GetMeta("X-Container-Meta-");
874                         var policies = client.GetMeta("X-Container-Policy-");
875
876                         var containerInfo = new ContainerInfo
877                                                 {
878                                                     Account=account,
879                                                     Name = container,
880                                                     StorageUrl=this.StorageUrl.ToString(),
881                                                     Count =
882                                                         long.Parse(client.GetHeaderValue("X-Container-Object-Count")),
883                                                     Bytes = long.Parse(client.GetHeaderValue("X-Container-Bytes-Used")),
884                                                     BlockHash = client.GetHeaderValue("X-Container-Block-Hash"),
885                                                     BlockSize=int.Parse(client.GetHeaderValue("X-Container-Block-Size")),
886                                                     Last_Modified=client.LastModified,
887                                                     Tags=tags,
888                                                     Policies=policies
889                                                 };
890                         
891
892                         return containerInfo;
893                     case HttpStatusCode.NotFound:
894                         return ContainerInfo.Empty;
895                     default:
896                         throw CreateWebException("GetContainerInfo", client.StatusCode);
897                 }
898             }
899         }
900
901         public void CreateContainer(string account, string container)
902         {
903             if (String.IsNullOrWhiteSpace(account))
904                 throw new ArgumentNullException("account");
905             if (String.IsNullOrWhiteSpace(container))
906                 throw new ArgumentNullException("container");
907             Contract.EndContractBlock();
908
909             using (var client = new RestClient(_baseClient))
910             {
911                 if (!String.IsNullOrWhiteSpace(account))
912                     client.BaseAddress = GetAccountUrl(account);
913
914                 client.PutWithRetry(container, 3);
915                 var expectedCodes = new[] {HttpStatusCode.Created, HttpStatusCode.Accepted, HttpStatusCode.OK};
916                 if (!expectedCodes.Contains(client.StatusCode))
917                     throw CreateWebException("CreateContainer", client.StatusCode);
918             }
919         }
920
921         public void DeleteContainer(string account, string container)
922         {
923             if (String.IsNullOrWhiteSpace(container))
924                 throw new ArgumentNullException("container", "The container property can't be empty");
925             Contract.EndContractBlock();
926
927             using (var client = new RestClient(_baseClient))
928             {
929                 if (!String.IsNullOrWhiteSpace(account))
930                     client.BaseAddress = GetAccountUrl(account);
931
932                 client.DeleteWithRetry(container, 3);
933                 var expectedCodes = new[] {HttpStatusCode.NotFound, HttpStatusCode.NoContent};
934                 if (!expectedCodes.Contains(client.StatusCode))
935                     throw CreateWebException("DeleteContainer", client.StatusCode);
936             }
937
938         }
939
940         /// <summary>
941         /// 
942         /// </summary>
943         /// <param name="account"></param>
944         /// <param name="container"></param>
945         /// <param name="objectName"></param>
946         /// <param name="fileName"></param>
947         /// <returns></returns>
948         /// <remarks>This method should have no timeout or a very long one</remarks>
949         //Asynchronously download the object specified by *objectName* in a specific *container* to 
950         // a local file
951         public async Task GetObject(string account, string container, string objectName, string fileName,CancellationToken cancellationToken)
952         {
953             if (String.IsNullOrWhiteSpace(container))
954                 throw new ArgumentNullException("container", "The container property can't be empty");
955             if (String.IsNullOrWhiteSpace(objectName))
956                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");            
957             Contract.EndContractBlock();
958
959             try
960             {
961                 //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient
962                 //object to avoid concurrency errors.
963                 //
964                 //Download operations take a long time therefore they have no timeout.
965                 using(var client = new RestClient(_baseClient) { Timeout = 0 })
966                 {
967                     if (!String.IsNullOrWhiteSpace(account))
968                         client.BaseAddress = GetAccountUrl(account);
969
970                     //The container and objectName are relative names. They are joined with the client's
971                     //BaseAddress to create the object's absolute address
972                     var builder = client.GetAddressBuilder(container, objectName);
973                     var uri = builder.Uri;
974
975                     //Download progress is reported to the Trace log
976                     Log.InfoFormat("[GET] START {0}", objectName);
977                     client.DownloadProgressChanged += (sender, args) =>
978                                                       Log.InfoFormat("[GET PROGRESS] {0} {1}% {2} of {3}",
979                                                                      fileName, args.ProgressPercentage,
980                                                                      args.BytesReceived,
981                                                                      args.TotalBytesToReceive);
982
983                     
984                     //Start downloading the object asynchronously
985                     await client.DownloadFileTaskAsync(uri, fileName,cancellationToken);
986
987                     //Once the download completes
988                     //Delete the local client object
989                 }
990                 //And report failure or completion
991             }
992             catch (Exception exc)
993             {
994                 Log.ErrorFormat("[GET] FAIL {0} with {1}", objectName, exc);
995                 throw;
996             }
997
998             Log.InfoFormat("[GET] END {0}", objectName);                                             
999
1000
1001         }
1002
1003         public Task<IList<string>> PutHashMap(string account, string container, string objectName, TreeHash hash)
1004         {
1005             if (String.IsNullOrWhiteSpace(container))
1006                 throw new ArgumentNullException("container");
1007             if (String.IsNullOrWhiteSpace(objectName))
1008                 throw new ArgumentNullException("objectName");
1009             if (hash==null)
1010                 throw new ArgumentNullException("hash");
1011             if (String.IsNullOrWhiteSpace(Token))
1012                 throw new InvalidOperationException("Invalid Token");
1013             if (StorageUrl == null)
1014                 throw new InvalidOperationException("Invalid Storage Url");
1015             Contract.EndContractBlock();
1016
1017
1018             //Don't use a timeout because putting the hashmap may be a long process
1019             var client = new RestClient(_baseClient) { Timeout = 0 };           
1020             if (!String.IsNullOrWhiteSpace(account))
1021                 client.BaseAddress = GetAccountUrl(account);
1022
1023             //The container and objectName are relative names. They are joined with the client's
1024             //BaseAddress to create the object's absolute address
1025             var builder = client.GetAddressBuilder(container, objectName);
1026             builder.Query = "format=json&hashmap";
1027             var uri = builder.Uri;
1028
1029
1030             //Send the tree hash as Json to the server            
1031             client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
1032             var jsonHash = hash.ToJson();
1033             var uploadTask=client.UploadStringTask(uri, "PUT", jsonHash);
1034             if (Log.IsDebugEnabled)
1035                 Log.DebugFormat("Hashes:\r\n{0}", jsonHash);
1036             return uploadTask.ContinueWith(t =>
1037             {
1038
1039                 var empty = (IList<string>)new List<string>();
1040                 
1041
1042                 //The server will respond either with 201-created if all blocks were already on the server
1043                 if (client.StatusCode == HttpStatusCode.Created)                    
1044                 {
1045                     //in which case we return an empty hash list
1046                     return empty;
1047                 }
1048                 //or with a 409-conflict and return the list of missing parts
1049                 //A 409 will cause an exception so we need to check t.IsFaulted to avoid propagating the exception                
1050                 if (t.IsFaulted)
1051                 {
1052                     var ex = t.Exception.InnerException;
1053                     var we = ex as WebException;
1054                     var response = we.Response as HttpWebResponse;
1055                     if (response!=null && response.StatusCode==HttpStatusCode.Conflict)
1056                     {
1057                         //In case of 409 the missing parts will be in the response content                        
1058                         using (var stream = response.GetResponseStream())
1059                         using(var reader=stream.GetLoggedReader(Log))
1060                         {
1061                             //We used to have to cleanup the content before returning it because it contains
1062                             //error content after the list of hashes
1063                             //
1064                             //As of 30/1/2012, the result is a proper Json array so we don't need to read the content
1065                             //line by line
1066                             
1067                             var serializer = new JsonSerializer();                            
1068                             serializer.Error += (sender, args) => Log.ErrorFormat("Deserialization error at [{0}] [{1}]", args.ErrorContext.Error, args.ErrorContext.Member);
1069                             var hashes = (List<string>)serializer.Deserialize(reader, typeof(List<string>));
1070                             return hashes;
1071                         }                        
1072                     }                    
1073                     //Any other status code is unexpected and the exception should be rethrown
1074                     Log.LogError(response);
1075                     throw ex;
1076                     
1077                 }
1078
1079                 //Any other status code is unexpected but there was no exception. We can probably continue processing
1080                 Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",client.StatusCode,client.StatusDescription);                    
1081                 
1082                 return empty;
1083             });
1084
1085         }
1086
1087
1088         public async Task<byte[]> GetBlock(string account, string container, Uri relativeUrl, long start, long? end, CancellationToken cancellationToken)
1089         {
1090             if (String.IsNullOrWhiteSpace(Token))
1091                 throw new InvalidOperationException("Invalid Token");
1092             if (StorageUrl == null)
1093                 throw new InvalidOperationException("Invalid Storage Url");
1094             if (String.IsNullOrWhiteSpace(container))
1095                 throw new ArgumentNullException("container");
1096             if (relativeUrl == null)
1097                 throw new ArgumentNullException("relativeUrl");
1098             if (end.HasValue && end < 0)
1099                 throw new ArgumentOutOfRangeException("end");
1100             if (start < 0)
1101                 throw new ArgumentOutOfRangeException("start");
1102             Contract.EndContractBlock();
1103
1104             //Don't use a timeout because putting the hashmap may be a long process
1105             using (var client = new RestClient(_baseClient) {Timeout = 0, RangeFrom = start, RangeTo = end})
1106             {
1107                 if (!String.IsNullOrWhiteSpace(account))
1108                     client.BaseAddress = GetAccountUrl(account);
1109
1110                 var builder = client.GetAddressBuilder(container, relativeUrl.ToString());
1111                 var uri = builder.Uri;
1112
1113                 var result = await client.DownloadDataTaskAsync(uri, cancellationToken);
1114                 return result;
1115             }
1116         }
1117
1118
1119         public async Task PostBlock(string account, string container, byte[] block, int offset, int count)
1120         {
1121             if (String.IsNullOrWhiteSpace(container))
1122                 throw new ArgumentNullException("container");
1123             if (block == null)
1124                 throw new ArgumentNullException("block");
1125             if (offset < 0 || offset >= block.Length)
1126                 throw new ArgumentOutOfRangeException("offset");
1127             if (count < 0 || count > block.Length)
1128                 throw new ArgumentOutOfRangeException("count");
1129             if (String.IsNullOrWhiteSpace(Token))
1130                 throw new InvalidOperationException("Invalid Token");
1131             if (StorageUrl == null)
1132                 throw new InvalidOperationException("Invalid Storage Url");                        
1133             Contract.EndContractBlock();
1134
1135
1136             try
1137             {
1138
1139             //Don't use a timeout because putting the hashmap may be a long process
1140                 using (var client = new RestClient(_baseClient) { Timeout = 0 })
1141                 {                    
1142                     if (!String.IsNullOrWhiteSpace(account))
1143                         client.BaseAddress = GetAccountUrl(account);
1144
1145                     var builder = client.GetAddressBuilder(container, "");
1146                     //We are doing an update
1147                     builder.Query = "update";
1148                     var uri = builder.Uri;
1149
1150                     client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
1151
1152                     Log.InfoFormat("[BLOCK POST] START");
1153
1154                     client.UploadProgressChanged += (sender, args) =>
1155                                                     Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}",
1156                                                                    args.ProgressPercentage, args.BytesSent,
1157                                                                    args.TotalBytesToSend);
1158                     client.UploadFileCompleted += (sender, args) =>
1159                                                   Log.InfoFormat("[BLOCK POST PROGRESS] Completed ");
1160
1161                     var buffer = new byte[count];
1162                     Buffer.BlockCopy(block, offset, buffer, 0, count);
1163                     //Send the block
1164                     await client.UploadDataTaskAsync(uri, "POST", buffer);                    
1165                     Log.InfoFormat("[BLOCK POST] END");
1166                 }
1167             }
1168             catch (Exception exc)
1169             {
1170                 Log.ErrorFormat("[BLOCK POST] FAIL with \r{0}", exc);                                        
1171                 throw;
1172             }
1173         }
1174
1175
1176         public async Task<TreeHash> GetHashMap(string account, string container, string objectName)
1177         {
1178             if (String.IsNullOrWhiteSpace(container))
1179                 throw new ArgumentNullException("container");
1180             if (String.IsNullOrWhiteSpace(objectName))
1181                 throw new ArgumentNullException("objectName");
1182             if (String.IsNullOrWhiteSpace(Token))
1183                 throw new InvalidOperationException("Invalid Token");
1184             if (StorageUrl == null)
1185                 throw new InvalidOperationException("Invalid Storage Url");
1186             Contract.EndContractBlock();
1187
1188             try
1189             {
1190                 //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient
1191                 //object to avoid concurrency errors.
1192                 //
1193                 //Download operations take a long time therefore they have no timeout.
1194                 //TODO: Do they really? this is a hashmap operation, not a download
1195                 
1196                 //Start downloading the object asynchronously
1197                 using (var client = new RestClient(_baseClient) { Timeout = 0 })
1198                 {
1199                     if (!String.IsNullOrWhiteSpace(account))
1200                         client.BaseAddress = GetAccountUrl(account);
1201
1202                     //The container and objectName are relative names. They are joined with the client's
1203                     //BaseAddress to create the object's absolute address
1204                     var builder = client.GetAddressBuilder(container, objectName);
1205                     builder.Query = "format=json&hashmap";
1206                     var uri = builder.Uri;
1207
1208
1209                     var json = await client.DownloadStringTaskAsync(uri);
1210                     var treeHash = TreeHash.Parse(json);
1211                     Log.InfoFormat("[GET HASH] END {0}", objectName);
1212                     return treeHash;
1213                 }
1214             }
1215             catch (Exception exc)
1216             {
1217                 Log.ErrorFormat("[GET HASH] END {0} with {1}", objectName, exc);
1218                 throw;
1219             }
1220
1221         }
1222
1223
1224         /// <summary>
1225         /// 
1226         /// </summary>
1227         /// <param name="account"></param>
1228         /// <param name="container"></param>
1229         /// <param name="objectName"></param>
1230         /// <param name="fileName"></param>
1231         /// <param name="hash">Optional hash value for the file. If no hash is provided, the method calculates a new hash</param>
1232         /// <remarks>>This method should have no timeout or a very long one</remarks>
1233         public async Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream")
1234         {
1235             if (String.IsNullOrWhiteSpace(container))
1236                 throw new ArgumentNullException("container", "The container property can't be empty");
1237             if (String.IsNullOrWhiteSpace(objectName))
1238                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
1239             if (String.IsNullOrWhiteSpace(fileName))
1240                 throw new ArgumentNullException("fileName", "The fileName property can't be empty");
1241 /*
1242             if (!File.Exists(fileName) && !Directory.Exists(fileName))
1243                 throw new FileNotFoundException("The file or directory does not exist",fileName);
1244 */
1245             
1246             try
1247             {
1248
1249                 using (var client = new RestClient(_baseClient) { Timeout = 0 })
1250                 {
1251                     if (!String.IsNullOrWhiteSpace(account))
1252                         client.BaseAddress = GetAccountUrl(account);
1253
1254                     var builder = client.GetAddressBuilder(container, objectName);
1255                     var uri = builder.Uri;
1256
1257                     string etag = hash ?? CalculateHash(fileName);
1258
1259                     client.Headers.Add("Content-Type", contentType);
1260                     client.Headers.Add("ETag", etag);
1261
1262
1263                     Log.InfoFormat("[PUT] START {0}", objectName);
1264                     client.UploadProgressChanged += (sender, args) =>
1265                                                         {
1266                                                             using (ThreadContext.Stacks["PUT"].Push("Progress"))
1267                                                             {
1268                                                                 Log.InfoFormat("{0} {1}% {2} of {3}", fileName,
1269                                                                                args.ProgressPercentage,
1270                                                                                args.BytesSent, args.TotalBytesToSend);
1271                                                             }
1272                                                         };
1273
1274                     client.UploadFileCompleted += (sender, args) =>
1275                                                       {
1276                                                           using (ThreadContext.Stacks["PUT"].Push("Progress"))
1277                                                           {
1278                                                               Log.InfoFormat("Completed {0}", fileName);
1279                                                           }
1280                                                       };                    
1281                     if (contentType=="application/directory")
1282                         await client.UploadDataTaskAsync(uri, "PUT", new byte[0]);
1283                     else
1284                         await client.UploadFileTaskAsync(uri, "PUT", fileName);
1285                 }
1286
1287                 Log.InfoFormat("[PUT] END {0}", objectName);
1288             }
1289             catch (Exception exc)
1290             {
1291                 Log.ErrorFormat("[PUT] END {0} with {1}", objectName, exc);
1292                 throw;
1293             }                
1294
1295         }
1296        
1297         
1298         private static string CalculateHash(string fileName)
1299         {
1300             Contract.Requires(!String.IsNullOrWhiteSpace(fileName));
1301             Contract.EndContractBlock();
1302
1303             string hash;
1304             using (var hasher = MD5.Create())
1305             using(var stream=File.OpenRead(fileName))
1306             {
1307                 var hashBuilder=new StringBuilder();
1308                 foreach (byte b in hasher.ComputeHash(stream))
1309                     hashBuilder.Append(b.ToString("x2").ToLower());
1310                 hash = hashBuilder.ToString();                
1311             }
1312             return hash;
1313         }
1314         
1315         public void MoveObject(string account, string sourceContainer, string oldObjectName, string targetContainer, string newObjectName)
1316         {
1317             if (String.IsNullOrWhiteSpace(sourceContainer))
1318                 throw new ArgumentNullException("sourceContainer", "The container property can't be empty");
1319             if (String.IsNullOrWhiteSpace(oldObjectName))
1320                 throw new ArgumentNullException("oldObjectName", "The oldObjectName property can't be empty");
1321             if (String.IsNullOrWhiteSpace(targetContainer))
1322                 throw new ArgumentNullException("targetContainer", "The container property can't be empty");
1323             if (String.IsNullOrWhiteSpace(newObjectName))
1324                 throw new ArgumentNullException("newObjectName", "The newObjectName property can't be empty");
1325             Contract.EndContractBlock();
1326
1327             var targetUrl = targetContainer + "/" + newObjectName;
1328             var sourceUrl = String.Format("/{0}/{1}", sourceContainer, oldObjectName);
1329
1330             using (var client = new RestClient(_baseClient))
1331             {
1332                 if (!String.IsNullOrWhiteSpace(account))
1333                     client.BaseAddress = GetAccountUrl(account);
1334
1335                 client.Headers.Add("X-Move-From", sourceUrl);
1336                 client.PutWithRetry(targetUrl, 3);
1337
1338                 var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created};
1339                 if (!expectedCodes.Contains(client.StatusCode))
1340                     throw CreateWebException("MoveObject", client.StatusCode);
1341             }
1342         }
1343
1344         public void DeleteObject(string account, string sourceContainer, string objectName)
1345         {            
1346             if (String.IsNullOrWhiteSpace(sourceContainer))
1347                 throw new ArgumentNullException("sourceContainer", "The container property can't be empty");
1348             if (String.IsNullOrWhiteSpace(objectName))
1349                 throw new ArgumentNullException("objectName", "The oldObjectName property can't be empty");
1350             Contract.EndContractBlock();
1351
1352             var targetUrl = FolderConstants.TrashContainer + "/" + objectName;
1353             var sourceUrl = String.Format("/{0}/{1}", sourceContainer, objectName);
1354
1355             using (var client = new RestClient(_baseClient))
1356             {
1357                 if (!String.IsNullOrWhiteSpace(account))
1358                     client.BaseAddress = GetAccountUrl(account);
1359
1360                 client.Headers.Add("X-Move-From", sourceUrl);
1361                 client.AllowedStatusCodes.Add(HttpStatusCode.NotFound);
1362                 Log.InfoFormat("[TRASH] [{0}] to [{1}]",sourceUrl,targetUrl);
1363                 client.PutWithRetry(targetUrl, 3);
1364
1365                 var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created,HttpStatusCode.NotFound};
1366                 if (!expectedCodes.Contains(client.StatusCode))
1367                     throw CreateWebException("DeleteObject", client.StatusCode);
1368             }
1369         }
1370
1371       
1372         private static WebException CreateWebException(string operation, HttpStatusCode statusCode)
1373         {
1374             return new WebException(String.Format("{0} failed with unexpected status code {1}", operation, statusCode));
1375         }
1376
1377
1378 /*
1379         public IEnumerable<ObjectInfo> ListDirectories(ContainerInfo container)
1380         {
1381             var directories=this.ListObjects(container.Account, container.Name, "/");
1382         }
1383 */
1384
1385         public bool CanUpload(string account, ObjectInfo cloudFile)
1386         {
1387             Contract.Requires(!String.IsNullOrWhiteSpace(account));
1388             Contract.Requires(cloudFile!=null);
1389
1390             using (var client = new RestClient(_baseClient))
1391             {
1392                 if (!String.IsNullOrWhiteSpace(account))
1393                     client.BaseAddress = GetAccountUrl(account);
1394
1395
1396                 var parts = cloudFile.Name.Split('/');
1397                 var folder = String.Join("/", parts,0,parts.Length-1);
1398
1399                 var fileUrl=String.Format("{0}/{1}/{2}.pithos.ignore",cloudFile.Container,folder,Guid.NewGuid());
1400
1401                 client.Parameters.Clear();
1402                 try
1403                 {
1404                     client.PutWithRetry(fileUrl, 3, @"application/octet-stream");
1405
1406                     var expectedCodes = new[] { HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created};
1407                     var result=(expectedCodes.Contains(client.StatusCode));
1408                     DeleteObject(account, cloudFile.Container, fileUrl);
1409                     return result;
1410                 }
1411                 catch
1412                 {
1413                     return false;
1414                 }
1415             }
1416         }
1417     }
1418
1419     public class ShareAccountInfo
1420     {
1421         public DateTime? last_modified { get; set; }
1422         public string name { get; set; }
1423     }
1424 }