[#1] Add object Get operation + code quality
Signed-off-by: Ivan Pchelintsev <i.pchelintsev@yadro.com>
This commit is contained in:
parent
9aa93d123d
commit
b307c2c899
17 changed files with 182 additions and 99 deletions
|
@ -2,6 +2,7 @@ using System.Security.Cryptography;
|
|||
using FrostFS.Container;
|
||||
using FrostFS.Netmap;
|
||||
using FrostFS.Object;
|
||||
using FrostFS.SDK.ClientV2.Interfaces;
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
using FrostFS.SDK.ModelsV2;
|
||||
|
|
|
@ -5,7 +5,7 @@ using DeleteResponse = FrostFS.Container.DeleteResponse;
|
|||
using GetResponse = FrostFS.Container.GetResponse;
|
||||
using PutResponse = FrostFS.Container.PutResponse;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
namespace FrostFS.SDK.ClientV2.Interfaces;
|
||||
|
||||
public interface IFrostFSClient
|
||||
{
|
||||
|
@ -14,6 +14,7 @@ public interface IFrostFSClient
|
|||
Task<GetResponse> GetContainerAsync(ContainerID containerId);
|
||||
Task<DeleteResponse> DeleteContainerAsync(ContainerID containerId);
|
||||
Task<HeadResponse> GetObjectHeadAsync(ContainerID containerId, ObjectID objectId);
|
||||
Task<Object.PutResponse> PutObjectAsync(Object.Header header, Stream payload);
|
||||
Task<Object.Object> GetObjectAsync(ContainerID containerId, ObjectID objectId);
|
||||
Task<Object.PutResponse> PutObjectAsync(Header header, Stream payload);
|
||||
Task<Object.DeleteResponse> DeleteObjectAsync(ContainerID containerId, ObjectID objectId);
|
||||
}
|
|
@ -19,14 +19,14 @@ public static class ContainerMapper
|
|||
|
||||
public static ModelsV2.Container ToModel(this Container.Container container)
|
||||
{
|
||||
var basicAclName = Enum.GetName(typeof(BasicACL), container.BasicAcl);
|
||||
var basicAclName = Enum.GetName(typeof(BasicAcl), container.BasicAcl);
|
||||
if (basicAclName is null)
|
||||
{
|
||||
throw new ArgumentException($"Unknown BasicACL rule. Value: '{container.BasicAcl}'.");
|
||||
}
|
||||
|
||||
return new ModelsV2.Container(
|
||||
Enum.Parse<BasicACL>(basicAclName),
|
||||
Enum.Parse<BasicAcl>(basicAclName),
|
||||
container.PlacementPolicy.ToModel()
|
||||
)
|
||||
{
|
||||
|
|
|
@ -24,12 +24,9 @@ public static class PlacementPolicyMapper
|
|||
|
||||
public static ModelsV2.Netmap.PlacementPolicy ToModel(this PlacementPolicy placementPolicy)
|
||||
{
|
||||
var replicas = new List<Replica>();
|
||||
foreach (var replica in placementPolicy.Replicas)
|
||||
{
|
||||
replicas.Add(replica.ToModel());
|
||||
}
|
||||
|
||||
return new ModelsV2.Netmap.PlacementPolicy(placementPolicy.Unique, replicas.ToArray());
|
||||
return new ModelsV2.Netmap.PlacementPolicy(
|
||||
placementPolicy.Unique,
|
||||
placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -52,16 +52,10 @@ public static class ObjectHeadMapper
|
|||
throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.");
|
||||
}
|
||||
|
||||
var attributes = new List<ObjectAttribute>();
|
||||
foreach (var attribute in header.Attributes)
|
||||
{
|
||||
attributes.Add(attribute.ToModel());
|
||||
}
|
||||
|
||||
return new ObjectHeader(
|
||||
ContainerId.FromHash(header.ContainerId.Value.ToByteArray()),
|
||||
Enum.Parse<ModelsV2.Enums.ObjectType>(objTypeName),
|
||||
attributes.ToArray()
|
||||
header.Attributes.Select(attribute => attribute.ToModel()).ToArray()
|
||||
)
|
||||
{
|
||||
Size = (long)header.PayloadLength,
|
||||
|
@ -69,3 +63,16 @@ public static class ObjectHeadMapper
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class ObjectMapper
|
||||
{
|
||||
public static ModelsV2.Object ToModel(this Object.Object obj)
|
||||
{
|
||||
return new ModelsV2.Object
|
||||
{
|
||||
Header = obj.Header.ToModel(),
|
||||
ObjectId = ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()),
|
||||
Payload = obj.Payload.ToByteArray()
|
||||
};
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ using FrostFS.Refs;
|
|||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.ModelsV2;
|
||||
using FrostFS.Session;
|
||||
using Version = FrostFS.SDK.ModelsV2.Version;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
|
||||
|
|
|
@ -18,14 +18,14 @@ namespace FrostFS.SDK.ClientV2
|
|||
public static byte[] SignRFC6979(this ECDsa key, byte[] data)
|
||||
{
|
||||
var digest = new Sha256Digest();
|
||||
var secp256r1 = SecNamedCurves.GetByName("secp256r1");
|
||||
var ec_parameters = new ECDomainParameters(secp256r1.Curve, secp256r1.G, secp256r1.N);
|
||||
var private_key = new ECPrivateKeyParameters(new BigInteger(1, key.PrivateKey()), ec_parameters);
|
||||
var secp256R1 = SecNamedCurves.GetByName("secp256r1");
|
||||
var ecParameters = new ECDomainParameters(secp256R1.Curve, secp256R1.G, secp256R1.N);
|
||||
var privateKey = new ECPrivateKeyParameters(new BigInteger(1, key.PrivateKey()), ecParameters);
|
||||
var signer = new ECDsaSigner(new HMacDsaKCalculator(digest));
|
||||
var hash = new byte[digest.GetDigestSize()];
|
||||
digest.BlockUpdate(data, 0, data.Length);
|
||||
digest.DoFinal(hash, 0);
|
||||
signer.Init(true, private_key);
|
||||
signer.Init(true, privateKey);
|
||||
var rs = signer.GenerateSignature(hash);
|
||||
var signature = new byte[RFC6979SignatureSize];
|
||||
var rbytes = rs[0].ToByteArrayUnsigned();
|
||||
|
@ -67,11 +67,11 @@ namespace FrostFS.SDK.ClientV2
|
|||
|
||||
public static Signature SignMessagePart(this ECDsa key, IMessage? data)
|
||||
{
|
||||
var data2sign = data is null ? Array.Empty<byte>() : data.ToByteArray();
|
||||
var data2Sign = data is null ? Array.Empty<byte>() : data.ToByteArray();
|
||||
var sig = new Signature
|
||||
{
|
||||
Key = ByteString.CopyFrom(key.PublicKey()),
|
||||
Sign = ByteString.CopyFrom(key.SignData(data2sign)),
|
||||
Sign = ByteString.CopyFrom(key.SignData(data2Sign)),
|
||||
};
|
||||
return sig;
|
||||
}
|
||||
|
|
|
@ -24,19 +24,19 @@ namespace FrostFS.SDK.ClientV2 {
|
|||
return rs;
|
||||
}
|
||||
|
||||
public static bool VerifyRFC6979(this byte[] public_key, byte[] data, byte[] sig)
|
||||
public static bool VerifyRFC6979(this byte[] publicKey, byte[] data, byte[] sig)
|
||||
{
|
||||
if (public_key is null || data is null || sig is null) return false;
|
||||
if (publicKey is null || data is null || sig is null) return false;
|
||||
var rs = DecodeSignature(sig);
|
||||
var digest = new Sha256Digest();
|
||||
var signer = new ECDsaSigner(new HMacDsaKCalculator(digest));
|
||||
var secp256r1 = SecNamedCurves.GetByName("secp256r1");
|
||||
var ec_parameters = new ECDomainParameters(secp256r1.Curve, secp256r1.G, secp256r1.N);
|
||||
var bc_public_key = new ECPublicKeyParameters(secp256r1.Curve.DecodePoint(public_key), ec_parameters);
|
||||
var secp256R1 = SecNamedCurves.GetByName("secp256r1");
|
||||
var ecParameters = new ECDomainParameters(secp256R1.Curve, secp256R1.G, secp256R1.N);
|
||||
var bcPublicKey = new ECPublicKeyParameters(secp256R1.Curve.DecodePoint(publicKey), ecParameters);
|
||||
var hash = new byte[digest.GetDigestSize()];
|
||||
digest.BlockUpdate(data, 0, data.Length);
|
||||
digest.DoFinal(hash, 0);
|
||||
signer.Init(false, bc_public_key);
|
||||
signer.Init(false, bcPublicKey);
|
||||
return signer.VerifySignature(hash, rs[0], rs[1]);
|
||||
}
|
||||
|
||||
|
@ -54,8 +54,8 @@ namespace FrostFS.SDK.ClientV2 {
|
|||
{
|
||||
if (sig is null || sig.Key is null || sig.Sign is null) return false;
|
||||
using var key = sig.Key.ToByteArray().LoadPublicKey();
|
||||
var data2verify = data is null ? Array.Empty<byte>() : data.ToByteArray();
|
||||
return key.VerifyData(data2verify, sig.Sign.ToByteArray());
|
||||
var data2Verify = data is null ? Array.Empty<byte>() : data.ToByteArray();
|
||||
return key.VerifyData(data2Verify, sig.Sign.ToByteArray());
|
||||
}
|
||||
|
||||
public static bool VerifyMatryoskaLevel(IMessage body, IMetaHeader meta, IVerificationHeader verification)
|
||||
|
@ -65,8 +65,7 @@ namespace FrostFS.SDK.ClientV2 {
|
|||
if (!verification.OriginSignature.VerifyMessagePart(origin)) return false;
|
||||
if (origin is null)
|
||||
return verification.BodySignature.VerifyMessagePart(body);
|
||||
if (verification.BodySignature is not null) return false;
|
||||
return VerifyMatryoskaLevel(body, meta.GetOrigin(), origin);
|
||||
return verification.BodySignature is null && VerifyMatryoskaLevel(body, meta.GetOrigin(), origin);
|
||||
}
|
||||
|
||||
public static bool Verify(this IVerificableMessage message)
|
||||
|
|
|
@ -65,25 +65,4 @@ public partial class Client
|
|||
request.Sign(_key);
|
||||
return await _containerServiceClient.DeleteAsync(request);
|
||||
}
|
||||
|
||||
// private void PrepareContainerSessionToken(RequestMetaHeader meta, SessionToken sessionToken, ContainerID? cid,
|
||||
// ContainerSessionContext.Types.Verb verb)
|
||||
// {
|
||||
// if (meta.SessionToken is not null) return;
|
||||
// meta.SessionToken = sessionToken;
|
||||
// var ctx = new ContainerSessionContext
|
||||
// {
|
||||
// Verb = verb
|
||||
// };
|
||||
// if (cid is null)
|
||||
// {
|
||||
// ctx.Wildcard = true;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// ctx.ContainerId = cid;
|
||||
// }
|
||||
// meta.SessionToken.Body.Container = ctx;
|
||||
// meta.SessionToken.Signature = _key.SignMessagePart(meta.SessionToken.Body);
|
||||
// }
|
||||
}
|
|
@ -29,10 +29,63 @@ public partial class Client
|
|||
return await _objectServiceClient.HeadAsync(request);
|
||||
}
|
||||
|
||||
// public async Task<GetResponse> GetObjectAsync(ContainerID cid, ObjectID oid)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
public async Task<Object.Object> GetObjectAsync(ContainerID cid, ObjectID oid)
|
||||
{
|
||||
var sessionToken = await CreateSessionAsync(uint.MaxValue);
|
||||
var request = new GetRequest
|
||||
{
|
||||
Body = new GetRequest.Types.Body
|
||||
{
|
||||
Raw = false,
|
||||
Address = new Address
|
||||
{
|
||||
ContainerId = cid,
|
||||
ObjectId = oid
|
||||
},
|
||||
}
|
||||
};
|
||||
request.AddMetaHeader();
|
||||
request.AddObjectSessionToken(
|
||||
sessionToken,
|
||||
cid,
|
||||
oid,
|
||||
ObjectSessionContext.Types.Verb.Get,
|
||||
_key
|
||||
);
|
||||
request.Sign(_key);
|
||||
|
||||
return await GetObject(request);
|
||||
}
|
||||
|
||||
private async Task<Object.Object> GetObject(GetRequest request)
|
||||
{
|
||||
using var stream = GetObjectInit(request);
|
||||
var obj = await stream.ReadHeader();
|
||||
var payload = new byte[obj.Header.PayloadLength];
|
||||
var offset = 0;
|
||||
var chunk = await stream.ReadChunk();
|
||||
while (chunk is not null)
|
||||
{
|
||||
chunk.CopyTo(payload, offset);
|
||||
offset += chunk.Length;
|
||||
chunk = await stream.ReadChunk();
|
||||
}
|
||||
obj.Payload = ByteString.CopyFrom(payload);
|
||||
return obj;
|
||||
}
|
||||
|
||||
private ObjectReader GetObjectInit(GetRequest initRequest)
|
||||
{
|
||||
if (initRequest is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(initRequest));
|
||||
}
|
||||
|
||||
return new ObjectReader
|
||||
{
|
||||
Call = _objectServiceClient.Get(initRequest)
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<PutResponse> PutObjectAsync(Header header, Stream payload)
|
||||
{
|
||||
|
@ -63,7 +116,7 @@ public partial class Client
|
|||
);
|
||||
request.Sign(_key);
|
||||
|
||||
using var stream = await InitObject(request);
|
||||
using var stream = await PutObjectInit(request);
|
||||
var buffer = new byte[Constants.ObjectChunkSize];
|
||||
var bufferLength = payload.Read(buffer, 0, Constants.ObjectChunkSize);
|
||||
while (bufferLength > 0)
|
||||
|
@ -81,7 +134,7 @@ public partial class Client
|
|||
return await stream.Close();
|
||||
}
|
||||
|
||||
private async Task<ObjectStreamer> InitObject(PutRequest initRequest)
|
||||
private async Task<ObjectStreamer> PutObjectInit(PutRequest initRequest)
|
||||
{
|
||||
if (initRequest is null)
|
||||
{
|
||||
|
@ -112,14 +165,47 @@ public partial class Client
|
|||
}
|
||||
}
|
||||
|
||||
internal class ObjectStreamer : IDisposable
|
||||
internal class ObjectReader : IDisposable
|
||||
{
|
||||
public AsyncClientStreamingCall<PutRequest, PutResponse> Call { get; init; }
|
||||
public AsyncServerStreamingCall<GetResponse> Call { get; init; }
|
||||
|
||||
public async Task<Object.Object> ReadHeader()
|
||||
{
|
||||
if (!await Call.ResponseStream.MoveNext())
|
||||
{
|
||||
throw new InvalidOperationException("unexpect end of stream");
|
||||
}
|
||||
var response = Call.ResponseStream.Current;
|
||||
if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Init)
|
||||
throw new InvalidOperationException("unexpect message type");
|
||||
return new Object.Object
|
||||
{
|
||||
ObjectId = response.Body.Init.ObjectId,
|
||||
Header = response.Body.Init.Header,
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<byte[]?> ReadChunk()
|
||||
{
|
||||
if (!await Call.ResponseStream.MoveNext())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var response = Call.ResponseStream.Current;
|
||||
if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Chunk)
|
||||
throw new InvalidOperationException("unexpect message type");
|
||||
return response.Body.Chunk.ToByteArray();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Call.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal class ObjectStreamer : IDisposable
|
||||
{
|
||||
public AsyncClientStreamingCall<PutRequest, PutResponse> Call { get; init; }
|
||||
|
||||
public async Task Write(PutRequest request)
|
||||
{
|
||||
|
@ -136,4 +222,9 @@ internal class ObjectStreamer : IDisposable
|
|||
await Call.RequestStream.CompleteAsync();
|
||||
return await Call.ResponseAsync;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Call.Dispose();
|
||||
}
|
||||
}
|
|
@ -7,12 +7,11 @@ namespace FrostFS.SDK.Cryptography
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte[] Concat(params byte[][] buffers)
|
||||
{
|
||||
int length = 0;
|
||||
for (int i = 0; i < buffers.Length; i++)
|
||||
length += buffers[i].Length;
|
||||
byte[] dst = new byte[length];
|
||||
int p = 0;
|
||||
foreach (byte[] src in buffers)
|
||||
var length = buffers.Sum(buffer => buffer.Length);
|
||||
|
||||
var dst = new byte[length];
|
||||
var p = 0;
|
||||
foreach (var src in buffers)
|
||||
{
|
||||
Buffer.BlockCopy(src, 0, dst, p, src.Length);
|
||||
p += src.Length;
|
||||
|
|
|
@ -22,8 +22,8 @@ public static class KeyExtension
|
|||
$"{nameof(Compress)} argument isn't uncompressed public key. " +
|
||||
$"expected length={UncompressedPublicKeyLength}, actual={publicKey.Length}"
|
||||
);
|
||||
var secp256r1 = SecNamedCurves.GetByName("secp256r1");
|
||||
var point = secp256r1.Curve.DecodePoint(publicKey);
|
||||
var secp256R1 = SecNamedCurves.GetByName("secp256r1");
|
||||
var point = secp256R1.Curve.DecodePoint(publicKey);
|
||||
return point.GetEncoded(true);
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,8 @@ public static class KeyExtension
|
|||
$"{nameof(Decompress)} argument isn't compressed public key. " +
|
||||
$"expected length={CompressedPublicKeyLength}, actual={publicKey.Length}"
|
||||
);
|
||||
var secp256r1 = SecNamedCurves.GetByName("secp256r1");
|
||||
var point = secp256r1.Curve.DecodePoint(publicKey);
|
||||
var secp256R1 = SecNamedCurves.GetByName("secp256r1");
|
||||
var point = secp256R1.Curve.DecodePoint(publicKey);
|
||||
return point.GetEncoded(false);
|
||||
}
|
||||
|
||||
|
@ -115,19 +115,19 @@ public static class KeyExtension
|
|||
return key.ExportParameters(true).D;
|
||||
}
|
||||
|
||||
public static ECDsa LoadPrivateKey(this byte[] private_key)
|
||||
public static ECDsa LoadPrivateKey(this byte[] privateKey)
|
||||
{
|
||||
var secp256r1 = SecNamedCurves.GetByName("secp256r1");
|
||||
var public_key =
|
||||
secp256r1.G.Multiply(new Org.BouncyCastle.Math.BigInteger(1, private_key)).GetEncoded(false)[1..];
|
||||
var secp256R1 = SecNamedCurves.GetByName("secp256r1");
|
||||
var publicKey =
|
||||
secp256R1.G.Multiply(new Org.BouncyCastle.Math.BigInteger(1, privateKey)).GetEncoded(false)[1..];
|
||||
var key = ECDsa.Create(new ECParameters
|
||||
{
|
||||
Curve = ECCurve.NamedCurves.nistP256,
|
||||
D = private_key,
|
||||
D = privateKey,
|
||||
Q = new ECPoint
|
||||
{
|
||||
X = public_key[..32],
|
||||
Y = public_key[32..]
|
||||
X = publicKey[..32],
|
||||
Y = publicKey[32..]
|
||||
}
|
||||
});
|
||||
return key;
|
||||
|
@ -135,20 +135,20 @@ public static class KeyExtension
|
|||
|
||||
public static ECDsa LoadWif(this string wif)
|
||||
{
|
||||
var private_key = GetPrivateKeyFromWIF(wif);
|
||||
return LoadPrivateKey(private_key);
|
||||
var privateKey = GetPrivateKeyFromWIF(wif);
|
||||
return LoadPrivateKey(privateKey);
|
||||
}
|
||||
|
||||
public static ECDsa LoadPublicKey(this byte[] public_key)
|
||||
public static ECDsa LoadPublicKey(this byte[] publicKey)
|
||||
{
|
||||
var public_key_full = public_key.Decompress()[1..];
|
||||
var publicKeyFull = publicKey.Decompress()[1..];
|
||||
var key = ECDsa.Create(new ECParameters
|
||||
{
|
||||
Curve = ECCurve.NamedCurves.nistP256,
|
||||
Q = new ECPoint
|
||||
{
|
||||
X = public_key_full[..32],
|
||||
Y = public_key_full[32..]
|
||||
X = publicKeyFull[..32],
|
||||
Y = publicKeyFull[32..]
|
||||
}
|
||||
});
|
||||
return key;
|
||||
|
|
|
@ -6,11 +6,11 @@ namespace FrostFS.SDK.ModelsV2;
|
|||
public class Container
|
||||
{
|
||||
public Guid Nonce { get; set; }
|
||||
public BasicACL BasicAcl { get; set; }
|
||||
public BasicAcl BasicAcl { get; set; }
|
||||
public PlacementPolicy PlacementPolicy { get; set; }
|
||||
public Version Version { get; set; }
|
||||
|
||||
public Container(BasicACL basicAcl, PlacementPolicy placementPolicy)
|
||||
public Container(BasicAcl basicAcl, PlacementPolicy placementPolicy)
|
||||
{
|
||||
Nonce = Guid.NewGuid();
|
||||
BasicAcl = basicAcl;
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace FrostFS.SDK.ModelsV2;
|
|||
|
||||
public class ContainerId
|
||||
{
|
||||
public string Value { get; }
|
||||
public string Value { get; set; }
|
||||
|
||||
public ContainerId(string id)
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@ using System.ComponentModel;
|
|||
|
||||
namespace FrostFS.SDK.ModelsV2.Enums;
|
||||
|
||||
public enum BasicACL
|
||||
public enum BasicAcl
|
||||
{
|
||||
[Description("Basic ACL for private container")]
|
||||
Private = 0x1C8C8CCC,
|
|
@ -37,5 +37,6 @@ public class ObjectHeader
|
|||
public class Object
|
||||
{
|
||||
public ObjectHeader Header { get; set; }
|
||||
public Stream Payload { get; set; }
|
||||
public ObjectId ObjectId { get; set; }
|
||||
public byte[] Payload { get; set; }
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using FrostFS.SDK.ClientV2;
|
||||
using FrostFS.SDK.ClientV2.Interfaces;
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.ModelsV2;
|
||||
|
||||
|
@ -16,14 +17,11 @@ public class FrostFsService
|
|||
|
||||
public async Task<ContainerId[]> ListContainersAsync()
|
||||
{
|
||||
var containersIds = new List<ContainerId>();
|
||||
var listContainersResponse = await _client.ListContainersAsync();
|
||||
foreach (var cid in listContainersResponse.Body.ContainerIds)
|
||||
{
|
||||
containersIds.Add(ContainerId.FromHash(cid.Value.ToByteArray()));
|
||||
}
|
||||
|
||||
return containersIds.ToArray();
|
||||
return listContainersResponse.Body.ContainerIds.Select(
|
||||
cid => ContainerId.FromHash(cid.Value.ToByteArray())
|
||||
).ToArray();
|
||||
}
|
||||
|
||||
public async Task<ContainerId> CreateContainerAsync(ModelsV2.Container container)
|
||||
|
@ -52,6 +50,15 @@ public class FrostFsService
|
|||
return getObjectHeadResponse.Body.Header.Header.ToModel();
|
||||
}
|
||||
|
||||
public async Task<ModelsV2.Object> GetObjectAsync(ContainerId containerId, ObjectId objectId)
|
||||
{
|
||||
var obj = await _client.GetObjectAsync(
|
||||
containerId.ToGrpcMessage(),
|
||||
objectId.ToGrpcMessage()
|
||||
);
|
||||
return obj.ToModel();
|
||||
}
|
||||
|
||||
public async Task<ObjectId> PutObjectAsync(ObjectHeader header, Stream payload)
|
||||
{
|
||||
var putObjectResponse = await _client.PutObjectAsync(header.ToGrpcMessage(), payload);
|
||||
|
|
Loading…
Reference in a new issue