Statistics
| Branch: | Revision:

root / trunk / Pithos.Network / CloudFilesClient.cs @ c92e02f3

History | View | Annotate | Download (55.9 kB)

1
// **CloudFilesClient** provides a simple client interface to CloudFiles and Pithos
2
//
3
// The class provides methods to upload/download files, delete files, manage containers
4

    
5

    
6
using System;
7
using System.Collections.Generic;
8
using System.Collections.Specialized;
9
using System.ComponentModel.Composition;
10
using System.Diagnostics.Contracts;
11
using System.IO;
12
using System.Linq;
13
using System.Net;
14
using System.Security.Cryptography;
15
using System.Text;
16
using System.Threading.Tasks;
17
using Newtonsoft.Json;
18
using Pithos.Interfaces;
19
using log4net;
20

    
21
namespace Pithos.Network
22
{
23
    [Export(typeof(ICloudClient))]
24
    public class CloudFilesClient:ICloudClient
25
    {
26
        //CloudFilesClient uses *_baseClient* internally to communicate with the server
27
        //RestClient provides a REST-friendly interface over the standard WebClient.
28
        private RestClient _baseClient;
29
        
30

    
31
        //During authentication the client provides a UserName 
32
        public string UserName { get; set; }
33
        
34
        //and and ApiKey to the server
35
        public string ApiKey { get; set; }
36
        
37
        //And receives an authentication Token. This token must be provided in ALL other operations,
38
        //in the X-Auth-Token header
39
        private string _token;
40
        public string Token
41
        {
42
            get { return _token; }
43
            set
44
            {
45
                _token = value;
46
                _baseClient.Headers["X-Auth-Token"] = value;
47
            }
48
        }
49

    
50
        //The client also receives a StorageUrl after authentication. All subsequent operations must
51
        //use this url
52
        public Uri StorageUrl { get; set; }
53

    
54

    
55
        protected Uri RootAddressUri { get; set; }
56

    
57
        private Uri _proxy;
58
        public Uri Proxy
59
        {
60
            get { return _proxy; }
61
            set
62
            {
63
                _proxy = value;
64
                if (_baseClient != null)
65
                    _baseClient.Proxy = new WebProxy(value);                
66
            }
67
        }
68

    
69
        public double DownloadPercentLimit { get; set; }
70
        public double UploadPercentLimit { get; set; }
71

    
72
        public string AuthenticationUrl { get; set; }
73

    
74
 
75
        public string VersionPath
76
        {
77
            get { return UsePithos ? "v1" : "v1.0"; }
78
        }
79

    
80
        public bool UsePithos { get; set; }
81

    
82

    
83
        private static readonly ILog Log = LogManager.GetLogger("CloudFilesClient");
84

    
85
        public CloudFilesClient(string userName, string apiKey)
86
        {
87
            UserName = userName;
88
            ApiKey = apiKey;
89
        }
90

    
91
        public CloudFilesClient(AccountInfo accountInfo)
92
        {
93
            if (accountInfo==null)
94
                throw new ArgumentNullException("accountInfo");
95
            Contract.Ensures(!String.IsNullOrWhiteSpace(Token));
96
            Contract.Ensures(StorageUrl != null);
97
            Contract.Ensures(_baseClient != null);
98
            Contract.Ensures(RootAddressUri != null);
99
            Contract.EndContractBlock();
100

    
101
            _baseClient = new RestClient
102
            {
103
                BaseAddress = accountInfo.StorageUri.ToString(),
104
                Timeout = 10000,
105
                Retries = 3
106
            };
107
            StorageUrl = accountInfo.StorageUri;
108
            Token = accountInfo.Token;
109
            UserName = accountInfo.UserName;
110

    
111
            //Get the root address (StorageUrl without the account)
112
            var storageUrl = StorageUrl.AbsoluteUri;
113
            var usernameIndex = storageUrl.LastIndexOf(UserName);
114
            var rootUrl = storageUrl.Substring(0, usernameIndex);
115
            RootAddressUri = new Uri(rootUrl);
116
        }
117

    
118

    
119
        public AccountInfo Authenticate()
120
        {
121
            if (String.IsNullOrWhiteSpace(UserName))
122
                throw new InvalidOperationException("UserName is empty");
123
            if (String.IsNullOrWhiteSpace(ApiKey))
124
                throw new InvalidOperationException("ApiKey is empty");
125
            if (String.IsNullOrWhiteSpace(AuthenticationUrl))
126
                throw new InvalidOperationException("AuthenticationUrl is empty");
127
            Contract.Ensures(!String.IsNullOrWhiteSpace(Token));
128
            Contract.Ensures(StorageUrl != null);
129
            Contract.Ensures(_baseClient != null);
130
            Contract.Ensures(RootAddressUri != null);
131
            Contract.EndContractBlock();
132

    
133

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

    
136
            var groups = new List<Group>();
137

    
138
            using (var authClient = new RestClient{BaseAddress=AuthenticationUrl})
139
            {
140
                if (Proxy != null)
141
                    authClient.Proxy = new WebProxy(Proxy);
142

    
143
                Contract.Assume(authClient.Headers!=null);
144

    
145
                authClient.Headers.Add("X-Auth-User", UserName);
146
                authClient.Headers.Add("X-Auth-Key", ApiKey);
147

    
148
                authClient.DownloadStringWithRetry(VersionPath, 3);
149

    
150
                authClient.AssertStatusOK("Authentication failed");
151

    
152
                var storageUrl = authClient.GetHeaderValue("X-Storage-Url");
153
                if (String.IsNullOrWhiteSpace(storageUrl))
154
                    throw new InvalidOperationException("Failed to obtain storage url");
155
                
156
                _baseClient = new RestClient
157
                {
158
                    BaseAddress = storageUrl,
159
                    Timeout = 10000,
160
                    Retries = 3
161
                };
162

    
163
                StorageUrl = new Uri(storageUrl);
164
                
165
                //Get the root address (StorageUrl without the account)
166
                var usernameIndex=storageUrl.LastIndexOf(UserName);
167
                var rootUrl = storageUrl.Substring(0, usernameIndex);
168
                RootAddressUri = new Uri(rootUrl);
169
                
170
                var token = authClient.GetHeaderValue("X-Auth-Token");
171
                if (String.IsNullOrWhiteSpace(token))
172
                    throw new InvalidOperationException("Failed to obtain token url");
173
                Token = token;
174

    
175
               /* var keys = authClient.ResponseHeaders.AllKeys.AsQueryable();
176
                groups = (from key in keys
177
                            where key.StartsWith("X-Account-Group-")
178
                            let name = key.Substring(16)
179
                            select new Group(name, authClient.ResponseHeaders[key]))
180
                        .ToList();
181
                    
182
*/
183
            }
184

    
185
            Log.InfoFormat("[AUTHENTICATE] End for {0}", UserName);
186
            
187

    
188
            return new AccountInfo {StorageUri = StorageUrl, Token = Token, UserName = UserName,Groups=groups};            
189

    
190
        }
191

    
192

    
193

    
194
        public IList<ContainerInfo> ListContainers(string account)
195
        {
196
            using (var client = new RestClient(_baseClient))
197
            {
198
                if (!String.IsNullOrWhiteSpace(account))
199
                    client.BaseAddress = GetAccountUrl(account);
200
                
201
                client.Parameters.Clear();
202
                client.Parameters.Add("format", "json");
203
                var content = client.DownloadStringWithRetry("", 3);
204
                client.AssertStatusOK("List Containers failed");
205

    
206
                if (client.StatusCode == HttpStatusCode.NoContent)
207
                    return new List<ContainerInfo>();
208
                var infos = JsonConvert.DeserializeObject<IList<ContainerInfo>>(content);
209
                
210
                foreach (var info in infos)
211
                {
212
                    info.Account = account;
213
                }
214
                return infos;
215
            }
216

    
217
        }
218

    
219
        private string GetAccountUrl(string account)
220
        {
221
            return new Uri(this.RootAddressUri, new Uri(account,UriKind.Relative)).AbsoluteUri;
222
        }
223

    
224
        public IList<ShareAccountInfo> ListSharingAccounts(DateTime? since=null)
225
        {
226
            using (log4net.ThreadContext.Stacks["Share"].Push("List Accounts"))
227
            {
228
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
229

    
230
                using (var client = new RestClient(_baseClient))
231
                {
232
                    client.Parameters.Clear();
233
                    client.Parameters.Add("format", "json");
234
                    client.IfModifiedSince = since;
235

    
236
                    //Extract the username from the base address
237
                    client.BaseAddress = RootAddressUri.AbsoluteUri;
238

    
239
                    var content = client.DownloadStringWithRetry(@"", 3);
240

    
241
                    client.AssertStatusOK("ListSharingAccounts failed");
242

    
243
                    //If the result is empty, return an empty list,
244
                    var infos = String.IsNullOrWhiteSpace(content)
245
                                    ? new List<ShareAccountInfo>()
246
                                //Otherwise deserialize the account list into a list of ShareAccountInfos
247
                                    : JsonConvert.DeserializeObject<IList<ShareAccountInfo>>(content);
248

    
249
                    Log.DebugFormat("END");
250
                    return infos;
251
                }
252
            }
253
        }
254

    
255
        //Request listing of all objects in a container modified since a specific time.
256
        //If the *since* value is missing, return all objects
257
        public IList<ObjectInfo> ListSharedObjects(DateTime? since = null)
258
        {
259

    
260
            using (log4net.ThreadContext.Stacks["Share"].Push("List Objects"))
261
            {
262
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
263

    
264
                var objects = new List<ObjectInfo>();
265
                var accounts = ListSharingAccounts(since);
266
                foreach (var account in accounts)
267
                {
268
                    var containers = ListContainers(account.name);
269
                    foreach (var container in containers)
270
                    {
271
                        var containerObjects = ListObjects(account.name, container.Name, null);
272
                        objects.AddRange(containerObjects);
273
                    }
274
                }
275
                if (Log.IsDebugEnabled) Log.DebugFormat("END");
276
                return objects;
277
            }
278
        }
279

    
280
        public void SetTags(ObjectInfo target,IDictionary<string,string> tags)
281
        {
282
            if (String.IsNullOrWhiteSpace(Token))
283
                throw new InvalidOperationException("The Token is not set");
284
            if (StorageUrl == null)
285
                throw new InvalidOperationException("The StorageUrl is not set");
286
            if (target == null)
287
                throw new ArgumentNullException("target");
288
            Contract.EndContractBlock();
289

    
290
            using (log4net.ThreadContext.Stacks["Share"].Push("Share Object"))
291
            {
292
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
293

    
294
                using (var client = new RestClient(_baseClient))
295
                {
296

    
297
                    client.BaseAddress = GetAccountUrl(target.Account);
298

    
299
                    client.Parameters.Clear();
300
                    client.Parameters.Add("update", "");
301

    
302
                    foreach (var tag in tags)
303
                    {
304
                        var headerTag = String.Format("X-Object-Meta-{0}", tag.Key);
305
                        client.Headers.Add(headerTag, tag.Value);
306
                    }
307
                    
308
                    var content = client.DownloadStringWithRetry(target.Container, 3);
309

    
310
                    
311
                    client.AssertStatusOK("SetTags failed");
312
                    //If the status is NOT ACCEPTED we have a problem
313
                    if (client.StatusCode != HttpStatusCode.Accepted)
314
                    {
315
                        Log.Error("Failed to set tags");
316
                        throw new Exception("Failed to set tags");
317
                    }
318

    
319
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
320
                }
321
            }
322

    
323

    
324
        }
325

    
326
        public void ShareObject(string account, string container, string objectName, string shareTo, bool read, bool write)
327
        {
328
            if (String.IsNullOrWhiteSpace(Token))
329
                throw new InvalidOperationException("The Token is not set");
330
            if (StorageUrl==null)
331
                throw new InvalidOperationException("The StorageUrl is not set");
332
            if (String.IsNullOrWhiteSpace(container))
333
                throw new ArgumentNullException("container");
334
            if (String.IsNullOrWhiteSpace(objectName))
335
                throw new ArgumentNullException("objectName");
336
            if (String.IsNullOrWhiteSpace(account))
337
                throw new ArgumentNullException("account");
338
            if (String.IsNullOrWhiteSpace(shareTo))
339
                throw new ArgumentNullException("shareTo");
340
            Contract.EndContractBlock();
341

    
342
            using (log4net.ThreadContext.Stacks["Share"].Push("Share Object"))
343
            {
344
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
345
                
346
                using (var client = new RestClient(_baseClient))
347
                {
348

    
349
                    client.BaseAddress = GetAccountUrl(account);
350

    
351
                    client.Parameters.Clear();
352
                    client.Parameters.Add("format", "json");
353

    
354
                    string permission = "";
355
                    if (write)
356
                        permission = String.Format("write={0}", shareTo);
357
                    else if (read)
358
                        permission = String.Format("read={0}", shareTo);
359
                    client.Headers.Add("X-Object-Sharing", permission);
360

    
361
                    var content = client.DownloadStringWithRetry(container, 3);
362

    
363
                    client.AssertStatusOK("ShareObject failed");
364

    
365
                    //If the result is empty, return an empty list,
366
                    var infos = String.IsNullOrWhiteSpace(content)
367
                                    ? new List<ObjectInfo>()
368
                                //Otherwise deserialize the object list into a list of ObjectInfos
369
                                    : JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
370

    
371
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
372
                }
373
            }
374

    
375

    
376
        }
377

    
378
        public AccountInfo GetAccountPolicies(AccountInfo accountInfo)
379
        {
380
            if (accountInfo==null)
381
                throw new ArgumentNullException("accountInfo");
382
            Contract.EndContractBlock();
383

    
384
            using (log4net.ThreadContext.Stacks["Account"].Push("GetPolicies"))
385
            {
386
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
387

    
388
                using (var client = new RestClient(_baseClient))
389
                {
390
                    if (!String.IsNullOrWhiteSpace(accountInfo.UserName))
391
                        client.BaseAddress = GetAccountUrl(accountInfo.UserName);
392

    
393
                    client.Parameters.Clear();
394
                    client.Parameters.Add("format", "json");                    
395
                    client.Head(String.Empty, 3);
396

    
397
                    var quotaValue=client.ResponseHeaders["X-Account-Policy-Quota"];
398
                    var bytesValue= client.ResponseHeaders["X-Account-Bytes-Used"];
399

    
400
                    long quota, bytes;
401
                    if (long.TryParse(quotaValue, out quota))
402
                        accountInfo.Quota = quota;
403
                    if (long.TryParse(bytesValue, out bytes))
404
                        accountInfo.BytesUsed = bytes;
405
                    
406
                    return accountInfo;
407

    
408
                }
409

    
410
            }
411
        }
412

    
413
        public void UpdateMetadata(ObjectInfo objectInfo)
414
        {
415
            if (objectInfo == null)
416
                throw new ArgumentNullException("objectInfo");
417
            Contract.EndContractBlock();
418

    
419
            using (log4net.ThreadContext.Stacks["Objects"].Push("UpdateMetadata"))
420
            {
421
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
422

    
423

    
424
                using(var client=new RestClient(_baseClient))
425
                {
426

    
427
                    client.BaseAddress = GetAccountUrl(objectInfo.Account);
428
                    
429
                    client.Parameters.Clear();
430
                    
431

    
432
                    //Set Tags
433
                    foreach (var tag in objectInfo.Tags)
434
                    {
435
                        var headerTag = String.Format("X-Object-Meta-{0}", tag.Key);
436
                        client.Headers.Add(headerTag, tag.Value);
437
                    }
438

    
439
                    //Set Permissions
440

    
441
                    var permissions=objectInfo.GetPermissionString();
442
                    client.SetNonEmptyHeaderValue("X-Object-Sharing",permissions);
443

    
444
                    client.SetNonEmptyHeaderValue("Content-Disposition",objectInfo.ContendDisposition);
445
                    client.SetNonEmptyHeaderValue("Content-Encoding",objectInfo.ContentEncoding);
446
                    client.SetNonEmptyHeaderValue("X-Object-Manifest",objectInfo.Manifest);
447
                    var isPublic = objectInfo.IsPublic.ToString().ToLower();
448
                    client.Headers.Add("X-Object-Public", isPublic);
449

    
450

    
451
                    var uriBuilder = client.GetAddressBuilder(objectInfo.Container, objectInfo.Name);
452
                    var uri = uriBuilder.Uri;
453

    
454
                    var content = client.UploadValues(uri,new NameValueCollection());
455

    
456

    
457
                    client.AssertStatusOK("UpdateMetadata failed");
458
                    //If the status is NOT ACCEPTED or OK we have a problem
459
                    if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK))
460
                    {
461
                        Log.Error("Failed to update metadata");
462
                        throw new Exception("Failed to update metadata");
463
                    }
464

    
465
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
466
                }
467
            }
468

    
469
        }
