Statistics
| Branch: | Revision:

root / trunk / Pithos.Network / CloudFilesClient.cs @ 126f90b3

History | View | Annotate | Download (58.6 kB)

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

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

    
47

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

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

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

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

    
97

    
98
        protected Uri RootAddressUri { get; set; }
99

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

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

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

    
128
        public string AuthenticationUrl { get; set; }
129

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

    
136
        public bool UsePithos { get; set; }
137

    
138

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

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

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

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

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

    
174

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

    
189

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

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

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

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

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

    
204
                authClient.DownloadStringWithRetry(VersionPath, 3);
205

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

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

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

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

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

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

    
247
        }
248

    
249

    
250

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

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

    
274
        }
275

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

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

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

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

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

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

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

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

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

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

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

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

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

    
353
                using (var client = new RestClient(_baseClient))
354
                {
355

    
356
                    client.BaseAddress = GetAccountUrl(target.Account);
357

    
358
                    client.Parameters.Clear();
359
                    client.Parameters.Add("update", "");
360

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

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

    
378
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
379
                }
380
            }
381

    
382

    
383
        }
384

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

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

    
408
                    client.BaseAddress = GetAccountUrl(account);
409

    
410
                    client.Parameters.Clear();
411
                    client.Parameters.Add("format", "json");
412

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

    
420
                    var content = client.DownloadStringWithRetry(container, 3);
421

    
422
                    client.AssertStatusOK("ShareObject failed");
423

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

    
430
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
431
                }
432
            }
433

    
434

    
435
        }
436

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

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

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

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

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

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

    
467
                }
468

    
469
            }
470
        }
471

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

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

    
482

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

    
486
                    client.BaseAddress = GetAccountUrl(objectInfo.Account);
487
                    
488
                    client.Parameters.Clear();
489
                    
490

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

    
498
                    //Set Permissions
499

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

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

    
509

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

    
513
                    client.UploadValues(uri,new NameValueCollection());
514

    
515

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

    
524
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
525
                }
526
            }
527

    
528
        }
529

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

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

    
540

    
541
                using(var client=new RestClient(_baseClient))
542
                {
543

    
544
                    client.BaseAddress = GetAccountUrl(containerInfo.Account);
545
                    
546
                    client.Parameters.Clear();
547
                    
548

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

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

    
564

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

    
568
                    client.UploadValues(uri,new NameValueCollection());
569

    
570

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

    
579
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
580
                }
581
            }
582

    
583
        }
584

    
585

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

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

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

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

    
606
                    client.AssertStatusOK("ListObjects failed");
607

    
608
                    //HACK: Must add it to all other operations as well
609
                    StatusCode = client.StatusCode;
610
                    //If the result is empty, return an empty list,
611
                    var infos = String.IsNullOrWhiteSpace(content)
612
                                    ? new List<ObjectInfo>()
613
                                //Otherwise deserialize the object list into a list of ObjectInfos
614
                                    : JsonConvert.DeserializeObject<IList<ObjectInfo>>(content);
615

    
616
                    foreach (var info in infos)
617
                    {
618
                        info.Container = container;
619
                        info.Account = account;
620
                        info.StorageUri = this.StorageUrl;
621
                    }
622
                    if (Log.IsDebugEnabled) Log.DebugFormat("END");
623
                    return infos;
624
                }
625
            }
626
        }
627

    
628
        public HttpStatusCode StatusCode { get; set; }
629

    
630

    
631
        public IList<ObjectInfo> ListObjects(string account, string container, string folder, DateTime? since = null)
