package http import ( "archive/tar" "archive/zip" "fmt" "io" "mime" "mime/multipart" "net/http" "net/url" "path/filepath" "github.com/gin-gonic/gin" "gitlink.org.cn/cloudream/common/consts/errorcode" "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/http/types" cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1" clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types" "gitlink.org.cn/cloudream/jcs-pub/common/ecode" "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/iterator" "gorm.io/gorm" ) // PackageService 包服务,负责处理包相关的HTTP请求。 type PackageService struct { *Server } // Package 返回PackageService的实例。 func (s *Server) Package() *PackageService { return &PackageService{ Server: s, } } func (s *PackageService) Get(ctx *gin.Context) { log := logger.WithField("HTTP", "Package.Get") var req cliapi.PackageGet if err := ctx.ShouldBindQuery(&req); err != nil { log.Warnf("binding body: %s", err.Error()) ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument")) return } pkg, err := s.svc.DB.Package().GetByID(s.svc.DB.DefCtx(), req.PackageID) if err != nil { log.Warnf("getting package: %s", err.Error()) if err == gorm.ErrRecordNotFound { ctx.JSON(http.StatusOK, types.Failed(ecode.DataNotFound, "package not found")) return } ctx.JSON(http.StatusOK, types.FailedError(err)) return } ctx.JSON(http.StatusOK, types.OK(cliapi.PackageGetResp{Package: pkg})) } func (s *PackageService) GetByFullName(ctx *gin.Context) { log := logger.WithField("HTTP", "Package.GetByFullName") var req cliapi.PackageGetByFullName if err := ctx.ShouldBindQuery(&req); err != nil { log.Warnf("binding query: %s", err.Error()) ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument")) return } pkg, err := s.svc.DB.Package().GetByFullName(s.svc.DB.DefCtx(), req.BucketName, req.PackageName) if err != nil { log.Warnf("getting package by name: %s", err.Error()) if err == gorm.ErrRecordNotFound { ctx.JSON(http.StatusOK, types.Failed(ecode.DataNotFound, "package not found")) return } ctx.JSON(http.StatusOK, types.FailedError(err)) return } ctx.JSON(http.StatusOK, types.OK(cliapi.PackageGetByFullNameResp{Package: pkg})) } // Create 处理创建新包的HTTP请求。 func (s *PackageService) Create(ctx *gin.Context) { log := logger.WithField("HTTP", "Package.Create") var req cliapi.PackageCreate if err := ctx.ShouldBindJSON(&req); err != nil { log.Warnf("binding body: %s", err.Error()) ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument")) return } pkg, err := s.svc.PackageSvc().Create(req.BucketID, req.Name) if err != nil { log.Warnf("creating package: %s", err.Error()) ctx.JSON(http.StatusOK, types.FailedError(err)) return } ctx.JSON(http.StatusOK, types.OK(cliapi.PackageCreateResp{ Package: pkg, })) } func (s *PackageService) CreateLoad(ctx *gin.Context) { log := logger.WithField("HTTP", "Package.CreateUpload") 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 } 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") ctx.JSON(http.StatusOK, types.Failed(ecode.BadArgument, "CopyTo and CopyToPath count not match")) return } copyToPath := make([]clitypes.JPath, 0, len(info.CopyToPath)) for _, p := range info.CopyToPath { copyToPath = append(copyToPath, clitypes.PathFromJcsPathString(p)) } up, err := s.svc.Uploader.BeginCreateUpload(info.BucketID, info.Name, info.CopyTo, copyToPath) if err != nil { log.Warnf("begin package create upload: %s", err.Error()) ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "%v", err)) return } defer up.Abort() var pathes []string for { file, err := mr.NextPart() if err == io.EOF { break } if err != nil { log.Warnf("read next part: %s", err.Error()) ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("read next part: %v", err))) return } path, err := url.PathUnescape(file.FileName()) if err != nil { log.Warnf("unescape filename: %s", err.Error()) ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("unescape filename %v: %v", file.FileName(), err))) return } path = filepath.ToSlash(path) err = up.Upload(clitypes.PathFromJcsPathString(path), file) if err != nil { log.Warnf("uploading file: %s", err.Error()) ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("uploading file %v: %v", file.FileName(), err))) return } pathes = append(pathes, path) } ret, err := up.Commit() if err != nil { log.Warnf("commit create upload: %s", err.Error()) ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, fmt.Sprintf("commit create upload: %v", err))) return } objs := make([]clitypes.Object, len(pathes)) for i := range pathes { objs[i] = ret.Objects[pathes[i]] } ctx.JSON(http.StatusOK, types.OK(cliapi.PackageCreateUploadResp{Package: ret.Package, Objects: objs})) } func (s *PackageService) Download(ctx *gin.Context) { log := logger.WithField("HTTP", "Package.Download") var req cliapi.PackageDownload if err := ctx.ShouldBindQuery(&req); err != nil { log.Warnf("binding query: %s", err.Error()) ctx.JSON(http.StatusBadRequest, types.Failed(errorcode.BadArgument, "missing argument or invalid argument")) return } pkg, iter, err := s.svc.Downloader.DownloadPackage(req.PackageID, req.Prefix) if err != nil { log.Warnf("downloading package: %s", err.Error()) ctx.JSON(http.StatusOK, types.Failed(errorcode.OperationFailed, err.Error())) return } defer iter.Close() if req.Zip { s.downloadZip(ctx, req, pkg, iter) } else { s.downloadTar(ctx, req, pkg, iter) } } func (s *PackageService) downloadZip(ctx *gin.Context, req cliapi.PackageDownload, pkg clitypes.Package, iter downloader.DownloadIterator) { log := logger.WithField("HTTP", "Package.Download") ctx.Header("Content-Disposition", "attachment; filename="+url.PathEscape(pkg.Name)+".zip") ctx.Header("Content-Type", "application/zip") ctx.Header("Content-Transfer-Encoding", "binary") zipFile := zip.NewWriter(ctx.Writer) defer zipFile.Close() for { item, err := iter.MoveNext() if err == iterator.ErrNoMoreItem { return } if err != nil { log.Warnf("iterating next object: %v", err) return } filePath := item.Object.Path if req.Prefix != "" && req.NewPrefix != nil { filePath = *req.NewPrefix + filePath[len(req.Prefix):] } zf, err := zipFile.Create(filePath) if err != nil { log.Warnf("creating zip file: %v", err) item.File.Close() return } _, err = io.Copy(zf, item.File) if err != nil { log.Warnf("copying file to zip: %v", err) item.File.Close() return } item.File.Close() } } func (s *PackageService) downloadTar(ctx *gin.Context, req cliapi.PackageDownload, pkg clitypes.Package, iter downloader.DownloadIterator) { log := logger.WithField("HTTP", "Package.Download") ctx.Header("Content-Disposition", "attachment; filename="+url.PathEscape(pkg.Name)+".tar") ctx.Header("Content-Type", "application/x-tar") ctx.Header("Content-Transfer-Encoding", "binary") tarFile := tar.NewWriter(ctx.Writer) defer tarFile.Close() for { item, err := iter.MoveNext() if err == iterator.ErrNoMoreItem { return } if err != nil { log.Warnf("iterating next object: %v", err) return } filePath := item.Object.Path if req.Prefix != "" && req.NewPrefix != nil { filePath = *req.NewPrefix + filePath[len(req.Prefix):] } err = tarFile.WriteHeader(&tar.Header{ Typeflag: tar.TypeReg, Name: filePath, Size: item.Object.Size, ModTime: item.Object.CreateTime, }) if err != nil { log.Warnf("creating tar header: %v", err) item.File.Close() return } _, err = io.Copy(tarFile, item.File) if err != nil { log.Warnf("copying file to tar: %v", err) item.File.Close() return } item.File.Close() } } func (s *PackageService) Delete(ctx *gin.Context) { log := logger.WithField("HTTP", "Package.Delete") var req cliapi.PackageDelete if err := ctx.ShouldBindJSON(&req); err != nil { log.Warnf("binding body: %s", err.Error()) ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument")) return } err := s.svc.PackageSvc().DeletePackage(req.PackageID) if err != nil { log.Warnf("deleting package: %s", err.Error()) ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "delete package failed")) return } ctx.JSON(http.StatusOK, types.OK(nil)) } func (s *PackageService) Clone(ctx *gin.Context) { log := logger.WithField("HTTP", "Package.Clone") var req cliapi.PackageClone if err := ctx.ShouldBindJSON(&req); err != nil { log.Warnf("binding body: %s", err.Error()) ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument")) return } pkg, err := s.svc.PackageSvc().Clone(req.PackageID, req.BucketID, req.Name) if err != nil { log.Warnf("cloning package: %s", err.Error()) ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "clone package failed")) return } ctx.JSON(http.StatusOK, types.OK(cliapi.PackageCloneResp{ Package: pkg, })) } func (s *PackageService) ListBucketPackages(ctx *gin.Context) { log := logger.WithField("HTTP", "Package.ListBucketPackages") var req cliapi.PackageListBucketPackages if err := ctx.ShouldBindQuery(&req); err != nil { log.Warnf("binding query: %s", err.Error()) ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument")) return } pkgs, err := s.svc.PackageSvc().GetBucketPackages(req.BucketID) if err != nil { log.Warnf("getting bucket packages: %s", err.Error()) ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "get bucket packages failed")) return } ctx.JSON(http.StatusOK, types.OK(cliapi.PackageListBucketPackagesResp{ Packages: pkgs, })) }