470

    
471
        public void UpdateMetadata(ContainerInfo containerInfo)
472
        {
473
            if (containerInfo == null)
474
                throw new ArgumentNullException("containerInfo");
475
            Contract.EndContractBlock();
476

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

    
481

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

    
485
                    client.BaseAddress = GetAccountUrl(containerInfo.Account);
486
                    
487
                    client.Parameters.Clear();
488
                    
489

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

    
497
                    
498
                    //Set Policies
499
                    foreach (var policy in containerInfo.Policies)
500
                    {
501
                        var headerPolicy = String.Format("X-Container-Policy-{0}", policy.Key);
502
                        client.Headers.Add(headerPolicy, policy.Value);
503
                    }
504

    
505

    
506
                    var uriBuilder = client.GetAddressBuilder(containerInfo.Name,"");
507
                    var uri = uriBuilder.Uri;
508

    
509
                    var content = client.UploadValues(uri,new NameValueCollection());
510

    
511

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

    
520
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
521
                }
522
            }
523

    
524
        }
525

    
526

    
527
        public IList<ObjectInfo> ListObjects(string account, string container, DateTime? since = null)
528
        {
529
            if (String.IsNullOrWhiteSpace(container))
530
                throw new ArgumentNullException("container");
531
            Contract.EndContractBlock();
532

    
533
            using (log4net.ThreadContext.Stacks["Objects"].Push("List"))
534
            {
535
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
536

    
537
                using (var client = new RestClient(_baseClient))
538
                {
539
                    if (!String.IsNullOrWhiteSpace(account))
540
                        client.BaseAddress = GetAccountUrl(account);
541

    
542
                    client.Parameters.Clear();
543
                    client.Parameters.Add("format", "json");
544
                    client.IfModifiedSince = since;
545
                    var content = client.DownloadStringWithRetry(container, 3);
546

    
547
                    client.AssertStatusOK("ListObjects failed");
548

    
549
                    //If the result is empty, return an empty list,
550
                    var infos = String.IsNullOrWhiteSpace(content)
551
                                    ? new List<ObjectInfo>()
552
                                //Otherwise deserialize the object list into a list of ObjectInfos
553
                                    : JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
554

    
555
                    foreach (var info in infos)
556
                    {
557
                        info.Container = container;
558
                        info.Account = account;
559
                    }
560
                    if (Log.IsDebugEnabled) Log.DebugFormat("START");
561
                    return infos;
562
                }
563
            }
564
        }
