1
0

storage.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. package lfsutil
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "io"
  6. "os"
  7. "path/filepath"
  8. "github.com/cockroachdb/errors"
  9. "gogs.io/gogs/internal/osutil"
  10. )
  11. var (
  12. ErrObjectNotExist = errors.New("object does not exist")
  13. ErrOIDMismatch = errors.New("content hash does not match OID")
  14. )
  15. // Storager is an storage backend for uploading and downloading LFS objects.
  16. type Storager interface {
  17. // Storage returns the name of the storage backend.
  18. Storage() Storage
  19. // Upload reads content from the io.ReadCloser and uploads as given oid.
  20. // The reader is closed once upload is finished. ErrInvalidOID is returned
  21. // if the given oid is not valid.
  22. Upload(oid OID, rc io.ReadCloser) (int64, error)
  23. // Download streams content of given oid to the io.Writer. It is caller's
  24. // responsibility the close the writer when needed. ErrObjectNotExist is
  25. // returned if the given oid does not exist.
  26. Download(oid OID, w io.Writer) error
  27. }
  28. // Storage is the storage type of an LFS object.
  29. type Storage string
  30. const (
  31. StorageLocal Storage = "local"
  32. )
  33. var _ Storager = (*LocalStorage)(nil)
  34. // LocalStorage is a LFS storage backend on local file system.
  35. type LocalStorage struct {
  36. // The root path for storing LFS objects.
  37. Root string
  38. // The path for storing temporary files during upload verification.
  39. TempDir string
  40. }
  41. func (*LocalStorage) Storage() Storage {
  42. return StorageLocal
  43. }
  44. func (s *LocalStorage) storagePath(oid OID) string {
  45. if len(oid) < 2 {
  46. return ""
  47. }
  48. return filepath.Join(s.Root, string(oid[0]), string(oid[1]), string(oid))
  49. }
  50. func (s *LocalStorage) Upload(oid OID, rc io.ReadCloser) (int64, error) {
  51. if !ValidOID(oid) {
  52. return 0, ErrInvalidOID
  53. }
  54. fpath := s.storagePath(oid)
  55. dir := filepath.Dir(fpath)
  56. defer rc.Close()
  57. if err := os.MkdirAll(dir, os.ModePerm); err != nil {
  58. return 0, errors.Wrap(err, "create directories")
  59. }
  60. // If the object file already exists, skip the upload and return the
  61. // existing file's size.
  62. if fi, err := os.Stat(fpath); err == nil {
  63. _, _ = io.Copy(io.Discard, rc)
  64. return fi.Size(), nil
  65. }
  66. // Write to a temp file and verify the content hash before publishing.
  67. // This ensures the final path always contains a complete, hash-verified
  68. // file, even when concurrent uploads of the same OID race.
  69. if err := os.MkdirAll(s.TempDir, os.ModePerm); err != nil {
  70. return 0, errors.Wrap(err, "create temp directory")
  71. }
  72. tmp, err := os.CreateTemp(s.TempDir, "upload-*")
  73. if err != nil {
  74. return 0, errors.Wrap(err, "create temp file")
  75. }
  76. tmpPath := tmp.Name()
  77. defer os.Remove(tmpPath)
  78. hash := sha256.New()
  79. written, err := io.Copy(tmp, io.TeeReader(rc, hash))
  80. if closeErr := tmp.Close(); err == nil && closeErr != nil {
  81. err = closeErr
  82. }
  83. if err != nil {
  84. return 0, errors.Wrap(err, "write object file")
  85. }
  86. if computed := hex.EncodeToString(hash.Sum(nil)); computed != string(oid) {
  87. return 0, ErrOIDMismatch
  88. }
  89. if err := os.Rename(tmpPath, fpath); err != nil && !os.IsExist(err) {
  90. return 0, errors.Wrap(err, "publish object file")
  91. }
  92. return written, nil
  93. }
  94. func (s *LocalStorage) Download(oid OID, w io.Writer) error {
  95. fpath := s.storagePath(oid)
  96. if !osutil.IsFile(fpath) {
  97. return ErrObjectNotExist
  98. }
  99. r, err := os.Open(fpath)
  100. if err != nil {
  101. return errors.Wrap(err, "open file")
  102. }
  103. defer r.Close()
  104. _, err = io.Copy(w, r)
  105. if err != nil {
  106. return errors.Wrap(err, "copy file")
  107. }
  108. return nil
  109. }