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  }