565

    
566

    
567

    
568
        public IList<ObjectInfo> ListObjects(string account, string container, string folder, DateTime? since = null)
569
        {
570
            if (String.IsNullOrWhiteSpace(container))
571
                throw new ArgumentNullException("container");
572
            if (String.IsNullOrWhiteSpace(folder))
573
                throw new ArgumentNullException("folder");
574
            Contract.EndContractBlock();
575

    
576
            using (log4net.ThreadContext.Stacks["Objects"].Push("List"))
577
            {
578
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
579

    
580
                using (var client = new RestClient(_baseClient))
581
                {
582
                    if (!String.IsNullOrWhiteSpace(account))
583
                        client.BaseAddress = GetAccountUrl(account);
584

    
585
                    client.Parameters.Clear();
586
                    client.Parameters.Add("format", "json");
587
                    client.Parameters.Add("path", folder);
588
                    client.IfModifiedSince = since;
589
                    var content = client.DownloadStringWithRetry(container, 3);
590
                    client.AssertStatusOK("ListObjects failed");
591

    
592
                    var infos = JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
593

    
594
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
595
                    return infos;
596
                }
597
            }
598
        }
599

    
600
 
601
        public bool ContainerExists(string account, string container)
602
        {
603
            if (String.IsNullOrWhiteSpace(container))
604
                throw new ArgumentNullException("container", "The container property can't be empty");
605
            Contract.EndContractBlock();
606

    
607
            using (log4net.ThreadContext.Stacks["Containters"].Push("Exists"))
608
            {
609
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
610

    
611
                using (var client = new RestClient(_baseClient))
612
                {
613
                    if (!String.IsNullOrWhiteSpace(account))
614
                        client.BaseAddress = GetAccountUrl(account);
615

    
616
                    client.Parameters.Clear();
617
                    client.Head(container, 3);
618
                                        
619
                    bool result;
620
                    switch (client.StatusCode)
621
                    {
622
                        case HttpStatusCode.OK:
623
                        case HttpStatusCode.NoContent:
624
                            result=true;
625
                            break;
626
                        case HttpStatusCode.NotFound:
627
                            result=false;
628
                            break;
629
                        default:
630
                            throw CreateWebException("ContainerExists", client.StatusCode);
631
                    }
632
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
633

    
634
                    return result;
635
                }
636
                
637
            }
638
        }
