package layer import ( "context" "errors" "fmt" "io" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" ) type partObj struct { OID oid.ID Size uint64 } type readerInitiator interface { initFrostFSObjectPayloadReader(ctx context.Context, p getFrostFSParams) (io.Reader, error) } // implements io.Reader of payloads of the object list stored in the FrostFS network. type multiObjectReader struct { ctx context.Context layer readerInitiator startPartOffset uint64 endPartLength uint64 prm getFrostFSParams curIndex int curReader io.Reader parts []partObj } type multiObjectReaderConfig struct { layer readerInitiator // the offset of complete object and total size to read off, ln uint64 bktInfo *data.BucketInfo parts []partObj } var ( errOffsetIsOutOfRange = errors.New("offset is out of payload range") errLengthIsOutOfRange = errors.New("length is out of payload range") errEmptyPartsList = errors.New("empty parts list") errorZeroRangeLength = errors.New("zero range length") ) func newMultiObjectReader(ctx context.Context, cfg multiObjectReaderConfig) (*multiObjectReader, error) { if len(cfg.parts) == 0 { return nil, errEmptyPartsList } r := &multiObjectReader{ ctx: ctx, layer: cfg.layer, prm: getFrostFSParams{ bktInfo: cfg.bktInfo, }, parts: cfg.parts, } if cfg.off+cfg.ln == 0 { return r, nil } if cfg.off > 0 && cfg.ln == 0 { return nil, errorZeroRangeLength } startPartIndex, startPartOffset := findStartPart(cfg) if startPartIndex == -1 { return nil, errOffsetIsOutOfRange } r.startPartOffset = startPartOffset endPartIndex, endPartLength := findEndPart(cfg) if endPartIndex == -1 { return nil, errLengthIsOutOfRange } r.endPartLength = endPartLength r.parts = cfg.parts[startPartIndex : endPartIndex+1] return r, nil } func findStartPart(cfg multiObjectReaderConfig) (index int, offset uint64) { position := cfg.off for i, part := range cfg.parts { // Strict inequality when searching for start position to avoid reading zero length part. if position < part.Size { return i, position } position -= part.Size } return -1, 0 } func findEndPart(cfg multiObjectReaderConfig) (index int, length uint64) { position := cfg.off + cfg.ln for i, part := range cfg.parts { // Non-strict inequality when searching for end position to avoid out of payload range error. if position <= part.Size { return i, position } position -= part.Size } return -1, 0 } func (x *multiObjectReader) Read(p []byte) (n int, err error) { if x.curReader != nil { n, err = x.curReader.Read(p) if !errors.Is(err, io.EOF) { return n, err } x.curIndex++ } if x.curIndex == len(x.parts) { return n, io.EOF } x.prm.oid = x.parts[x.curIndex].OID if x.curIndex == 0 { x.prm.off = x.startPartOffset x.prm.ln = x.parts[x.curIndex].Size - x.startPartOffset } if x.curIndex == len(x.parts)-1 { x.prm.ln = x.endPartLength - x.prm.off } x.curReader, err = x.layer.initFrostFSObjectPayloadReader(x.ctx, x.prm) if err != nil { return n, fmt.Errorf("init payload reader for the next part: %w", err) } x.prm.off = 0 x.prm.ln = 0 next, err := x.Read(p[n:]) return n + next, err }