diff --git a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/BucketServiceTests.cs b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/BucketServiceTests.cs index 8f1844b..7afe3b4 100644 --- a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/BucketServiceTests.cs +++ b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/BucketServiceTests.cs @@ -1,8 +1,6 @@ -using System.IO; -using System.Linq; -using System.Net; +using System.Net; using System.Threading.Tasks; -using AspNetCore.Yandex.ObjectStorage.Models; +using AspNetCore.Yandex.ObjectStorage.Bucket.Requests; using Bogus; using Xunit; @@ -56,6 +54,48 @@ public async Task DeleteBucket_Success() Assert.True(deleteResult.IsSuccess); } + [Fact(DisplayName = "[003] List bucket objects")] + public async Task ListBucketObjects_Success() + { + const string bucketName = "testbucketlib"; + + var result = await _yandexStorageService.BucketService.GetBucketListObjects(new BucketListObjectsParameters() + { + BucketName = bucketName + }); + + Assert.True(result.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + + var listObjectsResult = await result.ReadResultAsync(); + + Assert.True(listObjectsResult.IsSuccess); + + var listObjects = listObjectsResult.Value; + + Assert.Equal(2,listObjects.Contents.Count); + } + + + [Fact(DisplayName = "[004] Bucket list")] + public async Task BucketList_Success() + { + var result = await _yandexStorageService.BucketService.GetBucketList(); + + Assert.True(result.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + + var bucketListResult = await result.ReadResultAsync(); + + Assert.True(bucketListResult.IsSuccess); + + var bucketList = bucketListResult.Value; + + Assert.Single(bucketList.Buckets); + Assert.Equal("testbucketlib", bucketList.Buckets[0].Name); + } + + private async Task DeleteBucketAsync(string bucketName) { await _yandexStorageService.BucketService.CreateBucket(bucketName); diff --git a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/EnvironmentOptions.cs b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/EnvironmentOptions.cs index 41b34fa..79805b3 100644 --- a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/EnvironmentOptions.cs +++ b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/EnvironmentOptions.cs @@ -9,9 +9,9 @@ public static YandexStorageOptions GetFromEnvironment() { return new YandexStorageOptions { - BucketName = "testbucketlib", - AccessKey = "YCAJEmcixPRabFaAWHp-k_RA7", - SecretKey = "YCOflzMKw_FF5GjpxX2jMjSz7fgfj7cT_aItejSz" + BucketName = Environment.GetEnvironmentVariable("BucketName"), + AccessKey = Environment.GetEnvironmentVariable("AccessKey"), + SecretKey = Environment.GetEnvironmentVariable("SecretKey") }; } @@ -19,9 +19,9 @@ public static YandexStorageOptions GetFromEnvironmentWithNotDefaultLocation() { return new YandexStorageOptions { - BucketName = "testbucketlib", - AccessKey = "YCAJEmcixPRabFaAWHp-k_RA7", - SecretKey = "YCOflzMKw_FF5GjpxX2jMjSz7fgfj7cT_aItejSz", + BucketName = Environment.GetEnvironmentVariable("BucketName"), + AccessKey = Environment.GetEnvironmentVariable("AccessKey"), + SecretKey = Environment.GetEnvironmentVariable("SecretKey"), Location = "ru-central1", Endpoint = "s3.yandexcloud.net" }; diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/BucketService.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/BucketService.cs index c24bfd5..6e7a579 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/BucketService.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/BucketService.cs @@ -1,6 +1,10 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using AspNetCore.Yandex.ObjectStorage.Bucket.Builders; +using AspNetCore.Yandex.ObjectStorage.Bucket.Models; +using AspNetCore.Yandex.ObjectStorage.Bucket.Requests; +using AspNetCore.Yandex.ObjectStorage.Bucket.Responses; using AspNetCore.Yandex.ObjectStorage.Configuration; using AspNetCore.Yandex.ObjectStorage.Enums; using AspNetCore.Yandex.ObjectStorage.Models; @@ -26,11 +30,7 @@ public BucketService(YandexStorageOptions options) public async Task CreateBucket(string bucketName) { var builder = new BucketPutRequestBuilder(_options); - var createModel = new BucketCreateOptions - { - BucketName = bucketName - }; - var requestMessage = builder.Build(createModel).GetResult(); + var requestMessage = builder.Build(bucketName).GetResult(); using var client = new HttpClient(); var response = await client.SendAsync(requestMessage); @@ -43,14 +43,27 @@ public Task GetBucketMeta(string bucketName) throw new NotImplementedException(); } - public Task GetBucketListObjects(string bucketName) + public async Task GetBucketListObjects(BucketListObjectsParameters parameters) { - throw new NotImplementedException(); + var builder = new BucketListObjectsRequestBuilder(_options); + var requestMessage = builder.Build(parameters).GetResult(); + + using var client = new HttpClient(); + var response = await client.SendAsync(requestMessage); + + return new S3BucketObjectListResponse(response); } - public Task GetBucketList() + public async Task GetBucketList() { - throw new NotImplementedException(); + var requestMessage = new BucketListRequestBuilder(_options) + .Build() + .GetResult(); + + using var client = new HttpClient(); + var response = await client.SendAsync(requestMessage); + + return new S3BucketListResponse(response); } public async Task DeleteBucket(string bucketName) diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/BucketDeleteRequestBuilder.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketDeleteRequestBuilder.cs similarity index 94% rename from AspNetCore.Yandex.ObjectStorage/Bucket/BucketDeleteRequestBuilder.cs rename to AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketDeleteRequestBuilder.cs index 2336237..0008a31 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/BucketDeleteRequestBuilder.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketDeleteRequestBuilder.cs @@ -3,7 +3,7 @@ using AspNetCore.Yandex.ObjectStorage.Configuration; using AspNetCore.Yandex.ObjectStorage.Helpers; -namespace AspNetCore.Yandex.ObjectStorage.Bucket +namespace AspNetCore.Yandex.ObjectStorage.Bucket.Builders { internal class BucketDeleteRequestBuilder { diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListObjectsRequestBuilder.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListObjectsRequestBuilder.cs new file mode 100644 index 0000000..d64827e --- /dev/null +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListObjectsRequestBuilder.cs @@ -0,0 +1,59 @@ +using System; +using System.Net.Http; +using AspNetCore.Yandex.ObjectStorage.Bucket.Requests; +using AspNetCore.Yandex.ObjectStorage.Configuration; +using AspNetCore.Yandex.ObjectStorage.Helpers; + +namespace AspNetCore.Yandex.ObjectStorage.Bucket.Builders +{ + internal class BucketListObjectsRequestBuilder + { + private readonly YandexStorageOptions _options; + private HttpRequestMessage _request; + + internal BucketListObjectsRequestBuilder(YandexStorageOptions options) + { + _options = options; + } + + internal BucketListObjectsRequestBuilder Build(BucketListObjectsParameters parameters) + { + var url = $"{_options.Protocol}://{_options.Endpoint}/{parameters.BucketName}?{FormatParameters(parameters)}"; + + var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(url)); + var dateAmz = DateTime.UtcNow; + + requestMessage.AddBothHeaders(_options, dateAmz); + + string[] headers = { "host", "x-amz-content-sha256", "x-amz-date" }; + requestMessage.AddAuthHeader(_options, dateAmz, headers); + _request = requestMessage; + + return this; + } + + internal HttpRequestMessage GetResult() + { + return _request; + } + + private static string FormatParameters(BucketListObjectsParameters parameters) + { + var continueToken = string.IsNullOrEmpty(parameters.ContinueToken) + ? string.Empty + : $"&continuation-token={parameters.ContinueToken}"; + var delimiter = string.IsNullOrEmpty(parameters.Delimiter) + ? string.Empty + : $"&delimiter={parameters.Delimiter}"; + var maxKeys = $"&max-keys={parameters.MaxKeys}"; + var prefix = string.IsNullOrEmpty(parameters.Prefix) + ? string.Empty + : $"&prefix={parameters.Prefix}"; + var startAfter = string.IsNullOrEmpty(parameters.StartAfter) + ? string.Empty + : $"&start-after={parameters.StartAfter}"; + + return string.Concat("list-type=2", continueToken, delimiter, maxKeys, prefix, startAfter); + } + } +} \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListRequestBuilder.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListRequestBuilder.cs new file mode 100644 index 0000000..a347b9c --- /dev/null +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListRequestBuilder.cs @@ -0,0 +1,40 @@ +using System; +using System.Net.Http; +using AspNetCore.Yandex.ObjectStorage.Bucket.Requests; +using AspNetCore.Yandex.ObjectStorage.Configuration; +using AspNetCore.Yandex.ObjectStorage.Helpers; + +namespace AspNetCore.Yandex.ObjectStorage.Bucket.Builders +{ + internal class BucketListRequestBuilder + { + private readonly YandexStorageOptions _options; + private HttpRequestMessage _request; + + internal BucketListRequestBuilder(YandexStorageOptions options) + { + _options = options; + } + + internal BucketListRequestBuilder Build() + { + var url = $"{_options.Protocol}://{_options.Endpoint}"; + + var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(url)); + var dateAmz = DateTime.UtcNow; + + requestMessage.AddBothHeaders(_options, dateAmz); + + string[] headers = { "host", "x-amz-content-sha256", "x-amz-date" }; + requestMessage.AddAuthHeader(_options, dateAmz, headers); + _request = requestMessage; + + return this; + } + + internal HttpRequestMessage GetResult() + { + return _request; + } + } +} \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/BucketPutRequestBuilder.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketPutRequestBuilder.cs similarity index 80% rename from AspNetCore.Yandex.ObjectStorage/Bucket/BucketPutRequestBuilder.cs rename to AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketPutRequestBuilder.cs index f2685d8..5c82f42 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/BucketPutRequestBuilder.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketPutRequestBuilder.cs @@ -3,7 +3,7 @@ using AspNetCore.Yandex.ObjectStorage.Configuration; using AspNetCore.Yandex.ObjectStorage.Helpers; -namespace AspNetCore.Yandex.ObjectStorage.Bucket +namespace AspNetCore.Yandex.ObjectStorage.Bucket.Builders { internal class BucketPutRequestBuilder { @@ -15,9 +15,9 @@ internal BucketPutRequestBuilder(YandexStorageOptions options) _options = options; } - internal BucketPutRequestBuilder Build(BucketCreateOptions createModel) + internal BucketPutRequestBuilder Build(string bucketName) { - var requestMessage = new HttpRequestMessage(HttpMethod.Put, new Uri($"{_options.Protocol}://{_options.Endpoint}/{createModel.BucketName}")); + var requestMessage = new HttpRequestMessage(HttpMethod.Put, new Uri($"{_options.Protocol}://{_options.Endpoint}/{bucketName}")); var dateAmz = DateTime.UtcNow; requestMessage.AddBothHeaders(_options, dateAmz); diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/IBucketService.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/IBucketService.cs index c53e5aa..d6f49ce 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/IBucketService.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/IBucketService.cs @@ -1,4 +1,6 @@ using System.Threading.Tasks; +using AspNetCore.Yandex.ObjectStorage.Bucket.Requests; +using AspNetCore.Yandex.ObjectStorage.Bucket.Responses; using AspNetCore.Yandex.ObjectStorage.Models; namespace AspNetCore.Yandex.ObjectStorage.Bucket @@ -9,9 +11,9 @@ public interface IBucketService Task GetBucketMeta(string bucketName); - Task GetBucketListObjects(string bucketName); + Task GetBucketListObjects(BucketListObjectsParameters parameters); - Task GetBucketList(); + Task GetBucketList(); Task DeleteBucket(string bucketName); } diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Models/BucketListResult.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Models/BucketListResult.cs index 841ab40..bca67b7 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/Models/BucketListResult.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Models/BucketListResult.cs @@ -1,10 +1,27 @@ -namespace AspNetCore.Yandex.ObjectStorage.Bucket.Models +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace AspNetCore.Yandex.ObjectStorage.Bucket.Models { /// /// https://cloud.yandex.ru/docs/storage/s3/api-ref/bucket/list#response-scheme /// + [XmlRoot(ElementName="ListAllMyBucketsResult", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] public class BucketListResult { + [XmlArray("Buckets", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] + [XmlArrayItem("Bucket", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] + public List Buckets { get; set; } + } + [XmlRoot(ElementName="Bucket", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] + public class Bucket + { + [XmlElement(ElementName="Name", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] + public string Name { get; set; } + [XmlElement(ElementName="CreationDate", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] + public string CreationDate { get; set; } } + } \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Models/ObjectListResult.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Models/ObjectListResult.cs index 58be3e8..9b1eff8 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/Models/ObjectListResult.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Models/ObjectListResult.cs @@ -7,6 +7,7 @@ namespace AspNetCore.Yandex.ObjectStorage.Bucket.Models /// /// https://cloud.yandex.ru/docs/storage/s3/api-ref/bucket/listobjects#structureV2 /// + [XmlRoot(ElementName="ListBucketResult", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] public class ObjectListResult { /// @@ -18,7 +19,7 @@ public class ObjectListResult /// /// Описание объекта /// - [XmlElement(ElementName="Contents")] + [XmlElement(ElementName="Contents", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] public List Contents { get; set; } /// @@ -78,7 +79,14 @@ public class ObjectListResult public string StartAfter { get; set; } } - [XmlRoot(ElementName="Contents")] + + [XmlRoot(ElementName="CommonPrefixes")] + public class CommonPrefixes + { + public string Prefix { get; set; } + } + + [XmlRoot(ElementName="Contents", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] public class Contents { /// @@ -88,7 +96,8 @@ public class Contents /// /// Ключ объекта. /// - public string Key { get; set; } + [XmlElement(ElementName="Key")] + public string Filename { get; set; } /// /// Дата и время последнего изменения объекта. /// @@ -101,10 +110,16 @@ public class Contents /// Класс хранения объекта: STANDARD или COLD. /// public string StorageClass { get; set; } + + public Owner Owner { get; set; } } - [XmlRoot(ElementName="CommonPrefixes")] - public class CommonPrefixes { - public string Prefix { get; set; } + [XmlRoot(ElementName="Owner", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] + public class Owner + { + [XmlElement(ElementName="ID", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] + public string Id { get; set; } + [XmlElement(ElementName="DisplayName", Namespace="http://s3.amazonaws.com/doc/2006-03-01/")] + public string DisplayName { get; set; } } } \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Requests/BucketListObjectsParameters.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Requests/BucketListObjectsParameters.cs new file mode 100644 index 0000000..b2a60ee --- /dev/null +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Requests/BucketListObjectsParameters.cs @@ -0,0 +1,17 @@ +namespace AspNetCore.Yandex.ObjectStorage.Bucket.Requests +{ + public class BucketListObjectsParameters + { + public string BucketName { get; set; } + + public int MaxKeys { get; set; } = 1000; + + public string Delimiter { get; set; } + + public string Prefix { get; set; } + + public string StartAfter { get; set; } + + public string ContinueToken { get; set; } + } +} \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Responses/S3BucketListResponse.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Responses/S3BucketListResponse.cs new file mode 100644 index 0000000..f4aaec6 --- /dev/null +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Responses/S3BucketListResponse.cs @@ -0,0 +1,42 @@ +using System.Net.Http; +using System.Threading.Tasks; +using System.Xml.Serialization; +using AspNetCore.Yandex.ObjectStorage.Bucket.Models; +using AspNetCore.Yandex.ObjectStorage.Models; +using FluentResults; + +namespace AspNetCore.Yandex.ObjectStorage.Bucket.Responses +{ + public class S3BucketListResponse : BaseS3Response + { + public S3BucketListResponse(HttpResponseMessage response) : base(response) + { + } + + public async Task> ReadResultAsync() + { + if (IsSuccessStatusCode) + { + var xmlSerializer = new XmlSerializer(typeof(BucketListResult)); + await using var stream = await Response.Content.ReadAsStreamAsync(); + + var result = xmlSerializer.Deserialize(stream) as BucketListResult; + + return Result.Ok(result); + } + + return Result.Fail(await Response.Content.ReadAsStringAsync()); + + } + + public async Task> ReadResultAsStringAsync() + { + if (IsSuccessStatusCode) + { + return await Response.Content.ReadAsStringAsync(); + } + + return Result.Fail(await Response.Content.ReadAsStringAsync()); + } + } +} \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Responses/S3BucketObjectListResponse.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Responses/S3BucketObjectListResponse.cs new file mode 100644 index 0000000..dd19bd0 --- /dev/null +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Responses/S3BucketObjectListResponse.cs @@ -0,0 +1,42 @@ +using System.Net.Http; +using System.Threading.Tasks; +using System.Xml.Serialization; +using AspNetCore.Yandex.ObjectStorage.Bucket.Models; +using AspNetCore.Yandex.ObjectStorage.Models; +using FluentResults; + +namespace AspNetCore.Yandex.ObjectStorage.Bucket.Responses +{ + public class S3BucketObjectListResponse : BaseS3Response + { + public S3BucketObjectListResponse(HttpResponseMessage response) : base(response) + { + } + + public async Task> ReadResultAsync() + { + if (IsSuccessStatusCode) + { + var xmlSerializer = new XmlSerializer(typeof(ObjectListResult)); + await using var stream = await Response.Content.ReadAsStreamAsync(); + + var result = xmlSerializer.Deserialize(stream) as ObjectListResult; + + return Result.Ok(result); + } + + return Result.Fail(await Response.Content.ReadAsStringAsync()); + + } + + public async Task> ReadResultAsStringAsync() + { + if (IsSuccessStatusCode) + { + return await Response.Content.ReadAsStringAsync(); + } + + return Result.Fail(await Response.Content.ReadAsStringAsync()); + } + } +} \ No newline at end of file