From 1fdeca84e15bf65249c52031568d1035f90816b8 Mon Sep 17 00:00:00 2001 From: Alex Vanin Date: Wed, 2 Dec 2020 13:10:43 +0300 Subject: [PATCH] [#218] Support SplitInfo structure Signed-off-by: Alex Vanin --- pkg/client/object.go | 2 ++ pkg/object/splitinfo.go | 49 +++++++++++++++++++++++++++ pkg/object/splitinfo_test.go | 42 +++++++++++++++++++++++ v2/object/convert.go | 48 ++++++++++++++++++++++++++ v2/object/grpc/service.go | 9 +++++ v2/object/grpc/service.pb.go | Bin 143311 -> 144358 bytes v2/object/grpc/types.go | 21 ++++++++++++ v2/object/grpc/types.pb.go | Bin 34063 -> 37595 bytes v2/object/marshal.go | 64 +++++++++++++++++++++++++++++++++-- v2/object/marshal_test.go | 30 +++++++++++++--- v2/object/types.go | 52 ++++++++++++++++++++++++++++ 11 files changed, 310 insertions(+), 7 deletions(-) create mode 100644 pkg/object/splitinfo.go create mode 100644 pkg/object/splitinfo_test.go diff --git a/pkg/client/object.go b/pkg/client/object.go index 04812da1..df60f6fa 100644 --- a/pkg/client/object.go +++ b/pkg/client/object.go @@ -445,6 +445,8 @@ func (c *Client) getObjectV2(ctx context.Context, p *GetObjectParams, opts ...Ca } else { payload = append(payload, v.GetChunk()...) } + case *v2object.SplitInfo: // what else can we do here? + return nil, errors.New("object not found, split info has been provided") default: panic(fmt.Sprintf("unexpected Get object part type %T", v)) } diff --git a/pkg/object/splitinfo.go b/pkg/object/splitinfo.go new file mode 100644 index 00000000..1f7494d2 --- /dev/null +++ b/pkg/object/splitinfo.go @@ -0,0 +1,49 @@ +package object + +import ( + "github.com/nspcc-dev/neofs-api-go/v2/object" +) + +type SplitInfo object.SplitInfo + +func NewSplitInfoFromV2(v2 *object.SplitInfo) *SplitInfo { + return (*SplitInfo)(v2) +} + +func NewSplitInfo() *SplitInfo { + return NewSplitInfoFromV2(new(object.SplitInfo)) +} + +func (s *SplitInfo) ToV2() *object.SplitInfo { + return (*object.SplitInfo)(s) +} + +func (s *SplitInfo) SplitID() *SplitID { + return NewSplitIDFromV2( + (*object.SplitInfo)(s).GetSplitID(), + ) +} + +func (s *SplitInfo) SetSplitID(v *SplitID) { + (*object.SplitInfo)(s).SetSplitID(v.ToV2()) +} + +func (s *SplitInfo) LastPart() *ID { + return NewIDFromV2( + (*object.SplitInfo)(s).GetLastPart(), + ) +} + +func (s *SplitInfo) SetLastPart(v *ID) { + (*object.SplitInfo)(s).SetLastPart(v.ToV2()) +} + +func (s *SplitInfo) Link() *ID { + return NewIDFromV2( + (*object.SplitInfo)(s).GetLink(), + ) +} + +func (s *SplitInfo) SetLink(v *ID) { + (*object.SplitInfo)(s).SetLink(v.ToV2()) +} diff --git a/pkg/object/splitinfo_test.go b/pkg/object/splitinfo_test.go new file mode 100644 index 00000000..854defe8 --- /dev/null +++ b/pkg/object/splitinfo_test.go @@ -0,0 +1,42 @@ +package object_test + +import ( + "crypto/rand" + "testing" + + "github.com/nspcc-dev/neofs-api-go/pkg/object" + "github.com/stretchr/testify/require" +) + +func TestSplitInfo(t *testing.T) { + s := object.NewSplitInfo() + splitID := object.NewSplitID() + lastPart := generateID() + link := generateID() + + s.SetSplitID(splitID) + require.Equal(t, splitID, s.SplitID()) + + s.SetLastPart(lastPart) + require.Equal(t, lastPart, s.LastPart()) + + s.SetLink(link) + require.Equal(t, link, s.Link()) + + t.Run("to and from v2", func(t *testing.T) { + v2 := s.ToV2() + newS := object.NewSplitInfoFromV2(v2) + + require.Equal(t, s, newS) + }) +} + +func generateID() *object.ID { + var buf [32]byte + _, _ = rand.Read(buf[:]) + + id := object.NewID() + id.SetSHA256(buf) + + return id +} diff --git a/v2/object/convert.go b/v2/object/convert.go index 07686895..ddeafb1e 100644 --- a/v2/object/convert.go +++ b/v2/object/convert.go @@ -371,6 +371,46 @@ func ObjectFromGRPCMessage(m *object.Object) *Object { return o } +func SplitInfoToGRPCMessage(s *SplitInfo) *object.SplitInfo { + if s == nil { + return nil + } + + m := new(object.SplitInfo) + + m.SetSplitId(s.GetSplitID()) + + m.SetLastPart( + refs.ObjectIDToGRPCMessage(s.GetLastPart()), + ) + + m.SetLink( + refs.ObjectIDToGRPCMessage(s.GetLink()), + ) + + return m +} + +func SplitInfoFromGRPCMessage(m *object.SplitInfo) *SplitInfo { + if m == nil { + return nil + } + + r := new(SplitInfo) + + r.SetSplitID(m.GetSplitId()) + + r.SetLastPart( + refs.ObjectIDFromGRPCMessage(m.GetLastPart()), + ) + + r.SetLink( + refs.ObjectIDFromGRPCMessage(m.GetLink()), + ) + + return r +} + func GetRequestBodyToGRPCMessage(r *GetRequestBody) *object.GetRequest_Body { if r == nil { return nil @@ -520,6 +560,10 @@ func GetResponseBodyToGRPCMessage(r *GetResponseBody) *object.GetResponse_Body { m.SetChunk( GetObjectPartChunkToGRPCMessage(t), ) + case *SplitInfo: + m.SetSplitInfo( + SplitInfoToGRPCMessage(t), + ) default: panic(fmt.Sprintf("unknown object part %T", t)) } @@ -544,6 +588,10 @@ func GetResponseBodyFromGRPCMessage(m *object.GetResponse_Body) *GetResponseBody r.SetObjectPart( GetObjectPartChunkFromGRPCMessage(v), ) + case *object.GetResponse_Body_SplitInfo: + r.SetObjectPart( + SplitInfoFromGRPCMessage(v.SplitInfo), + ) default: panic(fmt.Sprintf("unknown object part %T", v)) } diff --git a/v2/object/grpc/service.go b/v2/object/grpc/service.go index aefa5b4c..7acad620 100644 --- a/v2/object/grpc/service.go +++ b/v2/object/grpc/service.go @@ -93,6 +93,15 @@ func (m *GetResponse_Body) SetChunk(v *GetResponse_Body_Chunk) { } } +// SetSplitInfo sets part of the object payload. +func (m *GetResponse_Body) SetSplitInfo(v *SplitInfo) { + if m != nil { + m.ObjectPart = &GetResponse_Body_SplitInfo{ + SplitInfo: v, + } + } +} + // SetBody sets body of the response. func (m *GetResponse) SetBody(v *GetResponse_Body) { if m != nil { diff --git a/v2/object/grpc/service.pb.go b/v2/object/grpc/service.pb.go index 77f84ef8a2e2877f8b33c941da8443d76c72176b..4d6c26a537d7a48b39890b3780106a0f84c8cbdd 100644 GIT binary patch delta 6317 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 delta 5277 zcmaJ_X;4&G7Ovxh&5jF-f=vrZ%hJ#bbknHNDC&rUs96*x(ky0C5mZDp3M$bUK_H2o zjByYunwm@&vCISwL2yBnOpN5mRAnkw)R{?AV=6{7Q<+R9lgxMS>juoE@~4Y;?|t_y z-*>)q9-n`qyLLm@+0k(Nd_&FX)1N$BLC*yr-go-?;al5uYl{;N=51zMSAP&aGJ1s5 z!SoQNb`?w@zY%jlWmRTnwMom;bU#fD5W za_3eGla)6zX#pRN)E++CgY9G&wvR0&KZ!1gi&m3A7j6{Cs!7L!M&$9(oD^p z4-Ign-$cE>#h%cttlvw?{NXf86dUe26w7DIkOY7f#y1=A=8gS`ZE+l|AR-Uuwkti)?3YfBp^Qx@0GC%u>) zkcXRi(AbbU1z zIAj&LE#N=L(G)hWgAWSdPxq6r_?{I9;+Y?zP_9b@oocn5#qk#fY z#0&A*Lx}E&1P$*~If$q^61;D#pdh(Cc{eR**G3v8=<3)f3j8^=2(Jv(z`=j*rhJav zuW{b(1_ztA!G*p$iB6Jo-T3e}xa#$FU1Qg@6`U;~=PDsW}Aa1G@(Nw9$ zV17vlguS(B`|&BvCPg z2IkDODSU**!TDDDjU-0}Q@IXjdhNKR%}zFz6)O^8-d462X}DoW8gktT5cCP zG>=2VarF=*yvQnrK9vC3%FjgUe3ydm(6=0aef=P`>qsQn?b;156wJpmP=ohDs^{vj|S+q8WLgAvh zNXj$O@MK{&+<$+r7Isk(lJmn9Ac5t?ajGR)v}Pc`78~Dl!eVDO#Y&d$6D@!KM=CgJ z2-ncl|Eh8oOb2Vw?8fn`JcRk=e1z$(O(oCj>UFV_zF1FD+Ec{`At4`J0QZ~A0fADl zRnrkegGiW{gN6X{-KGd@wQ*%5S=ew;YHlA$2TWsxBnAm$z*Dt0z8;Tj{=aH()#4SG zfhy${E0;?bde;xC*O>!lb>)LpDAO^*#55_>0ItcRh*pD~D1sq~DcX*UtYWQlCYajn zCLejYXM!5x7C+H3gOX;4lPe>U;)+yA%ukESq#>sQu}S5n=hAX{S9yghu}Pr?55|Qq zYotIyNCmfBCWoDQI3=_jDY-kBhukR=-n4=7?Y@xMc}vi4FknPzqJL%yXwW_$a`gQY z=nuFX#XU0+Vqrd|smz+S5uubnZA6RtZy}I?3L1F5nLgmZHX?NI+Efl%M!sE57W#Z7 z|MVcu^H4YXVZ^@cApopsh-U83-3542W~NbAj3XRe0yl6j+g}k;vDYpOGx_z0DO}Oh zVHZ+p)Nojt_8^9DA10gf^8V%Q3>wd+D=1b=G`Ktk68EZ1Cg)b%7yl=PsG~0%X`Ceb zd_F+O6kw8v8!J)!&}dZaO3Kihsh!(bVp7|+0x*CGK0K#@Oe)G$CLeI8dM{eSI|Zl< z@7$Cys#F53S_Pz6uOcOf?s`OfausDO#8VaGX6fj(8i4&X7H+OvO*!0=kHmR9x3A`c zn=Q)#V`d>3SokQV3yj8LC!xE(U4aYfn~|yqC<|tX%A6AxHWkA{V;CJ}cL~L+T}|t4 zIKziaXt_$K%ol1hJyeXSD&~OcZM8>_)l7M|Y*=ng921II^-Z|Bs2p&r>2er}pnvnak^>17o>kI_vDMA* zt;ep^ty(3`+7l}M-E07R~0Lvu*+)?#LA0&j7>OH+_-8z@H|@JNQ^e#uB3bSH*y zN||^Ynd(s=#iLK4e~pvDFq*ynE1qMvVxR(4Q#iwcBHx+=go_fvac`Fo;JX&Dnyg9J zcreW$r0Bx~FSSy=hBSIvxX`a+53n~(gQ%dPDG9-JjBkKhP}3uu@H5I6YIHIQA^&L; zdJ{@3gumVax*9iwIx6=~&5-x^^5MnMRC9AP2p~O-+=GF~V|1Ftu5yT%dgbvJuo!iq zhWv2u-h`_?^Sz$)UFe{Hn2zWx^us3CiPFGU4G#D5(l%41NKBkC3-8}I)*G}g`uqbR zF{b1Y(aZ@Fwd$gZ1*!>l256!GS~C1=C{$iW*Pg=1o&fJ;+!<|217BqHpD6XNz0VfA)0dBp->V@fQ-IB*^AG#df;kYX;{LE-8oxL&&*DSL5;=QCpw z5`B9IWr#N&K6o?xNdSYMkjuXHG(i=cT}hi7-xKO7CtNGO61PTfO$jvOrf9^iYeqx> zB#3XXMu)$(bJ#Zxv}`kHmXlE|wrobKl~{nW!5or-$nzFLsBdm1zpkSpw0|T=?4`^> z@1Rw`?wuwD9{qRny?Ru`%k`cK!(1W?A=<@X)q_%Z!cdiuJw;AUrfmFC5ct{i6l^?T z1abRxkN`|y6S-y|E#jbk_tlHz5OVy&Q}{U?g`cwhNaZhnnc$-&0vJX-joUCbHS@<$ zd%Ep25SESx3>P3&0B_#~WR4two~x0ns0oEp(TGQv9lOdS?5g=geX+dt47wkxaK7}| zx!kh=swI}jXke6aM>QPweqc|M1Ew$}K)w{r+fqv97nxELW!G9|TLTJvh&Mm)G&X*s zQ41oMuNOiA(S_6`d2KbmsnVN);e2ETaJ80!kG)%@#AEfH)8PPonBMWl>rnvp4+byh4Nqt$(tegd=Q(FrEy*GDd2?_LHzAj3^Bnp-d^$Os z{B(9@#BAr_(G=Bn^m!@_=6S~{0rPa_aeTy790yWuM*#2Dg=W@IJLW*BKgN|%I43y@lNn-K-Sd;<)3{9 zeKGwVAeR3+D0uE265)6EpgHM#Xr>Osx0rIs(jz)VEl+8D@3$!7FM7bq{vK4=vU4b>~;<5cr@@qW}jdPwM8;0Y;UX;e|UZ8vG zG`zX{BDR|U=-KzUf&WR}>I3wz^^r-3{}hQiyDY8tJ~`OZ1%Em_@R?M39_h$F4_nQb z(HuTJ4?A`(pI8XumeKb5d^Yihx95&NMRakA|)64!PS z+1+vxx<{ArP1gB>*TkcjU|`NAoa?=WD;&eFfQd0Y=Q6T}+z0WJe&qf;Ik}@B8FvhG z>*IdVBb%t@DF?B&ZeD*&G$hXMIvExr0d2Nvc&THcPOc=^bYNr_di}| B8ixP? diff --git a/v2/object/grpc/types.go b/v2/object/grpc/types.go index b1f46c84..df1151ab 100644 --- a/v2/object/grpc/types.go +++ b/v2/object/grpc/types.go @@ -200,3 +200,24 @@ func (m *ShortHeader) SetPayloadLength(v uint64) { m.PayloadLength = v } } + +// SetSplitId sets id of split hierarchy. +func (m *SplitInfo) SetSplitId(v []byte) { + if m != nil { + m.SplitId = v + } +} + +// SetLastPart sets id of most right child in split hierarchy. +func (m *SplitInfo) SetLastPart(v *refs.ObjectID) { + if m != nil { + m.LastPart = v + } +} + +// SetLink sets id of linking object in split hierarchy. +func (m *SplitInfo) SetLink(v *refs.ObjectID) { + if m != nil { + m.Link = v + } +} diff --git a/v2/object/grpc/types.pb.go b/v2/object/grpc/types.pb.go index 8d5eedef37f6904c81be9fe56f7e8f118b7657f2..48b2b736f4b4b9a88b53f675035fb8cf8da53d93 100644 GIT binary patch delta 2032 zcmbtUT}&fY6wWlu(iXP6Kw19n1rdR*{h3Y+t^CDJBx}%Rmu}P$B-3^Z1MN(ixuq=) z>8`|w-Nc{lNjA}F{EJWiWMh4kMU62UV+?De4{T!Ahkb$=(ZmNeuJ_()DT4bX55wH= zp7WjWeCKptykffbj_LZduEOII7LlyPRV^uESyhl4M|vtDV>B;Iny5wRbBJ6bHL@T@ zF%orMN=6bn7kX00^C%(e7^OrFTitG?DiVr{3c{MWENQxg60))=#mGMr^dGblF}hel zSVeJJE72fPiBYOj840PHJSQt+qEx?JmJrLzTv=4Pa9Lalr$jmtBhSqf;Te%MqNz!Q z=LxORO-gLgu$ zD~G(IX6P<-2BWB|U{O|dtA*+?ES1EVq@fJ8?m%Z~p53{umI+c|Qq-;nyWmD|jf+}5 zOIe)@x~dG*Be})Sl%`_U*M%q=>MBRUcn}q+@B8eb<2yLg|0$Es3F)eaE8}HtGGA=Q zN)TTy6GRDc!A>X~uW6#P4$7wYKk+R+d*=6{^O7#%-aeGKTKcnSxH6agDqK3%0qqU} zHiaV)c8Hd}G&o!TsCjRIuh%;&hko=liNCWiw$9#Px&GV#OtWv*x@xt?(@GTeW>J5I z^@m3$;6$URj#hdwE6}?QMO`b%dEv=W2Mn;HrYJhZCR( zHSoJ{3yzQO6xaPWGv|kMjs>_rVus<7@5t$=}oTqG4#BX$BMvZ~=HFc3*px-7tm$AAKJX=+hr3)F95< zO#=el9lswuGd(7PyeT^2_StS&f6M`2&NM^@S^B+5Jp0I*VuRev-ubAZ-q6je1`HzB zLrHlAKAm;L2Xi*S@u#35S*VK7Vy$pnss-C@wQ$cwTAOF&z6gCU86?ui-k9ltjc_N- z$&EJJEfXa8>D3i^ib4W|{5}e8mmubnFhHS+1r`-Bj1wsIz5xl$62m{+qQT{-^@&mv zr5cc*CHh&#O#|YMQL&2Ngh9^|18f#o6H{>GbS-N?X@?(Aw?S93n{;8R5?N2-=H!zMliE16EXtRuLi*TdZvD}13NlMsTf zY%6d$MDmC56ugwKhHLm;Gw}zJ51$WyNw-2Y{gBy9)`S2813pX#OhG5?EFrKiyGe=A z@_W_9ED|ut0;)5E91mleX|lktW@fkn_#<XR4@X6Ulll*J|5y%SFFR(3x#6N5F46H{e{axkPHn*~kUTWrIxL zMy?vvTorV!1Wml>U(ni$yQGK*F53nhxudYP;;AL8MpRDoe2C{^H-8eG`NqRwG;co) szMMaK7`&NxaC~t$|NN24fQv6&Ji)t<8 delta 794 zcmZ9JO-vI}6ooTmVcH4?w4$vVh7co-LwTKcT3TuoW1^J}n8uYsEU`q8pQdS{O<-Eu zGQ~g89-|u|7SG9X^Qh&?j1((C_`KOW~tt{OTFQWUqkLiyv_5+_s*K9CzAcl1MV1 zA<1eX8DbP9hF7;wLrjxH3@S2Hcoj*+^>oxgUIQr}iq`j#+fex_5~r#vpMm^disp8a zx0|VyVT!)(AsS5Mnd-uH)uu< z7#VX)Sf6#Ef4;s_YJgL1#R2sMHnlq3Qri$w8}UT##2xi8cJdZ{q5U`XyD8Vn5rlI+ zygP^o#Wp<4iJXiz3OqslC^=xu2RT3b@=?B9!n+we*78ry^k8CFm*_$B{1G-1={c7W zs6sT&+YI)0&d)rrXYEFLvOstu;qPoK#tZiaBC&+-T7tb85aYA5*wO;L z`~P@S4pH5*b_S2jq@lXpHJJoug3a>T{os^+WFnDH4W_b*xYZ)>leggBf)xV`4f}1Z h(Ms>VS#Hrc7N$+;U3_0zyrPdTsT`;1-yi