Statistics
| Branch: | Revision:

root / trunk / Pithos.Network / CloudFilesClient.cs @ 759bd3c4

History | View | Annotate | Download (58.4 kB)

1
#region
2
/* -----------------------------------------------------------------------
3
 * <copyright file="CloudFilesClient.cs" company="GRNet">
4
 * 
5
 * Copyright 2011-2012 GRNET S.A. All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or
8
 * without modification, are permitted provided that the following
9
 * conditions are met:
10
 *
11
 *   1. Redistributions of source code must retain the above
12
 *      copyright notice, this list of conditions and the following
13
 *      disclaimer.
14
 *
15
 *   2. Redistributions in binary form must reproduce the above
16
 *      copyright notice, this list of conditions and the following
17
 *      disclaimer in the documentation and/or other materials
18
 *      provided with the distribution.
19
 *
20
 *
21
 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
 * POSSIBILITY OF SUCH DAMAGE.
33
 *
34
 * The views and conclusions contained in the software and
35
 * documentation are those of the authors and should not be
36
 * interpreted as representing official policies, either expressed
37
 * or implied, of GRNET S.A.
38
 * </copyright>
39
 * -----------------------------------------------------------------------
40
 */
41
#endregion
42

    
43
// **CloudFilesClient** provides a simple client interface to CloudFiles and Pithos
44
//
45
// The class provides methods to upload/download files, delete files, manage containers
46

    
47

    
48
using System;
49
using System.Collections.Generic;
50
using System.Collections.Specialized;
51
using System.ComponentModel.Composition;
52
using System.Diagnostics;
53
using System.Diagnostics.Contracts;
54
using System.IO;
55
using System.Linq;
56
using System.Net;
57
using System.Security.Cryptography;
58
using System.Text;
59
using System.Threading.Tasks;
60
using Newtonsoft.Json;
61
using Pithos.Interfaces;
62
using log4net;
63

    
64
namespace Pithos.Network
65
{
66
    [Export(typeof(ICloudClient))]
67
    public class CloudFilesClient:ICloudClient
68
    {
69
        //CloudFilesClient uses *_baseClient* internally to communicate with the server
70
        //RestClient provides a REST-friendly interface over the standard WebClient.
71
        private RestClient _baseClient;
72
        
73

    
74
        //During authentication the client provides a UserName 
75
        public string UserName { get; set; }
76
        
77
        //and and ApiKey to the server
78
        public string ApiKey { get; set; }
79
        
80
        //And receives an authentication Token. This token must be provided in ALL other operations,
81
        //in the X-Auth-Token header
82
        private string _token;
83
        public string Token
84
        {
85
            get { return _token; }
86
            set
87
            {
88
                _token = value;
89
                _baseClient.Headers["X-Auth-Token"] = value;
90
            }
91
        }
92

    
93
        //The client also receives a StorageUrl after authentication. All subsequent operations must
94
        //use this url
95
        public Uri StorageUrl { get; set; }
96

    
97

    
98
        protected Uri RootAddressUri { get; set; }
99

    
100
       /* private WebProxy _proxy;
101
        public WebProxy Proxy
102
        {
103
            get { return _proxy; }
104
            set
105
            {
106
                _proxy = value;
107
                if (_baseClient != null)
108
                    _baseClient.Proxy = value;                
109
            }
110
        }
111
*/
112

    
113
        /* private Uri _proxy;
114
        public Uri Proxy
115
        {
116
            get { return _proxy; }
117
            set
118
            {
119
                _proxy = value;
120
                if (_baseClient != null)
121
                    _baseClient.Proxy = new WebProxy(value);                
122
            }
123
        }*/
124

    
125
        public double DownloadPercentLimit { get; set; }
126
        public double UploadPercentLimit { get; set; }
127

    
128
        public string AuthenticationUrl { get; set; }
129

    
130
 
131
        public string VersionPath
132
        {
133
            get { return UsePithos ? "v1" : "v1.0"; }
134
        }
135

    
136
        public bool UsePithos { get; set; }
137

    
138

    
139
        private static readonly ILog Log = LogManager.GetLogger("CloudFilesClient");
140

    
141
        public CloudFilesClient(string userName, string apiKey)
142
        {
143
            UserName = userName;
144
            ApiKey = apiKey;
145
        }
146

    
147
        public CloudFilesClient(AccountInfo accountInfo)
148
        {
149
            if (accountInfo==null)
150
                throw new ArgumentNullException("accountInfo");
151
            Contract.Ensures(!String.IsNullOrWhiteSpace(Token));
152
            Contract.Ensures(StorageUrl != null);
153
            Contract.Ensures(_baseClient != null);
154
            Contract.Ensures(RootAddressUri != null);
155
            Contract.EndContractBlock();          
156

    
157
            _baseClient = new RestClient
158
            {
159
                BaseAddress = accountInfo.StorageUri.ToString(),
160
                Timeout = 10000,
161
                Retries = 3,
162
            };
163
            StorageUrl = accountInfo.StorageUri;
164
            Token = accountInfo.Token;
165
            UserName = accountInfo.UserName;
166

    
167
            //Get the root address (StorageUrl without the account)
168
            var storageUrl = StorageUrl.AbsoluteUri;
169
            var usernameIndex = storageUrl.LastIndexOf(UserName);
170
            var rootUrl = storageUrl.Substring(0, usernameIndex);
171
            RootAddressUri = new Uri(rootUrl);
172
        }
173

    
174

    
175
        public AccountInfo Authenticate()
176
        {
177
            if (String.IsNullOrWhiteSpace(UserName))
178
                throw new InvalidOperationException("UserName is empty");
179
            if (String.IsNullOrWhiteSpace(ApiKey))
180
                throw new InvalidOperationException("ApiKey is empty");
181
            if (String.IsNullOrWhiteSpace(AuthenticationUrl))
182
                throw new InvalidOperationException("AuthenticationUrl is empty");
183
            Contract.Ensures(!String.IsNullOrWhiteSpace(Token));
184
            Contract.Ensures(StorageUrl != null);
185
            Contract.Ensures(_baseClient != null);
186
            Contract.Ensures(RootAddressUri != null);
187
            Contract.EndContractBlock();
188

    
189

    
190
            Log.InfoFormat("[AUTHENTICATE] Start for {0}", UserName);
191

    
192
            var groups = new List<Group>();
193

    
194
            using (var authClient = new RestClient{BaseAddress=AuthenticationUrl})
195
            {                
196
               /* if (Proxy != null)
197
                    authClient.Proxy = Proxy;*/
198

    
199
                Contract.Assume(authClient.Headers!=null);
200

    
201
                authClient.Headers.Add("X-Auth-User", UserName);
202
                authClient.Headers.Add("X-Auth-Key", ApiKey);
203

    
204
                authClient.DownloadStringWithRetry(VersionPath, 3);
205

    
206
                authClient.AssertStatusOK("Authentication failed");
207

    
208
                var storageUrl = authClient.GetHeaderValue("X-Storage-Url");
209
                if (String.IsNullOrWhiteSpace(storageUrl))
210
                    throw new InvalidOperationException("Failed to obtain storage url");
211
                
212
                _baseClient = new RestClient
213
                {
214
                    BaseAddress = storageUrl,
215
                    Timeout = 10000,
216
                    Retries = 3,
217
                    //Proxy=Proxy
218
                };
219

    
220
                StorageUrl = new Uri(storageUrl);
221
                
222
                //Get the root address (StorageUrl without the account)
223
                var usernameIndex=storageUrl.LastIndexOf(UserName);
224
                var rootUrl = storageUrl.Substring(0, usernameIndex);
225
                RootAddressUri = new Uri(rootUrl);
226
                
227
                var token = authClient.GetHeaderValue("X-Auth-Token");
228
                if (String.IsNullOrWhiteSpace(token))
229
                    throw new InvalidOperationException("Failed to obtain token url");
230
                Token = token;
231

    
232
               /* var keys = authClient.ResponseHeaders.AllKeys.AsQueryable();
233
                groups = (from key in keys
234
                            where key.StartsWith("X-Account-Group-")
235
                            let name = key.Substring(16)
236
                            select new Group(name, authClient.ResponseHeaders[key]))
237
                        .ToList();
238
                    
239
*/
240
            }
241

    
242
            Log.InfoFormat("[AUTHENTICATE] End for {0}", UserName);
243
            
244

    
245
            return new AccountInfo {StorageUri = StorageUrl, Token = Token, UserName = UserName,Groups=groups};            
246

    
247
        }
248

    
249

    
250

    
251
        public IList<ContainerInfo> ListContainers(string account)
252
        {
253
            using (var client = new RestClient(_baseClient))
254
            {
255
                if (!String.IsNullOrWhiteSpace(account))
256
                    client.BaseAddress = GetAccountUrl(account);
257
                
258
                client.Parameters.Clear();
259
                client.Parameters.Add("format", "json");
260
                var content = client.DownloadStringWithRetry("", 3);
261
                client.AssertStatusOK("List Containers failed");
262

    
263
                if (client.StatusCode == HttpStatusCode.NoContent)
264
                    return new List<ContainerInfo>();
265
                var infos = JsonConvert.DeserializeObject<IList<ContainerInfo>>(content);
266
                
267
                foreach (var info in infos)
268
                {
269
                    info.Account = account;
270
                }
271
                return infos;
272
            }
273

    
274
        }
275

    
276
        private string GetAccountUrl(string account)
277
        {
278
            return new Uri(RootAddressUri, new Uri(account,UriKind.Relative)).AbsoluteUri;
279
        }
280

    
281
        public IList<ShareAccountInfo> ListSharingAccounts(DateTime? since=null)
282
        {
283
            using (ThreadContext.Stacks["Share"].Push("List Accounts"))
284
            {
285
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
286

    
287
                using (var client = new RestClient(_baseClient))
288
                {
289
                    client.Parameters.Clear();
290
                    client.Parameters.Add("format", "json");
291
                    client.IfModifiedSince = since;
292

    
293
                    //Extract the username from the base address
294
                    client.BaseAddress = RootAddressUri.AbsoluteUri;
295

    
296
                    var content = client.DownloadStringWithRetry(@"", 3);
297

    
298
                    client.AssertStatusOK("ListSharingAccounts failed");
299

    
300
                    //If the result is empty, return an empty list,
301
                    var infos = String.IsNullOrWhiteSpace(content)
302
                                    ? new List<ShareAccountInfo>()
303
                                //Otherwise deserialize the account list into a list of ShareAccountInfos
304
                                    : JsonConvert.DeserializeObject<IList<ShareAccountInfo>>(content);
305

    
306
                    Log.DebugFormat("END");
307
                    return infos;
308
                }
309
            }
310
        }
311

    
312
        //Request listing of all objects in a container modified since a specific time.
313
        //If the *since* value is missing, return all objects
314
        public IList<ObjectInfo> ListSharedObjects(DateTime? since = null)
315
        {
316

    
317
            using (ThreadContext.Stacks["Share"].Push("List Objects"))
318
            {
319
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
320

    
321
                var objects = new List<ObjectInfo>();
322
                var accounts = ListSharingAccounts(since);
323
                foreach (var account in accounts)
324
                {
325
                    var containers = ListContainers(account.name);
326
                    foreach (var container in containers)
327
                    {
328
                        var containerObjects = ListObjects(account.name, container.Name);
329
                        objects.AddRange(containerObjects);
330
                    }
331
                }
332
                if (Log.IsDebugEnabled) Log.DebugFormat("END");
333
                return objects;
334
            }
335
        }
336

    
337
        public void SetTags(ObjectInfo target,IDictionary<string,string> tags)
338
        {
339
            if (String.IsNullOrWhiteSpace(Token))
340
                throw new InvalidOperationException("The Token is not set");
341
            if (StorageUrl == null)
342
                throw new InvalidOperationException("The StorageUrl is not set");
343
            if (target == null)
344
                throw new ArgumentNullException("target");
345
            Contract.EndContractBlock();
346

    
347
            using (ThreadContext.Stacks["Share"].Push("Share Object"))
348
            {
349
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
350

    
351
                using (var client = new RestClient(_baseClient))
352
                {
353

    
354
                    client.BaseAddress = GetAccountUrl(target.Account);
355

    
356
                    client.Parameters.Clear();
357
                    client.Parameters.Add("update", "");
358

    
359
                    foreach (var tag in tags)
360
                    {
361
                        var headerTag = String.Format("X-Object-Meta-{0}", tag.Key);
362
                        client.Headers.Add(headerTag, tag.Value);
363
                    }
364
                    
365
                    client.DownloadStringWithRetry(target.Container, 3);
366

    
367
                    
368
                    client.AssertStatusOK("SetTags failed");
369
                    //If the status is NOT ACCEPTED we have a problem
370
                    if (client.StatusCode != HttpStatusCode.Accepted)
371
                    {
372
                        Log.Error("Failed to set tags");
373
                        throw new Exception("Failed to set tags");
374
                    }
375

    
376
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
377
                }
378
            }
379

    
380

    
381
        }
382

    
383
        public void ShareObject(string account, string container, string objectName, string shareTo, bool read, bool write)
384
        {
385
            if (String.IsNullOrWhiteSpace(Token))
386
                throw new InvalidOperationException("The Token is not set");
387
            if (StorageUrl==null)
388
                throw new InvalidOperationException("The StorageUrl is not set");
389
            if (String.IsNullOrWhiteSpace(container))
390
                throw new ArgumentNullException("container");
391
            if (String.IsNullOrWhiteSpace(objectName))
392
                throw new ArgumentNullException("objectName");
393
            if (String.IsNullOrWhiteSpace(account))
394
                throw new ArgumentNullException("account");
395
            if (String.IsNullOrWhiteSpace(shareTo))
396
                throw new ArgumentNullException("shareTo");
397
            Contract.EndContractBlock();
398

    
399
            using (ThreadContext.Stacks["Share"].Push("Share Object"))
400
            {
401
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
402
                
403
                using (var client = new RestClient(_baseClient))
404
                {
405

    
406
                    client.BaseAddress = GetAccountUrl(account);
407

    
408
                    client.Parameters.Clear();
409
                    client.Parameters.Add("format", "json");
410

    
411
                    string permission = "";
412
                    if (write)
413
                        permission = String.Format("write={0}", shareTo);
414
                    else if (read)
415
                        permission = String.Format("read={0}", shareTo);
416
                    client.Headers.Add("X-Object-Sharing", permission);
417

    
418
                    var content = client.DownloadStringWithRetry(container, 3);
419

    
420
                    client.AssertStatusOK("ShareObject failed");
421

    
422
                    //If the result is empty, return an empty list,
423
                    var infos = String.IsNullOrWhiteSpace(content)
424
                                    ? new List<ObjectInfo>()
425
                                //Otherwise deserialize the object list into a list of ObjectInfos
426
                                    : JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
427

    
428
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
429
                }
430
            }
431

    
432

    
433
        }
434

    
435
        public AccountInfo GetAccountPolicies(AccountInfo accountInfo)
436
        {
437
            if (accountInfo==null)
438
                throw new ArgumentNullException("accountInfo");
439
            Contract.EndContractBlock();
440

    
441
            using (ThreadContext.Stacks["Account"].Push("GetPolicies"))
442
            {
443
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
444

    
445
                using (var client = new RestClient(_baseClient))
446
                {
447
                    if (!String.IsNullOrWhiteSpace(accountInfo.UserName))
448
                        client.BaseAddress = GetAccountUrl(accountInfo.UserName);
449

    
450
                    client.Parameters.Clear();
451
                    client.Parameters.Add("format", "json");                    
452
                    client.Head(String.Empty, 3);
453

    
454
                    var quotaValue=client.ResponseHeaders["X-Account-Policy-Quota"];
455
                    var bytesValue= client.ResponseHeaders["X-Account-Bytes-Used"];
456

    
457
                    long quota, bytes;
458
                    if (long.TryParse(quotaValue, out quota))
459
                        accountInfo.Quota = quota;
460
                    if (long.TryParse(bytesValue, out bytes))
461
                        accountInfo.BytesUsed = bytes;
462
                    
463
                    return accountInfo;
464

    
465
                }
466

    
467
            }
468
        }
469

    
470
        public void UpdateMetadata(ObjectInfo objectInfo)
471
        {
472
            if (objectInfo == null)
473
                throw new ArgumentNullException("objectInfo");
474
            Contract.EndContractBlock();
475

    
476
            using (ThreadContext.Stacks["Objects"].Push("UpdateMetadata"))
477
            {
478
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
479

    
480

    
481
                using(var client=new RestClient(_baseClient))
482
                {
483

    
484
                    client.BaseAddress = GetAccountUrl(objectInfo.Account);
485
                    
486
                    client.Parameters.Clear();
487
                    
488

    
489
                    //Set Tags
490
                    foreach (var tag in objectInfo.Tags)
491
                    {
492
                        var headerTag = String.Format("X-Object-Meta-{0}", tag.Key);
493
                        client.Headers.Add(headerTag, tag.Value);
494
                    }
495

    
496
                    //Set Permissions
497

    
498
                    var permissions=objectInfo.GetPermissionString();
499
                    client.SetNonEmptyHeaderValue("X-Object-Sharing",permissions);
500

    
501
                    client.SetNonEmptyHeaderValue("Content-Disposition",objectInfo.ContendDisposition);
502
                    client.SetNonEmptyHeaderValue("Content-Encoding",objectInfo.ContentEncoding);
503
                    client.SetNonEmptyHeaderValue("X-Object-Manifest",objectInfo.Manifest);
504
                    var isPublic = objectInfo.IsPublic.ToString().ToLower();
505
                    client.Headers.Add("X-Object-Public", isPublic);
506

    
507

    
508
                    var uriBuilder = client.GetAddressBuilder(objectInfo.Container, objectInfo.Name);
509
                    var uri = uriBuilder.Uri;
510

    
511
                    client.UploadValues(uri,new NameValueCollection());
512

    
513

    
514
                    client.AssertStatusOK("UpdateMetadata failed");
515
                    //If the status is NOT ACCEPTED or OK we have a problem
516
                    if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK))
517
                    {
518
                        Log.Error("Failed to update metadata");
519
                        throw new Exception("Failed to update metadata");
520
                    }
521

    
522
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
523
                }
524
            }
525

    
526
        }
