fb26abf059977bbd43e0db119b00f07643e4ab4c
[pithos-ms-client] / trunk / Pithos.Network / CloudFilesClient.cs
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel.Composition;
4 using System.Diagnostics;
5 using System.Diagnostics.Contracts;
6 using System.IO;
7 using System.Linq;
8 using System.Net;
9 using System.Security.Cryptography;
10 using System.Text;
11 using System.Threading.Tasks;
12 using Newtonsoft.Json;
13 using Pithos.Interfaces;
14 using WebHeaderCollection = System.Net.WebHeaderCollection;
15
16 namespace Pithos.Network
17 {
18     [Export(typeof(ICloudClient))]
19     public class CloudFilesClient:ICloudClient
20     {
21
22         private PithosClient _client;
23         private readonly TimeSpan _shortTimeout = TimeSpan.FromSeconds(10);
24         private readonly int _retries = 5;        
25         public string ApiKey { get; set; }
26         public string UserName { get; set; }
27         public Uri StorageUrl { get; set; }
28         public string Token { get; set; }
29         public Uri Proxy { get; set; }
30
31         public double DownloadPercentLimit { get; set; }
32         public double UploadPercentLimit { get; set; }
33
34         public string AuthenticationUrl { get; set; }
35
36  
37         public string VersionPath
38         {
39             get { return UsePithos ? "v1" : "v1.0"; }
40         }
41
42         public bool UsePithos { get; set; }
43
44         private bool _authenticated = false;
45
46         public void Authenticate(string userName,string apiKey)
47         {
48             Trace.TraceInformation("[AUTHENTICATE] Start for {0}", userName);
49             if (String.IsNullOrWhiteSpace(userName))
50                 throw new ArgumentNullException("userName", "The userName property can't be empty");
51             if (String.IsNullOrWhiteSpace(apiKey))
52                 throw new ArgumentNullException("apiKey", "The apiKey property can't be empty");
53
54             if (_authenticated)
55                 return;
56
57             UserName = userName;
58             ApiKey = apiKey;
59             
60             if (UsePithos)
61             {
62                 Token = ApiKey;
63                 string storageUrl = String.Format("{0}/{1}/{2}", AuthenticationUrl, VersionPath, UserName);
64                 StorageUrl = new Uri(storageUrl);
65             }
66             else
67             {
68
69                 string authUrl = String.Format("{0}/{1}", AuthenticationUrl, VersionPath);
70                 var authClient = new PithosClient{BaseAddress= authUrl};
71                 if (Proxy != null)
72                     authClient.Proxy = new WebProxy(Proxy);
73
74                 authClient.Headers.Add("X-Auth-User", UserName);
75                 authClient.Headers.Add("X-Auth-Key", ApiKey);
76
77                 var response = authClient.DownloadStringWithRetry("",3);
78
79                 authClient.AssertStatusOK("Authentication failed");
80
81                 //var keys = authClient.ResponseHeaders.AllKeys.AsQueryable();
82
83                 string storageUrl = authClient.GetHeaderValue("X-Storage-Url");
84                 if (String.IsNullOrWhiteSpace(storageUrl))
85                     throw new InvalidOperationException("Failed to obtain storage url");
86                 StorageUrl = new Uri(storageUrl);
87
88                 var token = authClient.GetHeaderValue("X-Auth-Token");
89                 if (String.IsNullOrWhiteSpace(token))
90                     throw new InvalidOperationException("Failed to obtain token url");
91                 Token = token;
92             }
93
94             /*_retryPolicy = new RetryPolicy { RetryCount = _retries };
95             _retryPolicy.RetryConditions.Add(new TimeoutRetryCondition());*/
96
97             _client = new PithosClient{
98                 BaseAddress  = StorageUrl.AbsoluteUri,                
99                 Timeout=10000,
100                 Retries=3};
101             if (Proxy!=null)
102                 _client.Proxy = new WebProxy(Proxy);
103             //_client.FileProgress += OnFileProgress;
104             
105             _client.Headers.Add("X-Auth-Token", Token);
106             /*if (UsePithos)
107             {
108                 _client.AddHeader("X-Auth-User", UserName);
109                 _client.AddHeader("X-Auth-Key",ApiKey);                
110             }*/
111
112             Trace.TraceInformation("[AUTHENTICATE] End for {0}", userName);
113         }
114
115        /* private void OnFileProgress(object sender, FileProgressEventArgs e)
116         {
117             Trace.TraceInformation("[PROGRESS] {0} {1:p} {2} of {3}",e.FileName,(double)e.BytesWritten/e.TotalBytes, e.BytesWritten,e.TotalBytes);            
118         }*/
119
120         public IList<ContainerInfo> ListContainers()
121         {                        
122             //Workaround for Hammock quirk: Hammock always
123             //appends a / unless a Path is specified.
124             
125             //Create a request with a complete path
126             //var request = new RestRequest { Path = StorageUrl.ToString(), RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
127             //request.AddParameter("format","json");
128             //Create a client clone
129
130             /*var url = String.Join("/", new[] { _client.Authority, StorageUrl.ToString() });
131             var builder=new UriBuilder(url);
132             builder.Query = "format=json";
133            
134             var client= new PithosClient(_client){Timeout=10};   */         
135             var content=_client.DownloadStringWithRetry("",3);
136             _client.Parameters.Clear();
137             _client.Parameters.Add("format", "json");
138             _client.AssertStatusOK("List Containers failed");
139
140             if (_client.StatusCode==HttpStatusCode.NoContent)
141                 return new List<ContainerInfo>();
142             var infos = JsonConvert.DeserializeObject<IList<ContainerInfo>>(content);
143             return infos;
144
145
146 /*
147             var client = new RestClient{Proxy=Proxy.ToString()};
148             foreach (var header in _client.GetAllHeaders())
149             {
150                 client.AddHeader(header.Name,header.Value);
151             }
152
153             
154
155
156
157             var response = client.Request(request);
158
159             if (response.StatusCode == HttpStatusCode.NoContent)
160                 return new List<ContainerInfo>();
161
162             ThrowIfNotStatusOK(response, "List Containers failed");
163
164
165             var infos=JsonConvert.DeserializeObject<IList<ContainerInfo>>(response.Content);
166             
167             return infos;*/
168         }
169
170         public IList<ObjectInfo> ListObjects(string container)
171         {
172             if (String.IsNullOrWhiteSpace(container))
173                 throw new ArgumentNullException("container", "The container property can't be empty");
174
175             Trace.TraceInformation("[START] ListObjects");
176
177             //var request = new RestRequest { Path = container, RetryPolicy = _retryPolicy, Timeout = TimeSpan.FromMinutes(1) };
178             //request.AddParameter("format", "json");
179             //var response = _client.Request(request);
180
181
182 /*
183             var url = String.Join("/", new[] { _client.Authority, container });
184             var builder = new UriBuilder(url) {Query = "format=json"};
185
186             var client = new PithosClient(_client) { Timeout = 60000 };
187 */
188             _client.Parameters.Clear();
189             _client.Parameters.Add("format", "json");
190             var content = _client.DownloadStringWithRetry(container, 3);
191
192             _client.AssertStatusOK("ListObjects failed");
193
194             var infos = JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
195
196             Trace.TraceInformation("[END] ListObjects");
197             return infos;
198         }
199
200
201
202         public IList<ObjectInfo> ListObjects(string container,string folder)
203         {
204             if (String.IsNullOrWhiteSpace(container))
205                 throw new ArgumentNullException("container", "The container property can't be empty");
206
207             Trace.TraceInformation("[START] ListObjects");
208
209            /* var request = new RestRequest { Path = container,RetryPolicy = _retryPolicy, Timeout = TimeSpan.FromMinutes(1) };
210             request.AddParameter("format", "json");
211             request.AddParameter("path", folder);*/
212             
213             _client.Parameters.Clear();
214             _client.Parameters.Add("format", "json");
215             _client.Parameters.Add("path", folder);
216             var content = _client.DownloadStringWithRetry(container, 3);
217             _client.AssertStatusOK("ListObjects failed");
218
219             var infos = JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
220
221            /* var response = _client.Request(request);
222             
223             var infos = InfosFromContent(response);*/
224
225             Trace.TraceInformation("[END] ListObjects");
226             return infos;
227         }
228
229  /*       private static IList<ObjectInfo> InfosFromContent(RestResponse response)
230         {
231             if (response.TimedOut)
232                 return new List<ObjectInfo>();
233
234             if (response.StatusCode == 0)
235                 return new List<ObjectInfo>();
236
237             if (response.StatusCode == HttpStatusCode.NoContent)
238                 return new List<ObjectInfo>();
239
240
241             var statusCode = (int)response.StatusCode;
242             if (statusCode < 200 || statusCode >= 300)
243             {
244                 Trace.TraceWarning("ListObjects failed with code {0} - {1}", response.StatusCode, response.StatusDescription);
245                 return new List<ObjectInfo>();
246             }
247
248             var infos = JsonConvert.DeserializeObject<IList<ObjectInfo>>(response.Content);
249             return infos;
250         }
251 */
252         public bool ContainerExists(string container)
253         {
254             if (String.IsNullOrWhiteSpace(container))
255                 throw new ArgumentNullException("container", "The container property can't be empty");
256
257             _client.Parameters.Clear();
258             _client.Head(container,3);
259             //var request = new RestRequest { Path = container, Method = WebMethod.Head, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };            
260             //var response = _client.Request(request);
261
262             switch (_client.StatusCode)
263             {
264                 case HttpStatusCode.OK:
265                 case HttpStatusCode.NoContent:
266                     return true;
267                 case HttpStatusCode.NotFound:
268                     return false;          
269                 default:
270                     throw CreateWebException("ContainerExists", _client.StatusCode);
271             }
272         }
273
274         public bool ObjectExists(string container,string objectName)
275         {
276             if (String.IsNullOrWhiteSpace(container))
277                 throw new ArgumentNullException("container", "The container property can't be empty");
278             if (String.IsNullOrWhiteSpace(objectName))
279                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
280
281
282 /*
283             var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Head,RetryPolicy = _retryPolicy, Timeout = _shortTimeout };
284             var response = _client.Request(request);
285 */
286             _client.Parameters.Clear();
287             _client.Head(container + "/" + objectName, 3);
288
289             switch (_client.StatusCode)
290             {
291                 case HttpStatusCode.OK:
292                 case HttpStatusCode.NoContent:
293                     return true;
294                 case HttpStatusCode.NotFound:
295                     return false;
296                 default:
297                     throw CreateWebException("ObjectExists", _client.StatusCode);
298             }
299             
300         }
301
302         public ObjectInfo GetObjectInfo(string container, string objectName)
303         {
304             if (String.IsNullOrWhiteSpace(container))
305                 throw new ArgumentNullException("container", "The container property can't be empty");
306             if (String.IsNullOrWhiteSpace(objectName))
307                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
308
309
310 /*
311             var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Head, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
312             var response = _client.Request(request);
313 */
314             try
315             {
316                 _client.Parameters.Clear();
317
318                 _client.Head(container + "/" + objectName, 3);
319
320                 if (_client.TimedOut)
321                     return ObjectInfo.Empty;
322
323                 switch (_client.StatusCode)
324                 {
325                     case HttpStatusCode.OK:
326                     case HttpStatusCode.NoContent:
327                         var keys = _client.ResponseHeaders.AllKeys.AsQueryable();
328                         var tags = (from key in keys
329                                     where key.StartsWith("X-Object-Meta-")
330                                     let name = key.Substring(14)
331                                     select new { Name = name, Value = _client.ResponseHeaders[name] })
332                             .ToDictionary(t => t.Name, t => t.Value);
333                         var extensions = (from key in keys
334                                           where key.StartsWith("X-Object-") && !key.StartsWith("X-Object-Meta-")
335                                           let name = key.Substring(9)
336                                           select new { Name = name, Value = _client.ResponseHeaders[name] })
337                             .ToDictionary(t => t.Name, t => t.Value);
338                         return new ObjectInfo
339                                    {
340                                        Name = objectName,
341                                        Bytes =
342                                            long.Parse(_client.GetHeaderValue("Content-Length")),
343                                        Hash = _client.GetHeaderValue("ETag"),
344                                        Content_Type = _client.GetHeaderValue("Content-Type"),
345                                        Tags = tags,
346                                        Extensions = extensions
347                                    };
348                     case HttpStatusCode.NotFound:
349                         return ObjectInfo.Empty;
350                     default:
351                         throw new WebException(
352                             String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}",
353                                           objectName, _client.StatusCode));
354                 }
355             }
356             catch (RetryException e)
357             {
358                 Trace.TraceWarning("[RETRY FAIL] GetObjectInfo for {0} failed.");
359                 return ObjectInfo.Empty;
360             }
361             catch (WebException e)
362             {                
363                 Trace.TraceError(String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}",
364                                               objectName, _client.StatusCode), e);
365                 throw;
366             }
367             
368         }
369
370         public void CreateFolder(string container, string folder)
371         {
372             if (String.IsNullOrWhiteSpace(container))
373                 throw new ArgumentNullException("container", "The container property can't be empty");
374             if (String.IsNullOrWhiteSpace(folder))
375                 throw new ArgumentNullException("folder", "The folder property can't be empty");
376
377             var folderUrl=String.Format("{0}/{1}",container,folder);
378 /*
379             var request = new RestRequest { Path = folderUrl, Method = WebMethod.Put, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
380             request.AddHeader("Content-Type", @"application/directory");
381             request.AddHeader("Content-Length", "0");
382 */
383             
384             _client.Parameters.Clear();
385             _client.Headers.Add("Content-Type", @"application/directory");
386             _client.Headers.Add("Content-Length", "0");
387             _client.PutWithRetry(folderUrl,3);
388
389             if (_client.StatusCode != HttpStatusCode.Created && _client.StatusCode != HttpStatusCode.Accepted)
390                 throw CreateWebException("CreateFolder", _client.StatusCode);
391
392         }
393
394         public ContainerInfo GetContainerInfo(string container)
395         {
396             if (String.IsNullOrWhiteSpace(container))
397                 throw new ArgumentNullException("container", "The container property can't be empty");
398
399 /*
400             var request = new RestRequest { Path = container, Method = WebMethod.Head, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
401             var response = _client.Request(request);
402 */
403             _client.Head(container);
404             switch (_client.StatusCode)
405             {
406                 case HttpStatusCode.NoContent:
407                     var containerInfo = new ContainerInfo
408                                             {
409                                                 Name = container,
410                                                 Count =long.Parse(_client.GetHeaderValue("X-Container-Object-Count")),
411                                                 Bytes = long.Parse(_client.GetHeaderValue("X-Container-Bytes-Used"))
412                                             };
413                     return containerInfo;
414                 case HttpStatusCode.NotFound:
415                     return ContainerInfo.Empty;                    
416                 default:
417                     throw CreateWebException("GetContainerInfo", _client.StatusCode);
418             }
419         }
420
421         public void CreateContainer(string container)
422         {
423             if (String.IsNullOrWhiteSpace(container))
424                 throw new ArgumentNullException("container", "The container property can't be empty");
425
426 /*
427             var request = new RestRequest { Path = container, Method = WebMethod.Put, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };            
428             var response = _client.Request(request);
429 */
430             _client.PutWithRetry(container,3);
431             var expectedCodes = new[]{HttpStatusCode.Created ,HttpStatusCode.Accepted , HttpStatusCode.OK};
432             if (!expectedCodes.Contains(_client.StatusCode))
433                 throw CreateWebException("CreateContainer", _client.StatusCode);
434         }
435
436         public void DeleteContainer(string container)
437         {
438             if (String.IsNullOrWhiteSpace(container))
439                 throw new ArgumentNullException("container", "The container property can't be empty");
440
441 /*
442             var request = new RestRequest { Path = container, Method = WebMethod.Delete, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
443             var response = _client.Request(request);
444 */
445             _client.DeleteWithRetry(container,3);
446             var expectedCodes = new[] { HttpStatusCode.NotFound, HttpStatusCode.NoContent};
447             if (!expectedCodes.Contains(_client.StatusCode))
448                 throw CreateWebException("DeleteContainer", _client.StatusCode);
449
450         }
451
452         /// <summary>
453         /// 
454         /// </summary>
455         /// <param name="container"></param>
456         /// <param name="objectName"></param>
457         /// <param name="fileName"></param>
458         /// <returns></returns>
459         /// <remarks>>This method should have no timeout or a very long one</remarks>
460         public Task GetObject(string container, string objectName, string fileName)
461         {
462             if (String.IsNullOrWhiteSpace(container))
463                 throw new ArgumentNullException("container", "The container property can't be empty");
464             if (String.IsNullOrWhiteSpace(objectName))
465                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
466             /*
467            var request = new RestRequest {Path = container + "/" + objectName, Method = WebMethod.Get};
468            
469                        if (DownloadPercentLimit > 0)
470                            request.TaskOptions = new TaskOptions<int> { RateLimitPercent = DownloadPercentLimit };
471            */
472             try
473             {
474                 var url = String.Join("/", _client.BaseAddress, container, objectName);
475                 var uri = new Uri(url);
476
477                 var client = new PithosClient(_client){Timeout=0};
478                /* if (!String.IsNullOrWhiteSpace(_client.Proxy))
479                     client.Proxy = new WebProxy(_client.Proxy);
480
481                 CopyHeaders(_client, client);*/
482
483                 Trace.TraceInformation("[GET] START {0}", objectName);
484                 client.DownloadProgressChanged += (sender, args) => 
485                     Trace.TraceInformation("[GET PROGRESS] {0} {1}% {2} of {3}",
486                                     fileName, args.ProgressPercentage,
487                                     args.BytesReceived,
488                                     args.TotalBytesToReceive);
489                 
490                 return _client.DownloadFileTask(uri, fileName)
491                     .ContinueWith(download =>
492                                       {
493                                           client.Dispose();
494
495                                           if (download.IsFaulted)
496                                           {
497                                               Trace.TraceError("[GET] FAIL for {0} with \r{1}", objectName,
498                                                                download.Exception);
499                                           }
500                                           else
501                                           {
502                                               Trace.TraceInformation("[GET] END {0}", objectName);                                             
503                                           }
504                                       });
505             }
506             catch (Exception exc)
507             {
508                 Trace.TraceError("[GET] END {0} with {1}", objectName, exc);
509                 throw;
510             }
511
512
513
514         }
515
516         /// <summary>
517         /// 
518         /// </summary>
519         /// <param name="container"></param>
520         /// <param name="objectName"></param>
521         /// <param name="fileName"></param>
522         /// <param name="hash">Optional hash value for the file. If no hash is provided, the method calculates a new hash</param>
523         /// <remarks>>This method should have no timeout or a very long one</remarks>
524         public Task PutObject(string container, string objectName, string fileName, string hash = null)
525         {
526             if (String.IsNullOrWhiteSpace(container))
527                 throw new ArgumentNullException("container", "The container property can't be empty");
528             if (String.IsNullOrWhiteSpace(objectName))
529                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
530             if (String.IsNullOrWhiteSpace(fileName))
531                 throw new ArgumentNullException("fileName", "The fileName property can't be empty");
532             if (!File.Exists(fileName))
533                 throw new FileNotFoundException("The file does not exist",fileName);
534
535             
536             try
537             {
538                 var url = String.Join("/",_client.BaseAddress,container,objectName);
539                 var uri = new Uri(url);
540
541                 var client = new PithosClient(_client){Timeout=0};           
542                 string etag = hash ?? CalculateHash(fileName);
543
544                 client.Headers.Add("Content-Type", "application/octet-stream");
545                 client.Headers.Add("ETag", etag);
546
547 /*
548                 if(!String.IsNullOrWhiteSpace(_client.Proxy))
549                     client.Proxy = new WebProxy(_client.Proxy);
550
551                 CopyHeaders(_client, client);
552 */
553
554                 Trace.TraceInformation("[PUT] START {0}", objectName);
555                 client.UploadProgressChanged += (sender, args) =>
556                 {
557                     Trace.TraceInformation("[PUT PROGRESS] {0} {1}% {2} of {3}", fileName, args.ProgressPercentage, args.BytesSent, args.TotalBytesToSend);
558                 };
559                
560                 return client.UploadFileTask(uri, "PUT", fileName)
561                     .ContinueWith(upload=>
562                                       {
563                                           client.Dispose();
564
565                                           if (upload.IsFaulted)
566                                           {                                              
567                                               Trace.TraceError("[PUT] FAIL for {0} with \r{1}",objectName,upload.Exception);
568                                           }
569                                           else
570                                             Trace.TraceInformation("[PUT] END {0}", objectName);
571                                       });
572             }
573             catch (Exception exc)
574             {
575                 Trace.TraceError("[PUT] END {0} with {1}", objectName, exc);
576                 throw;
577             }                
578
579         }
580        
581 /*
582         /// <summary>
583         /// Copies headers from a Hammock RestClient to a WebClient
584         /// </summary>
585         /// <param name="source">The RestClient from which the headers are copied</param>
586         /// <param name="target">The WebClient to which the headers are copied</param>
587         private static void CopyHeaders(RestClient source, WebClient target)
588         {
589             Contract.Requires(source!=null,"source can't be null");
590             Contract.Requires(target != null, "target can't be null");
591             if (source == null)
592                 throw new ArgumentNullException("source", "source can't be null");
593             if (source == null)
594                 throw new ArgumentNullException("target", "target can't be null");
595
596             foreach (var header in source.GetAllHeaders())
597             {
598                 target.Headers.Add(header.Name, header.Value);
599             }
600         }*/
601
602        /* /// <summary>
603         /// Copies headers from a Hammock RestClient to a WebClient
604         /// </summary>
605         /// <param name="source">The RestClient from which the headers are copied</param>
606         /// <param name="target">The WebClient to which the headers are copied</param>
607         private static void CopyHeaders(RestClient source, WebRequest target)
608         {
609             Contract.Requires(source!=null,"source can't be null");
610             Contract.Requires(target != null, "target can't be null");
611             if (source == null)
612                 throw new ArgumentNullException("source", "source can't be null");
613             if (source == null)
614                 throw new ArgumentNullException("target", "target can't be null");
615
616             foreach (var header in source.GetAllHeaders())
617             {
618                 target.Headers.Add(header.Name, header.Value);
619             }
620         }*/
621
622         
623         private static string CalculateHash(string fileName)
624         {
625             string hash;
626             using (var hasher = MD5.Create())
627             using(var stream=File.OpenRead(fileName))
628             {
629                 var hashBuilder=new StringBuilder();
630                 foreach (byte b in hasher.ComputeHash(stream))
631                     hashBuilder.Append(b.ToString("x2").ToLower());
632                 hash = hashBuilder.ToString();                
633             }
634             return hash;
635         }
636
637         public void DeleteObject(string container, string objectName)
638         {
639             if (String.IsNullOrWhiteSpace(container))
640                 throw new ArgumentNullException("container", "The container property can't be empty");
641             if (String.IsNullOrWhiteSpace(objectName))
642                 throw new ArgumentNullException("objectName", "The objectName property can't be empty");
643
644 /*
645             var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Delete, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
646             var response = _client.Request(request);
647 */
648             _client.DeleteWithRetry(container + "/" + objectName,3);
649
650             var expectedCodes = new[] { HttpStatusCode.NotFound, HttpStatusCode.NoContent };
651             if (!expectedCodes.Contains(_client.StatusCode))
652                 throw CreateWebException("DeleteObject", _client.StatusCode);
653    
654         }
655
656         public void MoveObject(string sourceContainer, string oldObjectName, string targetContainer,string newObjectName)
657         {
658             if (String.IsNullOrWhiteSpace(sourceContainer))
659                 throw new ArgumentNullException("sourceContainer", "The container property can't be empty");
660             if (String.IsNullOrWhiteSpace(oldObjectName))
661                 throw new ArgumentNullException("oldObjectName", "The oldObjectName property can't be empty");
662             if (String.IsNullOrWhiteSpace(targetContainer))
663                 throw new ArgumentNullException("targetContainer", "The container property can't be empty");
664             if (String.IsNullOrWhiteSpace(newObjectName))
665                 throw new ArgumentNullException("newObjectName", "The newObjectName property can't be empty");
666
667             var targetUrl = targetContainer + "/" + newObjectName;
668             var sourceUrl = String.Format("/{0}/{1}", sourceContainer, oldObjectName);
669
670 /*
671             var request = new RestRequest { Path = targetUrl, Method = WebMethod.Put };
672             request.AddHeader("X-Copy-From",sourceUrl);
673             request.AddPostContent(new byte[]{});
674             var response = _client.Request(request);
675 */
676
677             var client = new PithosClient(_client);
678             client.Headers.Add("X-Copy-From", sourceUrl);
679             client.PutWithRetry(targetUrl,3);
680
681             var expectedCodes = new[] { HttpStatusCode.OK ,HttpStatusCode.NoContent ,HttpStatusCode.Created };
682             if (expectedCodes.Contains(client.StatusCode))
683             {
684                 this.DeleteObject(sourceContainer,oldObjectName);
685             }                
686             else
687                 throw CreateWebException("MoveObject", client.StatusCode);
688         }
689
690       
691
692         /*private string GetHeaderValue(string headerName, WebHeaderCollection headers, IQueryable<string> keys)
693         {
694             if (keys.Any(key => key == headerName))
695                 return headers[headerName];
696             else
697                 throw new WebException(String.Format("The {0}  header is missing", headerName));
698         }*/
699
700        /* private static void ThrowIfNotStatusOK(RestResponse response, string message)
701         {
702             int status = (int)response.StatusCode;
703             ThrowIfNotStatusOK(status, message);
704         }
705
706         private static void ThrowIfNotStatusOK(HttpWebResponse response, string message)
707         {
708             int status = (int)response.StatusCode;
709             ThrowIfNotStatusOK(status, message);
710         }
711
712         private static void ThrowIfNotStatusOK(int status, string message)
713         {
714             if (status < 200 || status >= 300)
715                 throw new WebException(String.Format("{0} with code {1}", message, status));
716         }
717         */
718         private static WebException CreateWebException(string operation, HttpStatusCode statusCode)
719         {
720             return new WebException(String.Format("{0} failed with unexpected status code {1}", operation, statusCode));
721         }
722
723        /* public static Func<T> Retry<T>(Func<int,T> original, int retryCount,int timeout)
724         {
725             return () =>
726             {
727                 while (true)
728                 {
729                     try
730                     {
731                         return original(timeout);
732                     }
733                     catch (WebException e)
734                     {
735                         if (e.Status == WebExceptionStatus.Timeout)
736                         {
737                             if (retryCount == 0)
738                             {
739                                 throw;
740                             }
741                             retryCount--;
742                         }
743                         else
744                         {
745                             throw;
746                         }
747                     }
748                     catch (Exception e)
749                     {                                   
750                             throw;                        
751                     }
752                 }
753             };
754         } */
755
756
757     }
758 }