Revision b5061ac8 trunk/Pithos.Network/CloudFilesClient.cs
b/trunk/Pithos.Network/CloudFilesClient.cs | ||
---|---|---|
8 | 8 |
using System.Net; |
9 | 9 |
using System.Security.Cryptography; |
10 | 10 |
using System.Text; |
11 |
using System.Threading.Tasks; |
|
11 | 12 |
using Hammock; |
12 | 13 |
using Hammock.Caching; |
13 | 14 |
using Hammock.Retries; |
... | ... | |
28 | 29 |
private RestClient _client; |
29 | 30 |
private readonly TimeSpan _shortTimeout = TimeSpan.FromSeconds(10); |
30 | 31 |
private readonly int _retries = 5; |
32 |
private RetryPolicy _retryPolicy; |
|
31 | 33 |
public string ApiKey { get; set; } |
32 | 34 |
public string UserName { get; set; } |
33 | 35 |
public Uri StorageUrl { get; set; } |
34 | 36 |
public string Token { get; set; } |
35 | 37 |
public Uri Proxy { get; set; } |
38 |
|
|
39 |
public double DownloadPercentLimit { get; set; } |
|
40 |
public double UploadPercentLimit { get; set; } |
|
36 | 41 |
|
37 | 42 |
public string AuthUrl |
38 | 43 |
{ |
... | ... | |
48 | 53 |
|
49 | 54 |
public void Authenticate(string userName,string apiKey) |
50 | 55 |
{ |
56 |
Trace.TraceInformation("[AUTHENTICATE] Start for {0}",userName); |
|
51 | 57 |
if (String.IsNullOrWhiteSpace(userName)) |
52 | 58 |
throw new ArgumentNullException("userName","The userName property can't be empty"); |
53 | 59 |
if (String.IsNullOrWhiteSpace(apiKey)) |
... | ... | |
91 | 97 |
else |
92 | 98 |
Token = "0000"; |
93 | 99 |
|
94 |
var retryPolicy = new RetryPolicy { RetryCount = _retries };
|
|
95 |
retryPolicy.RetryConditions.Add(new TimeoutRetryCondition()); |
|
100 |
_retryPolicy = new RetryPolicy { RetryCount = _retries };
|
|
101 |
_retryPolicy.RetryConditions.Add(new TimeoutRetryCondition());
|
|
96 | 102 |
|
97 |
_client = new RestClient { Authority = StorageUrl.AbsoluteUri, Path = UserName, Proxy = proxy, RetryPolicy = retryPolicy, }; |
|
103 |
_client = new RestClient { Authority = StorageUrl.AbsoluteUri, Path = UserName, Proxy = proxy }; |
|
104 |
_client.FileProgress += OnFileProgress; |
|
98 | 105 |
|
99 | 106 |
_client.AddHeader("X-Auth-Token", Token); |
100 | 107 |
if (UsePithos) |
... | ... | |
103 | 110 |
_client.AddHeader("X-Auth-Key",ApiKey); |
104 | 111 |
} |
105 | 112 |
|
113 |
Trace.TraceInformation("[AUTHENTICATE] End for {0}", userName); |
|
114 |
} |
|
106 | 115 |
|
116 |
private void OnFileProgress(object sender, FileProgressEventArgs e) |
|
117 |
{ |
|
118 |
Trace.TraceInformation("[PROGRESS] {0} {1:p} {2} of {3}",e.FileName,(double)e.BytesWritten/e.TotalBytes, e.BytesWritten,e.TotalBytes); |
|
107 | 119 |
} |
108 | 120 |
|
109 | 121 |
public IList<ContainerInfo> ListContainers() |
... | ... | |
112 | 124 |
//appends a / unless a Path is specified. |
113 | 125 |
|
114 | 126 |
//Create a request with a complete path |
115 |
var request = new RestRequest { Path = StorageUrl.ToString(), Timeout = _shortTimeout }; |
|
127 |
var request = new RestRequest { Path = StorageUrl.ToString(), RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
|
|
116 | 128 |
request.AddParameter("format","json"); |
117 | 129 |
//Create a client clone |
118 | 130 |
var client = new RestClient{Proxy=Proxy.ToString()}; |
... | ... | |
139 | 151 |
if (String.IsNullOrWhiteSpace(container)) |
140 | 152 |
throw new ArgumentNullException("container", "The container property can't be empty"); |
141 | 153 |
|
142 |
var request = new RestRequest { Path = container, Timeout = _shortTimeout }; |
|
154 |
Trace.TraceInformation("[START] ListObjects"); |
|
155 |
|
|
156 |
var request = new RestRequest { Path = container, RetryPolicy = _retryPolicy, Timeout = TimeSpan.FromMinutes(1) }; |
|
143 | 157 |
request.AddParameter("format", "json"); |
144 | 158 |
var response = _client.Request(request); |
145 | 159 |
|
146 | 160 |
var infos = InfosFromContent(response); |
147 | 161 |
|
162 |
Trace.TraceInformation("[END] ListObjects"); |
|
148 | 163 |
return infos; |
149 | 164 |
} |
150 | 165 |
|
... | ... | |
155 | 170 |
if (String.IsNullOrWhiteSpace(container)) |
156 | 171 |
throw new ArgumentNullException("container", "The container property can't be empty"); |
157 | 172 |
|
158 |
var request = new RestRequest { Path = container, Timeout = _shortTimeout }; |
|
173 |
Trace.TraceInformation("[START] ListObjects"); |
|
174 |
|
|
175 |
var request = new RestRequest { Path = container,RetryPolicy = _retryPolicy, Timeout = TimeSpan.FromMinutes(1) }; |
|
159 | 176 |
request.AddParameter("format", "json"); |
160 | 177 |
request.AddParameter("path", folder); |
161 | 178 |
var response = _client.Request(request); |
162 | 179 |
|
163 | 180 |
var infos = InfosFromContent(response); |
164 | 181 |
|
182 |
Trace.TraceInformation("[END] ListObjects"); |
|
165 | 183 |
return infos; |
166 | 184 |
} |
167 | 185 |
|
... | ... | |
193 | 211 |
if (String.IsNullOrWhiteSpace(container)) |
194 | 212 |
throw new ArgumentNullException("container", "The container property can't be empty"); |
195 | 213 |
|
196 |
var request = new RestRequest { Path = container, Method = WebMethod.Head, Timeout = _shortTimeout }; |
|
214 |
var request = new RestRequest { Path = container, Method = WebMethod.Head, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
|
|
197 | 215 |
var response = _client.Request(request); |
198 | 216 |
|
199 | 217 |
switch(response.StatusCode) |
... | ... | |
215 | 233 |
throw new ArgumentNullException("objectName", "The objectName property can't be empty"); |
216 | 234 |
|
217 | 235 |
|
218 |
var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Head, Timeout = _shortTimeout }; |
|
236 |
var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Head,RetryPolicy = _retryPolicy, Timeout = _shortTimeout };
|
|
219 | 237 |
var response = _client.Request(request); |
220 | 238 |
|
221 | 239 |
switch (response.StatusCode) |
... | ... | |
239 | 257 |
throw new ArgumentNullException("objectName", "The objectName property can't be empty"); |
240 | 258 |
|
241 | 259 |
|
242 |
var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Head, Timeout = _shortTimeout }; |
|
260 |
var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Head, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
|
|
243 | 261 |
var response = _client.Request(request); |
244 | 262 |
|
245 | 263 |
if (response.TimedOut) |
... | ... | |
252 | 270 |
var keys = response.Headers.AllKeys.AsQueryable(); |
253 | 271 |
return new ObjectInfo |
254 | 272 |
{ |
255 |
Name=objectName,
|
|
273 |
Name = objectName,
|
|
256 | 274 |
Bytes = long.Parse(GetHeaderValue("Content-Length", response, keys)), |
257 | 275 |
Hash = GetHeaderValue("ETag", response, keys), |
258 | 276 |
Content_Type = GetHeaderValue("Content-Type", response, keys) |
... | ... | |
260 | 278 |
case HttpStatusCode.NotFound: |
261 | 279 |
return ObjectInfo.Empty; |
262 | 280 |
default: |
263 |
throw new WebException(String.Format("GetObjectInfo failed with unexpected status code {0}", response.StatusCode)); |
|
281 |
if (request.RetryState.RepeatCount > 0) |
|
282 |
{ |
|
283 |
Trace.TraceWarning("[RETRY FAIL] GetObjectInfo for {0} failed after {1} retries", |
|
284 |
objectName, request.RetryState.RepeatCount); |
|
285 |
return ObjectInfo.Empty; |
|
286 |
} |
|
287 |
if (response.InnerException != null) |
|
288 |
throw new WebException(String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}", objectName, response.StatusCode), response.InnerException); |
|
289 |
throw new WebException(String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}", objectName, response.StatusCode)); |
|
264 | 290 |
} |
265 | 291 |
} |
266 | 292 |
|
... | ... | |
272 | 298 |
throw new ArgumentNullException("folder", "The folder property can't be empty"); |
273 | 299 |
|
274 | 300 |
var folderUrl=String.Format("{0}/{1}",container,folder); |
275 |
var request = new RestRequest { Path = folderUrl, Method = WebMethod.Put, Timeout = _shortTimeout }; |
|
301 |
var request = new RestRequest { Path = folderUrl, Method = WebMethod.Put, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
|
|
276 | 302 |
request.AddHeader("Content-Type", @"application/directory"); |
277 | 303 |
request.AddHeader("Content-Length", "0"); |
278 | 304 |
|
... | ... | |
288 | 314 |
if (String.IsNullOrWhiteSpace(container)) |
289 | 315 |
throw new ArgumentNullException("container", "The container property can't be empty"); |
290 | 316 |
|
291 |
var request = new RestRequest { Path = container, Method = WebMethod.Head, Timeout = _shortTimeout }; |
|
317 |
var request = new RestRequest { Path = container, Method = WebMethod.Head, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
|
|
292 | 318 |
var response = _client.Request(request); |
293 | 319 |
|
294 | 320 |
switch(response.StatusCode) |
... | ... | |
314 | 340 |
if (String.IsNullOrWhiteSpace(container)) |
315 | 341 |
throw new ArgumentNullException("container", "The container property can't be empty"); |
316 | 342 |
|
317 |
var request = new RestRequest { Path = container, Method = WebMethod.Put, Timeout = _shortTimeout }; |
|
343 |
var request = new RestRequest { Path = container, Method = WebMethod.Put, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
|
|
318 | 344 |
|
319 | 345 |
var response = _client.Request(request); |
320 | 346 |
|
... | ... | |
327 | 353 |
if (String.IsNullOrWhiteSpace(container)) |
328 | 354 |
throw new ArgumentNullException("container", "The container property can't be empty"); |
329 | 355 |
|
330 |
var request = new RestRequest { Path = container, Method = WebMethod.Delete, Timeout = _shortTimeout }; |
|
356 |
var request = new RestRequest { Path = container, Method = WebMethod.Delete, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
|
|
331 | 357 |
var response = _client.Request(request); |
332 | 358 |
|
333 | 359 |
if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.NoContent) |
... | ... | |
352 | 378 |
throw new ArgumentNullException("objectName", "The objectName property can't be empty"); |
353 | 379 |
|
354 | 380 |
var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Get }; |
381 |
/* |
|
382 |
if (DownloadPercentLimit > 0) |
|
383 |
request.TaskOptions = new TaskOptions<int> { RateLimitPercent = DownloadPercentLimit }; |
|
384 |
*/ |
|
385 |
|
|
355 | 386 |
var response = _client.Request(request); |
356 | 387 |
|
357 | 388 |
if (response.StatusCode == HttpStatusCode.NotFound) |
... | ... | |
370 | 401 |
/// <param name="container"></param> |
371 | 402 |
/// <param name="objectName"></param> |
372 | 403 |
/// <param name="fileName"></param> |
404 |
/// <param name="hash">Optional hash value for the file. If no hash is provided, the method calculates a new hash</param> |
|
373 | 405 |
/// <remarks>>This method should have no timeout or a very long one</remarks> |
374 |
public void PutObject(string container, string objectName, string fileName)
|
|
406 |
public Task PutObject(string container, string objectName, string fileName, string hash = null)
|
|
375 | 407 |
{ |
376 | 408 |
if (String.IsNullOrWhiteSpace(container)) |
377 | 409 |
throw new ArgumentNullException("container", "The container property can't be empty"); |
... | ... | |
386 | 418 |
string url = container + "/" + objectName; |
387 | 419 |
|
388 | 420 |
var request = new RestRequest {Path=url,Method=WebMethod.Put}; |
389 |
request.TaskOptions=new TaskOptions<int>{RateLimitPercent=0.5}; |
|
390 | 421 |
|
391 |
string hash = CalculateHash(fileName); |
|
392 |
|
|
393 |
request.AddPostContent(File.ReadAllBytes(fileName)); |
|
422 |
/* |
|
423 |
if(UploadPercentLimit>0) |
|
424 |
request.TaskOptions=new TaskOptions<int>{RateLimitPercent=UploadPercentLimit}; |
|
425 |
*/ |
|
426 |
Trace.TraceInformation("[PUT] START {0}",objectName); |
|
427 |
string etag = hash??CalculateHash(fileName); |
|
428 |
request.AddFile(fileName, fileName, fileName); |
|
429 |
//request.AddPostContent(File.ReadAllBytes(fileName)); |
|
394 | 430 |
request.AddHeader("Content-Type","application/octet-stream"); |
395 |
request.AddHeader("ETag",hash); |
|
396 |
var response=_client.Request(request); |
|
397 |
_client.TaskOptions = new TaskOptions<int> {RateLimitPercent = 0.5}; |
|
398 |
if (response.StatusCode == HttpStatusCode.Created) |
|
399 |
return; |
|
400 |
if (response.StatusCode == HttpStatusCode.LengthRequired) |
|
401 |
throw new InvalidOperationException(); |
|
402 |
else |
|
403 |
throw new WebException(String.Format("GetObject failed with unexpected status code {0}", response.StatusCode)); |
|
431 |
request.AddHeader("ETag", etag); |
|
432 |
//_client.TaskOptions = new TaskOptions<int> {RateLimitPercent = 0.5}; |
|
433 |
try |
|
434 |
{ |
|
435 |
|
|
436 |
var response=_client.Request(request); |
|
437 |
Trace.TraceInformation("[PUT] END {0}", objectName); |
|
438 |
|
|
439 |
if (response.StatusCode == HttpStatusCode.Created) |
|
440 |
return Task.Factory.StartNew(()=>{}); |
|
441 |
if (response.StatusCode == HttpStatusCode.LengthRequired) |
|
442 |
throw new InvalidOperationException(); |
|
443 |
else |
|
444 |
throw new WebException(String.Format("GetObject failed with unexpected status code {0}", |
|
445 |
response.StatusCode)); |
|
446 |
/* return Task.Factory.FromAsync(_client.BeginRequest(request),ar=>_client.EndRequest(ar)) |
|
447 |
.ContinueWith(t=> |
|
448 |
{*/ |
|
449 |
} |
|
450 |
catch (Exception exc) |
|
451 |
{ |
|
452 |
Trace.TraceError("[PUT] END {0} with {1}", objectName, exc); |
|
453 |
throw; |
|
454 |
} |
|
455 |
|
|
456 |
/* |
|
457 |
var response = t.Result; |
|
458 |
if (t.IsFaulted) |
|
459 |
Trace.TraceError("[PUT] END {0} with {1}", objectName, t.Exception); |
|
460 |
else |
|
461 |
{ |
|
462 |
Trace.TraceInformation("[PUT] END {0}",objectName); |
|
463 |
} |
|
464 |
*/ |
|
465 |
/* if (response.StatusCode == HttpStatusCode.Created) |
|
466 |
return; |
|
467 |
if (response.StatusCode == HttpStatusCode.LengthRequired) |
|
468 |
throw new InvalidOperationException(); |
|
469 |
else |
|
470 |
throw new WebException(String.Format("GetObject failed with unexpected status code {0}", |
|
471 |
response.StatusCode));*/ |
|
472 |
/*});*/ |
|
404 | 473 |
} |
405 | 474 |
|
406 | 475 |
private static string CalculateHash(string fileName) |
... | ... | |
424 | 493 |
if (String.IsNullOrWhiteSpace(objectName)) |
425 | 494 |
throw new ArgumentNullException("objectName", "The objectName property can't be empty"); |
426 | 495 |
|
427 |
var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Delete, Timeout=_shortTimeout };
|
|
496 |
var request = new RestRequest { Path = container + "/" + objectName, Method = WebMethod.Delete, RetryPolicy = _retryPolicy,Timeout = _shortTimeout };
|
|
428 | 497 |
var response = _client.Request(request); |
429 | 498 |
|
430 | 499 |
if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.NoContent) |
Also available in: Unified diff