diff options
Diffstat (limited to 'internal/route/lfs/batch.go')
-rw-r--r-- | internal/route/lfs/batch.go | 182 |
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", + }) +} |