527

    
528
        public void UpdateMetadata(ContainerInfo containerInfo)
529
        {
530
            if (containerInfo == null)
531
                throw new ArgumentNullException("containerInfo");
532
            Contract.EndContractBlock();
533

    
534
            using (ThreadContext.Stacks["Containers"].Push("UpdateMetadata"))
535
            {
536
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
537

    
538

    
539
                using(var client=new RestClient(_baseClient))
540
                {
541

    
542
                    client.BaseAddress = GetAccountUrl(containerInfo.Account);
543
                    
544
                    client.Parameters.Clear();
545
                    
546

    
547
                    //Set Tags
548
                    foreach (var tag in containerInfo.Tags)
549
                    {
550
                        var headerTag = String.Format("X-Container-Meta-{0}", tag.Key);
551
                        client.Headers.Add(headerTag, tag.Value);
552
                    }
553

    
554
                    
555
                    //Set Policies
556
                    foreach (var policy in containerInfo.Policies)
557
                    {
558
                        var headerPolicy = String.Format("X-Container-Policy-{0}", policy.Key);
559
                        client.Headers.Add(headerPolicy, policy.Value);
560
                    }
561

    
562

    
563
                    var uriBuilder = client.GetAddressBuilder(containerInfo.Name,"");
564
                    var uri = uriBuilder.Uri;
565

    
566
                    client.UploadValues(uri,new NameValueCollection());
567

    
568

    
569
                    client.AssertStatusOK("UpdateMetadata failed");
570
                    //If the status is NOT ACCEPTED or OK we have a problem
571
                    if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK))
572
                    {
573
                        Log.Error("Failed to update metadata");
574
                        throw new Exception("Failed to update metadata");
575
                    }
576

    
577
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
578
                }
