storj.io/uplink@v1.13.0/private/storage/streams/streambatcher/batcher.go (about)

     1  // Copyright (C) 2023 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package streambatcher
     5  
     6  import (
     7  	"context"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/zeebo/errs"
    12  
    13  	"storj.io/common/pb"
    14  	"storj.io/common/storj"
    15  	"storj.io/uplink/private/metaclient"
    16  )
    17  
    18  // Info returns stream information gathered by the Batcher.
    19  type Info struct {
    20  	// CreationDate is the creation date of the stream extracted by the
    21  	// stream ID that is provided to the Batcher or gathered from the
    22  	// BeginObject response.
    23  	CreationDate time.Time
    24  
    25  	// PlainSize is the plain-text size of the stream aggregated from all
    26  	// MakeInlineSegment or CommitSegment batch items.
    27  	PlainSize int64
    28  
    29  	// Version is object version retrieved from CommitObject batch item.
    30  	Version []byte
    31  }
    32  
    33  // Batcher issues batch items related to a single stream. It aggregates
    34  // information about the stream required by callers to commit the stream. It
    35  // also learns the stream ID (unless already provided for part uploads) and
    36  // automatically injects it into batch items that need it.
    37  type Batcher struct {
    38  	miBatcher metaclient.Batcher
    39  
    40  	mu       sync.Mutex
    41  	streamID storj.StreamID
    42  	info     Info
    43  }
    44  
    45  // New returns a new Batcher that issues batch items for a stream. The streamID
    46  // can be nil (in the case of an object upload) or not (in the case of a part
    47  // upload). The batcher will discover the streamID in the former case when it
    48  // processes a BeginObject.
    49  func New(miBatcher metaclient.Batcher, streamID storj.StreamID) *Batcher {
    50  	return &Batcher{
    51  		miBatcher: miBatcher,
    52  		streamID:  streamID,
    53  	}
    54  }
    55  
    56  // Batch issues batch items for a stream. Once the streamID is known, it will
    57  // be injected into batch items that need it. If a BeginObject is issued, the
    58  // stream ID will be gleaned from it. If a BeginObject needs to be issued, it
    59  // must be the first batch item issued by the batcher.
    60  func (s *Batcher) Batch(ctx context.Context, batchItems ...metaclient.BatchItem) ([]metaclient.BatchResponse, error) {
    61  	s.mu.Lock()
    62  	defer s.mu.Unlock()
    63  
    64  	for _, item := range batchItems {
    65  		switch item := item.(type) {
    66  		case *metaclient.BeginSegmentParams:
    67  			item.StreamID = s.streamID
    68  		case *metaclient.MakeInlineSegmentParams:
    69  			item.StreamID = s.streamID
    70  			s.info.PlainSize += item.PlainSize
    71  		case *metaclient.CommitSegmentParams:
    72  			s.info.PlainSize += item.PlainSize
    73  		case *metaclient.CommitObjectParams:
    74  			item.StreamID = s.streamID
    75  		}
    76  	}
    77  
    78  	if len(batchItems) == 0 {
    79  		return nil, errs.New("programmer error: empty batch request")
    80  	}
    81  
    82  	resp, err := s.miBatcher.Batch(ctx, batchItems...)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	if len(resp) == 0 {
    88  		return nil, errs.New("programmer error: empty batch response")
    89  	}
    90  
    91  	if s.streamID == nil {
    92  		beginObject, err := resp[0].BeginObject()
    93  		if err != nil {
    94  			return nil, errs.New("programmer error: first batch must start with BeginObject: %w", err)
    95  		}
    96  		if beginObject.StreamID.IsZero() {
    97  			return nil, errs.New("stream ID missing from BeginObject response")
    98  		}
    99  		s.streamID = beginObject.StreamID
   100  	}
   101  
   102  	for _, response := range resp {
   103  		if response.IsCommitObject() {
   104  			commitObject, err := response.CommitObject()
   105  			if err != nil {
   106  				return nil, errs.New("programmer error: batch must be CommitObject: %w", err)
   107  			}
   108  
   109  			s.info = Info{
   110  				CreationDate: commitObject.Object.Created,
   111  				PlainSize:    commitObject.Object.PlainSize,
   112  			}
   113  			if commitObject.Object.Status == int32(pb.Object_COMMITTED_VERSIONED) {
   114  				s.info.Version = commitObject.Object.Version
   115  			}
   116  		}
   117  	}
   118  
   119  	return resp, nil
   120  }
   121  
   122  // StreamID returns the stream ID either provided to the Batcher or gleaned
   123  // from issuing a BeginObject request.
   124  func (s *Batcher) StreamID() storj.StreamID {
   125  	s.mu.Lock()
   126  	defer s.mu.Unlock()
   127  	return s.streamID
   128  }
   129  
   130  // Info returns the stream information gathered by the batch items.
   131  func (s *Batcher) Info() (Info, error) {
   132  	s.mu.Lock()
   133  	defer s.mu.Unlock()
   134  
   135  	if s.streamID == nil {
   136  		return Info{}, errs.New("stream ID is unexpectedly nil")
   137  	}
   138  
   139  	return s.info, nil
   140  }