Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (56 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(account))
824
                throw new ArgumentNullException("account");
825
            if (String.IsNullOrWhiteSpace(container))
826
                throw new ArgumentNullException("container");
827
            Contract.EndContractBlock();
828

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

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

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

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

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

    
858
        }
859

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

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

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

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

    
902

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

    
929

    
930

    
931
        }
932

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

    
947

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

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

    
959

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

    
964
            
965
            return uploadTask.ContinueWith(t =>
966
            {
967

    
968
                var empty = (IList<string>)new List<string>();
969
                
970

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

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

    
1016
        }
1017

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

    
1034

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

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

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

    
1051

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

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

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

    
1079
            client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
1080

    
1081
            Log.InfoFormat("[BLOCK POST] START");
1082

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

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

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

    
1109

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

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

    
1133

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

    
1169

    
1170

    
1171
        }
1172

    
1173

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

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

    
1202
                var builder = client.GetAddressBuilder(container, objectName);
1203
                var uri = builder.Uri;
1204

    
1205
                string etag = hash ?? CalculateHash(fileName);
1206

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

    
1210

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1330
        
1331
    }
1332

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