579
            }
580

    
581
        }
582

    
583

    
584
        public IList<ObjectInfo> ListObjects(string account, string container, DateTime? since = null)
585
        {
586
            if (String.IsNullOrWhiteSpace(container))
587
                throw new ArgumentNullException("container");
588
            Contract.EndContractBlock();
589

    
590
            using (ThreadContext.Stacks["Objects"].Push("List"))
591
            {
592
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
593

    
594
                using (var client = new RestClient(_baseClient))
595
                {
596
                    if (!String.IsNullOrWhiteSpace(account))
597
                        client.BaseAddress = GetAccountUrl(account);
598

    
599
                    client.Parameters.Clear();
600
                    client.Parameters.Add("format", "json");
601
                    client.IfModifiedSince = since;
602
                    var content = client.DownloadStringWithRetry(container, 3);
603

    
604
                    client.AssertStatusOK("ListObjects failed");
605

    
606
                    //If the result is empty, return an empty list,
607
                    var infos = String.IsNullOrWhiteSpace(content)
608
                                    ? new List<ObjectInfo>()
609
                                //Otherwise deserialize the object list into a list of ObjectInfos
610
                                    : JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
611

    
612
                    foreach (var info in infos)
613
                    {
614
                        info.Container = container;
615
                        info.Account = account;
616
                        info.StorageUri = this.StorageUrl;
617
                    }
618
                    if (Log.IsDebugEnabled) Log.DebugFormat("START");
619
                    return infos;
620
                }
621
            }
622
        }