639

    
640
        public bool ObjectExists(string account, string container, string objectName)
641
        {
642
            if (String.IsNullOrWhiteSpace(container))
643
                throw new ArgumentNullException("container", "The container property can't be empty");
644
            if (String.IsNullOrWhiteSpace(objectName))
645
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");
646
            Contract.EndContractBlock();
647

    
648
            using (var client = new RestClient(_baseClient))
649
            {
650
                if (!String.IsNullOrWhiteSpace(account))
651
                    client.BaseAddress = GetAccountUrl(account);
652

    
653
                client.Parameters.Clear();
654
                client.Head(container + "/" + objectName, 3);
655

    
656
                switch (client.StatusCode)
657
                {
658
                    case HttpStatusCode.OK:
659
                    case HttpStatusCode.NoContent:
660
                        return true;
661
                    case HttpStatusCode.NotFound:
662
                        return false;
663
                    default:
664
                        throw CreateWebException("ObjectExists", client.StatusCode);
665
                }
666
            }
667

    
668
        }
669

    
670
        public ObjectInfo GetObjectInfo(string account, string container, string objectName)
671
        {
672
            if (String.IsNullOrWhiteSpace(container))
673
                throw new ArgumentNullException("container", "The container property can't be empty");
674
            if (String.IsNullOrWhiteSpace(objectName))
675
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");
676
            Contract.EndContractBlock();
677

    
678
            using (log4net.ThreadContext.Stacks["Objects"].Push("GetObjectInfo"))
679
            {                
680

    
681
                using (var client = new RestClient(_baseClient))
682
                {
683
                    if (!String.IsNullOrWhiteSpace(account))
684
                        client.BaseAddress = GetAccountUrl(account);
685
                    try
686
                    {
687
                        client.Parameters.Clear();
688

    
689
                        client.Head(container + "/" + objectName, 3);
690

    
691
                        if (client.TimedOut)
692
                            return ObjectInfo.Empty;
693

    
694
                        switch (client.StatusCode)
695
                        {
696
                            case HttpStatusCode.OK:
697
                            case HttpStatusCode.NoContent:
698
                                var keys = client.ResponseHeaders.AllKeys.AsQueryable();
699
                                var tags = client.GetMeta("X-Object-Meta-");
700
                                var extensions = (from key in keys
701
                                                  where key.StartsWith("X-Object-") && !key.StartsWith("X-Object-Meta-")
702
                                                  select new {Name = key, Value = client.ResponseHeaders[key]})
703
                                    .ToDictionary(t => t.Name, t => t.Value);
704

    
705
                                var permissions=client.GetHeaderValue("X-Object-Sharing", true);
706
                                
707
                                
708
                                var info = new ObjectInfo
709
                                               {
710
                                                   Account = account,
711
                                                   Container = container,
712
                                                   Name = objectName,
713
                                                   Hash = client.GetHeaderValue("ETag"),
714
                                                   Content_Type = client.GetHeaderValue("Content-Type"),
715
                                                   Bytes = Convert.ToInt64(client.GetHeaderValue("Content-Length")),
716
                                                   Tags = tags,
717
                                                   Last_Modified = client.LastModified,
718
                                                   Extensions = extensions,
719
                                                   ContentEncoding=client.GetHeaderValue("Content-Encoding",true),
720
                                                   ContendDisposition = client.GetHeaderValue("Content-Disposition",true),
721
                                                   Manifest=client.GetHeaderValue("X-Object-Manifest",true),
722
                                                   PublicUrl=client.GetHeaderValue("X-Object-Public",true),                                                   
723
                                               };
724
                                info.SetPermissions(permissions);
725
                                return info;
726
                            case HttpStatusCode.NotFound:
727
                                return ObjectInfo.Empty;
728
                            default:
729
                                throw new WebException(
730
                                    String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}",
731
                                                  objectName, client.StatusCode));
732
                        }
733

    
734
                    }
735
                    catch (RetryException)
736
                    {
737
                        Log.WarnFormat("[RETRY FAIL] GetObjectInfo for {0} failed.");
738
                        return ObjectInfo.Empty;
739
                    }
740
                    catch (WebException e)
741
                    {
742
                        Log.Error(
743
                            String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}",
744
                                          objectName, client.StatusCode), e);
