Browse Source

Merge pull request #124 from agin719/cos-dev-v5

add ci && download checkpoint
master
agin719 4 years ago
committed by GitHub
parent
commit
8be093dc1b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      auth.go
  2. 7
      batch.go
  3. 17
      bucket.go
  4. 9
      bucket_origin.go
  5. 180
      bucket_origin_test.go
  6. 121
      bucket_policy_test.go
  7. 263
      ci.go
  8. 7
      ci_doc.go
  9. 497
      ci_test.go
  10. 33
      error_test.go
  11. 89
      example/CI/ci_QRcode.go
  12. 15
      example/CI/ci_get.go
  13. 99
      example/CI/ci_watermark.go
  14. 28
      example/CI/compression/ci_compression.go
  15. 68
      example/CI/compression/guetzli.go
  16. 16
      example/CI/content_auditing/ci_audio_auditing_job.go
  17. 7
      example/CI/content_auditing/ci_image_recognition.go
  18. 2
      example/bucket/putPolicy.go
  19. 132
      example/object/ci_doc_process.go
  20. 32
      example/object/directory.go
  21. 138
      object.go
  22. 1
      object_part.go
  23. 264
      object_test.go

21
auth.go

@ -48,6 +48,12 @@ var needSignHeaders = map[string]bool{
"x-cos-object-type": true,
}
var ciParameters = map[string]bool{
"imagemogr2/": true,
"watermark/": true,
"imageview2/": true,
}
func safeURLEncode(s string) string {
s = encodeURIComponent(s)
s = strings.Replace(s, "!", "%21", -1)
@ -205,8 +211,10 @@ func genFormatParameters(parameters url.Values) (formatParameters string, signed
for key, values := range parameters {
key = strings.ToLower(key)
for _, value := range values {
ps.Add(key, value)
signedParameterList = append(signedParameterList, key)
if !isCIParameter(key) {
ps.Add(key, value)
signedParameterList = append(signedParameterList, key)
}
}
}
//formatParameters = strings.ToLower(ps.Encode())
@ -246,6 +254,15 @@ func calHMACDigest(key, msg, signMethod string) []byte {
return h.Sum(nil)
}
func isCIParameter(key string) bool {
for k, v := range ciParameters {
if strings.HasPrefix(key, k) && v {
return true
}
}
return false
}
func isSignHeader(key string) bool {
for k, v := range needSignHeaders {
if key == k && v {

7
batch.go

@ -113,13 +113,6 @@ type BatchCreateJobResult struct {
JobId string `xml:"JobId,omitempty"`
}
func processETag(opt *BatchCreateJobOptions) *BatchCreateJobOptions {
if opt != nil && opt.Manifest != nil && opt.Manifest.Location != nil {
opt.Manifest.Location.ETag = "<ETag>" + opt.Manifest.Location.ETag + "</ETag>"
}
return opt
}
func (s *BatchService) CreateJob(ctx context.Context, opt *BatchCreateJobOptions, headers *BatchRequestHeaders) (*BatchCreateJobResult, *Response, error) {
var res BatchCreateJobResult
sendOpt := sendOptions{

17
bucket.go

@ -50,7 +50,19 @@ func (s *BucketService) Get(ctx context.Context, opt *BucketGetOptions) (*Bucket
}
// BucketPutOptions is same to the ACLHeaderOptions
type BucketPutOptions ACLHeaderOptions
type BucketPutOptions struct {
XCosACL string `header:"x-cos-acl,omitempty" url:"-" xml:"-"`
XCosGrantRead string `header:"x-cos-grant-read,omitempty" url:"-" xml:"-"`
XCosGrantWrite string `header:"x-cos-grant-write,omitempty" url:"-" xml:"-"`
XCosGrantFullControl string `header:"x-cos-grant-full-control,omitempty" url:"-" xml:"-"`
XCosGrantReadACP string `header:"x-cos-grant-read-acp,omitempty" url:"-" xml:"-"`
XCosGrantWriteACP string `header:"x-cos-grant-write-acp,omitempty" url:"-" xml:"-"`
CreateBucketConfiguration *CreateBucketConfiguration `header:"-" url:"-" xml:"-"`
}
type CreateBucketConfiguration struct {
XMLName xml.Name `xml:"CreateBucketConfiguration"`
BucketAZConfig string `xml:"BucketAZConfig,omitempty"`
}
// Put Bucket请求可以在指定账号下创建一个Bucket。
//
@ -62,6 +74,9 @@ func (s *BucketService) Put(ctx context.Context, opt *BucketPutOptions) (*Respon
method: http.MethodPut,
optHeader: opt,
}
if opt != nil && opt.CreateBucketConfiguration != nil {
sendOpt.body = opt.CreateBucketConfiguration
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}

9
bucket_origin.go

@ -12,10 +12,11 @@ type BucketPutOriginOptions struct {
}
type BucketOriginRule struct {
OriginType string `xml:"OriginType"`
OriginCondition *BucketOriginCondition `xml:"OriginCondition"`
OriginParameter *BucketOriginParameter `xml:"OriginParameter"`
OriginInfo *BucketOriginInfo `xml:"OriginInfo"`
RulePriority int `xml:"RulePriority,omitempty"`
OriginType string `xml:"OriginType,omitempty"`
OriginCondition *BucketOriginCondition `xml:"OriginCondition,omitempty"`
OriginParameter *BucketOriginParameter `xml:"OriginParameter,omitempty"`
OriginInfo *BucketOriginInfo `xml:"OriginInfo,omitempty"`
}
type BucketOriginCondition struct {

180
bucket_origin_test.go

@ -0,0 +1,180 @@
package cos
import (
"context"
"encoding/xml"
"fmt"
"net/http"
"reflect"
"testing"
)
func TestBucketService_GetOrigin(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
vs := values{
"origin": "",
}
testFormValues(t, r, vs)
fmt.Fprint(w, `<OriginConfiguration>
<OriginRule>
<RulePriority>1</RulePriority>
<OriginType>Mirror</OriginType>
<OriginCondition>
<HTTPStatusCode>404</HTTPStatusCode>
<Prefix></Prefix>
</OriginCondition>
<OriginParameter>
<Protocol>HTTP</Protocol>
<FollowQueryString>true</FollowQueryString>
<HttpHeader>
<NewHttpHeaders>
<Header>
<Key>x-cos</Key>
<Value>exampleHeader</Value>
</Header>
</NewHttpHeaders>
<FollowHttpHeaders>
<Header>
<Key>exampleHeaderKey</Key>
</Header>
</FollowHttpHeaders>
</HttpHeader>
<FollowRedirection>true</FollowRedirection>
<HttpRedirectCode>302</HttpRedirectCode>
</OriginParameter>
<OriginInfo>
<HostInfo>
<HostName>examplebucket-1250000000.cos.ap-shanghai.myqcloud.com</HostName>
</HostInfo>
</OriginInfo>
</OriginRule>
</OriginConfiguration>
`)
})
res, _, err := client.Bucket.GetOrigin(context.Background())
if err != nil {
t.Fatalf("Bucket.GetOrigin returned error %v", err)
}
want := &BucketGetOriginResult{
XMLName: xml.Name{Local: "OriginConfiguration"},
Rule: []BucketOriginRule{
{
OriginType: "Mirror",
RulePriority: 1,
OriginCondition: &BucketOriginCondition{
HTTPStatusCode: "404",
},
OriginParameter: &BucketOriginParameter{
Protocol: "HTTP",
FollowQueryString: true,
HttpHeader: &BucketOriginHttpHeader{
FollowHttpHeaders: []OriginHttpHeader{
{
Key: "exampleHeaderKey",
},
},
NewHttpHeaders: []OriginHttpHeader{
{
Key: "x-cos",
Value: "exampleHeader",
},
},
},
FollowRedirection: true,
HttpRedirectCode: "302",
},
OriginInfo: &BucketOriginInfo{
HostInfo: "examplebucket-1250000000.cos.ap-shanghai.myqcloud.com",
},
},
},
}
if !reflect.DeepEqual(res, want) {
t.Errorf("Bucket.GetOrigin returned %+v, want %+v", res, want)
}
}
func TestBucketService_PutOrigin(t *testing.T) {
setup()
defer teardown()
opt := &BucketPutOriginOptions{
XMLName: xml.Name{Local: "OriginConfiguration"},
Rule: []BucketOriginRule{
{
OriginType: "Mirror",
RulePriority: 1,
OriginCondition: &BucketOriginCondition{
HTTPStatusCode: "404",
},
OriginParameter: &BucketOriginParameter{
Protocol: "HTTP",
FollowQueryString: true,
HttpHeader: &BucketOriginHttpHeader{
FollowHttpHeaders: []OriginHttpHeader{
{
Key: "exampleHeaderKey",
},
},
NewHttpHeaders: []OriginHttpHeader{
{
Key: "x-cos",
Value: "exampleHeader",
},
},
},
FollowRedirection: true,
HttpRedirectCode: "302",
},
OriginInfo: &BucketOriginInfo{
HostInfo: "examplebucket-1250000000.cos.ap-shanghai.myqcloud.com",
},
},
},
}
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PUT")
vs := values{
"origin": "",
}
testFormValues(t, r, vs)
body := new(BucketPutOriginOptions)
xml.NewDecoder(r.Body).Decode(body)
want := opt
want.XMLName = xml.Name{Local: "OriginConfiguration"}
if !reflect.DeepEqual(body, want) {
t.Errorf("Bucket.PutOrigin request\n body: %+v\n, want %+v\n", body, want)
}
})
_, err := client.Bucket.PutOrigin(context.Background(), opt)
if err != nil {
t.Fatalf("Bucket.PutOrigin returned error: %v", err)
}
}
func TestBucketService_DeleteOrigin(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodDelete)
vs := values{
"origin": "",
}
testFormValues(t, r, vs)
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Bucket.DeleteOrigin(context.Background())
if err != nil {
t.Fatalf("Bucket.DeleteOrigin returned error: %v", err)
}
}

121
bucket_policy_test.go

@ -0,0 +1,121 @@
package cos
import (
"context"
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
)
func TestBucketService_GetPolicy(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
vs := values{
"policy": "",
}
testFormValues(t, r, vs)
fmt.Fprint(w, `{
"Statement": [
{
"Principal": {
"qcs": [
"qcs::cam::uin/100000000001:uin/100000000011"
]
},
"Effect": "allow",
"Action": [
"name/cos:GetBucket"
],
"Resource": [
"qcs::cos:ap-guangzhou:uid/1250000000:examplebucket-1250000000/*"
]
}
],
"version": "2.0"
}`)
})
res, _, err := client.Bucket.GetPolicy(context.Background())
if err != nil {
t.Fatalf("Bucket.GetPolicy returned error %v", err)
}
want := &BucketGetPolicyResult{
Statement: []BucketStatement{
{
Principal: map[string][]string{
"qcs": []string{"qcs::cam::uin/100000000001:uin/100000000011"},
},
Effect: "allow",
Action: []string{"name/cos:GetBucket"},
Resource: []string{"qcs::cos:ap-guangzhou:uid/1250000000:examplebucket-1250000000/*"},
},
},
Version: "2.0",
}
if !reflect.DeepEqual(res, want) {
t.Errorf("Bucket.GetPolicy returned %+v, want %+v", res, want)
}
}
func TestBucketService_PutPolicy(t *testing.T) {
setup()
defer teardown()
opt := &BucketPutPolicyOptions{
Statement: []BucketStatement{
{
Principal: map[string][]string{
"qcs": []string{"qcs::cam::uin/100000000001:uin/100000000011"},
},
Effect: "allow",
Action: []string{"name/cos:GetBucket"},
Resource: []string{"qcs::cos:ap-guangzhou:uid/1250000000:examplebucket-1250000000/*"},
},
},
Version: "2.0",
}
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PUT")
vs := values{
"policy": "",
}
testFormValues(t, r, vs)
body := new(BucketPutPolicyOptions)
json.NewDecoder(r.Body).Decode(body)
want := opt
if !reflect.DeepEqual(body, want) {
t.Errorf("Bucket.PutPolicy request\n body: %+v\n, want %+v\n", body, want)
}
})
_, err := client.Bucket.PutPolicy(context.Background(), opt)
if err != nil {
t.Fatalf("Bucket.PutPolicy returned error: %v", err)
}
}
func TestBucketService_DeletePolicy(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodDelete)
vs := values{
"policy": "",
}
testFormValues(t, r, vs)
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Bucket.DeletePolicy(context.Background())
if err != nil {
t.Fatalf("Bucket.DeletePolicy returned error: %v", err)
}
}

