aboutsummaryrefslogtreecommitdiff
path: root/internal/route/lfs/batch.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/route/lfs/batch.go')
-rw-r--r--internal/route/lfs/batch.go182
1 files changed, 182 insertions, 0 deletions
diff --git a/internal/route/lfs/batch.go b/internal/route/lfs/batch.go
new file mode 100644
index 00000000..ae53f5d3
--- /dev/null
+++ b/internal/route/lfs/batch.go
@@ -0,0 +1,182 @@
+// Copyright 2020 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package lfs
+
+import (
+ "fmt"
+ "net/http"
+
+ jsoniter "github.com/json-iterator/go"
+ log "unknwon.dev/clog/v2"
+
+ "gogs.io/gogs/internal/conf"
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+ "gogs.io/gogs/internal/lfsutil"
+ "gogs.io/gogs/internal/strutil"
+)
+
+// POST /{owner}/{repo}.git/info/lfs/object/batch
+func serveBatch(c *context.Context, owner *db.User, repo *db.Repository) {
+ var request batchRequest
+ defer c.Req.Request.Body.Close()
+ err := jsoniter.NewDecoder(c.Req.Request.Body).Decode(&request)
+ if err != nil {
+ responseJSON(c.Resp, http.StatusBadRequest, responseError{
+ Message: strutil.ToUpperFirst(err.Error()),
+ })
+ return
+ }
+
+ // NOTE: We only support basic transfer as of now.
+ transfer := transferBasic
+ // Example: https://try.gogs.io/gogs/gogs.git/info/lfs/object/basic
+ baseHref := fmt.Sprintf("%s%s/%s.git/info/lfs/objects/basic", conf.Server.ExternalURL, owner.Name, repo.Name)
+
+ objects := make([]batchObject, 0, len(request.Objects))
+ switch request.Operation {
+ case basicOperationUpload:
+ for _, obj := range request.Objects {
+ var actions batchActions
+ if lfsutil.ValidOID(obj.Oid) {
+ actions = batchActions{
+ Upload: &batchAction{
+ Href: fmt.Sprintf("%s/%s", baseHref, obj.Oid),
+ },
+ Verify: &batchAction{
+ Href: fmt.Sprintf("%s/verify", baseHref),
+ },
+ }
+ } else {
+ actions = batchActions{
+ Error: &batchError{
+ Code: http.StatusUnprocessableEntity,
+ Message: "Object has invalid oid",
+ },
+ }
+ }
+
+ objects = append(objects, batchObject{
+ Oid: obj.Oid,
+ Size: obj.Size,
+ Actions: actions,
+ })
+ }
+
+ case basicOperationDownload:
+ oids := make([]lfsutil.OID, 0, len(request.Objects))
+ for _, obj := range request.Objects {
+ oids = append(oids, obj.Oid)
+ }
+ stored, err := db.LFS.GetObjectsByOIDs(repo.ID, oids...)
+ if err != nil {
+ internalServerError(c.Resp)
+ log.Error("Failed to get objects [repo_id: %d, oids: %v]: %v", repo.ID, oids, err)
+ return
+ }
+ storedSet := make(map[lfsutil.OID]*db.LFSObject, len(stored))
+ for _, obj := range stored {
+ storedSet[obj.OID] = obj
+ }
+
+ for _, obj := range request.Objects {
+ var actions batchActions
+ if stored := storedSet[obj.Oid]; stored != nil {
+ if stored.Size != obj.Size {
+ actions.Error = &batchError{
+ Code: http.StatusUnprocessableEntity,
+ Message: "Object size mismatch",
+ }
+ } else {
+ actions.Download = &batchAction{
+ Href: fmt.Sprintf("%s/%s", baseHref, obj.Oid),
+ }
+ }
+ } else {
+ actions.Error = &batchError{
+ Code: http.StatusNotFound,
+ Message: "Object does not exist",
+ }
+ }
+
+ objects = append(objects, batchObject{
+ Oid: obj.Oid,
+ Size: obj.Size,
+ Actions: actions,
+ })
+ }
+
+ default:
+ responseJSON(c.Resp, http.StatusBadRequest, responseError{
+ Message: "Operation not recognized",
+ })
+ return
+ }
+
+ responseJSON(c.Resp, http.StatusOK, batchResponse{
+ Transfer: transfer,
+ Objects: objects,
+ })
+}
+
+// batchRequest defines the request payload for the batch endpoint.
+type batchRequest struct {
+ Operation string `json:"operation"`
+ Objects []struct {
+ Oid lfsutil.OID `json:"oid"`
+ Size int64 `json:"size"`
+ } `json:"objects"`
+}
+
+type batchError struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+}
+
+type batchAction struct {
+ Href string `json:"href"`
+}
+
+type batchActions struct {
+ Download *batchAction `json:"download,omitempty"`
+ Upload *batchAction `json:"upload,omitempty"`
+ Verify *batchAction `json:"verify,omitempty"`
+ Error *batchError `json:"error,omitempty"`
+}
+
+type batchObject struct {
+ Oid lfsutil.OID `json:"oid"`
+ Size int64 `json:"size"`
+ Actions batchActions `json:"actions"`
+}
+
+// batchResponse defines the response payload for the batch endpoint.
+type batchResponse struct {
+ Transfer string `json:"transfer"`
+ Objects []batchObject `json:"objects"`
+}
+
+type responseError struct {
+ Message string `json:"message"`
+}
+
+const contentType = "application/vnd.git-lfs+json"
+
+func responseJSON(w http.ResponseWriter, status int, v interface{}) {
+ w.Header().Set("Content-Type", contentType)
+
+ err := jsoniter.NewEncoder(w).Encode(v)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.WriteHeader(status)
+}
+
+func internalServerError(w http.ResponseWriter) {
+ responseJSON(w, http.StatusInternalServerError, responseError{
+ Message: "Internal server error",
+ })
+}