623

    
624

    
625

    
626
        public IList<ObjectInfo> ListObjects(string account, string container, string folder, DateTime? since = null)
627
        {
628
            if (String.IsNullOrWhiteSpace(container))
629
                throw new ArgumentNullException("container");
630
/*
631
            if (String.IsNullOrWhiteSpace(folder))
632
                throw new ArgumentNullException("folder");
633
*/
634
            Contract.EndContractBlock();
635

    
636
            using (ThreadContext.Stacks["Objects"].Push("List"))
637
            {
638
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
639

    
640
                using (var client = new RestClient(_baseClient))
641
                {
642
                    if (!String.IsNullOrWhiteSpace(account))
643
                        client.BaseAddress = GetAccountUrl(account);
644

    
645
                    client.Parameters.Clear();
646
                    client.Parameters.Add("format", "json");
647
                    client.Parameters.Add("path", folder);
648
                    client.IfModifiedSince = since;
649
                    var content = client.DownloadStringWithRetry(container, 3);
650
                    client.AssertStatusOK("ListObjects failed");
651

    
652
                    var infos = JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
653
                    foreach (var info in infos)
654
                    {
655
                        info.Account = account;
656
                        if (info.Container == null)
657
                            info.Container = container;
658
                        info.StorageUri = this.StorageUrl;
659
                    }
660
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
661
                    return infos;
662
                }
663
            }
664
        }
665

    
666
 
667
        public bool ContainerExists(string account, string container)
668
        {
669
            if (String.IsNullOrWhiteSpace(container))
670
                throw new ArgumentNullException("container", "The container property can't be empty");
671
            Contract.EndContractBlock();
672

    
673
            using (ThreadContext.Stacks["Containters"].Push("Exists"))
674
            {
675
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
676

    
677
                using (var client = new RestClient(_baseClient))
678
                {
679
                    if (!String.IsNullOrWhiteSpace(account))
680
                        client.BaseAddress = GetAccountUrl(account);
681

    
682
                    client.Parameters.Clear();
683
                    client.Head(container, 3);
684
                                        
685
                    bool result;
686
                    switch (client.StatusCode)
687
                    {
688
                        case HttpStatusCode.OK:
689
                        case HttpStatusCode.NoContent:
690
                            result=true;
691
                            break;
692
                        case HttpStatusCode.NotFound:
693
                            result=false;
694
                            break;
695
                        default:
696
                            throw CreateWebException("ContainerExists", client.StatusCode);
697
                    }
698
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
699

    
700
                    return result;
701
                }
702
                
703
            }
704
        }
705

    
706
        public bool ObjectExists(string account, string container, string objectName)
707
        {
708
            if (String.IsNullOrWhiteSpace(container))
709
                throw new ArgumentNullException("container", "The container property can't be empty");
710
            if (String.IsNullOrWhiteSpace(objectName))
711
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");
712
            Contract.EndContractBlock();
713

    
714
            using (var client = new RestClient(_baseClient))
715
            {
716
                if (!String.IsNullOrWhiteSpace(account))
717
                    client.BaseAddress = GetAccountUrl(account);
718

    
719
                client.Parameters.Clear();
720
                client.Head(container + "/" + objectName, 3);
721

    
722
                switch (client.StatusCode)
723
                {
724
                    case HttpStatusCode.OK:
725
                    case HttpStatusCode.NoContent:
726
                        return true;
727
                    case HttpStatusCode.NotFound:
728
                        return false;
729
                    default:
730
                        throw CreateWebException("ObjectExists", client.StatusCode);
731
                }
732
            }
733

    
734
        }
735

    
736
        public ObjectInfo GetObjectInfo(string account, string container, string objectName)