263
ci.go

@ -1,14 +1,18 @@
package cos
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"hash/crc64"
"io"
"net/http"
"os"
"strconv"
)
type CIService service
@ -54,15 +58,24 @@ type PicImageInfo struct {
Orientation int `xml:"Orientation,omitempty"`
}
type PicProcessObject struct {
Key string `xml:"Key,omitempty"`
Location string `xml:"Location,omitempty"`
Format string `xml:"Format,omitempty"`
Width int `xml:"Width,omitempty"`
Height int `xml:"Height,omitempty"`
Size int `xml:"Size,omitempty"`
Quality int `xml:"Quality,omitempty"`
ETag string `xml:"ETag,omitempty"`
WatermarkStatus int `xml:"WatermarkStatus,omitempty"`
Key string `xml:"Key,omitempty"`
Location string `xml:"Location,omitempty"`
Format string `xml:"Format,omitempty"`
Width int `xml:"Width,omitempty"`
Height int `xml:"Height,omitempty"`
Size int `xml:"Size,omitempty"`
Quality int `xml:"Quality,omitempty"`
ETag string `xml:"ETag,omitempty"`
WatermarkStatus int `xml:"WatermarkStatus,omitempty"`
CodeStatus int `xml:"CodeStatus,omitempty"`
QRcodeInfo []QRcodeInfo `xml:"QRcodeInfo,omitempty"`
}
type QRcodeInfo struct {
CodeUrl string `xml:"CodeUrl,omitempty"`
CodeLocation *CodeLocation `xml:"CodeLocation,omitempty"`
}
type CodeLocation struct {
Point []string `xml:"Point,omitempty"`
}
type picOperationsHeader struct {
@ -110,9 +123,10 @@ type RecognitionInfo struct {
}
// 图片审核 https://cloud.tencent.com/document/product/460/37318
func (s *CIService) ImageRecognition(ctx context.Context, name string, opt *ImageRecognitionOptions) (*ImageRecognitionResult, *Response, error) {
if opt != nil && opt.CIProcess == "" {
opt.CIProcess = "sensitive-content-recognition"
func (s *CIService) ImageRecognition(ctx context.Context, name string, DetectType string) (*ImageRecognitionResult, *Response, error) {
opt := &ImageRecognitionOptions{
CIProcess: "sensitive-content-recognition",
DetectType: DetectType,
}
var res ImageRecognitionResult
sendOpt := sendOptions{
@ -153,6 +167,7 @@ type PutVideoAuditingJobResult struct {
} `xml:"JobsDetail,omitempty"`
}
// 视频审核-创建任务 https://cloud.tencent.com/document/product/460/46427
func (s *CIService) PutVideoAuditingJob(ctx context.Context, opt *PutVideoAuditingJobOptions) (*PutVideoAuditingJobResult, *Response, error) {
var res PutVideoAuditingJobResult
sendOpt := sendOptions{
@ -167,11 +182,11 @@ func (s *CIService) PutVideoAuditingJob(ctx context.Context, opt *PutVideoAuditi
}
type GetVideoAuditingJobResult struct {
XMLName xml.Name `xml:"Response"`
JobsDetail *VideoAuditingJobDetail `xml:",omitempty"`
NonExistJobIds string `xml:",omitempty"`
XMLName xml.Name `xml:"Response"`
JobsDetail *AuditingJobDetail `xml:",omitempty"`
NonExistJobIds string `xml:",omitempty"`
}
type VideoAuditingJobDetail struct {
type AuditingJobDetail struct {
Code string `xml:",omitempty"`
Message string `xml:",omitempty"`
JobId string `xml:",omitempty"`
@ -194,6 +209,7 @@ type GetVideoAuditingJobSnapshot struct {
AdsInfo *RecognitionInfo `xml:",omitempty"`
}
// 视频审核-查询任务 https://cloud.tencent.com/document/product/460/46926
func (s *CIService) GetVideoAuditingJob(ctx context.Context, jobid string) (*GetVideoAuditingJobResult, *Response, error) {
var res GetVideoAuditingJobResult
sendOpt := sendOptions{
@ -206,7 +222,48 @@ func (s *CIService) GetVideoAuditingJob(ctx context.Context, jobid string) (*Get
return &res, resp, err
}
// ci put https://cloud.tencent.com/document/product/460/18147
type PutAudioAuditingJobOptions struct {
XMLName xml.Name `xml:"Request"`
InputObject string `xml:"Input>Object"`
Conf *AudioAuditingJobConf `xml:"Conf"`
}
type AudioAuditingJobConf struct {
DetectType string `xml:",omitempty"`
Callback string `xml:",omitempty"`
}
type PutAudioAuditingJobResult PutVideoAuditingJobResult
type GetAudioAuditingJobResult GetVideoAuditingJobResult
// 音频审核-创建任务 https://cloud.tencent.com/document/product/460/53395
func (s *CIService) PutAudioAuditingJob(ctx context.Context, opt *PutAudioAuditingJobOptions) (*PutAudioAuditingJobResult, *Response, error) {
var res PutAudioAuditingJobResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/audio/auditing",
method: http.MethodPost,
body: opt,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// 音频审核-查询任务 https://cloud.tencent.com/document/product/460/53396
func (s *CIService) GetAudioAuditingJob(ctx context.Context, jobid string) (*GetAudioAuditingJobResult, *Response, error) {
var res GetAudioAuditingJobResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/audio/auditing/" + jobid,
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// 图片持久化处理-上传时处理 https://cloud.tencent.com/document/product/460/18147
// 盲水印-上传时添加 https://cloud.tencent.com/document/product/460/19017
// 二维码识别-上传时识别 https://cloud.tencent.com/document/product/460/37513
func (s *CIService) Put(ctx context.Context, name string, r io.Reader, uopt *ObjectPutOptions) (*ImageProcessResult, *Response, error) {
if r == nil {
return nil, nil, fmt.Errorf("reader is nil")
@ -257,3 +314,175 @@ func (s *CIService) PutFromFile(ctx context.Context, name string, filePath strin
return s.Put(ctx, name, fd, opt)
}
// 基本图片处理 https://cloud.tencent.com/document/product/460/36540
// 盲水印-下载时添加 https://cloud.tencent.com/document/product/460/19017
func (s *CIService) Get(ctx context.Context, name string, operation string, opt *ObjectGetOptions, id ...string) (*Response, error) {
var u string
if len(id) == 1 {
u = fmt.Sprintf("/%s?versionId=%s&%s", encodeURIComponent(name), id[0], operation)
} else if len(id) == 0 {
u = fmt.Sprintf("/%s?%s", encodeURIComponent(name), operation)
} else {
return nil, errors.New("wrong params")
}
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodGet,
optQuery: opt,
optHeader: opt,
disableCloseBody: true,
}
resp, err := s.client.send(ctx, &sendOpt)
if opt != nil && opt.Listener != nil {
if err == nil && resp != nil {
if totalBytes, e := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64); e == nil {
resp.Body = TeeReader(resp.Body, nil, totalBytes, opt.Listener)
}
}
}
return resp, err
}
func (s *CIService) GetToFile(ctx context.Context, name, localpath, operation string, opt *ObjectGetOptions, id ...string) (*Response, error) {
resp, err := s.Get(ctx, name, operation, opt, id...)
if err != nil {
return resp, err
}
defer resp.Body.Close()
// If file exist, overwrite it
fd, err := os.OpenFile(localpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
if err != nil {
return resp, err
}
_, err = io.Copy(fd, resp.Body)
fd.Close()
if err != nil {
return resp, err
}
return resp, nil
}
type GetQRcodeResult struct {
XMLName xml.Name `xml:"Response"`
CodeStatus int `xml:"CodeStatus,omitempty"`
QRcodeInfo *QRcodeInfo `xml:"QRcodeInfo,omitempty"`
ResultImage string `xml:"ResultImage,omitempty"`
}
// 二维码识别-下载时识别 https://cloud.tencent.com/document/product/436/54070
func (s *CIService) GetQRcode(ctx context.Context, name string, cover int, opt *ObjectGetOptions, id ...string) (*GetQRcodeResult, *Response, error) {
var u string
if len(id) == 1 {
u = fmt.Sprintf("/%s?versionId=%s&ci-process=QRcode&cover=%v", encodeURIComponent(name), id[0], cover)
} else if len(id) == 0 {
u = fmt.Sprintf("/%s?ci-process=QRcode&cover=%v", encodeURIComponent(name), cover)
} else {
return nil, nil, errors.New("wrong params")
}
var res GetQRcodeResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodGet,
optQuery: opt,
optHeader: opt,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
type GenerateQRcodeOptions struct {
QRcodeContent string `url:"qrcode-content,omitempty"`
Mode int `url:"mode,omitempty"`
Width int `url:"width,omitempty"`
}
type GenerateQRcodeResult struct {
XMLName xml.Name `xml:"Response"`
ResultImage string `xml:"ResultImage,omitempty"`
}
// 二维码生成 https://cloud.tencent.com/document/product/436/54071
func (s *CIService) GenerateQRcode(ctx context.Context, opt *GenerateQRcodeOptions) (*GenerateQRcodeResult, *Response, error) {
var res GenerateQRcodeResult
sendOpt := &sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?ci-process=qrcode-generate",
method: http.MethodGet,
optQuery: opt,
result: &res,
}
resp, err := s.client.send(ctx, sendOpt)
return &res, resp, err
}
func (s *CIService) GenerateQRcodeToFile(ctx context.Context, filePath string, opt *GenerateQRcodeOptions) (*GenerateQRcodeResult, *Response, error) {
res, resp, err := s.GenerateQRcode(ctx, opt)
if err != nil {
return res, resp, err
}
// If file exist, overwrite it
fd, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
if err != nil {
return res, resp, err
}
defer fd.Close()
bs, err := base64.StdEncoding.DecodeString(res.ResultImage)
if err != nil {
return res, resp, err
}
fb := bytes.NewReader(bs)
_, err = io.Copy(fd, fb)
return res, resp, err
}
// 开通 Guetzli 压缩 https://cloud.tencent.com/document/product/460/30112
func (s *CIService) PutGuetzli(ctx context.Context) (*Response, error) {
sendOpt := &sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/?guetzli",
method: http.MethodPut,
}
resp, err := s.client.send(ctx, sendOpt)
return resp, err
}
type GetGuetzliResult struct {
XMLName xml.Name `xml:"GuetzliStatus"`
GuetzliStatus string `xml:",chardata"`
}
// 查询 Guetzli 状态 https://cloud.tencent.com/document/product/460/30111
func (s *CIService) GetGuetzli(ctx context.Context) (*GetGuetzliResult, *Response, error) {
var res GetGuetzliResult
sendOpt := &sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/?guetzli",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, sendOpt)
return &res, resp, err
}
// 关闭 Guetzli 压缩 https://cloud.tencent.com/document/product/460/30113
func (s *CIService) DeleteGuetzli(ctx context.Context) (*Response, error) {
sendOpt := &sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/?guetzli",
method: http.MethodDelete,
}
resp, err := s.client.send(ctx, sendOpt)
return resp, err
}

7
ci_doc.go

@ -73,6 +73,7 @@ type CreateDocProcessJobsResult struct {
JobsDetail DocProcessJobDetail `xml:"JobsDetail,omitempty"`
}
// 创建文档预览任务 https://cloud.tencent.com/document/product/436/54056
func (s *CIService) CreateDocProcessJobs(ctx context.Context, opt *CreateDocProcessJobsOptions) (*CreateDocProcessJobsResult, *Response, error) {
var res CreateDocProcessJobsResult
sendOpt := sendOptions{
@ -92,6 +93,7 @@ type DescribeDocProcessJobResult struct {
NonExistJobIds string `xml:"NonExistJobIds,omitempty"`
}
// 查询文档预览任务 https://cloud.tencent.com/document/product/436/54095
func (s *CIService) DescribeDocProcessJob(ctx context.Context, jobid string) (*DescribeDocProcessJobResult, *Response, error) {
var res DescribeDocProcessJobResult
sendOpt := sendOptions{
@ -121,6 +123,7 @@ type DescribeDocProcessJobsResult struct {
NextToken string `xml:"NextToken,omitempty"`
}
// 拉取符合条件的文档预览任务 https://cloud.tencent.com/document/product/436/54096
func (s *CIService) DescribeDocProcessJobs(ctx context.Context, opt *DescribeDocProcessJobsOptions) (*DescribeDocProcessJobsResult, *Response, error) {
var res DescribeDocProcessJobsResult
sendOpt := sendOptions{
@ -169,6 +172,7 @@ type DocProcessQueueNotifyConfig struct {
Event string `xml:"Event,omitempty"`
}
// 查询文档预览队列 https://cloud.tencent.com/document/product/436/54055
func (s *CIService) DescribeDocProcessQueues(ctx context.Context, opt *DescribeDocProcessQueuesOptions) (*DescribeDocProcessQueuesResult, *Response, error) {
var res DescribeDocProcessQueuesResult
sendOpt := sendOptions{
@ -196,6 +200,7 @@ type UpdateDocProcessQueueResult struct {
Queue *DocProcessQueue `xml:"Queue"`
}
// 更新文档预览队列 https://cloud.tencent.com/document/product/436/54094
func (s *CIService) UpdateDocProcessQueue(ctx context.Context, opt *UpdateDocProcessQueueOptions) (*UpdateDocProcessQueueResult, *Response, error) {
var res UpdateDocProcessQueueResult
sendOpt := sendOptions{
@ -233,6 +238,7 @@ type DocProcessBucket struct {
AliasBucketId string `xml:"AliasBucketId,omitempty"`
}
// 查询文档预览开通状态 https://cloud.tencent.com/document/product/436/54057
func (s *CIService) DescribeDocProcessBuckets(ctx context.Context, opt *DescribeDocProcessBucketsOptions) (*DescribeDocProcessBucketsResult, *Response, error) {
var res DescribeDocProcessBucketsResult
sendOpt := sendOptions{
@ -259,6 +265,7 @@ type DocPreviewOptions struct {
Zoom int `url:"zoom,omitempty"`
}
// 同步请求接口 https://cloud.tencent.com/document/product/436/54058
func (s *CIService) DocPreview(ctx context.Context, name string, opt *DocPreviewOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,

497
ci_test.go

@ -0,0 +1,497 @@
package cos
import (
"bytes"
"context"
"crypto/rand"
"encoding/json"
"encoding/xml"
"fmt"
"hash/crc64"
"io/ioutil"
"net/http"
"os"
"reflect"
"strconv"
"testing"
"time"
)
func TestCIService_EncodePicOperations(t *testing.T) {
opt := &PicOperations{
IsPicInfo: 1,
Rules: []PicOperationsRules{
{
FileId: "example.jpg",
Rule: "imageView2/format/png",
},
},
}
res := EncodePicOperations(opt)
jsonStr := `{"is_pic_info":1,"rules":[{"fileid":"example.jpg","rule":"imageView2/format/png"}]}`
if jsonStr != res {
t.Fatalf("EncodePicOperations Failed, returned:%v, want:%v", res, jsonStr)
}
}
func TestCIService_ImageProcess(t *testing.T) {
setup()
defer teardown()
name := "test.jpg"
opt := &ImageProcessOptions{
IsPicInfo: 1,
Rules: []PicOperationsRules{
{
FileId: "format.jpg",
Rule: "imageView2/format/png",
},
},
}
mux.HandleFunc("/test.jpg", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")
vs := values{
"image_process": "",
}
testFormValues(t, r, vs)
header := r.Header.Get("Pic-Operations")
body := new(ImageProcessOptions)
err := json.Unmarshal([]byte(header), body)
want := opt
if err != nil {
t.Errorf("CI.ImageProcess Failed: %v", err)
}
if !reflect.DeepEqual(want, body) {
t.Errorf("CI.ImageProcess Failed, wanted:%v, body:%v", want, body)
}
fmt.Fprint(w, `<UploadResult>
<OriginalInfo>
<Key>test.jpg</Key>
<Location>example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg</Location>
<ETag>&quot;8894dbe5e3ebfaf761e39b9d619c28f3327b8d85&quot;</ETag>
<ImageInfo>
<Format>PNG</Format>
<Width>103</Width>
<Height>99</Height>
<Quality>100</Quality>
<Ave>0xa08162</Ave>
<Orientation>0</Orientation>
</ImageInfo>
</OriginalInfo>
<ProcessResults>
<Object>
<Key>format.jpg</Key>
<Location>example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg</Location>
<Format>PNG</Format>
<Width>103</Width>
<Height>99</Height>
<Size>21351</Size>
<Quality>100</Quality>
<ETag>&quot;8894dbe5e3ebfaf761e39b9d619c28f3327b8d85&quot;</ETag>
</Object>
</ProcessResults>
</UploadResult>`)
})
want := &ImageProcessResult{
XMLName: xml.Name{Local: "UploadResult"},
OriginalInfo: &PicOriginalInfo{
Key: "test.jpg",
Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg",
ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
ImageInfo: &PicImageInfo{
Format: "PNG",
Width: 103,
Height: 99,
Quality: 100,
Ave: "0xa08162",
Orientation: 0,
},
},
ProcessResults: &PicProcessObject{
Key: "format.jpg",
Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg",
Format: "PNG",
Width: 103,
Height: 99,
Size: 21351,
Quality: 100,
ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
},
}
res, _, err := client.CI.ImageProcess(context.Background(), name, opt)
if err != nil {
t.Fatalf("CI.ImageProcess returned error: %v", err)
}
if !reflect.DeepEqual(res, want) {
t.Errorf("CI.ImageProcess failed, return:%v, want:%v", res, want)
}
}
func TestCIService_ImageRecognition(t *testing.T) {
setup()
defer teardown()
name := "test.jpg"
detectType := "porn,terrorist,politics"
mux.HandleFunc("/test.jpg", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
vs := values{
"ci-process": "sensitive-content-recognition",
"detect-type": "porn,terrorist,politics",
}
testFormValues(t, r, vs)
fmt.Fprint(w, `<RecognitionResult>
<PornInfo>
<Code>0</Code>
<Msg>OK</Msg>
<HitFlag>0</HitFlag>
<Score>0</Score>
<Label/>
</PornInfo>
<TerroristInfo>
<Code>0</Code>
<Msg>OK</Msg>
<HitFlag>0</HitFlag>
<Score>0</Score>
<Label/>
</TerroristInfo>
<PoliticsInfo>
<Code>0</Code>
<Msg>OK</Msg>
<HitFlag>0</HitFlag>
<Score>0</Score>
<Label/>
</PoliticsInfo>
</RecognitionResult>`)
})
want := &ImageRecognitionResult{
XMLName: xml.Name{Local: "RecognitionResult"},
PornInfo: &RecognitionInfo{
Code: 0,
Msg: "OK",
HitFlag: 0,
Score: 0,
},
TerroristInfo: &RecognitionInfo{
Code: 0,
Msg: "OK",
HitFlag: 0,
Score: 0,
},
PoliticsInfo: &RecognitionInfo{
Code: 0,
Msg: "OK",
HitFlag: 0,
Score: 0,
},
}
res, _, err := client.CI.ImageRecognition(context.Background(), name, detectType)
if err != nil {
t.Fatalf("CI.ImageRecognitionreturned error: %v", err)
}
if !reflect.DeepEqual(res, want) {
t.Errorf("CI.ImageRecognition failed, return:%v, want:%v", res, want)
}
}
func TestCIService_Put(t *testing.T) {
setup()
defer teardown()
name := "test.jpg"
data := make([]byte, 1024*1024*3)
rand.Read(data)
pic := &ImageProcessOptions{
IsPicInfo: 1,
Rules: []PicOperationsRules{
{
FileId: "format.jpg",
Rule: "imageView2/format/png",
},
},
}
mux.HandleFunc("/test.jpg", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PUT")
header := r.Header.Get("Pic-Operations")
body := new(ImageProcessOptions)
err := json.Unmarshal([]byte(header), body)
want := pic
if err != nil {
t.Errorf("CI.Put Failed: %v", err)
}
if !reflect.DeepEqual(want, body) {
t.Errorf("CI.Put Failed, wanted:%v, body:%v", want, body)
}
tb := crc64.MakeTable(crc64.ECMA)
ht := crc64.New(tb)
tr := TeeReader(r.Body, ht, 0, nil)
bs, err := ioutil.ReadAll(tr)
if err != nil {
t.Errorf("CI.Put ReadAll Failed: %v", err)
}
if bytes.Compare(bs, data) != 0 {
t.Errorf("CI.Put Failed, data isn't consistent")
}
crc := tr.Crc64()
w.Header().Add("x-cos-hash-crc64ecma", strconv.FormatUint(crc, 10))
fmt.Fprint(w, `<UploadResult>
<OriginalInfo>
<Key>test.jpg</Key>
<Location>example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg</Location>
<ETag>&quot;8894dbe5e3ebfaf761e39b9d619c28f3327b8d85&quot;</ETag>
<ImageInfo>
<Format>PNG</Format>
<Width>103</Width>
<Height>99</Height>
<Quality>100</Quality>
<Ave>0xa08162</Ave>
<Orientation>0</Orientation>
</ImageInfo>
</OriginalInfo>
<ProcessResults>
<Object>
<Key>format.jpg</Key>
<Location>example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg</Location>
<Format>PNG</Format>
<Width>103</Width>
<Height>99</Height>
<Size>21351</Size>
<Quality>100</Quality>
<ETag>&quot;8894dbe5e3ebfaf761e39b9d619c28f3327b8d85&quot;</ETag>
</Object>
</ProcessResults>
</UploadResult>`)
})
want := &ImageProcessResult{
XMLName: xml.Name{Local: "UploadResult"},
OriginalInfo: &PicOriginalInfo{
Key: "test.jpg",
Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg",
ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
ImageInfo: &PicImageInfo{
Format: "PNG",
Width: 103,
Height: 99,
Quality: 100,
Ave: "0xa08162",
Orientation: 0,
},
},
ProcessResults: &PicProcessObject{
Key: "format.jpg",
Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg",
Format: "PNG",
Width: 103,
Height: 99,
Size: 21351,
Quality: 100,
ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
},
}
f := bytes.NewReader(data)
opt := &ObjectPutOptions{
nil,
&ObjectPutHeaderOptions{
XOptionHeader: &http.Header{},
},
}
opt.XOptionHeader.Add("Pic-Operations", EncodePicOperations(pic))
res, _, err := client.CI.Put(context.Background(), name, f, opt)
if err != nil {
t.Fatalf("CI.Put returned error: %v", err)
}
if !reflect.DeepEqual(res, want) {
t.Errorf("CI.ImageProcess failed, return:%v, want:%v", res, want)
}
}
func TestCIService_PutFromFile(t *testing.T) {
setup()
defer teardown()
name := "test.jpg"
filePath := "test.file" + time.Now().Format(time.RFC3339)
newfile, err := os.Create(filePath)
if err != nil {
t.Fatalf("creat tmp file failed")
}
defer os.Remove(filePath)
data := make([]byte, 1024*1024*3)
rand.Read(data)
newfile.Write(data)
newfile.Close()
pic := &ImageProcessOptions{
IsPicInfo: 1,
Rules: []PicOperationsRules{
{
FileId: "format.jpg",
Rule: "imageView2/format/png",
},
},
}
mux.HandleFunc("/test.jpg", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PUT")
header := r.Header.Get("Pic-Operations")
body := new(ImageProcessOptions)
err := json.Unmarshal([]byte(header), body)
want := pic
if err != nil {
t.Errorf("CI.Put Failed: %v", err)
}
if !reflect.DeepEqual(want, body) {
t.Errorf("CI.Put Failed, wanted:%v, body:%v", want, body)
}
tb := crc64.MakeTable(crc64.ECMA)
ht := crc64.New(tb)
tr := TeeReader(r.Body, ht, 0, nil)
bs, err := ioutil.ReadAll(tr)
if err != nil {
t.Errorf("CI.Put ReadAll Failed: %v", err)
}
if bytes.Compare(bs, data) != 0 {
t.Errorf("CI.Put Failed, data isn't consistent")
}
crc := tr.Crc64()
w.Header().Add("x-cos-hash-crc64ecma", strconv.FormatUint(crc, 10))
fmt.Fprint(w, `<UploadResult>
<OriginalInfo>
<Key>test.jpg</Key>
<Location>example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg</Location>
<ETag>&quot;8894dbe5e3ebfaf761e39b9d619c28f3327b8d85&quot;</ETag>
<ImageInfo>
<Format>PNG</Format>
<Width>103</Width>
<Height>99</Height>
<Quality>100</Quality>
<Ave>0xa08162</Ave>
<Orientation>0</Orientation>
</ImageInfo>
</OriginalInfo>
<ProcessResults>
<Object>
<Key>format.jpg</Key>
<Location>example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg</Location>
<Format>PNG</Format>
<Width>103</Width>
<Height>99</Height>
<Size>21351</Size>
<Quality>100</Quality>
<ETag>&quot;8894dbe5e3ebfaf761e39b9d619c28f3327b8d85&quot;</ETag>
</Object>
</ProcessResults>
</UploadResult>`)
})
want := &ImageProcessResult{
XMLName: xml.Name{Local: "UploadResult"},
OriginalInfo: &PicOriginalInfo{
Key: "test.jpg",
Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/test.jpg",
ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
ImageInfo: &PicImageInfo{
Format: "PNG",
Width: 103,
Height: 99,
Quality: 100,
Ave: "0xa08162",
Orientation: 0,
},
},
ProcessResults: &PicProcessObject{
Key: "format.jpg",
Location: "example-1250000000.cos.ap-guangzhou.myqcloud.com/format.jpg",
Format: "PNG",
Width: 103,
Height: 99,
Size: 21351,
Quality: 100,
ETag: "\"8894dbe5e3ebfaf761e39b9d619c28f3327b8d85\"",
},
}
opt := &ObjectPutOptions{
nil,
&ObjectPutHeaderOptions{
XOptionHeader: &http.Header{},
},
}
opt.XOptionHeader.Add("Pic-Operations", EncodePicOperations(pic))
res, _, err := client.CI.PutFromFile(context.Background(), name, filePath, opt)
if err != nil {
t.Fatalf("CI.Put returned error: %v", err)
}
if !reflect.DeepEqual(res, want) {
t.Errorf("CI.ImageProcess failed, return:%v, want:%v", res, want)
}
}
func TestBucketService_GetGuetzli(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
vs := values{
"guetzli": "",
}
testFormValues(t, r, vs)
fmt.Fprint(w, `<GuetzliStatus>on</GuetzliStatus>`)
})
res, _, err := client.CI.GetGuetzli(context.Background())
if err != nil {
t.Fatalf("CI.GetGuetzli returned error %v", err)
}
want := &GetGuetzliResult{
XMLName: xml.Name{Local: "GuetzliStatus"},
GuetzliStatus: "on",
}
if !reflect.DeepEqual(res, want) {
t.Errorf("CI.GetGuetzli %+v, want %+v", res, want)
}
}
func TestBucketService_PutGuetzli(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PUT")
vs := values{
"guetzli": "",
}
testFormValues(t, r, vs)
})
_, err := client.CI.PutGuetzli(context.Background())
if err != nil {
t.Fatalf("CI.PutGuetzli returned error: %v", err)
}
}
func TestBucketService_DeleteGuetzli(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
vs := values{
"guetzli": "",
}
testFormValues(t, r, vs)
w.WriteHeader(http.StatusNoContent)
})
_, err := client.CI.DeleteGuetzli(context.Background())
if err != nil {
t.Fatalf("CI.PutGuetzli returned error: %v", err)
}
}

33
error_test.go

@ -87,3 +87,36 @@ func Test_checkResponse_with_error(t *testing.T) {
}
}
func Test_IsNotFoundError(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/test_404", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, `<?xml version='1.0' encoding='utf-8' ?>
<Error>
<Code>NoSuchKey</Code>
<Message>The specified key does not exist.</Message>
<Resource>examplebucket-1250000000.cos.ap-guangzhou.myqcloud.com/test_404</Resource>
<RequestId>NjA3OGY4NGFfNjJkMmMwYl8***</RequestId>
<TraceId>OGVmYzZiMmQzYjA2OWNh***</TraceId>
</Error>`)
})
req, _ := http.NewRequest("GET", client.BaseURL.ServiceURL.String()+"/test_404", nil)
resp, _ := client.client.Do(req)
err := checkResponse(resp)
e, ok := IsCOSError(err)
if !ok {
t.Errorf("IsCOSError Return Failed")
}
ok = IsNotFoundError(e)
if !ok {
t.Errorf("IsNotFoundError Return Failed")
}
if e.Code != "NoSuchKey" {
t.Errorf("Expected NoSuchKey error, got %+v", e.Code)
}
}

89
example/CI/ci_QRcode.go

@ -0,0 +1,89 @@
package main
import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"github.com/tencentyun/cos-go-sdk-v5"
"github.com/tencentyun/cos-go-sdk-v5/debug"
)
func log_status(err error) {
if err == nil {
return
}
if cos.IsNotFoundError(err) {
// WARN
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message)
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
// ERROR
} else {
fmt.Printf("ERROR: %v\n", err)
// ERROR
}
}
func main() {
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
b := &cos.BaseURL{BucketURL: u}
c := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: os.Getenv("COS_SECRETID"),
SecretKey: os.Getenv("COS_SECRETKEY"),
Transport: &debug.DebugRequestTransport{
RequestHeader: true,
// Notice when put a large file and set need the request body, might happend out of memory error.
RequestBody: false,
ResponseHeader: true,
ResponseBody: false,
},
},
})
opt := &cos.ObjectPutOptions{
nil,
&cos.ObjectPutHeaderOptions{
XOptionHeader: &http.Header{},
},
}
pic := &cos.PicOperations{
IsPicInfo: 1,
Rules: []cos.PicOperationsRules{
{
FileId: "format.jpg",
Rule: "QRcode/cover/1",
},
},
}
opt.XOptionHeader.Add("Pic-Operations", cos.EncodePicOperations(pic))
name := "test.jpg"
local_filename := "./QRcode.jpg"
res, _, err := c.CI.PutFromFile(context.Background(), name, local_filename, opt)
log_status(err)
fmt.Printf("%+v\n", res)
fmt.Printf("%+v\n", res.OriginalInfo)
fmt.Printf("%+v\n", res.ProcessResults)
res2, _, err := c.CI.GetQRcode(context.Background(), name, 0, nil)
log_status(err)
fmt.Printf("%+v\n", res2)
gopt := &cos.GenerateQRcodeOptions{
QRcodeContent: fmt.Sprintf("<%v>", res2.QRcodeInfo.CodeUrl),
Mode: 0,
Width: 200,
}
res3, _, err := c.CI.GenerateQRcode(context.Background(), gopt)
log_status(err)
fmt.Printf("%+v\n", res3)
_, _, err = c.CI.GenerateQRcodeToFile(context.Background(), "./downQRcode.jpg", gopt)
log_status(err)
}

15
example/object/ci_image_process.go → example/CI/ci_get.go

@ -42,22 +42,13 @@ func main() {
// Notice when put a large file and set need the request body, might happend out of memory error.
RequestBody: false,
ResponseHeader: true,
ResponseBody: true,
ResponseBody: false,
},
},
})
opt := &cos.ImageProcessOptions{
IsPicInfo: 1,
Rules: []cos.PicOperationsRules{
{
FileId: "format.jpg",
Rule: "imageView2/format/png",
},
},
}
name := "test.jpg"
res, _, err := c.CI.ImageProcess(context.Background(), name, opt)
filepath := "test.jpg"
_, err := c.CI.GetToFile(context.Background(), name, filepath, "imageMogr2/thumbnail/!50px", nil)
log_status(err)
fmt.Printf("%+v\n", res)
}

99
example/CI/ci_watermark.go

@ -0,0 +1,99 @@
package main
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"net/url"
"os"
"github.com/tencentyun/cos-go-sdk-v5"
"github.com/tencentyun/cos-go-sdk-v5/debug"
)
func log_status(err error) {
if err == nil {
return
}
if cos.IsNotFoundError(err) {
// WARN
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message)
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
// ERROR
} else {
fmt.Printf("ERROR: %v\n", err)
// ERROR
}
}
func main() {
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
b := &cos.BaseURL{BucketURL: u}
c := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: os.Getenv("COS_SECRETID"),
SecretKey: os.Getenv("COS_SECRETKEY"),
Transport: &debug.DebugRequestTransport{
RequestHeader: true,
// Notice when put a large file and set need the request body, might happend out of memory error.
RequestBody: false,
ResponseHeader: true,
ResponseBody: false,
},
},
})
// 上传时添加盲水印
opt := &cos.ObjectPutOptions{
nil,
&cos.ObjectPutHeaderOptions{
XOptionHeader: &http.Header{},
},
}
pic := &cos.PicOperations{
IsPicInfo: 1,
Rules: []cos.PicOperationsRules{
{
FileId: "format.jpg",
Rule: "watermark/3/type/3/text/" + base64.StdEncoding.EncodeToString([]byte("testwatermark")),
},
},
}
opt.XOptionHeader.Add("Pic-Operations", cos.EncodePicOperations(pic))
name := "test.jpg"
local_filename := "./test.jpg"
res, _, err := c.CI.PutFromFile(context.Background(), name, local_filename, opt)
log_status(err)
fmt.Printf("%+v\n", res)
// 下载时添加盲水印
name = "test.jpg"
filepath := "watermark.jpg"
_, err = c.CI.GetToFile(context.Background(), name, filepath, "watermark/3/type/3/text/"+base64.StdEncoding.EncodeToString([]byte("testwatermark")), nil)
// 提取盲水印
opt = &cos.ObjectPutOptions{
nil,
&cos.ObjectPutHeaderOptions{
XOptionHeader: &http.Header{},
},
}
pic = &cos.PicOperations{
IsPicInfo: 1,
Rules: []cos.PicOperationsRules{
{
FileId: "format2.jpg",
Rule: "watermark/4/type/3/text/" + base64.StdEncoding.EncodeToString([]byte("testwatermark")),
},
},
}
opt.XOptionHeader.Add("Pic-Operations", cos.EncodePicOperations(pic))
name = "test2.jpg"
_, err = c.Object.PutFromFile(context.Background(), name, filepath, opt)
log_status(err)
}

28
example/object/ci_put.go → example/CI/compression/ci_compression.go

@ -42,29 +42,17 @@ func main() {
// Notice when put a large file and set need the request body, might happend out of memory error.
RequestBody: false,
ResponseHeader: true,
ResponseBody: true,
ResponseBody: false,
},
},
})
opt := &cos.ObjectPutOptions{
nil,
&cos.ObjectPutHeaderOptions{
XOptionHeader: &http.Header{},
},
}
pic := &cos.PicOperations{
IsPicInfo: 1,
Rules: []cos.PicOperationsRules{
{
FileId: "format.jpg",
Rule: "imageView2/format/png",
},
},
}
opt.XOptionHeader.Add("Pic-Operations", cos.EncodePicOperations(pic))
name := "test.jpg"
local_filename := "./test.jpg"
_, err := c.Object.PutFromFile(context.Background(), name, local_filename, opt)
name := "test.png"
filepath := "test1.jpg"
_, err := c.CI.GetToFile(context.Background(), name, filepath, "imageMogr2/format/tpg", nil)
log_status(err)
filepath = "test2.jpg"
_, err = c.CI.GetToFile(context.Background(), name, filepath, "imageMogr2/format/heif", nil)
log_status(err)
}

68
example/CI/compression/guetzli.go

@ -0,0 +1,68 @@
package main
import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"time"
"github.com/tencentyun/cos-go-sdk-v5"
"github.com/tencentyun/cos-go-sdk-v5/debug"
)
func log_status(err error) {
if err == nil {
return
}
if cos.IsNotFoundError(err) {
// WARN
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message)
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
// ERROR
} else {
fmt.Printf("ERROR: %v\n", err)
// ERROR
}
}
func main() {
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
cu, _ := url.Parse("http://test-1259654469.pic.ap-guangzhou.myqcloud.com")
b := &cos.BaseURL{BucketURL: u, CIURL: cu}
c := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: os.Getenv("COS_SECRETID"),
SecretKey: os.Getenv("COS_SECRETKEY"),
Transport: &debug.DebugRequestTransport{
RequestHeader: true,
// Notice when put a large file and set need the request body, might happend out of memory error.
RequestBody: false,
ResponseHeader: true,
ResponseBody: true,
},
},
})
_, err := c.CI.PutGuetzli(context.Background())
log_status(err)
res, _, err := c.CI.GetGuetzli(context.Background())
log_status(err)
if res != nil && res.GuetzliStatus != "on" {
fmt.Printf("Error Status: %v\n", res.GuetzliStatus)
}
time.Sleep(time.Second * 3)
_, err = c.CI.DeleteGuetzli(context.Background())
log_status(err)
res, _, err = c.CI.GetGuetzli(context.Background())
log_status(err)
if res != nil && res.GuetzliStatus != "off" {
fmt.Printf("Error Status: %v\n", res.GuetzliStatus)
}
}

16
example/object/ci_video_auditing_job.go → example/CI/content_auditing/ci_audio_auditing_job.go

@ -47,25 +47,19 @@ func main() {
},
},
})
opt := &cos.PutVideoAuditingJobOptions{
InputObject: "demo.mp4",
Conf: &cos.VideoAuditingJobConf{
opt := &cos.PutAudioAuditingJobOptions{
InputObject: "test.mp3",
Conf: &cos.AudioAuditingJobConf{
DetectType: "Porn,Terrorism,Politics,Ads",
Snapshot: &cos.PutVideoAuditingJobSnapshot{
Mode: "Interval",
Start: 0.5,
TimeInterval: 50.5,
Count: 100,
},
},
}
res, _, err := c.CI.PutVideoAuditingJob(context.Background(), opt)
res, _, err := c.CI.PutAudioAuditingJob(context.Background(), opt)
log_status(err)
fmt.Printf("%+v\n", res)
time.Sleep(3 * time.Second)
res2, _, err := c.CI.GetVideoAuditingJob(context.Background(), res.JobsDetail.JobId)
res2, _, err := c.CI.GetAudioAuditingJob(context.Background(), res.JobsDetail.JobId)
log_status(err)
fmt.Printf("%+v\n", res2)
}

7
example/object/ci_image_recognition.go → example/CI/content_auditing/ci_image_recognition.go

@ -45,12 +45,9 @@ func main() {
},
},
})
opt := &cos.ImageRecognitionOptions{
DetectType: "porn,terrorist,politics",
}
DetectType := "porn,terrorist,politics"
name := "test.jpg"
res, _, err := c.CI.ImageRecognition(context.Background(), name, opt)
res, _, err := c.CI.ImageRecognition(context.Background(), name, DetectType)
log_status(err)
fmt.Printf("%+v\n", res)
}

2
example/bucket/putPolicy.go

@ -48,7 +48,7 @@ func main() {
Condition: map[string]map[string]interface{}{
"ip_not_equal": map[string]interface{}{
"qcs:ip": []string{
"192.168.1.1",
"<ip>",
},
},
},

132
example/object/ci_doc_process.go

@ -1,132 +0,0 @@
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"
"github.com/tencentyun/cos-go-sdk-v5"
"github.com/tencentyun/cos-go-sdk-v5/debug"
)
func log_status(err error) {
if err == nil {
return
}
if cos.IsNotFoundError(err) {
// WARN
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message)
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
// ERROR
} else {
fmt.Printf("ERROR: %v\n", err)
// ERROR
}
}
func main() {
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
cu, _ := url.Parse("https://test-1259654469.ci.ap-guangzhou.myqcloud.com")
b := &cos.BaseURL{BucketURL: u, CIURL: cu}
c := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: os.Getenv("COS_SECRETID"),
SecretKey: os.Getenv("COS_SECRETKEY"),
Transport: &debug.DebugRequestTransport{
RequestHeader: true,
// Notice when put a large file and set need the request body, might happend out of memory error.
RequestBody: true,
ResponseHeader: true,
ResponseBody: true,
},
},
})
// 1、UpdateDocProcessQueue
updateQueueOpt := &cos.UpdateDocProcessQueueOptions{
Name: "queue-doc-process-1",
QueueID: "p111a8dd208104ce3b11c78398f658ca8",
State: "Active",
NotifyConfig: &cos.DocProcessQueueNotifyConfig{
State: "Off",
},
}
updateQueueRes, _, err := c.CI.UpdateDocProcessQueue(context.Background(), updateQueueOpt)
log_status(err)
fmt.Printf("%+v\n", updateQueueRes)
// 2、DescribeDocProcessQueues
DescribeQueueOpt := &cos.DescribeDocProcessQueuesOptions{
QueueIds: "p111a8dd208104ce3b11c78398f658ca8,p4318f85d2aa14c43b1dba6f9b78be9b3,aacb2bb066e9c4478834d4196e76c49d3",
PageNumber: 1,
PageSize: 2,
}
DescribeQueueRes, _, err := c.CI.DescribeDocProcessQueues(context.Background(), DescribeQueueOpt)
log_status(err)
fmt.Printf("%+v\n", DescribeQueueRes)
// 3、DescribeDocProcessBuckets
BucketsOpt := &cos.DescribeDocProcessBucketsOptions{
Regions: "All",
}
BucketsRes, _, err := c.CI.DescribeDocProcessBuckets(context.Background(), BucketsOpt)
log_status(err)
fmt.Printf("%+v\n", BucketsRes)
// 4、CreateDocProcessJobs
createJobOpt := &cos.CreateDocProcessJobsOptions{
Tag: "DocProcess",
Input: &cos.DocProcessJobInput{
Object: "form.pdf",
},
Operation: &cos.DocProcessJobOperation{
Output: &cos.DocProcessJobOutput{
Region: "ap-guangzhou",
Object: "test-doc${Number}",
Bucket: "test-1259654469",
},
DocProcess: &cos.DocProcessJobDocProcess{
TgtType: "png",
StartPage: 1,
EndPage: -1,
ImageParams: "watermark/1/image/aHR0cDovL3Rlc3QwMDUtMTI1MTcwNDcwOC5jb3MuYXAtY2hvbmdxaW5nLm15cWNsb3VkLmNvbS8xLmpwZw==/gravity/southeast",
},
},
QueueId: "p111a8dd208104ce3b11c78398f658ca8",
}
createJobRes, _, err := c.CI.CreateDocProcessJobs(context.Background(), createJobOpt)
log_status(err)
fmt.Printf("%+v\n", createJobRes.JobsDetail)
// 5、DescribeDocProcessJob
DescribeJobRes, _, err := c.CI.DescribeDocProcessJob(context.Background(), createJobRes.JobsDetail.JobId)
log_status(err)
fmt.Printf("%+v\n", DescribeJobRes.JobsDetail)
// 6、DescribeDocProcessJobs
DescribeJobsOpt := &cos.DescribeDocProcessJobsOptions{
QueueId: "p111a8dd208104ce3b11c78398f658ca8",
Tag: "DocProcess",
}
DescribeJobsRes, _, err := c.CI.DescribeDocProcessJobs(context.Background(), DescribeJobsOpt)
log_status(err)
fmt.Printf("%+v\n", DescribeJobsRes)
// 7、doc-preview
opt := &cos.DocPreviewOptions{
Page: 1,
}
resp, err := c.CI.DocPreview(context.Background(), "form.pdf", opt)
log_status(err)
fd, _ := os.OpenFile("form.pdf", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
io.Copy(fd, resp.Body)
fd.Close()
}

32
example/object/directory.go

@ -59,4 +59,36 @@ func main() {
// 删除文件夹
_, err = c.Object.Delete(context.Background(), name)
log_status(err)
// 上传到虚拟目录
dir := "exampledir/"
filename := "exampleobject"
key := dir + filename
f := strings.NewReader("test file")
_, err = c.Object.Put(context.Background(), key, f, nil)
log_status(err)
// 删除文件夹内所有文件
var marker string
opt := &cos.BucketGetOptions{
Prefix: dir,
MaxKeys: 1000,
}
isTruncated := true
for isTruncated {
opt.Marker = marker
v, _, err := c.Bucket.Get(context.Background(), opt)
if err != nil {
log_status(err)
break
}
for _, content := range v.Contents {
_, err = c.Object.Delete(context.Background(), content.Key)
if err != nil {
log_status(err)
}
}
isTruncated = v.IsTruncated
marker = v.NextMarker
}
}

138
object.go

@ -3,6 +3,7 @@ package cos
import (
"context"
"crypto/md5"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
@ -568,6 +569,20 @@ type MultiDownloadOptions struct {
Opt *ObjectGetOptions
PartSize int64
ThreadPoolSize int
CheckPoint bool
CheckPointFile string
}
type MultiDownloadCPInfo struct {
Size int64 `json:"contentLength,omitempty"`
ETag string `json:"eTag,omitempty"`
CRC64 string `json:"crc64ecma,omitempty"`
LastModified string `json:"lastModified,omitempty"`
DownloadedBlocks []DownloadedBlock `json:"downloadedBlocks,omitempty"`
}
type DownloadedBlock struct {
From int64 `json:"from,omitempty"`
To int64 `json:"to,omitempty"`
}
type Chunk struct {
@ -584,6 +599,7 @@ type Jobs struct {
UploadId string
FilePath string
RetryTimes int
VersionId []string
Chunk Chunk
Data io.Reader
Opt *ObjectUploadPartOptions
@ -663,7 +679,7 @@ func downloadWorker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results
for {
var res Results
res.PartNumber = j.Chunk.Number
resp, err := s.Get(context.Background(), j.Name, j.DownOpt)
resp, err := s.Get(context.Background(), j.Name, j.DownOpt, j.VersionId...)
res.err = err
res.Resp = resp
if err != nil {
@ -825,7 +841,6 @@ func (s *ObjectService) checkUploadedParts(ctx context.Context, name, UploadID,
}
// MultiUpload/Upload 为高级upload接口,并发分块上传
// 注意该接口目前只供参考
//
// 当 partSize > 0 时,由调用者指定分块大小,否则由 SDK 自动切分,单位为MB
// 由调用者指定分块大小时,请确认分块数量不超过10000
@ -870,8 +885,9 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
return nil, rsp, err
}
result := &CompleteMultipartUploadResult{
Key: name,
ETag: rsp.Header.Get("ETag"),
Location: fmt.Sprintf("%s/%s", s.client.BaseURL.BucketURL, name),
Key: name,
ETag: rsp.Header.Get("ETag"),
}
if rsp != nil && s.client.Conf.EnableCRC {
scoscrc := rsp.Header.Get("x-cos-hash-crc64ecma")
@ -1001,7 +1017,6 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
if err != nil {
s.AbortMultipartUpload(ctx, name, uploadID)
return v, resp, err
}
@ -1047,7 +1062,50 @@ func SplitSizeIntoChunks(totalBytes int64, partSize int64) ([]Chunk, int, error)
return chunks, int(partNum), nil
}
func (s *ObjectService) Download(ctx context.Context, name string, filepath string, opt *MultiDownloadOptions) (*Response, error) {
func (s *ObjectService) checkDownloadedParts(opt *MultiDownloadCPInfo, chfile string, chunks []Chunk) (*MultiDownloadCPInfo, bool) {
var defaultRes MultiDownloadCPInfo
defaultRes = *opt
fd, err := os.Open(chfile)
// checkpoint 文件不存在
if err != nil && os.IsNotExist(err) {
// 创建 checkpoint 文件
fd, _ = os.OpenFile(chfile, os.O_RDONLY|os.O_CREATE|os.O_TRUNC, 0660)
fd.Close()
return &defaultRes, false
}
if err != nil {
return &defaultRes, false
}
defer fd.Close()
var res MultiDownloadCPInfo
err = json.NewDecoder(fd).Decode(&res)
if err != nil {
return &defaultRes, false
}
// 与COS的文件比较
if res.CRC64 != opt.CRC64 || res.ETag != opt.ETag || res.Size != opt.Size || res.LastModified != opt.LastModified || len(res.DownloadedBlocks) == 0 {
return &defaultRes, false
}
// len(chunks) 大于1,否则为简单下载, chunks[0].Size为partSize
partSize := chunks[0].Size
for _, v := range res.DownloadedBlocks {
index := v.From / partSize
to := chunks[index].OffSet + chunks[index].Size - 1
if chunks[index].OffSet != v.From || to != v.To {
// 重置chunks
for i, _ := range chunks {
chunks[i].Done = false
}
return &defaultRes, false
}
chunks[index].Done = true
}
return &res, true
}
func (s *ObjectService) Download(ctx context.Context, name string, filepath string, opt *MultiDownloadOptions, id ...string) (*Response, error) {
// 参数校验
if opt == nil {
opt = &MultiDownloadOptions{}
@ -1057,7 +1115,7 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri
}
// 获取文件长度和CRC
var coscrc string
resp, err := s.Head(ctx, name, nil)
resp, err := s.Head(ctx, name, nil, id...)
if err != nil {
return resp, err
}
@ -1076,7 +1134,7 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri
}
// 直接下载到文件
if partNum == 0 || partNum == 1 {
rsp, err := s.GetToFile(ctx, name, filepath, opt.Opt)
rsp, err := s.GetToFile(ctx, name, filepath, opt.Opt, id...)
if err != nil {
return rsp, err
}
@ -1097,12 +1155,39 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri
}
return rsp, err
}
// 创建文件
nfile, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
if err != nil {
return resp, err
// 断点续载
var resumableFlag bool
var resumableInfo *MultiDownloadCPInfo
var cpfd *os.File
var cpfile string
if opt.CheckPoint {
cpInfo := &MultiDownloadCPInfo{
LastModified: resp.Header.Get("Last-Modified"),
ETag: resp.Header.Get("ETag"),
CRC64: coscrc,
Size: totalBytes,
}
cpfile = opt.CheckPointFile
if cpfile == "" {
cpfile = fmt.Sprintf("%s.cosresumabletask", filepath)
}
resumableInfo, resumableFlag = s.checkDownloadedParts(cpInfo, cpfile, chunks)
cpfd, err = os.OpenFile(cpfile, os.O_RDWR, 0660)
if err != nil {
return nil, fmt.Errorf("Open CheckPoint File[%v] Failed:%v", cpfile, err)
}
}
if !resumableFlag {
// 创建文件
nfile, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
if err != nil {
if cpfd != nil {
cpfd.Close()
}
return resp, err
}
nfile.Close()
}
nfile.Close()
var poolSize int
if opt.ThreadPoolSize > 0 {
@ -1118,6 +1203,9 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri
go func() {
for _, chunk := range chunks {
if chunk.Done {
continue
}
var downOpt ObjectGetOptions
if opt.Opt != nil {
downOpt = *opt.Opt
@ -1130,23 +1218,45 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri
Chunk: chunk,
DownOpt: &downOpt,
}
if len(id) > 0 {
job.VersionId = append(job.VersionId, id...)
}
chjobs <- job
}
close(chjobs)
}()
err = nil
for i := 0; i < partNum; i++ {
if chunks[i].Done {
continue
}
res := <-chresults
if res.Resp == nil || res.err != nil {
err = fmt.Errorf("part %d get resp Content. error: %s", res.PartNumber, res.err.Error())
continue
}
// Dump CheckPoint Info
if opt.CheckPoint {
cpfd.Truncate(0)
cpfd.Seek(0, os.SEEK_SET)
resumableInfo.DownloadedBlocks = append(resumableInfo.DownloadedBlocks, DownloadedBlock{
From: chunks[res.PartNumber-1].OffSet,
To: chunks[res.PartNumber-1].OffSet + chunks[res.PartNumber-1].Size - 1,
})
json.NewEncoder(cpfd).Encode(resumableInfo)
}
}
close(chresults)
if cpfd != nil {
cpfd.Close()
}
if err != nil {
return nil, err
}
// 下载成功,删除checkpoint文件
if opt.CheckPoint {
os.Remove(cpfile)
}
if coscrc != "" && s.client.Conf.EnableCRC {
icoscrc, _ := strconv.ParseUint(coscrc, 10, 64)
fd, err := os.Open(filepath)

1
object_part.go

@ -523,6 +523,7 @@ func (s *ObjectService) MultiCopy(ctx context.Context, name string, sourceURL st
v, resp, err := s.CompleteMultipartUpload(ctx, name, uploadID, optcom)
if err != nil {
s.AbortMultipartUpload(ctx, name, uploadID)
return nil, resp, err
}
cpres := &ObjectCopyResult{
ETag: v.ETag,

264
object_test.go

@ -25,6 +25,8 @@ func TestObjectService_Get(t *testing.T) {
setup()
defer teardown()
name := "test/hello.txt"
contentLength := 1024 * 1024 * 10
data := make([]byte, contentLength)
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
@ -32,27 +34,70 @@ func TestObjectService_Get(t *testing.T) {
"response-content-type": "text/html",
}
testFormValues(t, r, vs)
testHeader(t, r, "Range", "bytes=0-3")
fmt.Fprint(w, `hello`)
strRange := r.Header.Get("Range")
slice1 := strings.Split(strRange, "=")
slice2 := strings.Split(slice1[1], "-")
start, _ := strconv.ParseInt(slice2[0], 10, 64)
end, _ := strconv.ParseInt(slice2[1], 10, 64)
io.Copy(w, bytes.NewBuffer(data[start:end+1]))
})
for i := 0; i < 3; i++ {
math_rand.Seed(time.Now().UnixNano())
rangeStart := math_rand.Intn(contentLength)
rangeEnd := rangeStart + math_rand.Intn(contentLength-rangeStart)
if rangeEnd == rangeStart || rangeStart >= contentLength-1 {
continue
}
opt := &ObjectGetOptions{
ResponseContentType: "text/html",
Range: fmt.Sprintf("bytes=%v-%v", rangeStart, rangeEnd),
}
resp, err := client.Object.Get(context.Background(), name, opt)
if err != nil {
t.Fatalf("Object.Get returned error: %v", err)
}
b, _ := ioutil.ReadAll(resp.Body)
if bytes.Compare(b, data[rangeStart:rangeEnd+1]) != 0 {
t.Errorf("Object.Get Failed")
}
}
}
func TestObjectService_GetToFile(t *testing.T) {
setup()
defer teardown()
name := "test/hello.txt"
data := make([]byte, 1024*1024*10)
rand.Read(data)
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
vs := values{
"response-content-type": "text/html",
}
testFormValues(t, r, vs)
testHeader(t, r, "Range", "bytes=0-3")
io.Copy(w, bytes.NewReader(data))
})
opt := &ObjectGetOptions{
ResponseContentType: "text/html",
Range: "bytes=0-3",
}
resp, err := client.Object.Get(context.Background(), name, opt)
filePath := "test.file" + time.Now().Format(time.RFC3339)
_, err := client.Object.GetToFile(context.Background(), name, filePath, opt)
if err != nil {
t.Fatalf("Object.Get returned error: %v", err)
}
b, _ := ioutil.ReadAll(resp.Body)
ref := string(b)
want := "hello"
if !reflect.DeepEqual(ref, want) {
t.Errorf("Object.Get returned %+v, want %+v", ref, want)
defer os.Remove(filePath)
fd, err := os.Open(filePath)
if err != nil {
}
defer fd.Close()
bs, _ := ioutil.ReadAll(fd)
if bytes.Compare(bs, data) != 0 {
t.Errorf("Object.GetToFile data isn't consistent")
}
}
func TestObjectService_Put(t *testing.T) {
@ -132,6 +177,7 @@ func TestObjectService_PutFromFile(t *testing.T) {
opt := &ObjectPutOptions{
ObjectPutHeaderOptions: &ObjectPutHeaderOptions{
ContentType: "text/html",
Listener: &DefaultProgressListener{},
},
ACLHeaderOptions: &ACLHeaderOptions{
XCosACL: "private",
@ -518,6 +564,7 @@ func TestObjectService_Upload2(t *testing.T) {
}
}
/*
func TestObjectService_Download(t *testing.T) {
setup()
defer teardown()
@ -590,3 +637,198 @@ func TestObjectService_Download(t *testing.T) {
t.Fatalf("Object.Upload returned error: %v", err)
}
}
*/
func TestObjectService_DownloadWithCheckPoint(t *testing.T) {
setup()
defer teardown()
filePath := "rsp.file" + time.Now().Format(time.RFC3339)
newfile, err := os.Create(filePath)
if err != nil {
t.Fatalf("create tmp file failed")
}
defer os.Remove(filePath)
// 源文件内容
totalBytes := int64(1024*1024*9 + 123)
partSize := 1024 * 1024
b := make([]byte, totalBytes)
_, err = rand.Read(b)
newfile.Write(b)
newfile.Close()
tb := crc64.MakeTable(crc64.ECMA)
localcrc := strconv.FormatUint(crc64.Update(0, tb, b), 10)
oddok := false
var oddcount, evencount int
mux.HandleFunc("/test.go.download", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodHead {
w.Header().Add("Content-Length", strconv.FormatInt(totalBytes, 10))
w.Header().Add("x-cos-hash-crc64ecma", localcrc)
return
}
strRange := r.Header.Get("Range")
slice1 := strings.Split(strRange, "=")
slice2 := strings.Split(slice1[1], "-")
start, _ := strconv.ParseInt(slice2[0], 10, 64)
end, _ := strconv.ParseInt(slice2[1], 10, 64)
if (start/int64(partSize))%2 == 1 {
if oddok {
io.Copy(w, bytes.NewBuffer(b[start:end+1]))
} else {
// 数据校验失败, Download不会做重试
io.Copy(w, bytes.NewBuffer(b[start:end]))
}
oddcount++
} else {
io.Copy(w, bytes.NewBuffer(b[start:end+1]))
evencount++
}
})
opt := &MultiDownloadOptions{
ThreadPoolSize: 3,
PartSize: 1,
CheckPoint: true,
}
downPath := "down.file" + time.Now().Format(time.RFC3339)
defer os.Remove(downPath)
_, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt)
if err == nil {
// 偶数块下载完成,奇数块下载失败
t.Fatalf("Object.Download returned error: %v", err)
}
fd, err := os.Open(downPath)
if err != nil {
t.Fatalf("Object Download Open File Failed:%v", err)
}
offset := 0
for i := 0; i < 10; i++ {
bs, _ := ioutil.ReadAll(io.LimitReader(fd, int64(partSize)))
offset += len(bs)
if i%2 == 1 {
bs[len(bs)-1] = b[offset-1]
}
if bytes.Compare(bs, b[i*partSize:offset]) != 0 {
t.Fatalf("Compare Error, index:%v, len:%v, offset:%v", i, len(bs), offset)
}
}
fd.Close()
if oddcount != 5 || evencount != 5 {
t.Fatalf("Object.Download failed, odd:%v, even:%v", oddcount, evencount)
}
// 设置奇数块OK
oddok = true
_, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt)
if err != nil {
// 下载成功
t.Fatalf("Object.Download returned error: %v", err)
}
if oddcount != 10 || evencount != 5 {
t.Fatalf("Object.Download failed, odd:%v, even:%v", oddcount, evencount)
}
}
func TestObjectService_GetTagging(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
vs := values{
"tagging": "",
}
testFormValues(t, r, vs)
fmt.Fprint(w, `<Tagging>
<TagSet>
<Tag>
<Key>test_k2</Key>
<Value>test_v2</Value>
</Tag>
<Tag>
<Key>test_k3</Key>
<Value>test_vv</Value>
</Tag>
</TagSet>
</Tagging>`)
})
res, _, err := client.Object.GetTagging(context.Background(), "test")
if err != nil {
t.Fatalf("Object.GetTagging returned error %v", err)
}
want := &ObjectGetTaggingResult{
XMLName: xml.Name{Local: "Tagging"},
TagSet: []ObjectTaggingTag{
{"test_k2", "test_v2"},
{"test_k3", "test_vv"},
},
}
if !reflect.DeepEqual(res, want) {
t.Errorf("Object.GetTagging returned %+v, want %+v", res, want)
}
}
func TestObjectService_PutTagging(t *testing.T) {
setup()
defer teardown()
opt := &ObjectPutTaggingOptions{
TagSet: []ObjectTaggingTag{
{
Key: "test_k2",
Value: "test_v2",
},
{
Key: "test_k3",
Value: "test_v3",
},
},
}
mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
v := new(ObjectPutTaggingOptions)
xml.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PUT")
vs := values{
"tagging": "",
}
testFormValues(t, r, vs)
want := opt
want.XMLName = xml.Name{Local: "Tagging"}
if !reflect.DeepEqual(v, want) {
t.Errorf("Object.PutTagging request body: %+v, want %+v", v, want)
}
})
_, err := client.Object.PutTagging(context.Background(), "test", opt)
if err != nil {
t.Fatalf("Object.PutTagging returned error: %v", err)
}
}
func TestObjectService_DeleteTagging(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodDelete)
vs := values{
"tagging": "",
}
testFormValues(t, r, vs)
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Object.DeleteTagging(context.Background(), "test")
if err != nil {
t.Fatalf("Object.DeleteTagging returned error: %v", err)
}
}
Loading…
Cancel
Save