632
        {
633
            if (String.IsNullOrWhiteSpace(container))
634
                throw new ArgumentNullException("container");
635
/*
636
            if (String.IsNullOrWhiteSpace(folder))
637
                throw new ArgumentNullException("folder");
638
*/
639
            Contract.EndContractBlock();
640

    
641
            using (ThreadContext.Stacks["Objects"].Push("List"))
642
            {
643
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
644

    
645
                using (var client = new RestClient(_baseClient))
646
                {
647
                    if (!String.IsNullOrWhiteSpace(account))
648
                        client.BaseAddress = GetAccountUrl(account);
649

    
650
                    client.Parameters.Clear();
651
                    client.Parameters.Add("format", "json");
652
                    client.Parameters.Add("path", folder);
653
                    client.IfModifiedSince = since;
654
                    var content = client.DownloadStringWithRetry(container, 3);
655
                    client.AssertStatusOK("ListObjects failed");
656

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

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

    
678
            using (ThreadContext.Stacks["Containters"].Push("Exists"))
679
            {
680
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
681

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

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

    
705
                    return result;
706
                }
707
                
708
            }
709
        }
710

    
711
        public bool ObjectExists(string account, string container, string objectName)
712
        {
713
            if (String.IsNullOrWhiteSpace(container))
714
                throw new ArgumentNullException("container", "The container property can't be empty");
715
            if (String.IsNullOrWhiteSpace(objectName))
716
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");
717
            Contract.EndContractBlock();
718

    
719
            using (var client = new RestClient(_baseClient))
720
            {
721
                if (!String.IsNullOrWhiteSpace(account))
722
                    client.BaseAddress = GetAccountUrl(account);
723

    
724
                client.Parameters.Clear();
725
                client.Head(container + "/" + objectName, 3);
726

    
727
                switch (client.StatusCode)
728
                {
729
                    case HttpStatusCode.OK:
730
                    case HttpStatusCode.NoContent:
731
                        return true;
732
                    case HttpStatusCode.NotFound:
733
                        return false;
734
                    default:
735
                        throw CreateWebException("ObjectExists", client.StatusCode);
736
                }
737
            }
738

    
739
        }
740

    
741
        public ObjectInfo GetObjectInfo(string account, string container, string objectName)
742
        {
743
            if (String.IsNullOrWhiteSpace(container))
744
                throw new ArgumentNullException("container", "The container property can't be empty");
745
            if (String.IsNullOrWhiteSpace(objectName))
746
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");
747
            Contract.EndContractBlock();
748

    
749
            using (ThreadContext.Stacks["Objects"].Push("GetObjectInfo"))
750
            {                
751

    
752
                using (var client = new RestClient(_baseClient))
753
                {
754
                    if (!String.IsNullOrWhiteSpace(account))
755
                        client.BaseAddress = GetAccountUrl(account);
756
                    try
757
                    {
758
                        client.Parameters.Clear();
759

    
760
                        client.Head(container + "/" + objectName, 3);
761

    
762
                        if (client.TimedOut)
763
                            return ObjectInfo.Empty;
764

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

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

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

    
821
        }
822

    
823
        public void CreateFolder(string account, string container, string folder)
824
        {
825
            if (String.IsNullOrWhiteSpace(container))
826
                throw new ArgumentNullException("container", "The container property can't be empty");
827
            if (String.IsNullOrWhiteSpace(folder))
828
                throw new ArgumentNullException("folder", "The folder property can't be empty");
829
            Contract.EndContractBlock();
830

    
831
            var folderUrl=String.Format("{0}/{1}",container,folder);
832
            using (var client = new RestClient(_baseClient))
833
            {
834
                if (!String.IsNullOrWhiteSpace(account))
835
                    client.BaseAddress = GetAccountUrl(account);
836

    
837
                client.Parameters.Clear();
838
                client.Headers.Add("Content-Type", @"application/directory");
839
                client.Headers.Add("Content-Length", "0");
840
                client.PutWithRetry(folderUrl, 3);
841

    
842
                if (client.StatusCode != HttpStatusCode.Created && client.StatusCode != HttpStatusCode.Accepted)
843
                    throw CreateWebException("CreateFolder", client.StatusCode);
844
            }
845
        }
846

    
847
     
848

    
849
        public ContainerInfo GetContainerInfo(string account, string container)
850
        {
851
            if (String.IsNullOrWhiteSpace(container))
852
                throw new ArgumentNullException("container", "The container property can't be empty");
853
            Contract.EndContractBlock();
854

    
855
            using (var client = new RestClient(_baseClient))
856
            {
857
                if (!String.IsNullOrWhiteSpace(account))
858
                    client.BaseAddress = GetAccountUrl(account);                
859

    
860
                client.Head(container);
861
                switch (client.StatusCode)
862
                {
863
                    case HttpStatusCode.OK:
864
                    case HttpStatusCode.NoContent:
865
                        var tags = client.GetMeta("X-Container-Meta-");
866
                        var policies = client.GetMeta("X-Container-Policy-");
867

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

    
883
                        return containerInfo;
884
                    case HttpStatusCode.NotFound:
885
                        return ContainerInfo.Empty;
886
                    default:
887
                        throw CreateWebException("GetContainerInfo", client.StatusCode);
888
                }
889
            }
890
        }
891

    
892
        public void CreateContainer(string account, string container)
893
        {
894
            if (String.IsNullOrWhiteSpace(account))
895
                throw new ArgumentNullException("account");
896
            if (String.IsNullOrWhiteSpace(container))
897
                throw new ArgumentNullException("container");
898
            Contract.EndContractBlock();
899

    
900
            using (var client = new RestClient(_baseClient))
901
            {
902
                if (!String.IsNullOrWhiteSpace(account))
903
                    client.BaseAddress = GetAccountUrl(account);
904

    
905
                client.PutWithRetry(container, 3);
906
                var expectedCodes = new[] {HttpStatusCode.Created, HttpStatusCode.Accepted, HttpStatusCode.OK};
907
                if (!expectedCodes.Contains(client.StatusCode))
908
                    throw CreateWebException("CreateContainer", client.StatusCode);
909
            }
910
        }
911

    
912
        public void DeleteContainer(string account, string container)
913
        {
914
            if (String.IsNullOrWhiteSpace(container))
915
                throw new ArgumentNullException("container", "The container property can't be empty");
916
            Contract.EndContractBlock();
917

    
918
            using (var client = new RestClient(_baseClient))
919
            {
920
                if (!String.IsNullOrWhiteSpace(account))
921
                    client.BaseAddress = GetAccountUrl(account);
922

    
923
                client.DeleteWithRetry(container, 3);
924
                var expectedCodes = new[] {HttpStatusCode.NotFound, HttpStatusCode.NoContent};
925
                if (!expectedCodes.Contains(client.StatusCode))
926
                    throw CreateWebException("DeleteContainer", client.StatusCode);
927
            }
928

    
929
        }
930

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

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

    
960
                //The container and objectName are relative names. They are joined with the client's
961
                //BaseAddress to create the object's absolute address
962
                var builder = client.GetAddressBuilder(container, objectName);
963
                var uri = builder.Uri;
964

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

    
973

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

    
1000

    
1001

    
1002
        }
1003

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

    
1018

    
1019
            //Don't use a timeout because putting the hashmap may be a long process
1020
            var client = new RestClient(_baseClient) { Timeout = 0 };           
1021
            if (!String.IsNullOrWhiteSpace(account))
1022
                client.BaseAddress = GetAccountUrl(account);
1023

    
1024
            //The container and objectName are relative names. They are joined with the client's
1025
            //BaseAddress to create the object's absolute address
1026
            var builder = client.GetAddressBuilder(container, objectName);
1027
            builder.Query = "format=json&hashmap";
1028
            var uri = builder.Uri;
1029

    
1030

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

    
1039
                var empty = (IList<string>)new List<string>();
1040
                
1041

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

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

    
1084
        }
1085

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

    
1102

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

    
1108
            var builder = client.GetAddressBuilder(container, relativeUrl.ToString());
1109
            var uri = builder.Uri;
1110

    
1111
            return client.DownloadDataTask(uri)
1112
                .ContinueWith(t=>
1113
                                  {
1114
                                      client.Dispose();
1115
                                      return t.Result;
1116
                                  });
1117
        }
1118

    
1119

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

    
1136

    
1137
            try
1138
            {
1139

    
1140
            //Don't use a timeout because putting the hashmap may be a long process
1141
                using (var client = new RestClient(_baseClient) { Timeout = 0 })
1142
                {
1143
                    if (!String.IsNullOrWhiteSpace(account))
1144
                        client.BaseAddress = GetAccountUrl(account);
1145

    
1146
                    var builder = client.GetAddressBuilder(container, "");
1147
                    //We are doing an update
1148
                    builder.Query = "update";
1149
                    var uri = builder.Uri;
1150

    
1151
                    client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
1152

    
1153
                    Log.InfoFormat("[BLOCK POST] START");
1154

    
1155
                    client.UploadProgressChanged += (sender, args) =>
1156
                                                    Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}",
1157
                                                                   args.ProgressPercentage, args.BytesSent,
1158
                                                                   args.TotalBytesToSend);
1159
                    client.UploadFileCompleted += (sender, args) =>
1160
                                                  Log.InfoFormat("[BLOCK POST PROGRESS] Completed ");
1161

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

    
1176

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

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

    
1203
                    //The container and objectName are relative names. They are joined with the client's
1204
                    //BaseAddress to create the object's absolute address
1205
                    var builder = client.GetAddressBuilder(container, objectName);
1206
                    builder.Query = "format=json&hashmap";
1207
                    var uri = builder.Uri;
1208

    
1209

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

    
1222
        }
