| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- package lfsutil
- import (
- "crypto/sha256"
- "encoding/hex"
- "io"
- "os"
- "path/filepath"
- "github.com/cockroachdb/errors"
- "gogs.io/gogs/internal/osutil"
- )
- var (
- ErrObjectNotExist = errors.New("object does not exist")
- ErrOIDMismatch = errors.New("content hash does not match OID")
- )
- // Storager is an storage backend for uploading and downloading LFS objects.
- type Storager interface {
- // Storage returns the name of the storage backend.
- Storage() Storage
- // Upload reads content from the io.ReadCloser and uploads as given oid.
- // The reader is closed once upload is finished. ErrInvalidOID is returned
- // if the given oid is not valid.
- Upload(oid OID, rc io.ReadCloser) (int64, error)
- // Download streams content of given oid to the io.Writer. It is caller's
- // responsibility the close the writer when needed. ErrObjectNotExist is
- // returned if the given oid does not exist.
- Download(oid OID, w io.Writer) error
- }
- // Storage is the storage type of an LFS object.
- type Storage string
- const (
- StorageLocal Storage = "local"
- )
- var _ Storager = (*LocalStorage)(nil)
- // LocalStorage is a LFS storage backend on local file system.
- type LocalStorage struct {
- // The root path for storing LFS objects.
- Root string
- // The path for storing temporary files during upload verification.
- TempDir string
- }
- func (*LocalStorage) Storage() Storage {
- return StorageLocal
- }
- func (s *LocalStorage) storagePath(oid OID) string {
- if len(oid) < 2 {
- return ""
- }
- return filepath.Join(s.Root, string(oid[0]), string(oid[1]), string(oid))
- }
- func (s *LocalStorage) Upload(oid OID, rc io.ReadCloser) (int64, error) {
- if !ValidOID(oid) {
- return 0, ErrInvalidOID
- }
- fpath := s.storagePath(oid)
- dir := filepath.Dir(fpath)
- defer rc.Close()
- if err := os.MkdirAll(dir, os.ModePerm); err != nil {
- return 0, errors.Wrap(err, "create directories")
- }
- // If the object file already exists, skip the upload and return the
- // existing file's size.
- if fi, err := os.Stat(fpath); err == nil {
- _, _ = io.Copy(io.Discard, rc)
- return fi.Size(), nil
- }
- // Write to a temp file and verify the content hash before publishing.
- // This ensures the final path always contains a complete, hash-verified
- // file, even when concurrent uploads of the same OID race.
- if err := os.MkdirAll(s.TempDir, os.ModePerm); err != nil {
- return 0, errors.Wrap(err, "create temp directory")
- }
- tmp, err := os.CreateTemp(s.TempDir, "upload-*")
- if err != nil {
- return 0, errors.Wrap(err, "create temp file")
- }
- tmpPath := tmp.Name()
- defer os.Remove(tmpPath)
- hash := sha256.New()
- written, err := io.Copy(tmp, io.TeeReader(rc, hash))
- if closeErr := tmp.Close(); err == nil && closeErr != nil {
- err = closeErr
- }
- if err != nil {
- return 0, errors.Wrap(err, "write object file")
- }
- if computed := hex.EncodeToString(hash.Sum(nil)); computed != string(oid) {
- return 0, ErrOIDMismatch
- }
- if err := os.Rename(tmpPath, fpath); err != nil && !os.IsExist(err) {
- return 0, errors.Wrap(err, "publish object file")
- }
- return written, nil
- }
- func (s *LocalStorage) Download(oid OID, w io.Writer) error {
- fpath := s.storagePath(oid)
- if !osutil.IsFile(fpath) {
- return ErrObjectNotExist
- }
- r, err := os.Open(fpath)
- if err != nil {
- return errors.Wrap(err, "open file")
- }
- defer r.Close()
- _, err = io.Copy(w, r)
- if err != nil {
- return errors.Wrap(err, "copy file")
- }
- return nil
- }
|