745
                        throw;
746
                    }
747
                }                
748
            }
749

    
750
        }
751

    
752
        public void CreateFolder(string account, string container, string folder)
753
        {
754
            if (String.IsNullOrWhiteSpace(container))
755
                throw new ArgumentNullException("container", "The container property can't be empty");
756
            if (String.IsNullOrWhiteSpace(folder))
757
                throw new ArgumentNullException("folder", "The folder property can't be empty");
758
            Contract.EndContractBlock();
759

    
760
            var folderUrl=String.Format("{0}/{1}",container,folder);
761
            using (var client = new RestClient(_baseClient))
762
            {
763
                if (!String.IsNullOrWhiteSpace(account))
764
                    client.BaseAddress = GetAccountUrl(account);
765

    
766
                client.Parameters.Clear();
767
                client.Headers.Add("Content-Type", @"application/directory");
768
                client.Headers.Add("Content-Length", "0");
769
                client.PutWithRetry(folderUrl, 3);
770

    
771
                if (client.StatusCode != HttpStatusCode.Created && client.StatusCode != HttpStatusCode.Accepted)
772
                    throw CreateWebException("CreateFolder", client.StatusCode);
773
            }
774
        }
775

    
776
     
777

    
778
        public ContainerInfo GetContainerInfo(string account, string container)
779
        {
780
            if (String.IsNullOrWhiteSpace(container))
781
                throw new ArgumentNullException("container", "The container property can't be empty");
782
            Contract.EndContractBlock();
783

    
784
            using (var client = new RestClient(_baseClient))
785
            {
786
                if (!String.IsNullOrWhiteSpace(account))
787
                    client.BaseAddress = GetAccountUrl(account);                
788

    
789
                client.Head(container);
790
                switch (client.StatusCode)
791
                {
792
                    case HttpStatusCode.OK:
793
                    case HttpStatusCode.NoContent:
794
                        var tags = client.GetMeta("X-Container-Meta-");
795
                        var policies = client.GetMeta("X-Container-Policy-");
796

    
797
                        var containerInfo = new ContainerInfo
798
                                                {
799
                                                    Account=account,
800
                                                    Name = container,
801
                                                    Count =
802
                                                        long.Parse(client.GetHeaderValue("X-Container-Object-Count")),
803
                                                    Bytes = long.Parse(client.GetHeaderValue("X-Container-Bytes-Used")),
804
                                                    BlockHash = client.GetHeaderValue("X-Container-Block-Hash"),
805
                                                    BlockSize=int.Parse(client.GetHeaderValue("X-Container-Block-Size")),
806
                                                    Last_Modified=client.LastModified,
807
                                                    Tags=tags,
808
                                                    Policies=policies
809
                                                };
810
                        
811

    
812
                        return containerInfo;
813
                    case HttpStatusCode.NotFound:
814
                        return ContainerInfo.Empty;
815
                    default:
816
                        throw CreateWebException("GetContainerInfo", client.StatusCode);
817
                }
818
            }
819
        }
820

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

    
827
            using (var client = new RestClient(_baseClient))
828
            {
829
                if (!String.IsNullOrWhiteSpace(account))
830
                    client.BaseAddress = GetAccountUrl(account);
831

    
832
                client.PutWithRetry(container, 3);
833
                var expectedCodes = new[] {HttpStatusCode.Created, HttpStatusCode.Accepted, HttpStatusCode.OK};
834
                if (!expectedCodes.Contains(client.StatusCode))
835
                    throw CreateWebException("CreateContainer", client.StatusCode);
836
            }
837
        }
838

    
839
        public void DeleteContainer(string account, string container)
840
        {
841
            if (String.IsNullOrWhiteSpace(container))
842
                throw new ArgumentNullException("container", "The container property can't be empty");
843
            Contract.EndContractBlock();
844

    
845
            using (var client = new RestClient(_baseClient))
846
            {
847
                if (!String.IsNullOrWhiteSpace(account))
848
                    client.BaseAddress = GetAccountUrl(account);
849

    
850
                client.DeleteWithRetry(container, 3);
851
                var expectedCodes = new[] {HttpStatusCode.NotFound, HttpStatusCode.NoContent};
852
                if (!expectedCodes.Contains(client.StatusCode))
853
                    throw CreateWebException("DeleteContainer", client.StatusCode);
854
            }
855

    
856
        }
857

    
858
        /// <summary>
859
        /// 
860
        /// </summary>
861
        /// <param name="account"></param>
862
        /// <param name="container"></param>
863
        /// <param name="objectName"></param>
864
        /// <param name="fileName"></param>
865
        /// <returns></returns>
866
        /// <remarks>This method should have no timeout or a very long one</remarks>
867
        //Asynchronously download the object specified by *objectName* in a specific *container* to 
868
        // a local file
869
        public Task GetObject(string account, string container, string objectName, string fileName)
