|
- 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/db"
- "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"
- "gitlink.org.cn/cloudream/jcs-pub/common/ecode"
- "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/iterator"
- "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/publock/reqbuilder"
- jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types"
- "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([]jcstypes.JPath, 0, len(info.CopyToPath))
- for _, p := range info.CopyToPath {
- copyToPath = append(copyToPath, jcstypes.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(jcstypes.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([]jcstypes.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 jcstypes.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 jcstypes.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,
- }))
- }
-
- func (s *PackageService) Pin(ctx *gin.Context) {
- log := logger.WithField("HTTP", "Package.Pin")
-
- var req cliapi.PackagePin
- 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
- }
-
- lock, err := reqbuilder.NewBuilder().Package().Pin(req.PackageID).MutexLock(s.svc.PubLock)
- if err != nil {
- ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "lock package: %v", err))
- return
- }
- defer lock.Unlock()
-
- d := s.svc.DB
- err = d.DoTx(func(tx db.SQLContext) error {
- _, err := d.Package().GetByID(tx, req.PackageID)
- if err != nil {
- return err
- }
-
- return d.Package().SetPinned(tx, req.PackageID, req.Pin)
- })
- if err != nil {
- if err == gorm.ErrRecordNotFound {
- ctx.JSON(http.StatusOK, types.Failed(ecode.DataNotFound, "package not found"))
- } else {
- ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "%v", err))
- }
- return
- }
-
- ctx.JSON(http.StatusOK, types.OK(cliapi.PackagePinResp{}))
- }
|