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 }