870
        {
871
            if (String.IsNullOrWhiteSpace(container))
872
                throw new ArgumentNullException("container", "The container property can't be empty");
873
            if (String.IsNullOrWhiteSpace(objectName))
874
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");            
875
            Contract.EndContractBlock();
876

    
877
            try
878
            {
879
                //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient
880
                //object to avoid concurrency errors.
881
                //
882
                //Download operations take a long time therefore they have no timeout.
883
                var client = new RestClient(_baseClient) { Timeout = 0 };
884
                if (!String.IsNullOrWhiteSpace(account))
885
                    client.BaseAddress = GetAccountUrl(account);
886

    
887
                //The container and objectName are relative names. They are joined with the client's
888
                //BaseAddress to create the object's absolute address
889
                var builder = client.GetAddressBuilder(container, objectName);
890
                var uri = builder.Uri;
891

    
892
                //Download progress is reported to the Trace log
893
                Log.InfoFormat("[GET] START {0}", objectName);
894
                client.DownloadProgressChanged += (sender, args) => 
895
                    Log.InfoFormat("[GET PROGRESS] {0} {1}% {2} of {3}",
896
                                    fileName, args.ProgressPercentage,
897
                                    args.BytesReceived,
898
                                    args.TotalBytesToReceive);                                
899

    
900

    
901
                //Start downloading the object asynchronously
902
                var downloadTask = client.DownloadFileTask(uri, fileName);
903
                
904
                //Once the download completes
905
                return downloadTask.ContinueWith(download =>
906
                                      {
907
                                          //Delete the local client object
908
                                          client.Dispose();
909
                                          //And report failure or completion
910
                                          if (download.IsFaulted)
911
                                          {
912
                                              Log.ErrorFormat("[GET] FAIL for {0} with \r{1}", objectName,
913
                                                               download.Exception);
914
                                          }
915
                                          else
916
                                          {
917
                                              Log.InfoFormat("[GET] END {0}", objectName);                                             
918
                                          }
919
                                      });
920
            }
921
            catch (Exception exc)
922
            {
923
                Log.ErrorFormat("[GET] END {0} with {1}", objectName, exc);
924
                throw;
925
            }
926

    
927

    
928

    
929
        }
930

    
931
        public Task<IList<string>> PutHashMap(string account, string container, string objectName, TreeHash hash)
932
        {
933
            if (String.IsNullOrWhiteSpace(container))
934
                throw new ArgumentNullException("container");
935
            if (String.IsNullOrWhiteSpace(objectName))
936
                throw new ArgumentNullException("objectName");
937
            if (hash==null)
938
                throw new ArgumentNullException("hash");
939
            if (String.IsNullOrWhiteSpace(Token))
940
                throw new InvalidOperationException("Invalid Token");
941
            if (StorageUrl == null)
942
                throw new InvalidOperationException("Invalid Storage Url");
943
            Contract.EndContractBlock();
944

    
945

    
946
            //Don't use a timeout because putting the hashmap may be a long process
947
            var client = new RestClient(_baseClient) { Timeout = 0 };
948
            if (!String.IsNullOrWhiteSpace(account))
949
                client.BaseAddress = GetAccountUrl(account);
950

    
951
            //The container and objectName are relative names. They are joined with the client's
952
            //BaseAddress to create the object's absolute address
953
            var builder = client.GetAddressBuilder(container, objectName);
954
            builder.Query = "format=json&hashmap";
955
            var uri = builder.Uri;
956

    
957

    
958
            //Send the tree hash as Json to the server            
959
            client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
960
            var uploadTask=client.UploadStringTask(uri, "PUT", hash.ToJson());
961

    
962
            
963
            return uploadTask.ContinueWith(t =>
964
            {
965

    
966
                var empty = (IList<string>)new List<string>();
967
                
968

    
969
                //The server will respond either with 201-created if all blocks were already on the server
970
                if (client.StatusCode == HttpStatusCode.Created)                    
971
                {
972
                    //in which case we return an empty hash list
973
                    return empty;
974
                }
975
                //or with a 409-conflict and return the list of missing parts
976
                //A 409 will cause an exception so we need to check t.IsFaulted to avoid propagating the exception                
977
                if (t.IsFaulted)
978
                {
979
                    var ex = t.Exception.InnerException;
980
                    var we = ex as WebException;
981
                    var response = we.Response as HttpWebResponse;
982
                    if (response!=null && response.StatusCode==HttpStatusCode.Conflict)
983
                    {
984
                        //In case of 409 the missing parts will be in the response content                        
985
                        using (var stream = response.GetResponseStream())
986
                        using(var reader=new StreamReader(stream))
987
                        {
988
                            //We need to cleanup the content before returning it because it contains
989
                            //error content after the list of hashes
990
                            var hashes = new List<string>();
991
                            string line=null;
992
                            //All lines up to the first empty line are hashes
993
                            while(!String.IsNullOrWhiteSpace(line=reader.ReadLine()))
994
                            {
995
                                hashes.Add(line);
996
                            }
997

    
998
                            return hashes;
999
                        }                        
1000
                    }
1001
                    else
1002
                        //Any other status code is unexpected and the exception should be rethrown
1003
                        throw ex;
1004
                    
1005
                }
1006
                //Any other status code is unexpected but there was no exception. We can probably continue processing
1007
                else
1008
                {
1009
                    Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",client.StatusCode,client.StatusDescription);                    
1010
                }
1011
                return empty;
1012
            });
1013

    
1014
        }
1015

    
1016
        public Task<byte[]> GetBlock(string account, string container, Uri relativeUrl, long start, long? end)
1017
        {
1018
            if (String.IsNullOrWhiteSpace(Token))
1019
                throw new InvalidOperationException("Invalid Token");
1020
            if (StorageUrl == null)
1021
                throw new InvalidOperationException("Invalid Storage Url");
1022
            if (String.IsNullOrWhiteSpace(container))
1023
                throw new ArgumentNullException("container");
1024
            if (relativeUrl== null)
1025
                throw new ArgumentNullException("relativeUrl");
1026
            if (end.HasValue && end<0)
1027
                throw new ArgumentOutOfRangeException("end");
1028
            if (start<0)
1029
                throw new ArgumentOutOfRangeException("start");
1030
            Contract.EndContractBlock();
1031

    
1032

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

    
1038
            var builder = client.GetAddressBuilder(container, relativeUrl.ToString());
1039
            var uri = builder.Uri;
1040

    
1041
            return client.DownloadDataTask(uri)
1042
                .ContinueWith(t=>
1043
                                  {
1044
                                      client.Dispose();
1045
                                      return t.Result;
1046
                                  });
1047
        }
