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