code.gitea.io/gitea@v1.21.7/services/packages/container/blob_uploader.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package container 5 6 import ( 7 "context" 8 "errors" 9 "io" 10 "os" 11 12 packages_model "code.gitea.io/gitea/models/packages" 13 packages_module "code.gitea.io/gitea/modules/packages" 14 "code.gitea.io/gitea/modules/setting" 15 "code.gitea.io/gitea/modules/util" 16 ) 17 18 var ( 19 // errWriteAfterRead occurs if Write is called after a read operation 20 errWriteAfterRead = errors.New("write is unsupported after a read operation") 21 // errOffsetMissmatch occurs if the file offset is different than the model 22 errOffsetMissmatch = errors.New("offset mismatch between file and model") 23 ) 24 25 // BlobUploader handles chunked blob uploads 26 type BlobUploader struct { 27 *packages_model.PackageBlobUpload 28 *packages_module.MultiHasher 29 file *os.File 30 reading bool 31 } 32 33 func buildFilePath(id string) string { 34 return util.FilePathJoinAbs(setting.Packages.ChunkedUploadPath, id) 35 } 36 37 // NewBlobUploader creates a new blob uploader for the given id 38 func NewBlobUploader(ctx context.Context, id string) (*BlobUploader, error) { 39 model, err := packages_model.GetBlobUploadByID(ctx, id) 40 if err != nil { 41 return nil, err 42 } 43 44 hash := packages_module.NewMultiHasher() 45 if len(model.HashStateBytes) != 0 { 46 if err := hash.UnmarshalBinary(model.HashStateBytes); err != nil { 47 return nil, err 48 } 49 } 50 51 f, err := os.OpenFile(buildFilePath(model.ID), os.O_RDWR|os.O_CREATE, 0o666) 52 if err != nil { 53 return nil, err 54 } 55 56 return &BlobUploader{ 57 model, 58 hash, 59 f, 60 false, 61 }, nil 62 } 63 64 // Close implements io.Closer 65 func (u *BlobUploader) Close() error { 66 return u.file.Close() 67 } 68 69 // Append appends a chunk of data and updates the model 70 func (u *BlobUploader) Append(ctx context.Context, r io.Reader) error { 71 if u.reading { 72 return errWriteAfterRead 73 } 74 75 offset, err := u.file.Seek(0, io.SeekEnd) 76 if err != nil { 77 return err 78 } 79 if offset != u.BytesReceived { 80 return errOffsetMissmatch 81 } 82 83 n, err := io.Copy(io.MultiWriter(u.file, u.MultiHasher), r) 84 if err != nil { 85 return err 86 } 87 88 // fast path if nothing was written 89 if n == 0 { 90 return nil 91 } 92 93 u.BytesReceived += n 94 95 u.HashStateBytes, err = u.MultiHasher.MarshalBinary() 96 if err != nil { 97 return err 98 } 99 100 return packages_model.UpdateBlobUpload(ctx, u.PackageBlobUpload) 101 } 102 103 func (u *BlobUploader) Size() int64 { 104 return u.BytesReceived 105 } 106 107 // Read implements io.Reader 108 func (u *BlobUploader) Read(p []byte) (int, error) { 109 if !u.reading { 110 _, err := u.file.Seek(0, io.SeekStart) 111 if err != nil { 112 return 0, err 113 } 114 115 u.reading = true 116 } 117 118 return u.file.Read(p) 119 } 120 121 // Remove deletes the data and the model of a blob upload 122 func RemoveBlobUploadByID(ctx context.Context, id string) error { 123 if err := packages_model.DeleteBlobUploadByID(ctx, id); err != nil { 124 return err 125 } 126 127 err := os.Remove(buildFilePath(id)) 128 if err != nil && !os.IsNotExist(err) { 129 return err 130 } 131 132 return nil 133 }