1223

    
1224

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

    
1251
                using (var client = new RestClient(_baseClient) { Timeout = 0 })
1252
                {
1253
                    if (!String.IsNullOrWhiteSpace(account))
1254
                        client.BaseAddress = GetAccountUrl(account);
1255

    
1256
                    var builder = client.GetAddressBuilder(container, objectName);
1257
                    var uri = builder.Uri;
1258

    
1259
                    string etag = hash ?? CalculateHash(fileName);
1260

    
1261
                    client.Headers.Add("Content-Type", contentType);
1262
                    client.Headers.Add("ETag", etag);
1263

    
1264

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

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

    
1289
                Log.InfoFormat("[PUT] END {0}", objectName);
1290
            }
1291
            catch (Exception exc)
1292
            {
1293
                Log.ErrorFormat("[PUT] END {0} with {1}", objectName, exc);
1294
                throw;
1295
            }                
1296

    
1297
        }
1298
       
1299
        
1300
        private static string CalculateHash(string fileName)
1301
        {
1302
            Contract.Requires(!String.IsNullOrWhiteSpace(fileName));
1303
            Contract.EndContractBlock();
1304

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

    
1329
            var targetUrl = targetContainer + "/" + newObjectName;
1330
            var sourceUrl = String.Format("/{0}/{1}", sourceContainer, oldObjectName);
1331

    
1332
            using (var client = new RestClient(_baseClient))
1333
            {
1334
                if (!String.IsNullOrWhiteSpace(account))
1335
                    client.BaseAddress = GetAccountUrl(account);
1336

    
1337
                client.Headers.Add("X-Move-From", sourceUrl);
1338
                client.PutWithRetry(targetUrl, 3);
1339

    
1340
                var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created};
1341
                if (!expectedCodes.Contains(client.StatusCode))
1342
                    throw CreateWebException("MoveObject", client.StatusCode);
1343
            }
