package http import ( "archive/tar" "archive/zip" "fmt" "io" "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/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" ) // 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.PackageSvc().Get(req.PackageID) if err != nil { log.Warnf("getting package: %s", err.Error()) 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.PackageSvc().GetByFullName(req.BucketName, req.PackageName) if err != nil { log.Warnf("getting package by name: %s", err.Error()) 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, })) } type PackageCreateUpload struct { Info cliapi.PackageCreateUploadInfo `form:"info" binding:"required"` Files []*multipart.FileHeader `form:"files"` } func (s *PackageService) CreateLoad(ctx *gin.Context) { 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")) return } if len(req.Info.CopyTo) != len(req.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(req.Info.CopyToPath)) for _, p := range req.Info.CopyToPath { copyToPath = append(copyToPath, clitypes.PathFromJcsPathString(p)) } up, err := s.svc.Uploader.BeginCreateUpload(req.Info.BucketID, req.Info.Name, req.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 := range req.Files { f, err := file.Open() 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))) 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), f) 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, })) }