package api import ( "fmt" "io" "mime" "net/http" "net/url" "strings" "time" "gitlink.org.cn/cloudream/common/consts/errorcode" "gitlink.org.cn/cloudream/common/pkgs/iterator" "gitlink.org.cn/cloudream/common/sdks" "gitlink.org.cn/cloudream/common/utils/http2" "gitlink.org.cn/cloudream/common/utils/serder" jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types" ) type ObjectService struct { *Client } func (c *Client) Object() *ObjectService { return &ObjectService{ Client: c, } } const ObjectListPathByPath = "/object/listByPath" type ObjectListByPath struct { PackageID jcstypes.PackageID `form:"packageID" binding:"required" url:"packageID" json:"packageID"` Path string `form:"path" url:"path" json:"path"` // 允许为空字符串 IsPrefix bool `form:"isPrefix" url:"isPrefix" json:"isPrefix"` NoRecursive bool `form:"noRecursive" url:"noRecursive" json:"noRecursive"` // 仅当isPrefix为true时有效,表示仅查询直接属于Prefix下的对象,对于更深的对象,返回它们的公共前缀 MaxKeys int `form:"maxKeys" url:"maxKeys" json:"maxKeys"` ContinuationToken string `form:"continuationToken" url:"continuationToken" json:"continuationToken"` // 用于分页,如果为空字符串,表示从头开始 } func (r *ObjectListByPath) MakeParam() *sdks.RequestParam { return sdks.MakeQueryParam(http.MethodGet, ObjectListPathByPath, r) } type ObjectListByPathResp struct { CommonPrefixes []string `json:"commonPrefixes"` // 仅在IsPrefix为true且NoRecursive为true时有效,包含更深层对象的shared prefix Objects []jcstypes.Object `json:"objects"` // 如果IsPrefix为true且NoRecursive为false,则返回所有匹配的对象,否则只返回直接属于Prefix下的对象 IsTruncated bool `json:"isTruncated"` // 是否还有更多对象 NextContinuationToken string `json:"nextContinuationToken"` // 用于分页,如果IsTruncated为true,则下次请求的ContinuationToken为该值 } func (r *ObjectListByPathResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) ListByPath(req ObjectListByPath) (*ObjectListByPathResp, error) { return JSONAPI(&c.cfg, c.httpCli, &req, &ObjectListByPathResp{}) } const ObjectListByIDsPath = "/object/listByIDs" type ObjectListByIDs struct { ObjectIDs []jcstypes.ObjectID `form:"objectIDs" binding:"required" url:"objectIDs"` } func (r *ObjectListByIDs) MakeParam() *sdks.RequestParam { return sdks.MakeQueryParam(http.MethodGet, ObjectListByIDsPath, r) } type ObjectListByIDsResp struct { Objects []*jcstypes.Object `json:"object"` // 与ObjectIDs一一对应,如果某个ID不存在,则对应位置为nil } func (r *ObjectListByIDsResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) ListByIDs(req ObjectListByIDs) (*ObjectListByIDsResp, error) { return JSONAPI(&c.cfg, c.httpCli, &req, &ObjectListByIDsResp{}) } const ObjectUploadPath = "/object/upload" type ObjectUpload struct { Info ObjectUploadInfo Files UploadObjectIterator `json:"-"` } type ObjectUploadInfo struct { PackageID jcstypes.PackageID `json:"packageID" binding:"required"` Affinity jcstypes.UserSpaceID `json:"affinity"` CopyTo []jcstypes.UserSpaceID `json:"copyTo"` CopyToPath []string `json:"copyToPath"` } type UploadingObject struct { Path string File io.ReadCloser } type UploadObjectIterator = iterator.Iterator[*UploadingObject] type ObjectUploadResp struct { Uploadeds []jcstypes.Object `json:"uploadeds"` } func (c *ObjectService) Upload(req ObjectUpload) (*ObjectUploadResp, error) { type uploadInfo struct { Info string `url:"info"` } url, err := url.JoinPath(c.cfg.EndPoint, "v1", ObjectUploadPath) if err != nil { return nil, err } infoJSON, err := serder.ObjectToJSON(req.Info) if err != nil { return nil, fmt.Errorf("upload info to json: %w", err) } resp, err := PostMultiPart(&c.cfg, c.httpCli, url, uploadInfo{Info: string(infoJSON)}, iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) { return &http2.IterMultiPartFile{ FieldName: "files", FileName: src.Path, File: src.File, }, nil })) if err != nil { return nil, err } contType := resp.Header.Get("Content-Type") if strings.Contains(contType, http2.ContentTypeJSON) { var err error var codeResp response[ObjectUploadResp] if codeResp, err = serder.JSONToObjectStreamEx[response[ObjectUploadResp]](resp.Body); err != nil { return nil, fmt.Errorf("parsing response: %w", err) } if codeResp.Code == errorcode.OK { return &codeResp.Data, nil } return nil, codeResp.ToError() } return nil, fmt.Errorf("unknow response content type: %s", contType) } const ObjectDownloadPath = "/object/download" type ObjectDownload struct { ObjectID jcstypes.ObjectID `form:"objectID" url:"objectID" binding:"required"` Offset int64 `form:"offset" url:"offset,omitempty"` Length *int64 `form:"length" url:"length,omitempty"` } func (r *ObjectDownload) MakeParam() *sdks.RequestParam { return sdks.MakeQueryParam(http.MethodGet, ObjectDownloadPath, r) } type DownloadingObject struct { Path string File io.ReadCloser } func (c *ObjectService) Download(req ObjectDownload) (*DownloadingObject, error) { u, err := url.JoinPath(c.cfg.EndPoint, "v1") if err != nil { return nil, err } httpReq, err := req.MakeParam().MakeRequest(u) if err != nil { return nil, err } resp, err := c.httpCli.Do(httpReq) if err != nil { return nil, err } contType := resp.Header.Get("Content-Type") if strings.Contains(contType, http2.ContentTypeJSON) { var codeResp response[any] if err := serder.JSONToObjectStream(resp.Body, &codeResp); err != nil { return nil, fmt.Errorf("parsing response: %w", err) } return nil, codeResp.ToError() } _, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition")) if err != nil { return nil, fmt.Errorf("parsing content disposition: %w", err) } return &DownloadingObject{ Path: params["filename"], File: resp.Body, }, nil } const ObjectDownloadByPathPath = "/object/downloadByPath" type ObjectDownloadByPath struct { PackageID jcstypes.PackageID `form:"packageID" url:"packageID" binding:"required"` Path string `form:"path" url:"path" binding:"required"` Offset int64 `form:"offset" url:"offset,omitempty"` Length *int64 `form:"length" url:"length,omitempty"` } func (r *ObjectDownloadByPath) MakeParam() *sdks.RequestParam { return sdks.MakeQueryParam(http.MethodGet, ObjectDownloadByPathPath, r) } func (c *ObjectService) DownloadByPath(req ObjectDownloadByPath) (*DownloadingObject, error) { u, err := url.JoinPath(c.cfg.EndPoint, "v1") if err != nil { return nil, err } httpReq, err := req.MakeParam().MakeRequest(u) if err != nil { return nil, err } resp, err := c.httpCli.Do(httpReq) if err != nil { return nil, err } contType := resp.Header.Get("Content-Type") if strings.Contains(contType, http2.ContentTypeJSON) { var codeResp response[any] if err := serder.JSONToObjectStream(resp.Body, &codeResp); err != nil { return nil, fmt.Errorf("parsing response: %w", err) } return nil, codeResp.ToError() } _, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition")) if err != nil { return nil, fmt.Errorf("parsing content disposition: %w", err) } return &DownloadingObject{ Path: params["filename"], File: resp.Body, }, nil } const ObjectUpdateInfoPath = "/object/updateInfo" type UpdatingObject struct { ObjectID jcstypes.ObjectID `json:"objectID" binding:"required"` UpdateTime time.Time `json:"updateTime" binding:"required"` } func (u *UpdatingObject) ApplyTo(obj *jcstypes.Object) { obj.UpdateTime = u.UpdateTime } type ObjectUpdateInfo struct { Updatings []UpdatingObject `json:"updatings" binding:"required"` } func (r *ObjectUpdateInfo) MakeParam() *sdks.RequestParam { return sdks.MakeJSONParam(http.MethodPost, ObjectUpdateInfoPath, r) } type ObjectUpdateInfoResp struct { Successes []jcstypes.ObjectID `json:"successes"` } func (r *ObjectUpdateInfoResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) UpdateInfo(req ObjectUpdateInfo) (*ObjectUpdateInfoResp, error) { return JSONAPI(&c.cfg, c.httpCli, &req, &ObjectUpdateInfoResp{}) } const ObjectUpdateInfoByPathPath = "/object/updateInfoByPath" type ObjectUpdateInfoByPath struct { PackageID jcstypes.PackageID `json:"packageID" binding:"required"` Path string `json:"path" binding:"required"` UpdateTime time.Time `json:"updateTime" binding:"required"` } func (r *ObjectUpdateInfoByPath) MakeParam() *sdks.RequestParam { return sdks.MakeJSONParam(http.MethodPost, ObjectUpdateInfoByPathPath, r) } type ObjectUpdateInfoByPathResp struct{} func (r *ObjectUpdateInfoByPathResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) UpdateInfoByPath(req ObjectUpdateInfoByPath) (*ObjectUpdateInfoByPathResp, error) { return JSONAPI(&c.cfg, c.httpCli, &req, &ObjectUpdateInfoByPathResp{}) } const ObjectMovePath = "/object/move" type MovingObject struct { ObjectID jcstypes.ObjectID `json:"objectID" binding:"required"` PackageID jcstypes.PackageID `json:"packageID" binding:"required"` Path string `json:"path" binding:"required"` } func (m *MovingObject) ApplyTo(obj *jcstypes.Object) { obj.PackageID = m.PackageID obj.Path = m.Path } type ObjectMove struct { Movings []MovingObject `json:"movings" binding:"required"` } func (r *ObjectMove) MakeParam() *sdks.RequestParam { return sdks.MakeJSONParam(http.MethodPost, ObjectMovePath, r) } type ObjectMoveResp struct { Successes []jcstypes.ObjectID `json:"successes"` } func (r *ObjectMoveResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) Move(req ObjectMove) (*ObjectMoveResp, error) { return JSONAPI(&c.cfg, c.httpCli, &req, &ObjectMoveResp{}) } const ObjectDeletePath = "/object/delete" type ObjectDelete struct { ObjectIDs []jcstypes.ObjectID `json:"objectIDs" binding:"required"` } func (r *ObjectDelete) MakeParam() *sdks.RequestParam { return sdks.MakeJSONParam(http.MethodPost, ObjectDeletePath, r) } type ObjectDeleteResp struct{} func (r *ObjectDeleteResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) Delete(req ObjectDelete) error { return JSONAPINoData(&c.cfg, c.httpCli, &req) } const ObjectDeleteByPathPath = "/object/deleteByPath" type ObjectDeleteByPath struct { PackageID jcstypes.PackageID `json:"packageID" binding:"required"` Path string `json:"path" binding:"required"` } func (r *ObjectDeleteByPath) MakeParam() *sdks.RequestParam { return sdks.MakeJSONParam(http.MethodPost, ObjectDeleteByPathPath, r) } type ObjectDeleteByPathResp struct{} func (r *ObjectDeleteByPathResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) DeleteByPath(req ObjectDeleteByPath) error { return JSONAPINoData(&c.cfg, c.httpCli, &req) } const ObjectClonePath = "/object/clone" type ObjectClone struct { Clonings []CloningObject `json:"clonings" binding:"required"` } func (r *ObjectClone) MakeParam() *sdks.RequestParam { return sdks.MakeJSONParam(http.MethodPost, ObjectClonePath, r) } type CloningObject struct { ObjectID jcstypes.ObjectID `json:"objectID" binding:"required"` NewPath string `json:"newPath" binding:"required"` NewPackageID jcstypes.PackageID `json:"newPackageID" binding:"required"` } type ObjectCloneResp struct { Objects []*jcstypes.Object `json:"objects"` } func (r *ObjectCloneResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) Clone(req ObjectClone) (*ObjectCloneResp, error) { return JSONAPI(&c.cfg, c.httpCli, &req, &ObjectCloneResp{}) } const ObjectGetPackageObjectsPath = "/object/getPackageObjects" type ObjectGetPackageObjects struct { PackageID jcstypes.PackageID `form:"packageID" url:"packageID" binding:"required"` } func (r *ObjectGetPackageObjects) MakeParam() *sdks.RequestParam { return sdks.MakeQueryParam(http.MethodGet, ObjectGetPackageObjectsPath, r) } type ObjectGetPackageObjectsResp struct { Objects []jcstypes.Object `json:"objects"` } func (r *ObjectGetPackageObjectsResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) GetPackageObjects(req ObjectGetPackageObjects) (*ObjectGetPackageObjectsResp, error) { return JSONAPI(&c.cfg, c.httpCli, &req, &ObjectGetPackageObjectsResp{}) } const ObjectNewMultipartUploadPath = "/object/newMultipartUpload" type ObjectNewMultipartUpload struct { PackageID jcstypes.PackageID `json:"packageID" binding:"required"` Path string `json:"path" binding:"required"` } func (r *ObjectNewMultipartUpload) MakeParam() *sdks.RequestParam { return sdks.MakeJSONParam(http.MethodPost, ObjectNewMultipartUploadPath, r) } type ObjectNewMultipartUploadResp struct { Object jcstypes.Object `json:"object"` } func (r *ObjectNewMultipartUploadResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) NewMultipartUpload(req ObjectNewMultipartUpload) (*ObjectNewMultipartUploadResp, error) { return JSONAPI(&c.cfg, c.httpCli, &req, &ObjectNewMultipartUploadResp{}) } const ObjectUploadPartPath = "/object/uploadPart" type ObjectUploadPart struct { ObjectUploadPartInfo File io.ReadCloser `json:"-"` } type ObjectUploadPartInfo struct { ObjectID jcstypes.ObjectID `json:"objectID" binding:"required"` Index int `json:"index"` } type ObjectUploadPartResp struct{} func (c *ObjectService) UploadPart(req ObjectUploadPart) (*ObjectUploadPartResp, error) { url, err := url.JoinPath(c.cfg.EndPoint, ObjectUploadPartPath) if err != nil { return nil, err } infoJSON, err := serder.ObjectToJSON(req) if err != nil { return nil, fmt.Errorf("upload info to json: %w", err) } resp, err := http2.PostMultiPart(url, http2.MultiPartRequestParam{ Form: map[string]string{"info": string(infoJSON)}, Files: iterator.Array(&http2.IterMultiPartFile{ FieldName: "file", File: req.File, }), }) if err != nil { return nil, err } contType := resp.Header.Get("Content-Type") if strings.Contains(contType, http2.ContentTypeJSON) { var err error var codeResp response[ObjectUploadPartResp] if codeResp, err = serder.JSONToObjectStreamEx[response[ObjectUploadPartResp]](resp.Body); err != nil { return nil, fmt.Errorf("parsing response: %w", err) } if codeResp.Code == errorcode.OK { return &codeResp.Data, nil } return nil, codeResp.ToError() } return nil, fmt.Errorf("unknow response content type: %s", contType) } const ObjectCompleteMultipartUploadPath = "/object/completeMultipartUpload" type ObjectCompleteMultipartUpload struct { ObjectID jcstypes.ObjectID `json:"objectID" binding:"required"` Indexes []int `json:"indexes" binding:"required"` } func (r *ObjectCompleteMultipartUpload) MakeParam() *sdks.RequestParam { return sdks.MakeJSONParam(http.MethodPost, ObjectCompleteMultipartUploadPath, r) } type ObjectCompleteMultipartUploadResp struct { Object jcstypes.Object `json:"object"` } func (r *ObjectCompleteMultipartUploadResp) ParseResponse(resp *http.Response) error { return sdks.ParseCodeDataJSONResponse(resp, r) } func (c *ObjectService) CompleteMultipartUpload(req ObjectCompleteMultipartUpload) (*ObjectCompleteMultipartUploadResp, error) { return JSONAPI(&c.cfg, c.httpCli, &req, &ObjectCompleteMultipartUploadResp{}) }