1344
        }
1345

    
1346
        public void DeleteObject(string account, string sourceContainer, string objectName)
1347
        {            
1348
            if (String.IsNullOrWhiteSpace(sourceContainer))
1349
                throw new ArgumentNullException("sourceContainer", "The container property can't be empty");
1350
            if (String.IsNullOrWhiteSpace(objectName))
1351
                throw new ArgumentNullException("objectName", "The oldObjectName property can't be empty");
1352
            Contract.EndContractBlock();
1353

    
1354
            var targetUrl = FolderConstants.TrashContainer + "/" + objectName;
1355
            var sourceUrl = String.Format("/{0}/{1}", sourceContainer, objectName);
1356

    
1357
            using (var client = new RestClient(_baseClient))
1358
            {
1359
                if (!String.IsNullOrWhiteSpace(account))
1360
                    client.BaseAddress = GetAccountUrl(account);
1361

    
1362
                client.Headers.Add("X-Move-From", sourceUrl);
1363
                client.AllowedStatusCodes.Add(HttpStatusCode.NotFound);
1364
                client.PutWithRetry(targetUrl, 3);
1365

    
1366
                var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created,HttpStatusCode.NotFound};
1367
                if (!expectedCodes.Contains(client.StatusCode))
1368
                    throw CreateWebException("DeleteObject", client.StatusCode);
1369
            }
1370
        }
1371

    
1372
      
1373
        private static WebException CreateWebException(string operation, HttpStatusCode statusCode)
1374
        {
1375
            return new WebException(String.Format("{0} failed with unexpected status code {1}", operation, statusCode));
1376
        }
1377

    
1378

    
1379
/*
1380
        public IEnumerable<ObjectInfo> ListDirectories(ContainerInfo container)
1381
        {
1382
            var directories=this.ListObjects(container.Account, container.Name, "/");
1383
        }
1384
*/
1385
    }
1386

    
1387
    public class ShareAccountInfo
1388
    {
1389
        public DateTime? last_modified { get; set; }
1390
        public string name { get; set; }
1391
    }
1392
}