737
        {
738
            if (String.IsNullOrWhiteSpace(container))
739
                throw new ArgumentNullException("container", "The container property can't be empty");
740
            if (String.IsNullOrWhiteSpace(objectName))
741
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");
742
            Contract.EndContractBlock();
743

    
744
            using (ThreadContext.Stacks["Objects"].Push("GetObjectInfo"))
745
            {                
746

    
747
                using (var client = new RestClient(_baseClient))
748
                {
749
                    if (!String.IsNullOrWhiteSpace(account))
750
                        client.BaseAddress = GetAccountUrl(account);
751
                    try
752
                    {
753
                        client.Parameters.Clear();
754

    
755
                        client.Head(container + "/" + objectName, 3);
756

    
757
                        if (client.TimedOut)
758
                            return ObjectInfo.Empty;
759

    
760
                        switch (client.StatusCode)
761
                        {
762
                            case HttpStatusCode.OK:
763
                            case HttpStatusCode.NoContent:
764
                                var keys = client.ResponseHeaders.AllKeys.AsQueryable();
765
                                var tags = client.GetMeta("X-Object-Meta-");
766
                                var extensions = (from key in keys
767
                                                  where key.StartsWith("X-Object-") && !key.StartsWith("X-Object-Meta-")
768
                                                  select new {Name = key, Value = client.ResponseHeaders[key]})
769
                                    .ToDictionary(t => t.Name, t => t.Value);
770

    
771
                                var permissions=client.GetHeaderValue("X-Object-Sharing", true);
772
                                
773
                                
774
                                var info = new ObjectInfo
775
                                               {
776
                                                   Account = account,
777
                                                   Container = container,
778
                                                   Name = objectName,
779
                                                   Hash = client.GetHeaderValue("ETag"),
780
                                                   Content_Type = client.GetHeaderValue("Content-Type"),
781
                                                   Bytes = Convert.ToInt64(client.GetHeaderValue("Content-Length",true)),
782
                                                   Tags = tags,
783
                                                   Last_Modified = client.LastModified,
784
                                                   Extensions = extensions,
785
                                                   ContentEncoding=client.GetHeaderValue("Content-Encoding",true),
786
                                                   ContendDisposition = client.GetHeaderValue("Content-Disposition",true),
787
                                                   Manifest=client.GetHeaderValue("X-Object-Manifest",true),
788
                                                   PublicUrl=client.GetHeaderValue("X-Object-Public",true),                                                   
789
                                               };
790
                                info.SetPermissions(permissions);
791
                                return info;
792
                            case HttpStatusCode.NotFound:
793
                                return ObjectInfo.Empty;
794
                            default:
795
                                throw new WebException(
796
                                    String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}",
797
                                                  objectName, client.StatusCode));
798
                        }
799

    
800
                    }
801
                    catch (RetryException)
802
                    {
803
                        Log.WarnFormat("[RETRY FAIL] GetObjectInfo for {0} failed.",objectName);
804
                        return ObjectInfo.Empty;
805
                    }
806
                    catch (WebException e)
807
                    {
808
                        Log.Error(
809
                            String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}",
810
                                          objectName, client.StatusCode), e);
811
                        throw;
812
                    }
813
                }                
814
            }
815

    
816
        }
817

    
818
        public void CreateFolder(string account, string container, string folder)
819
        {
820
            if (String.IsNullOrWhiteSpace(container))
821
                throw new ArgumentNullException("container", "The container property can't be empty");
822
            if (String.IsNullOrWhiteSpace(folder))
823
                throw new ArgumentNullException("folder", "The folder property can't be empty");
824
            Contract.EndContractBlock();
825

    
826
            var folderUrl=String.Format("{0}/{1}",container,folder);
827
            using (var client = new RestClient(_baseClient))
828
            {
829
                if (!String.IsNullOrWhiteSpace(account))
830
                    client.BaseAddress = GetAccountUrl(account);
831

    
832
                client.Parameters.Clear();
833
                client.Headers.Add("Content-Type", @"application/directory");
834
                client.Headers.Add("Content-Length", "0");
835
                client.PutWithRetry(folderUrl, 3);
836

    
837
                if (client.StatusCode != HttpStatusCode.Created && client.StatusCode != HttpStatusCode.Accepted)
838
                    throw CreateWebException("CreateFolder", client.StatusCode);
839
            }
840
        }
841

    
842
     
843

    
844
        public ContainerInfo GetContainerInfo(string account, string container)
845
        {
846
            if (String.IsNullOrWhiteSpace(container))
847
                throw new ArgumentNullException("container", "The container property can't be empty");
848
            Contract.EndContractBlock();
849

    
850
            using (var client = new RestClient(_baseClient))
851
            {
852
                if (!String.IsNullOrWhiteSpace(account))
853
                    client.BaseAddress = GetAccountUrl(account);                
854

    
855
                client.Head(container);
856
                switch (client.StatusCode)
857
                {
858
                    case HttpStatusCode.OK:
859
                    case HttpStatusCode.NoContent:
860
                        var tags = client.GetMeta("X-Container-Meta-");
861
                        var policies = client.GetMeta("X-Container-Policy-");
862

    
863
                        var containerInfo = new ContainerInfo
864
                                                {
865
                                                    Account=account,
866
                                                    Name = container,
867
                                                    Count =
868
                                                        long.Parse(client.GetHeaderValue("X-Container-Object-Count")),
869
                                                    Bytes = long.Parse(client.GetHeaderValue("X-Container-Bytes-Used")),
870
                                                    BlockHash = client.GetHeaderValue("X-Container-Block-Hash"),
871
                                                    BlockSize=int.Parse(client.GetHeaderValue("X-Container-Block-Size")),
872
                                                    Last_Modified=client.LastModified,
873
                                                    Tags=tags,
874
                                                    Policies=policies
875
                                                };
876
                        
877

    
878
                        return containerInfo;
879
                    case HttpStatusCode.NotFound:
880
                        return ContainerInfo.Empty;
881
                    default:
882
                        throw CreateWebException("GetContainerInfo", client.StatusCode);
883
                }
884
            }
885
        }
886

    
887
        public void CreateContainer(string account, string container)
888
        {
889
            if (String.IsNullOrWhiteSpace(account))
890
                throw new ArgumentNullException("account");
891
            if (String.IsNullOrWhiteSpace(container))
892
                throw new ArgumentNullException("container");
893
            Contract.EndContractBlock();
894

    
895
            using (var client = new RestClient(_baseClient))
896
            {
897
                if (!String.IsNullOrWhiteSpace(account))
898
                    client.BaseAddress = GetAccountUrl(account);
899

    
900
                client.PutWithRetry(container, 3);
901
                var expectedCodes = new[] {HttpStatusCode.Created, HttpStatusCode.Accepted, HttpStatusCode.OK};
902
                if (!expectedCodes.Contains(client.StatusCode))
903
                    throw CreateWebException("CreateContainer", client.StatusCode);
904
            }
905
        }
906

    
907
        public void DeleteContainer(string account, string container)
908
        {
909
            if (String.IsNullOrWhiteSpace(container))
910
                throw new ArgumentNullException("container", "The container property can't be empty");
911
            Contract.EndContractBlock();
912

    
913
            using (var client = new RestClient(_baseClient))
914
            {
915
                if (!String.IsNullOrWhiteSpace(account))
916
                    client.BaseAddress = GetAccountUrl(account);
917

    
918
                client.DeleteWithRetry(container, 3);
919
                var expectedCodes = new[] {HttpStatusCode.NotFound, HttpStatusCode.NoContent};
920
                if (!expectedCodes.Contains(client.StatusCode))
921
                    throw CreateWebException("DeleteContainer", client.StatusCode);
922
            }
923

    
924
        }
