storj.io/uplink@v1.13.0/private/stream/upload_part.go (about) 1 // Copyright (C) 2021 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/common/storj" 15 "storj.io/uplink/private/storage/streams" 16 ) 17 18 // PartUpload implements Writer and Closer for writing to part. 19 type PartUpload struct { 20 ctx context.Context 21 streams *streams.Store 22 writer *io.PipeWriter 23 errgroup errgroup.Group 24 25 // mu protects closed 26 mu sync.Mutex 27 closed bool 28 29 // metaMu protects meta 30 metaMu sync.RWMutex 31 meta *streams.Meta 32 } 33 34 // NewUploadPart creates new part upload. 35 func NewUploadPart(ctx context.Context, bucket, key string, streamID storj.StreamID, partNumber uint32, eTagCh <-chan []byte, streamsStore *streams.Store) *PartUpload { 36 reader, writer := io.Pipe() 37 38 upload := PartUpload{ 39 ctx: ctx, 40 streams: streamsStore, 41 writer: writer, 42 } 43 44 upload.errgroup.Go(func() error { 45 part, err := streamsStore.PutPart(ctx, bucket, key, streamID, partNumber, eTagCh, reader) 46 if err != nil { 47 err = Error.Wrap(err) 48 return errs.Combine(err, reader.CloseWithError(err)) 49 } 50 51 upload.metaMu.Lock() 52 upload.meta = &streams.Meta{ 53 Size: part.Size, 54 Modified: part.Modified, 55 } 56 upload.metaMu.Unlock() 57 58 return nil 59 }) 60 61 return &upload 62 } 63 64 // close transitions the upload to being closed and returns an error 65 // if it is already closed. 66 func (upload *PartUpload) close() error { 67 upload.mu.Lock() 68 defer upload.mu.Unlock() 69 70 if upload.closed { 71 return Error.New("already closed") 72 } 73 74 upload.closed = true 75 return nil 76 } 77 78 // isClosed returns true if the upload is already closed. 79 func (upload *PartUpload) isClosed() bool { 80 upload.mu.Lock() 81 defer upload.mu.Unlock() 82 83 return upload.closed 84 } 85 86 // Write writes len(data) bytes from data to the underlying data stream. 87 // 88 // See io.Writer for more details. 89 func (upload *PartUpload) Write(data []byte) (n int, err error) { 90 if upload.isClosed() { 91 return 0, Error.New("already closed") 92 } 93 return upload.writer.Write(data) 94 } 95 96 // Commit closes the stream and releases the underlying resources. 97 func (upload *PartUpload) Commit() error { 98 if err := upload.close(); err != nil { 99 return err 100 } 101 102 // Wait for our launched goroutine to return. 103 return errs.Combine( 104 upload.writer.Close(), 105 upload.errgroup.Wait(), 106 ) 107 } 108 109 // Abort closes the stream with an error so that it does not successfully commit and 110 // releases the underlying resources. 111 func (upload *PartUpload) Abort() error { 112 if err := upload.close(); err != nil { 113 return err 114 } 115 116 // Wait for our launched goroutine to return. We do not need any of the 117 // errors from the abort because they will just be things stating that 118 // it was aborted. 119 _ = upload.writer.CloseWithError(Error.New("aborted")) 120 _ = upload.errgroup.Wait() 121 122 return nil 123 } 124 125 // Meta returns the part metadata. 126 // 127 // Will return nil if the upload is still in progress. 128 func (upload *PartUpload) Meta() *streams.Meta { 129 upload.metaMu.RLock() 130 defer upload.metaMu.RUnlock() 131 132 // we can safely return the pointer because it doesn't change after the 133 // upload finishes 134 return upload.meta 135 }