batch.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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 lfs
  5. import (
  6. "fmt"
  7. "net/http"
  8. jsoniter "github.com/json-iterator/go"
  9. "gopkg.in/macaron.v1"
  10. log "unknwon.dev/clog/v2"
  11. "gogs.io/gogs/internal/conf"
  12. "gogs.io/gogs/internal/db"
  13. "gogs.io/gogs/internal/lfsutil"
  14. "gogs.io/gogs/internal/strutil"
  15. )
  16. // POST /{owner}/{repo}.git/info/lfs/object/batch
  17. func serveBatch(c *macaron.Context, owner *db.User, repo *db.Repository) {
  18. var request batchRequest
  19. defer c.Req.Request.Body.Close()
  20. err := jsoniter.NewDecoder(c.Req.Request.Body).Decode(&request)
  21. if err != nil {
  22. responseJSON(c.Resp, http.StatusBadRequest, responseError{
  23. Message: strutil.ToUpperFirst(err.Error()),
  24. })
  25. return
  26. }
  27. // NOTE: We only support basic transfer as of now.
  28. transfer := transferBasic
  29. // Example: https://try.gogs.io/gogs/gogs.git/info/lfs/object/basic
  30. baseHref := fmt.Sprintf("%s%s/%s.git/info/lfs/objects/basic", conf.Server.ExternalURL, owner.Name, repo.Name)
  31. objects := make([]batchObject, 0, len(request.Objects))
  32. switch request.Operation {
  33. case basicOperationUpload:
  34. for _, obj := range request.Objects {
  35. var actions batchActions
  36. if lfsutil.ValidOID(obj.Oid) {
  37. actions = batchActions{
  38. Upload: &batchAction{
  39. Href: fmt.Sprintf("%s/%s", baseHref, obj.Oid),
  40. },
  41. Verify: &batchAction{
  42. Href: fmt.Sprintf("%s/verify", baseHref),
  43. },
  44. }
  45. } else {
  46. actions = batchActions{
  47. Error: &batchError{
  48. Code: http.StatusUnprocessableEntity,
  49. Message: "Object has invalid oid",
  50. },
  51. }
  52. }
  53. objects = append(objects, batchObject{
  54. Oid: obj.Oid,
  55. Size: obj.Size,
  56. Actions: actions,
  57. })
  58. }
  59. case basicOperationDownload:
  60. oids := make([]lfsutil.OID, 0, len(request.Objects))
  61. for _, obj := range request.Objects {
  62. oids = append(oids, obj.Oid)
  63. }
  64. stored, err := db.LFS.GetObjectsByOIDs(repo.ID, oids...)
  65. if err != nil {
  66. internalServerError(c.Resp)
  67. log.Error("Failed to get objects [repo_id: %d, oids: %v]: %v", repo.ID, oids, err)
  68. return
  69. }
  70. storedSet := make(map[lfsutil.OID]*db.LFSObject, len(stored))
  71. for _, obj := range stored {
  72. storedSet[obj.OID] = obj
  73. }
  74. for _, obj := range request.Objects {
  75. var actions batchActions
  76. if stored := storedSet[obj.Oid]; stored != nil {
  77. if stored.Size != obj.Size {
  78. actions.Error = &batchError{
  79. Code: http.StatusUnprocessableEntity,
  80. Message: "Object size mismatch",
  81. }
  82. } else {
  83. actions.Download = &batchAction{
  84. Href: fmt.Sprintf("%s/%s", baseHref, obj.Oid),
  85. }
  86. }
  87. } else {
  88. actions.Error = &batchError{
  89. Code: http.StatusNotFound,
  90. Message: "Object does not exist",
  91. }
  92. }
  93. objects = append(objects, batchObject{
  94. Oid: obj.Oid,
  95. Size: obj.Size,
  96. Actions: actions,
  97. })
  98. }
  99. default:
  100. responseJSON(c.Resp, http.StatusBadRequest, responseError{
  101. Message: "Operation not recognized",
  102. })
  103. return
  104. }
  105. responseJSON(c.Resp, http.StatusOK, batchResponse{
  106. Transfer: transfer,
  107. Objects: objects,
  108. })
  109. }
  110. // batchRequest defines the request payload for the batch endpoint.
  111. type batchRequest struct {
  112. Operation string `json:"operation"`
  113. Objects []struct {
  114. Oid lfsutil.OID `json:"oid"`
  115. Size int64 `json:"size"`
  116. } `json:"objects"`
  117. }
  118. type batchError struct {
  119. Code int `json:"code"`
  120. Message string `json:"message"`
  121. }
  122. type batchAction struct {
  123. Href string `json:"href"`
  124. }
  125. type batchActions struct {
  126. Download *batchAction `json:"download,omitempty"`
  127. Upload *batchAction `json:"upload,omitempty"`
  128. Verify *batchAction `json:"verify,omitempty"`
  129. Error *batchError `json:"error,omitempty"`
  130. }
  131. type batchObject struct {
  132. Oid lfsutil.OID `json:"oid"`
  133. Size int64 `json:"size"`
  134. Actions batchActions `json:"actions"`
  135. }
  136. // batchResponse defines the response payload for the batch endpoint.
  137. type batchResponse struct {
  138. Transfer string `json:"transfer"`
  139. Objects []batchObject `json:"objects"`
  140. }
  141. type responseError struct {
  142. Message string `json:"message"`
  143. }
  144. const contentType = "application/vnd.git-lfs+json"
  145. func responseJSON(w http.ResponseWriter, status int, v interface{}) {
  146. w.Header().Set("Content-Type", contentType)
  147. w.WriteHeader(status)
  148. err := jsoniter.NewEncoder(w).Encode(v)
  149. if err != nil {
  150. log.Error("Failed to encode JSON: %v", err)
  151. return
  152. }
  153. }