925

    
926
        /// <summary>
927
        /// 
928
        /// </summary>
929
        /// <param name="account"></param>
930
        /// <param name="container"></param>
931
        /// <param name="objectName"></param>
932
        /// <param name="fileName"></param>
933
        /// <returns></returns>
934
        /// <remarks>This method should have no timeout or a very long one</remarks>
935
        //Asynchronously download the object specified by *objectName* in a specific *container* to 
936
        // a local file
937
        public Task GetObject(string account, string container, string objectName, string fileName)
938
        {
939
            if (String.IsNullOrWhiteSpace(container))
940
                throw new ArgumentNullException("container", "The container property can't be empty");
941
            if (String.IsNullOrWhiteSpace(objectName))
942
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");            
943
            Contract.EndContractBlock();
944

    
945
            try
946
            {
947
                //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient
948
                //object to avoid concurrency errors.
949
                //
950
                //Download operations take a long time therefore they have no timeout.
951
                var client = new RestClient(_baseClient) { Timeout = 0 };
952
                if (!String.IsNullOrWhiteSpace(account))
953
                    client.BaseAddress = GetAccountUrl(account);
954

    
955
                //The container and objectName are relative names. They are joined with the client's
956
                //BaseAddress to create the object's absolute address
957
                var builder = client.GetAddressBuilder(container, objectName);
958
                var uri = builder.Uri;
959

    
960
                //Download progress is reported to the Trace log
961
                Log.InfoFormat("[GET] START {0}", objectName);
962
                client.DownloadProgressChanged += (sender, args) => 
963
                    Log.InfoFormat("[GET PROGRESS] {0} {1}% {2} of {3}",
964
                                    fileName, args.ProgressPercentage,
965
                                    args.BytesReceived,
966
                                    args.TotalBytesToReceive);                                
967

    
968

    
969
                //Start downloading the object asynchronously
970
                var downloadTask = client.DownloadFileTask(uri, fileName);
971
                
972
                //Once the download completes
973
                return downloadTask.ContinueWith(download =>
974
                                      {
975
                                          //Delete the local client object
976
                                          client.Dispose();
977
                                          //And report failure or completion
978
                                          if (download.IsFaulted)
979
                                          {
980
                                              Log.ErrorFormat("[GET] FAIL for {0} with \r{1}", objectName,
981
                                                               download.Exception);
982
                                          }
983
                                          else
984
                                          {
985
                                              Log.InfoFormat("[GET] END {0}", objectName);                                             
986
                                          }
987
                                      });
988
            }
989
            catch (Exception exc)
990
            {
991
                Log.ErrorFormat("[GET] END {0} with {1}", objectName, exc);
992
                throw;
993
            }
994

    
995

    
996

    
997
        }
998

    
999
        public Task<IList<string>> PutHashMap(string account, string container, string objectName, TreeHash hash)
1000
        {
1001
            if (String.IsNullOrWhiteSpace(container))
1002
                throw new ArgumentNullException("container");
1003
            if (String.IsNullOrWhiteSpace(objectName))
1004
                throw new ArgumentNullException("objectName");
1005
            if (hash==null)
1006
                throw new ArgumentNullException("hash");
1007
            if (String.IsNullOrWhiteSpace(Token))
1008
                throw new InvalidOperationException("Invalid Token");
1009
            if (StorageUrl == null)
1010
                throw new InvalidOperationException("Invalid Storage Url");
1011
            Contract.EndContractBlock();
1012

    
1013

    
1014
            //Don't use a timeout because putting the hashmap may be a long process
1015
            var client = new RestClient(_baseClient) { Timeout = 0 };           
1016
            if (!String.IsNullOrWhiteSpace(account))
1017
                client.BaseAddress = GetAccountUrl(account);
1018

    
1019
            //The container and objectName are relative names. They are joined with the client's
1020
            //BaseAddress to create the object's absolute address
1021
            var builder = client.GetAddressBuilder(container, objectName);
1022
            builder.Query = "format=json&hashmap";
1023
            var uri = builder.Uri;
1024

    
1025

    
1026
            //Send the tree hash as Json to the server            
1027
            client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
1028
            var jsonHash = hash.ToJson();
1029
            var uploadTask=client.UploadStringTask(uri, "PUT", jsonHash);
1030
            
1031
            return uploadTask.ContinueWith(t =>
1032
            {
1033

    
1034
                var empty = (IList<string>)new List<string>();
1035
                
1036

    
1037
                //The server will respond either with 201-created if all blocks were already on the server
1038
                if (client.StatusCode == HttpStatusCode.Created)                    
1039
                {
1040
                    //in which case we return an empty hash list
1041
                    return empty;
1042
                }
1043
                //or with a 409-conflict and return the list of missing parts
1044
                //A 409 will cause an exception so we need to check t.IsFaulted to avoid propagating the exception                
1045
                if (t.IsFaulted)
1046
                {
1047
                    var ex = t.Exception.InnerException;
1048
                    var we = ex as WebException;
1049
                    var response = we.Response as HttpWebResponse;
1050
                    if (response!=null && response.StatusCode==HttpStatusCode.Conflict)
1051
                    {
1052
                        //In case of 409 the missing parts will be in the response content                        
1053
                        using (var stream = response.GetResponseStream())
1054
                        using(var reader=new StreamReader(stream))
1055
                        {
1056
                            Debug.Assert(stream.Position == 0);
1057
                            //We used to have to cleanup the content before returning it because it contains
1058
                            //error content after the list of hashes
1059
                            //
1060
                            //As of 30/1/2012, the result is a proper Json array so we don't need to read the content
1061
                            //line by line
1062
                            
1063
                            var serializer = new JsonSerializer();                            
1064
                            var hashes=(List<string>)serializer.Deserialize(reader, typeof (List<string>));
1065

    
1066
                            return hashes;
1067
                        }                        
1068
                    }                    
1069
                    //Any other status code is unexpected and the exception should be rethrown
1070
                    throw ex;
1071
                    
1072
                }
1073
                //Any other status code is unexpected but there was no exception. We can probably continue processing
1074
                Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",client.StatusCode,client.StatusDescription);                    
1075
                
1076
                return empty;
1077
            });
1078

    
1079
        }
1080

    
1081
        public Task<byte[]> GetBlock(string account, string container, Uri relativeUrl, long start, long? end)