1048

    
1049

    
1050
        public Task PostBlock(string account, string container, byte[] block, int offset, int count)
1051
        {
1052
            if (String.IsNullOrWhiteSpace(container))
1053
                throw new ArgumentNullException("container");
1054
            if (block == null)
1055
                throw new ArgumentNullException("block");
1056
            if (offset < 0 || offset >= block.Length)
1057
                throw new ArgumentOutOfRangeException("offset");
1058
            if (count < 0 || count > block.Length)
1059
                throw new ArgumentOutOfRangeException("count");
1060
            if (String.IsNullOrWhiteSpace(Token))
1061
                throw new InvalidOperationException("Invalid Token");
1062
            if (StorageUrl == null)
1063
                throw new InvalidOperationException("Invalid Storage Url");                        
1064
            Contract.EndContractBlock();
1065

    
1066
                        
1067
            //Don't use a timeout because putting the hashmap may be a long process
1068
            var client = new RestClient(_baseClient) { Timeout = 0 };
1069
            if (!String.IsNullOrWhiteSpace(account))
1070
                client.BaseAddress = GetAccountUrl(account);
1071

    
1072
            var builder = client.GetAddressBuilder(container, "");
1073
            //We are doing an update
1074
            builder.Query = "update";
1075
            var uri = builder.Uri;
1076

    
1077
            client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
1078

    
1079
            Log.InfoFormat("[BLOCK POST] START");
1080

    
1081
            client.UploadProgressChanged += (sender, args) => 
1082
                Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}",
1083
                                    args.ProgressPercentage, args.BytesSent,
1084
                                    args.TotalBytesToSend);
1085
            client.UploadFileCompleted += (sender, args) => 
1086
                Log.InfoFormat("[BLOCK POST PROGRESS] Completed ");
1087

    
1088
            
1089
            //Send the block
1090
            var uploadTask = client.UploadDataTask(uri, "POST", block)
1091
            .ContinueWith(upload =>
1092
            {
1093
                client.Dispose();
1094

    
1095
                if (upload.IsFaulted)
1096
                {
1097
                    var exception = upload.Exception.InnerException;
1098
                    Log.ErrorFormat("[BLOCK POST] FAIL with \r{0}", exception);                        
1099
                    throw exception;
1100
                }
1101
                    
1102
                Log.InfoFormat("[BLOCK POST] END");
1103
            });
1104
            return uploadTask;            
1105
        }
1106

    
1107

    
1108
        public Task<TreeHash> GetHashMap(string account, string container, string objectName)
1109
        {
1110
            if (String.IsNullOrWhiteSpace(container))
1111
                throw new ArgumentNullException("container");
1112
            if (String.IsNullOrWhiteSpace(objectName))
1113
                throw new ArgumentNullException("objectName");
1114
            if (String.IsNullOrWhiteSpace(Token))
1115
                throw new InvalidOperationException("Invalid Token");
1116
            if (StorageUrl == null)
1117
                throw new InvalidOperationException("Invalid Storage Url");
1118
            Contract.EndContractBlock();
1119

    
1120
            try
1121
            {
1122
                //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient
1123
                //object to avoid concurrency errors.
1124
                //
1125
                //Download operations take a long time therefore they have no timeout.
1126
                //TODO: Do we really? this is a hashmap operation, not a download
1127
                var client = new RestClient(_baseClient) { Timeout = 0 };
1128
                if (!String.IsNullOrWhiteSpace(account))
1129
                    client.BaseAddress = GetAccountUrl(account);
1130

    
1131

    
1132
                //The container and objectName are relative names. They are joined with the client's
1133
                //BaseAddress to create the object's absolute address
1134
                var builder = client.GetAddressBuilder(container, objectName);
1135
                builder.Query = "format=json&hashmap";
1136
                var uri = builder.Uri;
1137
                
1138
                //Start downloading the object asynchronously
1139
                var downloadTask = client.DownloadStringTask(uri);
1140
                
1141
                //Once the download completes
1142
                return downloadTask.ContinueWith(download =>
1143
                {
1144
                    //Delete the local client object
1145
                    client.Dispose();
1146
                    //And report failure or completion
1147
                    if (download.IsFaulted)
1148
                    {
1149
                        Log.ErrorFormat("[GET HASH] FAIL for {0} with \r{1}", objectName,
1150
                                        download.Exception);
1151
                        throw download.Exception;
1152
                    }
1153
                                          
1154
                    //The server will return an empty string if the file is empty
1155
                    var json = download.Result;
1156
                    var treeHash = TreeHash.Parse(json);
1157
                    Log.InfoFormat("[GET HASH] END {0}", objectName);                                             
1158
                    return treeHash;
1159
                });
1160
            }
1161
            catch (Exception exc)
1162
            {
1163
                Log.ErrorFormat("[GET HASH] END {0} with {1}", objectName, exc);
1164
                throw;
1165
            }
1166

    
1167

    
1168

    
1169
        }
1170

    
1171

    
1172
        /// <summary>
1173
        /// 
1174
        /// </summary>
1175
        /// <param name="account"></param>
1176
        /// <param name="container"></param>
1177
        /// <param name="objectName"></param>
1178
        /// <param name="fileName"></param>
1179
        /// <param name="hash">Optional hash value for the file. If no hash is provided, the method calculates a new hash</param>
1180
        /// <remarks>>This method should have no timeout or a very long one</remarks>
1181
        public Task PutObject(string account, string container, string objectName, string fileName, string hash = null)
