git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/object/patcher/patcher.go (about)

     1  package patcher
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  
     9  	objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
    10  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/transformer"
    11  )
    12  
    13  var (
    14  	ErrOffsetExceedsSize       = errors.New("patch offset exceeds object size")
    15  	ErrInvalidPatchOffsetOrder = errors.New("invalid patch offset order")
    16  	ErrPayloadPatchIsNil       = errors.New("nil payload patch")
    17  	ErrAttrPatchAlreadyApplied = errors.New("attribute patch already applied")
    18  )
    19  
    20  // PatchRes is the result of patch application.
    21  type PatchRes struct {
    22  	AccessIdentifiers *transformer.AccessIdentifiers
    23  }
    24  
    25  // PatchApplier is the interface that provides method to apply header and payload patches.
    26  type PatchApplier interface {
    27  	// ApplyAttributesPatch applies the patch only for the object's attributes.
    28  	//
    29  	// ApplyAttributesPatch can't be invoked few times, otherwise it returns `ErrAttrPatchAlreadyApplied` error.
    30  	//
    31  	// The call is idempotent for the original header if it's invoked with empty `newAttrs` and
    32  	// `replaceAttrs = false`.
    33  	ApplyAttributesPatch(ctx context.Context, newAttrs []objectSDK.Attribute, replaceAttrs bool) error
    34  
    35  	// ApplyPayloadPatch applies the patch for the object's payload.
    36  	//
    37  	// ApplyPayloadPatch returns `ErrPayloadPatchIsNil` error if patch is nil.
    38  	ApplyPayloadPatch(ctx context.Context, payloadPatch *objectSDK.PayloadPatch) error
    39  
    40  	// Close closes PatchApplier when the patch stream is over.
    41  	Close(context.Context) (PatchRes, error)
    42  }
    43  
    44  // RangeProvider is the interface that provides a method to get original object payload
    45  // by a given range.
    46  type RangeProvider interface {
    47  	// GetRange reads an original object payload by the given range.
    48  	// The method returns io.Reader over the data range only. This means if the data is read out,
    49  	// then GetRange has to be invoked to provide reader over the next range.
    50  	GetRange(ctx context.Context, rng *objectSDK.Range) io.Reader
    51  }
    52  
    53  type patcher struct {
    54  	rangeProvider RangeProvider
    55  
    56  	objectWriter transformer.ChunkedObjectWriter
    57  
    58  	currOffset uint64
    59  
    60  	originalPayloadSize uint64
    61  
    62  	hdr *objectSDK.Object
    63  
    64  	attrPatchAlreadyApplied bool
    65  
    66  	readerBuffSize int
    67  }
    68  
    69  const (
    70  	DefaultReaderBufferSize = 64 * 1024
    71  )
    72  
    73  // Params is parameters to initialize patcher.
    74  type Params struct {
    75  	// Original object header.
    76  	Header *objectSDK.Object
    77  
    78  	// Range provider.
    79  	RangeProvider RangeProvider
    80  
    81  	// ObjectWriter is the writer that writes the patched object.
    82  	ObjectWriter transformer.ChunkedObjectWriter
    83  
    84  	// The size of the buffer used by the original payload range reader.
    85  	// If it's set to <=0, then `DefaultReaderBufferSize` is used.
    86  	ReaderBufferSize int
    87  }
    88  
    89  func New(prm Params) PatchApplier {
    90  	readerBufferSize := prm.ReaderBufferSize
    91  	if readerBufferSize <= 0 {
    92  		readerBufferSize = DefaultReaderBufferSize
    93  	}
    94  
    95  	return &patcher{
    96  		rangeProvider: prm.RangeProvider,
    97  
    98  		objectWriter: prm.ObjectWriter,
    99  
   100  		hdr: prm.Header,
   101  
   102  		originalPayloadSize: prm.Header.PayloadSize(),
   103  
   104  		readerBuffSize: readerBufferSize,
   105  	}
   106  }
   107  
   108  func (p *patcher) ApplyAttributesPatch(ctx context.Context, newAttrs []objectSDK.Attribute, replaceAttrs bool) error {
   109  	defer func() {
   110  		p.attrPatchAlreadyApplied = true
   111  	}()
   112  
   113  	if p.attrPatchAlreadyApplied {
   114  		return ErrAttrPatchAlreadyApplied
   115  	}
   116  
   117  	if replaceAttrs {
   118  		p.hdr.SetAttributes(newAttrs...)
   119  	} else if len(newAttrs) > 0 {
   120  		mergedAttrs := mergeAttributes(newAttrs, p.hdr.Attributes())
   121  		p.hdr.SetAttributes(mergedAttrs...)
   122  	}
   123  
   124  	if err := p.objectWriter.WriteHeader(ctx, p.hdr); err != nil {
   125  		return fmt.Errorf("writer header: %w", err)
   126  	}
   127  	return nil
   128  }
   129  
   130  func (p *patcher) ApplyPayloadPatch(ctx context.Context, payloadPatch *objectSDK.PayloadPatch) error {
   131  	if payloadPatch == nil {
   132  		return ErrPayloadPatchIsNil
   133  	}
   134  
   135  	if payloadPatch.Range.GetOffset() < p.currOffset {
   136  		return fmt.Errorf("%w: current = %d, previous = %d", ErrInvalidPatchOffsetOrder, payloadPatch.Range.GetOffset(), p.currOffset)
   137  	}
   138  
   139  	if payloadPatch.Range.GetOffset() > p.originalPayloadSize {
   140  		return fmt.Errorf("%w: offset = %d, object size = %d", ErrOffsetExceedsSize, payloadPatch.Range.GetOffset(), p.originalPayloadSize)
   141  	}
   142  
   143  	var err error
   144  	if p.currOffset, err = p.applyPatch(ctx, payloadPatch, p.currOffset); err != nil {
   145  		return fmt.Errorf("apply patch: %w", err)
   146  	}
   147  
   148  	return nil
   149  }
   150  
   151  func (p *patcher) Close(ctx context.Context) (PatchRes, error) {
   152  	rng := new(objectSDK.Range)
   153  	rng.SetOffset(p.currOffset)
   154  	rng.SetLength(p.originalPayloadSize - p.currOffset)
   155  
   156  	// copy remaining originial payload
   157  	if err := p.copyRange(ctx, rng); err != nil {
   158  		return PatchRes{}, fmt.Errorf("copy payload: %w", err)
   159  	}
   160  
   161  	aid, err := p.objectWriter.Close(ctx)
   162  	if err != nil {
   163  		return PatchRes{}, fmt.Errorf("close object writer: %w", err)
   164  	}
   165  
   166  	return PatchRes{
   167  		AccessIdentifiers: aid,
   168  	}, nil
   169  }
   170  
   171  func (p *patcher) copyRange(ctx context.Context, rng *objectSDK.Range) error {
   172  	rdr := p.rangeProvider.GetRange(ctx, rng)
   173  	for {
   174  		buffOrigPayload := make([]byte, p.readerBuffSize)
   175  		n, readErr := rdr.Read(buffOrigPayload)
   176  		if readErr != nil {
   177  			if readErr != io.EOF {
   178  				return fmt.Errorf("read: %w", readErr)
   179  			}
   180  		}
   181  		_, wrErr := p.objectWriter.Write(ctx, buffOrigPayload[:n])
   182  		if wrErr != nil {
   183  			return fmt.Errorf("write: %w", wrErr)
   184  		}
   185  		if readErr == io.EOF {
   186  			break
   187  		}
   188  	}
   189  	return nil
   190  }
   191  
   192  func (p *patcher) applyPatch(ctx context.Context, payloadPatch *objectSDK.PayloadPatch, offset uint64) (newOffset uint64, err error) {
   193  	newOffset = offset
   194  
   195  	// write the original payload chunk before the start of the patch
   196  	if payloadPatch.Range.GetOffset() > offset {
   197  		rng := new(objectSDK.Range)
   198  		rng.SetOffset(offset)
   199  		rng.SetLength(payloadPatch.Range.GetOffset() - offset)
   200  
   201  		if err = p.copyRange(ctx, rng); err != nil {
   202  			err = fmt.Errorf("copy payload: %w", err)
   203  			return
   204  		}
   205  
   206  		newOffset = payloadPatch.Range.GetOffset()
   207  	}
   208  
   209  	// apply patch
   210  	if _, err = p.objectWriter.Write(ctx, payloadPatch.Chunk); err != nil {
   211  		return
   212  	}
   213  
   214  	if payloadPatch.Range.GetLength() > 0 {
   215  		newOffset += payloadPatch.Range.GetLength()
   216  	}
   217  
   218  	return
   219  }
   220  
   221  func mergeAttributes(newAttrs, oldAttrs []objectSDK.Attribute) []objectSDK.Attribute {
   222  	attrMap := make(map[string]string, len(newAttrs))
   223  
   224  	for _, attr := range newAttrs {
   225  		attrMap[attr.Key()] = attr.Value()
   226  	}
   227  
   228  	for i := range oldAttrs {
   229  		newVal, ok := attrMap[oldAttrs[i].Key()]
   230  		if !ok {
   231  			continue
   232  		}
   233  		oldAttrs[i].SetValue(newVal)
   234  		delete(attrMap, oldAttrs[i].Key())
   235  	}
   236  
   237  	for _, newAttr := range newAttrs {
   238  		if _, ok := attrMap[newAttr.Key()]; ok {
   239  			oldAttrs = append(oldAttrs, newAttr)
   240  		}
   241  	}
   242  
   243  	return oldAttrs
   244  }