1082
        {
1083
            if (String.IsNullOrWhiteSpace(Token))
1084
                throw new InvalidOperationException("Invalid Token");
1085
            if (StorageUrl == null)
1086
                throw new InvalidOperationException("Invalid Storage Url");
1087
            if (String.IsNullOrWhiteSpace(container))
1088
                throw new ArgumentNullException("container");
1089
            if (relativeUrl== null)
1090
                throw new ArgumentNullException("relativeUrl");
1091
            if (end.HasValue && end<0)
1092
                throw new ArgumentOutOfRangeException("end");
1093
            if (start<0)
1094
                throw new ArgumentOutOfRangeException("start");
1095
            Contract.EndContractBlock();
1096

    
1097

    
1098
            //Don't use a timeout because putting the hashmap may be a long process
1099
            var client = new RestClient(_baseClient) {Timeout = 0, RangeFrom = start, RangeTo = end};
1100
            if (!String.IsNullOrWhiteSpace(account))
1101
                client.BaseAddress = GetAccountUrl(account);
1102

    
1103
            var builder = client.GetAddressBuilder(container, relativeUrl.ToString());
1104
            var uri = builder.Uri;
1105

    
1106
            return client.DownloadDataTask(uri)
1107
                .ContinueWith(t=>
1108
                                  {
1109
                                      client.Dispose();
1110
                                      return t.Result;
1111
                                  });
1112
        }
1113

    
1114

    
1115
        public async Task PostBlock(string account, string container, byte[] block, int offset, int count)
1116
        {
1117
            if (String.IsNullOrWhiteSpace(container))
1118
                throw new ArgumentNullException("container");
1119
            if (block == null)
1120
                throw new ArgumentNullException("block");
1121
            if (offset < 0 || offset >= block.Length)
1122
                throw new ArgumentOutOfRangeException("offset");
1123
            if (count < 0 || count > block.Length)
1124
                throw new ArgumentOutOfRangeException("count");
1125
            if (String.IsNullOrWhiteSpace(Token))
1126
                throw new InvalidOperationException("Invalid Token");
1127
            if (StorageUrl == null)
1128
                throw new InvalidOperationException("Invalid Storage Url");                        
1129
            Contract.EndContractBlock();
1130

    
1131

    
1132
            try
1133
            {
1134

    
1135
            //Don't use a timeout because putting the hashmap may be a long process
1136
                using (var client = new RestClient(_baseClient) { Timeout = 0 })
1137
                {
1138
                    if (!String.IsNullOrWhiteSpace(account))
1139
                        client.BaseAddress = GetAccountUrl(account);
1140

    
1141
                    var builder = client.GetAddressBuilder(container, "");
1142
                    //We are doing an update
1143
                    builder.Query = "update";
1144
                    var uri = builder.Uri;
1145

    
1146
                    client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
1147

    
1148
                    Log.InfoFormat("[BLOCK POST] START");
1149

    
1150
                    client.UploadProgressChanged += (sender, args) =>
1151
                                                    Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}",
1152
                                                                   args.ProgressPercentage, args.BytesSent,
1153
                                                                   args.TotalBytesToSend);
1154
                    client.UploadFileCompleted += (sender, args) =>
1155
                                                  Log.InfoFormat("[BLOCK POST PROGRESS] Completed ");
1156

    
1157
                    var buffer = new byte[count];
1158
                    Buffer.BlockCopy(block, offset, buffer, 0, count);
1159
                    //Send the block
1160
                    await client.UploadDataTask(uri, "POST", buffer);
1161
                    Log.InfoFormat("[BLOCK POST] END");
1162
                }
1163
            }
1164
            catch (Exception exc)
1165
            {
1166
                Log.ErrorFormat("[BLOCK POST] FAIL with \r{0}", exc);                                        
1167
                throw;
1168
            }
1169
        }
1170

    
1171

    
1172
        public async Task<TreeHash> GetHashMap(string account, string container, string objectName)
1173
        {
1174
            if (String.IsNullOrWhiteSpace(container))
1175
                throw new ArgumentNullException("container");
1176
            if (String.IsNullOrWhiteSpace(objectName))
1177
                throw new ArgumentNullException("objectName");
1178
            if (String.IsNullOrWhiteSpace(Token))
1179
                throw new InvalidOperationException("Invalid Token");
1180
            if (StorageUrl == null)
1181
                throw new InvalidOperationException("Invalid Storage Url");
1182
            Contract.EndContractBlock();
1183

    
1184
            try
1185
            {
1186
                //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient
1187
                //object to avoid concurrency errors.
1188
                //
1189
                //Download operations take a long time therefore they have no timeout.
1190
                //TODO: Do they really? this is a hashmap operation, not a download
1191
                
1192
                //Start downloading the object asynchronously
1193
                using (var client = new RestClient(_baseClient) { Timeout = 0 })
1194
                {
1195
                    if (!String.IsNullOrWhiteSpace(account))
1196
                        client.BaseAddress = GetAccountUrl(account);
1197

    
1198
                    //The container and objectName are relative names. They are joined with the client's
1199
                    //BaseAddress to create the object's absolute address
1200
                    var builder = client.GetAddressBuilder(container, objectName);
1201
                    builder.Query = "format=json&hashmap";
1202
                    var uri = builder.Uri;
1203

    
1204

    
1205
                    var json = await client.DownloadStringTaskAsync(uri);
1206
                    var treeHash = TreeHash.Parse(json);
1207
                    Log.InfoFormat("[GET HASH] END {0}", objectName);
1208
                    return treeHash;
1209
                }
1210
            }
1211
            catch (Exception exc)
1212
            {
1213
                Log.ErrorFormat("[GET HASH] END {0} with {1}", objectName, exc);
1214
                throw;
1215
            }
1216

    
1217
        }
1218

    
1219

    
1220
        /// <summary>
1221
        /// 
1222
        /// </summary>
1223
        /// <param name="account"></param>
1224
        /// <param name="container"></param>
1225
        /// <param name="objectName"></param>
1226
        /// <param name="fileName"></param>
1227
        /// <param name="hash">Optional hash value for the file. If no hash is provided, the method calculates a new hash</param>
1228
        /// <remarks>>This method should have no timeout or a very long one</remarks>
1229
        public async Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream")
