storj.io/uplink@v1.13.0/private/stream/upload.go (about)

     1  // Copyright (C) 2019 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package stream
     5  
     6  import (
     7  	"context"
     8  	"io"
     9  	"sync"
    10  
    11  	"github.com/zeebo/errs"
    12  	"golang.org/x/sync/errgroup"
    13  
    14  	"storj.io/uplink/private/metaclient"
    15  	"storj.io/uplink/private/storage/streams"
    16  )
    17  
    18  // Upload implements Writer and Closer for writing to stream.
    19  type Upload struct {
    20  	ctx      context.Context
    21  	stream   *metaclient.MutableStream
    22  	streams  *streams.Store
    23  	writer   *io.PipeWriter
    24  	errgroup errgroup.Group
    25  
    26  	// mu protects closed
    27  	mu     sync.Mutex
    28  	closed bool
    29  
    30  	// metamu protects meta
    31  	metamu sync.RWMutex
    32  	meta   *streams.Meta
    33  }
    34  
    35  // NewUpload creates new stream upload.
    36  func NewUpload(ctx context.Context, stream *metaclient.MutableStream, streamsStore *streams.Store) *Upload {
    37  	reader, writer := io.Pipe()
    38  
    39  	upload := Upload{
    40  		ctx:     ctx,
    41  		stream:  stream,
    42  		streams: streamsStore,
    43  		writer:  writer,
    44  	}
    45  
    46  	upload.errgroup.Go(func() error {
    47  		m, err := streamsStore.Put(ctx, stream.BucketName(), stream.Path(), reader, stream, stream.Expires())
    48  		if err != nil {
    49  			err = Error.Wrap(err)
    50  			return errs.Combine(err, reader.CloseWithError(err))
    51  		}
    52  
    53  		upload.metamu.Lock()
    54  		upload.meta = &m
    55  		upload.metamu.Unlock()
    56  
    57  		return nil
    58  	})
    59  
    60  	return &upload
    61  }
    62  
    63  // close transitions the upload to being closed and returns an error
    64  // if it is already closed.
    65  func (upload *Upload) close() error {
    66  	upload.mu.Lock()
    67  	defer upload.mu.Unlock()
    68  
    69  	if upload.closed {
    70  		return Error.New("already closed")
    71  	}
    72  
    73  	upload.closed = true
    74  	return nil
    75  }
    76  
    77  // isClosed returns true if the upload is already closed.
    78  func (upload *Upload) isClosed() bool {
    79  	upload.mu.Lock()
    80  	defer upload.mu.Unlock()
    81  
    82  	return upload.closed
    83  }
    84  
    85  // Write writes len(data) bytes from data to the underlying data stream.
    86  //
    87  // See io.Writer for more details.
    88  func (upload *Upload) Write(data []byte) (n int, err error) {
    89  	if upload.isClosed() {
    90  		return 0, Error.New("already closed")
    91  	}
    92  	return upload.writer.Write(data)
    93  }
    94  
    95  // Commit closes the stream and releases the underlying resources.
    96  func (upload *Upload) Commit() error {
    97  	if err := upload.close(); err != nil {
    98  		return err
    99  	}
   100  
   101  	// Wait for our launched goroutine to return.
   102  	return errs.Combine(
   103  		upload.writer.Close(),
   104  		upload.errgroup.Wait(),
   105  	)
   106  }
   107  
   108  // Abort closes the stream with an error so that it does not successfully commit and
   109  // releases the underlying resources.
   110  func (upload *Upload) Abort() error {
   111  	if err := upload.close(); err != nil {
   112  		return err
   113  	}
   114  
   115  	// Wait for our launched goroutine to return. We do not need any of the
   116  	// errors from the abort because they will just be things stating that
   117  	// it was aborted.
   118  	_ = upload.writer.CloseWithError(Error.New("aborted"))
   119  	_ = upload.errgroup.Wait()
   120  
   121  	return nil
   122  }
   123  
   124  // Meta returns the metadata of the uploaded object.
   125  //
   126  // Will return nil if the upload is still in progress.
   127  func (upload *Upload) Meta() *streams.Meta {
   128  	upload.metamu.RLock()
   129  	defer upload.metamu.RUnlock()
   130  
   131  	// we can safely return the pointer because it doesn't change after the
   132  	// upload finishes
   133  	return upload.meta
   134  }