| @@ -3,6 +3,7 @@ package http | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "io" | "io" | ||||
| "mime" | |||||
| "mime/multipart" | "mime/multipart" | ||||
| "net/http" | "net/http" | ||||
| "net/url" | "net/url" | ||||
| @@ -11,7 +12,9 @@ import ( | |||||
| "github.com/gin-gonic/gin" | "github.com/gin-gonic/gin" | ||||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | "gitlink.org.cn/cloudream/common/pkgs/logger" | ||||
| "gitlink.org.cn/cloudream/common/utils/http2" | |||||
| "gitlink.org.cn/cloudream/common/utils/math2" | "gitlink.org.cn/cloudream/common/utils/math2" | ||||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||||
| "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader" | "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader" | ||||
| "gitlink.org.cn/cloudream/jcs-pub/client/internal/http/types" | "gitlink.org.cn/cloudream/jcs-pub/client/internal/http/types" | ||||
| cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1" | cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1" | ||||
| @@ -69,27 +72,53 @@ func (s *ObjectService) ListByIDs(ctx *gin.Context) { | |||||
| ctx.JSON(http.StatusOK, types.OK(cliapi.ObjectListByIDsResp{Objects: objs})) | ctx.JSON(http.StatusOK, types.OK(cliapi.ObjectListByIDsResp{Objects: objs})) | ||||
| } | } | ||||
| type ObjectUpload struct { | |||||
| Info cliapi.ObjectUploadInfo `form:"info" binding:"required"` | |||||
| Files []*multipart.FileHeader `form:"files"` | |||||
| } | |||||
| func (s *ObjectService) Upload(ctx *gin.Context) { | func (s *ObjectService) Upload(ctx *gin.Context) { | ||||
| log := logger.WithField("HTTP", "Object.Upload") | log := logger.WithField("HTTP", "Object.Upload") | ||||
| var req ObjectUpload | |||||
| if err := ctx.ShouldBind(&req); err != nil { | |||||
| log.Warnf("binding body: %s", err.Error()) | |||||
| ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument")) | |||||
| contType := ctx.GetHeader("Content-Type") | |||||
| mtype, params, err := mime.ParseMediaType(contType) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "parse content-type: %v", err)) | |||||
| return | |||||
| } | |||||
| if mtype != http2.ContentTypeMultiPart { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "content-type %v not supported", mtype)) | |||||
| return | |||||
| } | |||||
| boundary := params["boundary"] | |||||
| if boundary == "" { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "missing boundary in content-type")) | |||||
| return | |||||
| } | |||||
| mr := multipart.NewReader(ctx.Request.Body, boundary) | |||||
| p, err := mr.NextPart() | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "read next part: %v", err)) | |||||
| return | return | ||||
| } | } | ||||
| copyToPath := make([]clitypes.JPath, 0, len(req.Info.CopyToPath)) | |||||
| for _, p := range req.Info.CopyToPath { | |||||
| var info cliapi.ObjectUploadInfo | |||||
| err = serder.JSONToObjectStream(p, &info) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "parse upload info: %v", err)) | |||||
| return | |||||
| } | |||||
| if info.PackageID == 0 { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "missing packageID in upload info")) | |||||
| return | |||||
| } | |||||
| copyToPath := make([]clitypes.JPath, 0, len(info.CopyToPath)) | |||||
| for _, p := range info.CopyToPath { | |||||
| copyToPath = append(copyToPath, clitypes.PathFromJcsPathString(p)) | copyToPath = append(copyToPath, clitypes.PathFromJcsPathString(p)) | ||||
| } | } | ||||
| up, err := s.svc.Uploader.BeginUpdate(req.Info.PackageID, req.Info.Affinity, req.Info.CopyTo, copyToPath) | |||||
| up, err := s.svc.Uploader.BeginUpdate(info.PackageID, info.Affinity, info.CopyTo, copyToPath) | |||||
| if err != nil { | if err != nil { | ||||
| log.Warnf("begin update: %s", err.Error()) | log.Warnf("begin update: %s", err.Error()) | ||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("begin update: %v", err))) | ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("begin update: %v", err))) | ||||
| @@ -98,26 +127,30 @@ func (s *ObjectService) Upload(ctx *gin.Context) { | |||||
| defer up.Abort() | defer up.Abort() | ||||
| var pathes []string | var pathes []string | ||||
| for _, file := range req.Files { | |||||
| f, err := file.Open() | |||||
| for { | |||||
| file, err := mr.NextPart() | |||||
| if err == io.EOF { | |||||
| break | |||||
| } | |||||
| if err != nil { | if err != nil { | ||||
| log.Warnf("open file: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("open file %v: %v", file.Filename, err))) | |||||
| log.Warnf("read next part: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("read next part: %v", err))) | |||||
| return | return | ||||
| } | } | ||||
| path, err := url.PathUnescape(file.Filename) | |||||
| path, err := url.PathUnescape(file.FileName()) | |||||
| if err != nil { | if err != nil { | ||||
| log.Warnf("unescape filename: %s", err.Error()) | log.Warnf("unescape filename: %s", err.Error()) | ||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("unescape filename %v: %v", file.Filename, err))) | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("unescape filename %v: %v", file.FileName(), err))) | |||||
| return | return | ||||
| } | } | ||||
| path = filepath.ToSlash(path) | path = filepath.ToSlash(path) | ||||
| err = up.Upload(clitypes.PathFromJcsPathString(path), f) | |||||
| err = up.Upload(clitypes.PathFromJcsPathString(path), file) | |||||
| if err != nil { | if err != nil { | ||||
| log.Warnf("uploading file: %s", err.Error()) | log.Warnf("uploading file: %s", err.Error()) | ||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("uploading file %v: %v", file.Filename, err))) | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("uploading file %v: %v", file.FileName(), err))) | |||||
| return | return | ||||
| } | } | ||||
| pathes = append(pathes, path) | pathes = append(pathes, path) | ||||
| @@ -5,6 +5,7 @@ import ( | |||||
| "archive/zip" | "archive/zip" | ||||
| "fmt" | "fmt" | ||||
| "io" | "io" | ||||
| "mime" | |||||
| "mime/multipart" | "mime/multipart" | ||||
| "net/http" | "net/http" | ||||
| "net/url" | "net/url" | ||||
| @@ -13,6 +14,8 @@ import ( | |||||
| "github.com/gin-gonic/gin" | "github.com/gin-gonic/gin" | ||||
| "gitlink.org.cn/cloudream/common/consts/errorcode" | "gitlink.org.cn/cloudream/common/consts/errorcode" | ||||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | "gitlink.org.cn/cloudream/common/pkgs/logger" | ||||
| "gitlink.org.cn/cloudream/common/utils/http2" | |||||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||||
| "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader" | "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader" | ||||
| "gitlink.org.cn/cloudream/jcs-pub/client/internal/http/types" | "gitlink.org.cn/cloudream/jcs-pub/client/internal/http/types" | ||||
| cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1" | cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1" | ||||
| @@ -106,33 +109,54 @@ func (s *PackageService) Create(ctx *gin.Context) { | |||||
| })) | })) | ||||
| } | } | ||||
| type PackageCreateUpload struct { | |||||
| Info cliapi.PackageCreateUploadInfo `form:"info" binding:"required"` | |||||
| Files []*multipart.FileHeader `form:"files"` | |||||
| } | |||||
| func (s *PackageService) CreateLoad(ctx *gin.Context) { | func (s *PackageService) CreateLoad(ctx *gin.Context) { | ||||
| log := logger.WithField("HTTP", "Package.CreateUpload") | log := logger.WithField("HTTP", "Package.CreateUpload") | ||||
| var req PackageCreateUpload | |||||
| if err := ctx.ShouldBind(&req); err != nil { | |||||
| log.Warnf("binding body: %s", err.Error()) | |||||
| ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument")) | |||||
| contType := ctx.GetHeader("Content-Type") | |||||
| mtype, params, err := mime.ParseMediaType(contType) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "parse content-type: %v", err)) | |||||
| return | |||||
| } | |||||
| if mtype != http2.ContentTypeMultiPart { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "content-type %v not supported", mtype)) | |||||
| return | |||||
| } | |||||
| boundary := params["boundary"] | |||||
| if boundary == "" { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "missing boundary in content-type")) | |||||
| return | return | ||||
| } | } | ||||
| if len(req.Info.CopyTo) != len(req.Info.CopyToPath) { | |||||
| mr := multipart.NewReader(ctx.Request.Body, boundary) | |||||
| p, err := mr.NextPart() | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "read next part: %v", err)) | |||||
| return | |||||
| } | |||||
| var info cliapi.PackageCreateUploadInfo | |||||
| err = serder.JSONToObjectStream(p, &info) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "parse upload info: %v", err)) | |||||
| return | |||||
| } | |||||
| if len(info.CopyTo) != len(info.CopyToPath) { | |||||
| log.Warnf("CopyTo and CopyToPath count not match") | log.Warnf("CopyTo and CopyToPath count not match") | ||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.BadArgument, "CopyTo and CopyToPath count not match")) | ctx.JSON(http.StatusOK, types.Failed(ecode.BadArgument, "CopyTo and CopyToPath count not match")) | ||||
| return | return | ||||
| } | } | ||||
| copyToPath := make([]clitypes.JPath, 0, len(req.Info.CopyToPath)) | |||||
| for _, p := range req.Info.CopyToPath { | |||||
| copyToPath := make([]clitypes.JPath, 0, len(info.CopyToPath)) | |||||
| for _, p := range info.CopyToPath { | |||||
| copyToPath = append(copyToPath, clitypes.PathFromJcsPathString(p)) | copyToPath = append(copyToPath, clitypes.PathFromJcsPathString(p)) | ||||
| } | } | ||||
| up, err := s.svc.Uploader.BeginCreateUpload(req.Info.BucketID, req.Info.Name, req.Info.CopyTo, copyToPath) | |||||
| up, err := s.svc.Uploader.BeginCreateUpload(info.BucketID, info.Name, info.CopyTo, copyToPath) | |||||
| if err != nil { | if err != nil { | ||||
| log.Warnf("begin package create upload: %s", err.Error()) | log.Warnf("begin package create upload: %s", err.Error()) | ||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "%v", err)) | ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "%v", err)) | ||||
| @@ -141,26 +165,29 @@ func (s *PackageService) CreateLoad(ctx *gin.Context) { | |||||
| defer up.Abort() | defer up.Abort() | ||||
| var pathes []string | var pathes []string | ||||
| for _, file := range req.Files { | |||||
| f, err := file.Open() | |||||
| for { | |||||
| file, err := mr.NextPart() | |||||
| if err == io.EOF { | |||||
| break | |||||
| } | |||||
| if err != nil { | if err != nil { | ||||
| log.Warnf("open file: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("open file %v: %v", file.Filename, err))) | |||||
| log.Warnf("read next part: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("read next part: %v", err))) | |||||
| return | return | ||||
| } | } | ||||
| path, err := url.PathUnescape(file.Filename) | |||||
| path, err := url.PathUnescape(file.FileName()) | |||||
| if err != nil { | if err != nil { | ||||
| log.Warnf("unescape filename: %s", err.Error()) | log.Warnf("unescape filename: %s", err.Error()) | ||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("unescape filename %v: %v", file.Filename, err))) | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("unescape filename %v: %v", file.FileName(), err))) | |||||
| return | return | ||||
| } | } | ||||
| path = filepath.ToSlash(path) | path = filepath.ToSlash(path) | ||||
| err = up.Upload(clitypes.PathFromJcsPathString(path), f) | |||||
| err = up.Upload(clitypes.PathFromJcsPathString(path), file) | |||||
| if err != nil { | if err != nil { | ||||
| log.Warnf("uploading file: %s", err.Error()) | log.Warnf("uploading file: %s", err.Error()) | ||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("uploading file %v: %v", file.Filename, err))) | |||||
| ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("uploading file %v: %v", file.FileName(), err))) | |||||
| return | return | ||||
| } | } | ||||
| pathes = append(pathes, path) | pathes = append(pathes, path) | ||||
| @@ -82,7 +82,7 @@ func (c *ObjectService) ListByIDs(req ObjectListByIDs) (*ObjectListByIDsResp, er | |||||
| const ObjectUploadPath = "/object/upload" | const ObjectUploadPath = "/object/upload" | ||||
| type ObjectUpload struct { | type ObjectUpload struct { | ||||
| ObjectUploadInfo | |||||
| Info ObjectUploadInfo | |||||
| Files UploadObjectIterator `json:"-"` | Files UploadObjectIterator `json:"-"` | ||||
| } | } | ||||
| @@ -114,7 +114,7 @@ func (c *ObjectService) Upload(req ObjectUpload) (*ObjectUploadResp, error) { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| infoJSON, err := serder.ObjectToJSON(req) | |||||
| infoJSON, err := serder.ObjectToJSON(req.Info) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, fmt.Errorf("upload info to json: %w", err) | return nil, fmt.Errorf("upload info to json: %w", err) | ||||
| } | } | ||||
| @@ -32,7 +32,7 @@ func Test_PackageGet(t *testing.T) { | |||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| _, err = cli.Object().Upload(ObjectUpload{ | _, err = cli.Object().Upload(ObjectUpload{ | ||||
| ObjectUploadInfo: ObjectUploadInfo{ | |||||
| Info: ObjectUploadInfo{ | |||||
| PackageID: createResp.Package.PackageID, | PackageID: createResp.Package.PackageID, | ||||
| }, | }, | ||||
| Files: iterator.Array( | Files: iterator.Array( | ||||
| @@ -84,7 +84,7 @@ func Test_Object(t *testing.T) { | |||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| _, err = cli.Object().Upload(ObjectUpload{ | _, err = cli.Object().Upload(ObjectUpload{ | ||||
| ObjectUploadInfo: ObjectUploadInfo{ | |||||
| Info: ObjectUploadInfo{ | |||||
| PackageID: createResp.Package.PackageID, | PackageID: createResp.Package.PackageID, | ||||
| Affinity: stgAff, | Affinity: stgAff, | ||||
| }, | }, | ||||
| @@ -153,7 +153,7 @@ func Test_Storage(t *testing.T) { | |||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| _, err = cli.Object().Upload(ObjectUpload{ | _, err = cli.Object().Upload(ObjectUpload{ | ||||
| ObjectUploadInfo: ObjectUploadInfo{ | |||||
| Info: ObjectUploadInfo{ | |||||
| PackageID: createResp.Package.PackageID, | PackageID: createResp.Package.PackageID, | ||||
| }, | }, | ||||
| Files: iterator.Array( | Files: iterator.Array( | ||||
| @@ -250,7 +250,7 @@ func Test_Sign(t *testing.T) { | |||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| _, err = cli.Object().Upload(ObjectUpload{ | _, err = cli.Object().Upload(ObjectUpload{ | ||||
| ObjectUploadInfo: ObjectUploadInfo{ | |||||
| Info: ObjectUploadInfo{ | |||||
| PackageID: createResp.Package.PackageID, | PackageID: createResp.Package.PackageID, | ||||
| }, | }, | ||||
| Files: iterator.Array( | Files: iterator.Array( | ||||
| @@ -91,7 +91,7 @@ func puto(c *cobra.Command, ctx *cmd.CommandContext, opt option, args []string) | |||||
| startTime := time.Now() | startTime := time.Now() | ||||
| _, err = ctx.Client.Object().Upload(cliapi.ObjectUpload{ | _, err = ctx.Client.Object().Upload(cliapi.ObjectUpload{ | ||||
| ObjectUploadInfo: cliapi.ObjectUploadInfo{ | |||||
| Info: cliapi.ObjectUploadInfo{ | |||||
| PackageID: pkgID, | PackageID: pkgID, | ||||
| }, | }, | ||||
| Files: iterator.Array(&cliapi.UploadingObject{ | Files: iterator.Array(&cliapi.UploadingObject{ | ||||
| @@ -130,7 +130,7 @@ func putp(c *cobra.Command, ctx *cmd.CommandContext, opt option, args []string) | |||||
| startTime := time.Now() | startTime := time.Now() | ||||
| _, err = ctx.Client.Object().Upload(cliapi.ObjectUpload{ | _, err = ctx.Client.Object().Upload(cliapi.ObjectUpload{ | ||||
| ObjectUploadInfo: cliapi.ObjectUploadInfo{ | |||||
| Info: cliapi.ObjectUploadInfo{ | |||||
| PackageID: pkgID, | PackageID: pkgID, | ||||
| }, | }, | ||||
| Files: iterator.Array(&cliapi.UploadingObject{ | Files: iterator.Array(&cliapi.UploadingObject{ | ||||
| @@ -154,7 +154,7 @@ func putp(c *cobra.Command, ctx *cmd.CommandContext, opt option, args []string) | |||||
| startTime := time.Now() | startTime := time.Now() | ||||
| _, err = ctx.Client.Object().Upload(cliapi.ObjectUpload{ | _, err = ctx.Client.Object().Upload(cliapi.ObjectUpload{ | ||||
| ObjectUploadInfo: cliapi.ObjectUploadInfo{ | |||||
| Info: cliapi.ObjectUploadInfo{ | |||||
| PackageID: pkgID, | PackageID: pkgID, | ||||
| }, | }, | ||||
| Files: iter, | Files: iter, | ||||