2023-06-27 12:49:20 +00:00
|
|
|
package layer
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
|
2023-07-18 11:48:33 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
2023-06-27 12:49:20 +00:00
|
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
|
|
)
|
|
|
|
|
|
|
|
type partObj struct {
|
|
|
|
OID oid.ID
|
|
|
|
Size uint64
|
|
|
|
}
|
|
|
|
|
2023-07-18 11:48:33 +00:00
|
|
|
type readerInitiator interface {
|
|
|
|
initFrostFSObjectPayloadReader(ctx context.Context, p getFrostFSParams) (io.Reader, error)
|
|
|
|
}
|
|
|
|
|
2023-06-27 12:49:20 +00:00
|
|
|
// implements io.Reader of payloads of the object list stored in the FrostFS network.
|
|
|
|
type multiObjectReader struct {
|
|
|
|
ctx context.Context
|
|
|
|
|
2023-07-18 11:48:33 +00:00
|
|
|
layer readerInitiator
|
2023-06-27 12:49:20 +00:00
|
|
|
|
2023-07-18 11:48:33 +00:00
|
|
|
startPartOffset uint64
|
|
|
|
endPartLength uint64
|
2023-06-27 12:49:20 +00:00
|
|
|
|
|
|
|
prm getFrostFSParams
|
|
|
|
|
2023-07-18 11:48:33 +00:00
|
|
|
curIndex int
|
2023-06-27 12:49:20 +00:00
|
|
|
curReader io.Reader
|
|
|
|
|
|
|
|
parts []partObj
|
|
|
|
}
|
|
|
|
|
2023-07-18 11:48:33 +00:00
|
|
|
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) {
|
2023-09-19 08:00:41 +00:00
|
|
|
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
|
|
|
|
}
|
2023-07-18 11:48:33 +00:00
|
|
|
|
2023-09-19 08:00:41 +00:00
|
|
|
return -1, 0
|
2023-07-18 11:48:33 +00:00
|
|
|
}
|
|
|
|
|
2023-09-19 08:00:41 +00:00
|
|
|
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.
|
2023-07-18 11:48:33 +00:00
|
|
|
if position <= part.Size {
|
|
|
|
return i, position
|
|
|
|
}
|
|
|
|
position -= part.Size
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1, 0
|
|
|
|
}
|
|
|
|
|
2023-06-27 12:49:20 +00:00
|
|
|
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
|
|
|
|
}
|
2023-07-18 11:48:33 +00:00
|
|
|
x.curIndex++
|
2023-06-27 12:49:20 +00:00
|
|
|
}
|
|
|
|
|
2023-07-18 11:48:33 +00:00
|
|
|
if x.curIndex == len(x.parts) {
|
2023-06-27 12:49:20 +00:00
|
|
|
return n, io.EOF
|
|
|
|
}
|
|
|
|
|
2023-07-18 11:48:33 +00:00
|
|
|
x.prm.oid = x.parts[x.curIndex].OID
|
2023-06-27 12:49:20 +00:00
|
|
|
|
2023-07-18 11:48:33 +00:00
|
|
|
if x.curIndex == 0 {
|
|
|
|
x.prm.off = x.startPartOffset
|
|
|
|
x.prm.ln = x.parts[x.curIndex].Size - x.startPartOffset
|
|
|
|
}
|
2023-06-27 12:49:20 +00:00
|
|
|
|
2023-07-18 11:48:33 +00:00
|
|
|
if x.curIndex == len(x.parts)-1 {
|
|
|
|
x.prm.ln = x.endPartLength - x.prm.off
|
2023-06-27 12:49:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|