1230
        {
1231
            if (String.IsNullOrWhiteSpace(container))
1232
                throw new ArgumentNullException("container", "The container property can't be empty");
1233
            if (String.IsNullOrWhiteSpace(objectName))
1234
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");
1235
            if (String.IsNullOrWhiteSpace(fileName))
1236
                throw new ArgumentNullException("fileName", "The fileName property can't be empty");
1237
/*
1238
            if (!File.Exists(fileName) && !Directory.Exists(fileName))
1239
                throw new FileNotFoundException("The file or directory does not exist",fileName);
1240
*/
1241
            Contract.EndContractBlock();
1242
            
1243
            try
1244
            {
1245

    
1246
                using (var client = new RestClient(_baseClient) { Timeout = 0 })
1247
                {
1248
                    if (!String.IsNullOrWhiteSpace(account))
1249
                        client.BaseAddress = GetAccountUrl(account);
1250

    
1251
                    var builder = client.GetAddressBuilder(container, objectName);
1252
                    var uri = builder.Uri;
1253

    
1254
                    string etag = hash ?? CalculateHash(fileName);
1255

    
1256
                    client.Headers.Add("Content-Type", contentType);
1257
                    client.Headers.Add("ETag", etag);
1258

    
1259

    
1260
                    Log.InfoFormat("[PUT] START {0}", objectName);
1261
                    client.UploadProgressChanged += (sender, args) =>
1262
                                                        {
1263
                                                            using (ThreadContext.Stacks["PUT"].Push("Progress"))
1264
                                                            {
1265
                                                                Log.InfoFormat("{0} {1}% {2} of {3}", fileName,
1266
                                                                               args.ProgressPercentage,
1267
                                                                               args.BytesSent, args.TotalBytesToSend);
1268
                                                            }
1269
                                                        };
1270

    
1271
                    client.UploadFileCompleted += (sender, args) =>
1272
                                                      {
1273
                                                          using (ThreadContext.Stacks["PUT"].Push("Progress"))
1274
                                                          {
1275
                                                              Log.InfoFormat("Completed {0}", fileName);
1276
                                                          }
1277
                                                      };
1278
                    if (contentType=="application/directory")
1279
                        await client.UploadDataTaskAsync(uri, "PUT", new byte[0]);
1280
                    else
1281
                        await client.UploadFileTaskAsync(uri, "PUT", fileName);
1282
                }
1283

    
1284
                Log.InfoFormat("[PUT] END {0}", objectName);
1285
            }
1286
            catch (Exception exc)
1287
            {
1288
                Log.ErrorFormat("[PUT] END {0} with {1}", objectName, exc);
1289
                throw;
1290
            }                
1291

    
1292
        }
1293
       
1294
        
1295
        private static string CalculateHash(string fileName)
1296
        {
1297
            Contract.Requires(!String.IsNullOrWhiteSpace(fileName));
1298
            Contract.EndContractBlock();
1299

    
1300
            string hash;
1301
            using (var hasher = MD5.Create())
1302
            using(var stream=File.OpenRead(fileName))
1303
            {
1304
                var hashBuilder=new StringBuilder();
1305
                foreach (byte b in hasher.ComputeHash(stream))
1306
                    hashBuilder.Append(b.ToString("x2").ToLower());
1307
                hash = hashBuilder.ToString();                
1308
            }
1309
            return hash;
1310
        }
1311
        
1312
        public void MoveObject(string account, string sourceContainer, string oldObjectName, string targetContainer, string newObjectName)
1313
        {
1314
            if (String.IsNullOrWhiteSpace(sourceContainer))
1315
                throw new ArgumentNullException("sourceContainer", "The container property can't be empty");
1316
            if (String.IsNullOrWhiteSpace(oldObjectName))
1317
                throw new ArgumentNullException("oldObjectName", "The oldObjectName property can't be empty");
1318
            if (String.IsNullOrWhiteSpace(targetContainer))
1319
                throw new ArgumentNullException("targetContainer", "The container property can't be empty");
1320
            if (String.IsNullOrWhiteSpace(newObjectName))
1321
                throw new ArgumentNullException("newObjectName", "The newObjectName property can't be empty");
1322
            Contract.EndContractBlock();
1323

    
1324
            var targetUrl = targetContainer + "/" + newObjectName;
1325
            var sourceUrl = String.Format("/{0}/{1}", sourceContainer, oldObjectName);
1326

    
1327
            using (var client = new RestClient(_baseClient))
1328
            {
1329
                if (!String.IsNullOrWhiteSpace(account))
1330
                    client.BaseAddress = GetAccountUrl(account);
1331

    
1332
                client.Headers.Add("X-Move-From", sourceUrl);
1333
                client.PutWithRetry(targetUrl, 3);
1334

    
1335
                var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created};
1336
                if (!expectedCodes.Contains(client.StatusCode))
1337
                    throw CreateWebException("MoveObject", client.StatusCode);
1338
            }
1339
        }
1340

    
1341
        public void DeleteObject(string account, string sourceContainer, string objectName)
1342
        {            
1343
            if (String.IsNullOrWhiteSpace(sourceContainer))
1344
                throw new ArgumentNullException("sourceContainer", "The container property can't be empty");
1345
            if (String.IsNullOrWhiteSpace(objectName))
1346
                throw new ArgumentNullException("objectName", "The oldObjectName property can't be empty");
1347
            Contract.EndContractBlock();
1348

    
1349
            var targetUrl = FolderConstants.TrashContainer + "/" + objectName;
1350
            var sourceUrl = String.Format("/{0}/{1}", sourceContainer, objectName);
1351

    
1352
            using (var client = new RestClient(_baseClient))
1353
            {
1354
                if (!String.IsNullOrWhiteSpace(account))
1355
                    client.BaseAddress = GetAccountUrl(account);
1356

    
1357
                client.Headers.Add("X-Move-From", sourceUrl);
1358
                client.AllowedStatusCodes.Add(HttpStatusCode.NotFound);
1359
                client.PutWithRetry(targetUrl, 3);
1360

    
1361
                var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created,HttpStatusCode.NotFound};
1362
                if (!expectedCodes.Contains(client.StatusCode))
1363
                    throw CreateWebException("DeleteObject", client.StatusCode);
1364
            }
1365
        }
1366

    
1367
      
1368
        private static WebException CreateWebException(string operation, HttpStatusCode statusCode)
1369
        {
1370
            return new WebException(String.Format("{0} failed with unexpected status code {1}", operation, statusCode));
1371
        }
1372

    
1373

    
1374
/*
1375
        public IEnumerable<ObjectInfo> ListDirectories(ContainerInfo container)
1376
        {
1377
            var directories=this.ListObjects(container.Account, container.Name, "/");
1378
        }
1379
*/
1380
    }
1381

    
1382
    public class ShareAccountInfo
1383
    {
1384
        public DateTime? last_modified { get; set; }
1385
        public string name { get; set; }
1386
    }
1387
}