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