lfs.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. // Copyright 2020 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package db
  5. import (
  6. "fmt"
  7. "io"
  8. "os"
  9. "path/filepath"
  10. "time"
  11. "github.com/jinzhu/gorm"
  12. "github.com/pkg/errors"
  13. "gogs.io/gogs/internal/conf"
  14. "gogs.io/gogs/internal/errutil"
  15. "gogs.io/gogs/internal/lfsutil"
  16. )
  17. // LFSStore is the persistent interface for LFS objects.
  18. //
  19. // NOTE: All methods are sorted in alphabetical order.
  20. type LFSStore interface {
  21. // CreateObject streams io.ReadCloser to target storage and creates a record in database.
  22. CreateObject(repoID int64, oid lfsutil.OID, rc io.ReadCloser, storage lfsutil.Storage) error
  23. // GetObjectByOID returns the LFS object with given OID. It returns ErrLFSObjectNotExist
  24. // when not found.
  25. GetObjectByOID(repoID int64, oid lfsutil.OID) (*LFSObject, error)
  26. // GetObjectsByOIDs returns LFS objects found within "oids". The returned list could have
  27. // less elements if some oids were not found.
  28. GetObjectsByOIDs(repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error)
  29. }
  30. var LFS LFSStore
  31. type lfs struct {
  32. *gorm.DB
  33. }
  34. // LFSObject is the relation between an LFS object and a repository.
  35. type LFSObject struct {
  36. RepoID int64 `gorm:"PRIMARY_KEY;AUTO_INCREMENT:false"`
  37. OID lfsutil.OID `gorm:"PRIMARY_KEY;column:oid"`
  38. Size int64 `gorm:"NOT NULL"`
  39. Storage lfsutil.Storage `gorm:"NOT NULL"`
  40. CreatedAt time.Time `gorm:"NOT NULL"`
  41. }
  42. func (db *lfs) CreateObject(repoID int64, oid lfsutil.OID, rc io.ReadCloser, storage lfsutil.Storage) error {
  43. if storage != lfsutil.StorageLocal {
  44. return errors.New("only local storage is supported")
  45. }
  46. var ioerr error
  47. fpath := lfsutil.StorageLocalPath(conf.LFS.ObjectsPath, oid)
  48. defer func() {
  49. rc.Close()
  50. // NOTE: Only remove the file if there is an IO error, it is OK to
  51. // leave the file when the whole operation failed with a DB error,
  52. // a retry on client side can safely overwrite the same file as OID
  53. // is seen as unique to every file.
  54. if ioerr != nil {
  55. _ = os.Remove(fpath)
  56. }
  57. }()
  58. ioerr = os.MkdirAll(filepath.Dir(fpath), os.ModePerm)
  59. if ioerr != nil {
  60. return errors.Wrap(ioerr, "create directories")
  61. }
  62. w, ioerr := os.Create(fpath)
  63. if ioerr != nil {
  64. return errors.Wrap(ioerr, "create file")
  65. }
  66. defer w.Close()
  67. written, ioerr := io.Copy(w, rc)
  68. if ioerr != nil {
  69. return errors.Wrap(ioerr, "copy file")
  70. }
  71. object := &LFSObject{
  72. RepoID: repoID,
  73. OID: oid,
  74. Size: written,
  75. Storage: storage,
  76. }
  77. return db.DB.Create(object).Error
  78. }
  79. type ErrLFSObjectNotExist struct {
  80. args errutil.Args
  81. }
  82. func IsErrLFSObjectNotExist(err error) bool {
  83. _, ok := err.(ErrLFSObjectNotExist)
  84. return ok
  85. }
  86. func (err ErrLFSObjectNotExist) Error() string {
  87. return fmt.Sprintf("LFS object does not exist: %v", err.args)
  88. }
  89. func (ErrLFSObjectNotExist) NotFound() bool {
  90. return true
  91. }
  92. func (db *lfs) GetObjectByOID(repoID int64, oid lfsutil.OID) (*LFSObject, error) {
  93. object := new(LFSObject)
  94. err := db.Where("repo_id = ? AND oid = ?", repoID, oid).First(object).Error
  95. if err != nil {
  96. if gorm.IsRecordNotFoundError(err) {
  97. return nil, ErrLFSObjectNotExist{args: errutil.Args{"repoID": repoID, "oid": oid}}
  98. }
  99. return nil, err
  100. }
  101. return object, err
  102. }
  103. func (db *lfs) GetObjectsByOIDs(repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error) {
  104. if len(oids) == 0 {
  105. return []*LFSObject{}, nil
  106. }
  107. objects := make([]*LFSObject, 0, len(oids))
  108. err := db.Where("repo_id = ? AND oid IN (?)", repoID, oids).Find(&objects).Error
  109. if err != nil && err != gorm.ErrRecordNotFound {
  110. return nil, err
  111. }
  112. return objects, nil
  113. }