1182
        {
1183
            if (String.IsNullOrWhiteSpace(container))
1184
                throw new ArgumentNullException("container", "The container property can't be empty");
1185
            if (String.IsNullOrWhiteSpace(objectName))
1186
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");
1187
            if (String.IsNullOrWhiteSpace(fileName))
1188
                throw new ArgumentNullException("fileName", "The fileName property can't be empty");
1189
            if (!File.Exists(fileName))
1190
                throw new FileNotFoundException("The file does not exist",fileName);
1191
            Contract.EndContractBlock();
1192
            
1193
            try
1194
            {
1195

    
1196
                var client = new RestClient(_baseClient){Timeout=0};
1197
                if (!String.IsNullOrWhiteSpace(account))
1198
                    client.BaseAddress = GetAccountUrl(account);
1199

    
1200
                var builder = client.GetAddressBuilder(container, objectName);
1201
                var uri = builder.Uri;
1202

    
1203
                string etag = hash ?? CalculateHash(fileName);
1204

    
1205
                client.Headers.Add("Content-Type", "application/octet-stream");
1206
                client.Headers.Add("ETag", etag);
1207

    
1208

    
1209
                Log.InfoFormat("[PUT] START {0}", objectName);
1210
                client.UploadProgressChanged += (sender, args) =>
1211
                {
1212
                    using (log4net.ThreadContext.Stacks["PUT"].Push("Progress"))
1213
                    {
1214
                        Log.InfoFormat("{0} {1}% {2} of {3}", fileName, args.ProgressPercentage,
1215
                                       args.BytesSent, args.TotalBytesToSend);
1216
                    }
1217
                };
1218

    
1219
                client.UploadFileCompleted += (sender, args) =>
1220
                {
1221
                    using (log4net.ThreadContext.Stacks["PUT"].Push("Progress"))
1222
                    {
1223
                        Log.InfoFormat("Completed {0}", fileName);
1224
                    }
1225
                };
1226
                return client.UploadFileTask(uri, "PUT", fileName)
1227
                    .ContinueWith(upload=>
1228
                                      {
1229
                                          client.Dispose();
1230

    
1231
                                          if (upload.IsFaulted)
1232
                                          {
1233
                                              var exc = upload.Exception.InnerException;
1234
                                              Log.ErrorFormat("[PUT] FAIL for {0} with \r{1}",objectName,exc);
1235
                                              throw exc;
1236
                                          }
1237
                                          else
1238
                                            Log.InfoFormat("[PUT] END {0}", objectName);
1239
                                      });
1240
            }
1241
            catch (Exception exc)
1242
            {
1243
                Log.ErrorFormat("[PUT] END {0} with {1}", objectName, exc);
1244
                throw;
1245
            }                
1246

    
1247
        }
1248
       
1249
        
1250
        private static string CalculateHash(string fileName)
1251
        {
1252
            Contract.Requires(!String.IsNullOrWhiteSpace(fileName));
1253
            Contract.EndContractBlock();
1254

    
1255
            string hash;
1256
            using (var hasher = MD5.Create())
1257
            using(var stream=File.OpenRead(fileName))
1258
            {
1259
                var hashBuilder=new StringBuilder();
1260
                foreach (byte b in hasher.ComputeHash(stream))
1261
                    hashBuilder.Append(b.ToString("x2").ToLower());
1262
                hash = hashBuilder.ToString();                
1263
            }
1264
            return hash;
1265
        }
1266
        
1267
        public void MoveObject(string account, string sourceContainer, string oldObjectName, string targetContainer, string newObjectName)
1268
        {
1269
            if (String.IsNullOrWhiteSpace(sourceContainer))
1270
                throw new ArgumentNullException("sourceContainer", "The container property can't be empty");
1271
            if (String.IsNullOrWhiteSpace(oldObjectName))
1272
                throw new ArgumentNullException("oldObjectName", "The oldObjectName property can't be empty");
1273
            if (String.IsNullOrWhiteSpace(targetContainer))
1274
                throw new ArgumentNullException("targetContainer", "The container property can't be empty");
1275
            if (String.IsNullOrWhiteSpace(newObjectName))
1276
                throw new ArgumentNullException("newObjectName", "The newObjectName property can't be empty");
1277
            Contract.EndContractBlock();
1278

    
1279
            var targetUrl = targetContainer + "/" + newObjectName;
1280
            var sourceUrl = String.Format("/{0}/{1}", sourceContainer, oldObjectName);
1281

    
1282
            using (var client = new RestClient(_baseClient))
1283
            {
1284
                if (!String.IsNullOrWhiteSpace(account))
1285
                    client.BaseAddress = GetAccountUrl(account);
1286

    
1287
                client.Headers.Add("X-Move-From", sourceUrl);
1288
                client.PutWithRetry(targetUrl, 3);
1289

    
1290
                var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created};
1291
                if (!expectedCodes.Contains(client.StatusCode))
1292
                    throw CreateWebException("MoveObject", client.StatusCode);
1293
            }
1294
        }
1295

    
1296
        public void DeleteObject(string account, string sourceContainer, string objectName)
1297
        {            
1298
            if (String.IsNullOrWhiteSpace(sourceContainer))
1299
                throw new ArgumentNullException("sourceContainer", "The container property can't be empty");
1300
            if (String.IsNullOrWhiteSpace(objectName))
1301
                throw new ArgumentNullException("objectName", "The oldObjectName property can't be empty");
1302
            Contract.EndContractBlock();
1303

    
1304
            var targetUrl = FolderConstants.TrashContainer + "/" + objectName;
1305
            var sourceUrl = String.Format("/{0}/{1}", sourceContainer, objectName);
1306

    
1307
            using (var client = new RestClient(_baseClient))
1308
            {
1309
                if (!String.IsNullOrWhiteSpace(account))
1310
                    client.BaseAddress = GetAccountUrl(account);
1311

    
1312
                client.Headers.Add("X-Move-From", sourceUrl);
1313
                client.AllowedStatusCodes.Add(HttpStatusCode.NotFound);
1314
                client.PutWithRetry(targetUrl, 3);
1315

    
1316
                var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created,HttpStatusCode.NotFound};
1317
                if (!expectedCodes.Contains(client.StatusCode))
1318
                    throw CreateWebException("DeleteObject", client.StatusCode);
1319
            }
1320
        }
1321

    
1322
      
1323
        private static WebException CreateWebException(string operation, HttpStatusCode statusCode)
1324
        {
1325
            return new WebException(String.Format("{0} failed with unexpected status code {1}", operation, statusCode));
1326
        }
1327

    
1328
        
1329
    }
1330

    
1331
    public class ShareAccountInfo
1332
    {
1333
        public DateTime? last_modified { get; set; }
1334
        public string name { get; set; }
1335
    }
1336
}