Browse Source

Merge pull request #93 from agin719/cos-v4-dev

Cos v4 dev
tags/v0.7.13 v0.7.13
agin719 4 years ago
committed by GitHub
parent
commit
08b8ff8ae7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      batch.go
  2. 102
      bucket_test.go
  3. 17
      ci.go
  4. 271
      ci_doc.go
  5. 2
      cos.go
  6. 2
      costesting/ci_test.go
  7. 14
      example/bucket/intelligenttiering.go
  8. 132
      example/object/ci_doc_process.go
  9. 25
      example/object/get.go
  10. 17
      example/object/put.go
  11. 59
      example/object/put_with_timeout.go
  12. 21
      example/object/upload.go
  13. 40
      example/object/uploadPart.go
  14. 28
      helper.go
  15. 61
      object.go
  16. 10
      object_part.go
  17. 2
      object_test.go
  18. 135
      progress.go
  19. 8
      service_test.go

4
batch.go

@ -13,7 +13,7 @@ type BatchRequestHeaders struct {
XCosAppid int `header:"x-cos-appid" xml:"-" url:"-"` XCosAppid int `header:"x-cos-appid" xml:"-" url:"-"`
ContentLength string `header:"Content-Length,omitempty" xml:"-" url:"-"` ContentLength string `header:"Content-Length,omitempty" xml:"-" url:"-"`
ContentType string `header:"Content-Type,omitempty" xml:"-" url:"-"` ContentType string `header:"Content-Type,omitempty" xml:"-" url:"-"`
Headers *http.Header `header:"-" xml:"-", url:"-"`
Headers *http.Header `header:"-" xml:"-" url:"-"`
} }
// BatchProgressSummary // BatchProgressSummary
@ -244,7 +244,7 @@ func (s *BatchService) UpdateJobPriority(ctx context.Context, opt *BatchUpdatePr
type BatchUpdateStatusOptions struct { type BatchUpdateStatusOptions struct {
JobId string `header:"-" url:"-" xml:"-"` JobId string `header:"-" url:"-" xml:"-"`
RequestedJobStatus string `url:"requestedJobStatus" header:"-" xml:"-"` RequestedJobStatus string `url:"requestedJobStatus" header:"-" xml:"-"`
StatusUpdateReason string `url:"statusUpdateReason,omitempty" header:"-", xml:"-"`
StatusUpdateReason string `url:"statusUpdateReason,omitempty" header:"-" xml:"-"`
} }
type BatchUpdateStatusResult struct { type BatchUpdateStatusResult struct {
XMLName xml.Name `xml:"UpdateJobStatusResult"` XMLName xml.Name `xml:"UpdateJobStatusResult"`

102
bucket_test.go

@ -152,14 +152,14 @@ func TestBucketService_Head(t *testing.T) {
} }
func TestBucketService_GetObjectVersions(t *testing.T) { func TestBucketService_GetObjectVersions(t *testing.T) {
setup()
defer teardown()
setup()
defer teardown()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
w.WriteHeader(http.StatusOK)
vs := values{
"versions": "",
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
w.WriteHeader(http.StatusOK)
vs := values{
"versions": "",
"delimiter": "/", "delimiter": "/",
} }
testFormValues(t, r, vs) testFormValues(t, r, vs)
@ -203,51 +203,51 @@ func TestBucketService_GetObjectVersions(t *testing.T) {
</Owner> </Owner>
</DeleteMarker> </DeleteMarker>
</ListVersionsResult>`) </ListVersionsResult>`)
})
})
want := &BucketGetObjectVersionsResult {
XMLName: xml.Name { Local: "ListVersionsResult" },
Name: "examplebucket-1250000000",
MaxKeys: 1000,
IsTruncated: false,
Delimiter: "/",
CommonPrefixes: []string {
"example-folder-1/",
"example-folder-2/",
},
Version: []ListVersionsResultVersion {
{
Key: "example-object-1.jpg",
VersionId: "MTg0NDUxNzgxMjEzNTU3NTk1Mjg",
IsLatest: true,
LastModified: "2019-08-16T10:45:53.000Z",
ETag: "\"5d1143df07a17b23320d0da161e2819e\"",
Size: 30,
StorageClass: "STANDARD",
Owner: &Owner {
ID: "1250000000",
DisplayName: "1250000000",
},
},
},
DeleteMarker: []ListVersionsResultDeleteMarker {
{
Key: "example-object-1.jpg",
VersionId: "MTg0NDUxNzgxMjEzNjE1OTcxMzM",
IsLatest: false,
LastModified: "2019-08-16T10:45:47.000Z",
Owner: &Owner {
ID: "1250000000",
DisplayName: "1250000000",
},
},
},
}
opt := &BucketGetObjectVersionsOptions {
Delimiter: "/",
}
res, _, err := client.Bucket.GetObjectVersions(context.Background(), opt)
if err != nil {
want := &BucketGetObjectVersionsResult{
XMLName: xml.Name{Local: "ListVersionsResult"},
Name: "examplebucket-1250000000",
MaxKeys: 1000,
IsTruncated: false,
Delimiter: "/",
CommonPrefixes: []string{
"example-folder-1/",
"example-folder-2/",
},
Version: []ListVersionsResultVersion{
{
Key: "example-object-1.jpg",
VersionId: "MTg0NDUxNzgxMjEzNTU3NTk1Mjg",
IsLatest: true,
LastModified: "2019-08-16T10:45:53.000Z",
ETag: "\"5d1143df07a17b23320d0da161e2819e\"",
Size: 30,
StorageClass: "STANDARD",
Owner: &Owner{
ID: "1250000000",
DisplayName: "1250000000",
},
},
},
DeleteMarker: []ListVersionsResultDeleteMarker{
{
Key: "example-object-1.jpg",
VersionId: "MTg0NDUxNzgxMjEzNjE1OTcxMzM",
IsLatest: false,
LastModified: "2019-08-16T10:45:47.000Z",
Owner: &Owner{
ID: "1250000000",
DisplayName: "1250000000",
},
},
},
}
opt := &BucketGetObjectVersionsOptions{
Delimiter: "/",
}
res, _, err := client.Bucket.GetObjectVersions(context.Background(), opt)
if err != nil {
t.Fatalf("Bucket.GetObjectVersions returned error: %v", err) t.Fatalf("Bucket.GetObjectVersions returned error: %v", err)
} }
if !reflect.DeepEqual(res, want) { if !reflect.DeepEqual(res, want) {

17
ci.go

@ -48,13 +48,14 @@ type PicImageInfo struct {
Quality int `xml:"Quality,omitempty"` Quality int `xml:"Quality,omitempty"`
} }
type PicProcessObject struct { 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"`
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"`
WatermarkStatus int `xml:"WatermarkStatus,omitempty"`
} }
type picOperationsHeader struct { type picOperationsHeader struct {
@ -171,7 +172,7 @@ type VideoAuditingJobDetail struct {
CreationTime string `xml:",omitempty"` CreationTime string `xml:",omitempty"`
Object string `xml:",omitempty"` Object string `xml:",omitempty"`
SnapshotCount string `xml:",omitempty"` SnapshotCount string `xml:",omitempty"`
result int `xml:",omitempty"`
Result int `xml:",omitempty"`
PornInfo *RecognitionInfo `xml:",omitempty"` PornInfo *RecognitionInfo `xml:",omitempty"`
TerrorismInfo *RecognitionInfo `xml:",omitempty"` TerrorismInfo *RecognitionInfo `xml:",omitempty"`
PoliticsInfo *RecognitionInfo `xml:",omitempty"` PoliticsInfo *RecognitionInfo `xml:",omitempty"`

271
ci_doc.go

@ -0,0 +1,271 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
type DocProcessJobInput struct {
Object string `xml:"Object,omitempty"`
}
type DocProcessJobOutput struct {
Region string `xml:"Region,omitempty"`
Bucket string `xml:"Bucket,omitempty"`
Object string `xml:"Object,omitempty"`
}
type DocProcessJobDocProcess struct {
SrcType string `xml:"SrcType,omitempty"`
TgtType string `xml:"TgtType,omitempty"`
SheetId int `xml:"SheetId,omitempty"`
StartPage int `xml:"StartPage,omitempty"`
EndPage int `xml:"EndPage,omitempty"`
ImageParams string `xml:"ImageParams,omitempty"`
DocPassword string `xml:"DocPassword,omitempty"`
Comments int `xml:"Comments,omitempty"`
PaperDirection int `xml:"PaperDirection,omitempty"`
Quality int `xml:"Quality,omitempty"`
Zoom int `xml:"Zoom,omitempty"`
}
type DocProcessJobDocProcessResult struct {
FailPageCount int `xml:",omitempty"`
SuccPageCount int `xml:"SuccPageCount,omitempty"`
TaskId string `xml:"TaskId,omitempty"`
TgtType string `xml:"TgtType,omitempty"`
TotalPageCount int `xml:"TotalPageCount,omitempty"`
PageInfo struct {
PageNo int `xml:"PageNo,omitempty"`
TgtUri string `xml:"TgtUri,omitempty"`
} `xml:"PageInfo,omitempty"`
}
type DocProcessJobOperation struct {
Output *DocProcessJobOutput `xml:"Output,omitempty"`
DocProcess *DocProcessJobDocProcess `xml:"DocProcess,omitempty"`
DocProcessResult *DocProcessJobDocProcessResult `xml:"DocProcessResult,omitempty"`
}
type DocProcessJobDetail struct {
Code string `xml:"Code,omitempty"`
Message string `xml:"Message,omitempty"`
JobId string `xml:"JobId,omitempty"`
Tag string `xml:"Tag,omitempty"`
State string `xml:"State,omitempty"`
CreationTime string `xml:"CreationTime,omitempty"`
QueueId string `xml:"QueueId,omitempty"`
Input *DocProcessJobInput `xml:"Input,omitempty"`
Operation *DocProcessJobOperation `xml:"Operation,omitempty"`
}
type CreateDocProcessJobsOptions struct {
XMLName xml.Name `xml:"Request"`
Tag string `xml:"Tag,omitempty"`
Input *DocProcessJobInput `xml:"Input,omitempty"`
Operation *DocProcessJobOperation `xml:"Operation,omitempty"`
QueueId string `xml:"QueueId,omitempty"`
}
type CreateDocProcessJobsResult struct {
XMLName xml.Name `xml:"Response"`
JobsDetail DocProcessJobDetail `xml:"JobsDetail,omitempty"`
}
func (s *CIService) CreateDocProcessJobs(ctx context.Context, opt *CreateDocProcessJobsOptions) (*CreateDocProcessJobsResult, *Response, error) {
var res CreateDocProcessJobsResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/doc_jobs",
method: http.MethodPost,
body: opt,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
type DescribeDocProcessJobResult struct {
XMLName xml.Name `xml:"Response"`
JobsDetail *DocProcessJobDetail `xml:"JobsDetail,omitempty"`
NonExistJobIds string `xml:"NonExistJobIds,omitempty"`
}
func (s *CIService) DescribeDocProcessJob(ctx context.Context, jobid string) (*DescribeDocProcessJobResult, *Response, error) {
var res DescribeDocProcessJobResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/doc_jobs/" + jobid,
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
type DescribeDocProcessJobsOptions struct {
QueueId string `url:"queueId,omitempty"`
Tag string `url:"tag,omitempty"`
OrderByTime string `url:"orderByTime,omitempty"`
NextToken string `url:"nextToken,omitempty"`
Size int `url:"size,omitempty"`
States string `url:"states,omitempty"`
StartCreationTime string `url:"startCreationTime,omitempty"`
EndCreationTime string `url:"endCreationTime,omitempty"`
}
type DescribeDocProcessJobsResult struct {
XMLName xml.Name `xml:"Response"`
JobsDetail []DocProcessJobDetail `xml:"JobsDetail,omitempty"`
NextToken string `xml:"NextToken,omitempty"`
}
func (s *CIService) DescribeDocProcessJobs(ctx context.Context, opt *DescribeDocProcessJobsOptions) (*DescribeDocProcessJobsResult, *Response, error) {
var res DescribeDocProcessJobsResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/doc_jobs",
optQuery: opt,
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
type DescribeDocProcessQueuesOptions struct {
QueueIds string `url:"queueIds,omitempty"`
State string `url:"state,omitempty"`
PageNumber int `url:"pageNumber,omitempty"`
PageSize int `url:"pageSize,omitempty"`
}
type DescribeDocProcessQueuesResult struct {
XMLName xml.Name `xml:"Response"`
RequestId string `xml:"RequestId,omitempty"`
TotalCount int `xml:"TotalCount,omitempty"`
PageNumber int `xml:"PageNumber,omitempty"`
PageSize int `xml:"PageSize,omitempty"`
QueueList []DocProcessQueue `xml:"QueueList,omitempty"`
NonExistPIDs []string `xml:"NonExistPIDs,omitempty"`
}
type DocProcessQueue struct {
QueueId string `xml:"QueueId,omitempty"`
Name string `xml:"Name,omitempty"`
State string `xml:"State,omitempty"`
MaxSize int `xml:"MaxSize,omitempty"`
MaxConcurrent int `xml:"MaxConcurrent,omitempty"`
UpdateTime string `xml:"UpdateTime,omitempty"`
CreateTime string `xml:"CreateTime,omitempty"`
NotifyConfig *DocProcessQueueNotifyConfig `xml:"NotifyConfig,omitempty"`
}
type DocProcessQueueNotifyConfig struct {
Url string `xml:"Url,omitempty"`
State string `xml:"State,omitempty"`
Type string `xml:"Type,omitempty"`
Event string `xml:"Event,omitempty"`
}
func (s *CIService) DescribeDocProcessQueues(ctx context.Context, opt *DescribeDocProcessQueuesOptions) (*DescribeDocProcessQueuesResult, *Response, error) {
var res DescribeDocProcessQueuesResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/docqueue",
optQuery: opt,
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
type UpdateDocProcessQueueOptions struct {
XMLName xml.Name `xml:"Request"`
Name string `xml:"Name,omitempty"`
QueueID string `xml:"QueueID,omitempty"`
State string `xml:"State,omitempty"`
NotifyConfig *DocProcessQueueNotifyConfig `xml:"NotifyConfig,omitempty"`
}
type UpdateDocProcessQueueResult struct {
XMLName xml.Name `xml:"Response"`
RequestId string `xml:"RequestId"`
Queue *DocProcessQueue `xml:"Queue"`
}
func (s *CIService) UpdateDocProcessQueue(ctx context.Context, opt *UpdateDocProcessQueueOptions) (*UpdateDocProcessQueueResult, *Response, error) {
var res UpdateDocProcessQueueResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/docqueue/" + opt.QueueID,
body: opt,
method: http.MethodPut,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
type DescribeDocProcessBucketsOptions struct {
Regions string `url:"regions,omitempty"`
BucketNames string `url:"bucketNames,omitempty"`
BucketName string `url:"bucketName,omitempty"`
PageNumber int `url:"pageNumber,omitempty"`
PageSize int `url:"pageSize,omitempty"`
}
type DescribeDocProcessBucketsResult struct {
XMLName xml.Name `xml:"Response"`
RequestId string `xml:"RequestId,omitempty"`
TotalCount int `xml:"TotalCount,omitempty"`
PageNumber int `xml:"PageNumber,omitempty"`
PageSize int `xml:"PageSize,omitempty"`
DocBucketList []DocProcessBucket `xml:"DocBucketList,omitempty"`
}
type DocProcessBucket struct {
BucketId string `xml:"BucketId,omitempty"`
Name string `xml:"Name,omitempty"`
Region string `xml:"Region,omitempty"`
CreateTime string `xml:"CreateTime,omitempty"`
AliasBucketId string `xml:"AliasBucketId,omitempty"`
}
func (s *CIService) DescribeDocProcessBuckets(ctx context.Context, opt *DescribeDocProcessBucketsOptions) (*DescribeDocProcessBucketsResult, *Response, error) {
var res DescribeDocProcessBucketsResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.CIURL,
uri: "/docbucket",
optQuery: opt,
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
type DocPreviewOptions struct {
SrcType string `url:"srcType,omitempty"`
Page int `url:"page,omitempty"`
ImageParams string `url:"ImageParams,omitempty"`
Sheet int `url:"sheet,omitempty"`
DstType string `url:"dstType,omitempty"`
Password string `url:"password,omitempty"`
Comment int `url:"comment,omitempty"`
ExcelPaperDirection int `url:"excelPaperDirection,omitempty"`
Quality int `url:"quality,omitempty"`
Zoom int `url:"zoom,omitempty"`
}
func (s *CIService) DocPreview(ctx context.Context, name string, opt *DocPreviewOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/" + encodeURIComponent(name) + "?ci-process=doc-preview",
optQuery: opt,
method: http.MethodGet,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}

2
cos.go

@ -22,7 +22,7 @@ import (
const ( const (
// Version current go sdk version // Version current go sdk version
Version = "0.7.12"
Version = "0.7.13"
userAgent = "cos-go-sdk-v5/" + Version userAgent = "cos-go-sdk-v5/" + Version
contentTypeXML = "application/xml" contentTypeXML = "application/xml"
defaultServiceBaseURL = "http://service.cos.myqcloud.com" defaultServiceBaseURL = "http://service.cos.myqcloud.com"

2
costesting/ci_test.go

@ -226,6 +226,7 @@ func (s *CosTestSuite) TestVersionAndReplication() {
} }
_, err := s.Client.Bucket.PutVersioning(context.Background(), opt) _, err := s.Client.Bucket.PutVersioning(context.Background(), opt)
assert.Nil(s.T(), err, "PutVersioning Failed") assert.Nil(s.T(), err, "PutVersioning Failed")
time.Sleep(time.Second)
v, _, err := s.Client.Bucket.GetVersioning(context.Background()) v, _, err := s.Client.Bucket.GetVersioning(context.Background())
assert.Nil(s.T(), err, "GetVersioning Failed") assert.Nil(s.T(), err, "GetVersioning Failed")
assert.Equal(s.T(), "Enabled", v.Status, "Get Wrong Version status") assert.Equal(s.T(), "Enabled", v.Status, "Get Wrong Version status")
@ -248,6 +249,7 @@ func (s *CosTestSuite) TestVersionAndReplication() {
_, err = s.Client.Bucket.PutBucketReplication(context.Background(), repOpt) _, err = s.Client.Bucket.PutBucketReplication(context.Background(), repOpt)
assert.Nil(s.T(), err, "PutBucketReplication Failed") assert.Nil(s.T(), err, "PutBucketReplication Failed")
time.Sleep(time.Second)
vr, _, err := s.Client.Bucket.GetBucketReplication(context.Background()) vr, _, err := s.Client.Bucket.GetBucketReplication(context.Background())
assert.Nil(s.T(), err, "GetBucketReplication Failed") assert.Nil(s.T(), err, "GetBucketReplication Failed")
for _, r := range vr.Rule { for _, r := range vr.Rule {

14
example/bucket/intelligenttiering.go

@ -48,13 +48,13 @@ func main() {
}, },
}) })
opt := &cos.BucketPutIntelligentTieringOptions {
Status: "Enabled",
Transition: &cos.BucketIntelligentTieringTransition {
Days: 30,
},
}
_, err := c.Bucket.PutIntelligentTiering(context.Background(), opt)
opt := &cos.BucketPutIntelligentTieringOptions{
Status: "Enabled",
Transition: &cos.BucketIntelligentTieringTransition{
Days: 30,
},
}
_, err := c.Bucket.PutIntelligentTiering(context.Background(), opt)
log_status(err) log_status(err)
res, _, err := c.Bucket.GetIntelligentTiering(context.Background()) res, _, err := c.Bucket.GetIntelligentTiering(context.Background())
log_status(err) log_status(err)

132
example/object/ci_doc_process.go

@ -0,0 +1,132 @@
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()
}

25
example/object/get.go

@ -19,7 +19,7 @@ func log_status(err error) {
} }
if cos.IsNotFoundError(err) { if cos.IsNotFoundError(err) {
// WARN // WARN
fmt.Println("WARN: Resource is not existed")
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok { } else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code) fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message) fmt.Printf("ERROR: Message: %v\n", e.Message)
@ -33,7 +33,7 @@ func log_status(err error) {
} }
func main() { func main() {
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com")
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
b := &cos.BaseURL{BucketURL: u} b := &cos.BaseURL{BucketURL: u}
c := cos.NewClient(b, &http.Client{ c := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{ Transport: &cos.AuthorizationTransport{
@ -48,8 +48,8 @@ func main() {
}, },
}) })
// Case1 Download object into ReadCloser(). the body needs to be closed
name := "test/hello.txt"
// Case1 通过resp.Body下载对象,Body需要关闭
name := "test/example"
resp, err := c.Object.Get(context.Background(), name, nil) resp, err := c.Object.Get(context.Background(), name, nil)
log_status(err) log_status(err)
@ -57,8 +57,8 @@ func main() {
resp.Body.Close() resp.Body.Close()
fmt.Printf("%s\n", string(bs)) fmt.Printf("%s\n", string(bs))
// Case2 Download object to local file. the body needs to be closed
fd, err := os.OpenFile("hello.txt", os.O_WRONLY|os.O_CREATE, 0660)
// Case2 下载对象到文件. Body需要关闭
fd, err := os.OpenFile("test", os.O_WRONLY|os.O_CREATE, 0660)
log_status(err) log_status(err)
defer fd.Close() defer fd.Close()
@ -68,11 +68,11 @@ func main() {
io.Copy(fd, resp.Body) io.Copy(fd, resp.Body)
resp.Body.Close() resp.Body.Close()
// Case3 Download object to local file path
_, err = c.Object.GetToFile(context.Background(), name, "hello_1.txt", nil)
// Case3 下载对象到文件
_, err = c.Object.GetToFile(context.Background(), name, "test", nil)
log_status(err) log_status(err)
// Case4 Download object with range header, can used to concurrent download
// Case4 range下载对象,可以根据range实现并发下载
opt := &cos.ObjectGetOptions{ opt := &cos.ObjectGetOptions{
ResponseContentType: "text/html", ResponseContentType: "text/html",
Range: "bytes=0-3", Range: "bytes=0-3",
@ -82,4 +82,11 @@ func main() {
bs, _ = ioutil.ReadAll(resp.Body) bs, _ = ioutil.ReadAll(resp.Body)
resp.Body.Close() resp.Body.Close()
fmt.Printf("%s\n", string(bs)) fmt.Printf("%s\n", string(bs))
// Case5 下载对象到文件,查看下载进度
opt = &cos.ObjectGetOptions{
Listener: &cos.DefaultProgressListener{},
}
_, err = c.Object.GetToFile(context.Background(), name, "test", opt)
log_status(err)
} }

17
example/object/put.go

@ -19,7 +19,7 @@ func log_status(err error) {
} }
if cos.IsNotFoundError(err) { if cos.IsNotFoundError(err) {
// WARN // WARN
fmt.Println("WARN: Resource is not existed")
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok { } else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code) fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message) fmt.Printf("ERROR: Message: %v\n", e.Message)
@ -44,20 +44,19 @@ func main() {
// Notice when put a large file and set need the request body, might happend out of memory error. // Notice when put a large file and set need the request body, might happend out of memory error.
RequestBody: false, RequestBody: false,
ResponseHeader: true, ResponseHeader: true,
ResponseBody: true,
ResponseBody: false,
}, },
}, },
}) })
// Case1 normal put object
name := "test/objectPut.go"
// Case1 上传对象
name := "test/example"
f := strings.NewReader("test") f := strings.NewReader("test")
_, err := c.Object.Put(context.Background(), name, f, nil) _, err := c.Object.Put(context.Background(), name, f, nil)
log_status(err) log_status(err)
// Case2 put object with the options
name = "test/put_option.go"
// Case2 使用options上传对象
f = strings.NewReader("test xxx") f = strings.NewReader("test xxx")
opt := &cos.ObjectPutOptions{ opt := &cos.ObjectPutOptions{
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
@ -71,7 +70,11 @@ func main() {
_, err = c.Object.Put(context.Background(), name, f, opt) _, err = c.Object.Put(context.Background(), name, f, opt)
log_status(err) log_status(err)
// Case3 put object by local file path
// Case3 通过本地文件上传对象
_, err = c.Object.PutFromFile(context.Background(), name, "./test", nil) _, err = c.Object.PutFromFile(context.Background(), name, "./test", nil)
log_status(err) log_status(err)
// Case4 查看上传进度
opt.ObjectPutHeaderOptions.Listener = &cos.DefaultProgressListener{}
_, err = c.Object.PutFromFile(context.Background(), name, "./test", opt)
} }

59
example/object/put_with_timeout.go

@ -0,0 +1,59 @@
package main
import (
"context"
"fmt"
"net/url"
"os"
"time"
"net/http"
"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,
},
},
Timeout: 5 * time.Second, // HTTP超时时间
})
// Case1 上传对象
name := "test/example"
// Case3 通过本地文件上传对象
ctx, _ := context.WithTimeout(context.Background(), 2*time.Second) // context超时时间
_, err := c.Object.PutFromFile(ctx, name, "./test", nil) // 请求的超时时间为 min{context超时时间, HTTP超时时间}
log_status(err)
}

21
example/object/upload.go

@ -49,9 +49,26 @@ func main() {
}, },
}) })
// Case1 多线程上传对象
opt := &cos.MultiUploadOptions{
ThreadPoolSize: 3,
}
v, _, err := c.Object.Upload( v, _, err := c.Object.Upload(
context.Background(), "gomulput1G", "./test1G", nil,
context.Background(), "gomulput1G", "./test1G", opt,
)
log_status(err)
fmt.Printf("Case1 done, %v\n", v)
// Case2 多线程上传对象,查看上传进度
opt.OptIni = &cos.InitiateMultipartUploadOptions{
nil,
&cos.ObjectPutHeaderOptions{
Listener: &cos.DefaultProgressListener{},
},
}
v, _, err = c.Object.Upload(
context.Background(), "gomulput1G", "./test1G", opt,
) )
log_status(err) log_status(err)
fmt.Println(v)
fmt.Printf("Case2 done, %v\n", v)
} }

40
example/object/uploadPart.go

@ -41,7 +41,7 @@ func initUpload(c *cos.Client, name string) *cos.InitiateMultipartUploadResult {
} }
func main() { func main() {
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com")
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
b := &cos.BaseURL{BucketURL: u} b := &cos.BaseURL{BucketURL: u}
c := cos.NewClient(b, &http.Client{ c := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{ Transport: &cos.AuthorizationTransport{
@ -49,20 +49,50 @@ func main() {
SecretKey: os.Getenv("COS_SECRETKEY"), SecretKey: os.Getenv("COS_SECRETKEY"),
Transport: &debug.DebugRequestTransport{ Transport: &debug.DebugRequestTransport{
RequestHeader: true, RequestHeader: true,
RequestBody: true,
RequestBody: false,
ResponseHeader: true, ResponseHeader: true,
ResponseBody: true,
ResponseBody: false,
}, },
}, },
}) })
optcom := &cos.CompleteMultipartUploadOptions{}
name := "test/test_multi_upload.go" name := "test/test_multi_upload.go"
up := initUpload(c, name) up := initUpload(c, name)
uploadID := up.UploadID uploadID := up.UploadID
fd, err := os.Open("test")
if err != nil {
fmt.Printf("Open File Error: %v\n", err)
return
}
defer fd.Close()
stat, err := fd.Stat()
if err != nil {
fmt.Printf("Stat File Error: %v\n", err)
return
}
opt := &cos.ObjectUploadPartOptions{
Listener: &cos.DefaultProgressListener{},
ContentLength: int(stat.Size()),
}
resp, err := c.Object.UploadPart(
context.Background(), name, uploadID, 1, fd, opt,
)
optcom.Parts = append(optcom.Parts, cos.Object{
PartNumber: 1, ETag: resp.Header.Get("ETag"),
})
log_status(err)
f := strings.NewReader("test heoo") f := strings.NewReader("test heoo")
_, err := c.Object.UploadPart(
context.Background(), name, uploadID, 1, f, nil,
resp, err = c.Object.UploadPart(
context.Background(), name, uploadID, 2, f, nil,
) )
log_status(err) log_status(err)
optcom.Parts = append(optcom.Parts, cos.Object{
PartNumber: 2, ETag: resp.Header.Get("ETag"),
})
_, _, err = c.Object.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
log_status(err)
} }

28
helper.go

@ -5,8 +5,11 @@ import (
"crypto/md5" "crypto/md5"
"crypto/sha1" "crypto/sha1"
"fmt" "fmt"
"io"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strings"
) )
// 计算 md5 或 sha1 时的分块大小 // 计算 md5 或 sha1 时的分块大小
@ -112,3 +115,28 @@ func DecodeURIComponent(s string) (string, error) {
func EncodeURIComponent(s string) string { func EncodeURIComponent(s string) string {
return encodeURIComponent(s) return encodeURIComponent(s)
} }
func GetReaderLen(reader io.Reader) (length int64, err error) {
switch v := reader.(type) {
case *bytes.Buffer:
length = int64(v.Len())
case *bytes.Reader:
length = int64(v.Len())
case *strings.Reader:
length = int64(v.Len())
case *os.File:
stat, ferr := v.Stat()
if ferr != nil {
err = fmt.Errorf("can't get reader length: %s", ferr.Error())
} else {
length = stat.Size()
}
case *io.LimitedReader:
length = int64(v.N)
case FixedLengthReader:
length = v.Size()
default:
err = fmt.Errorf("can't get reader content length, unkown reader type")
}
return
}

61
object.go

@ -12,6 +12,7 @@ import (
"net/url" "net/url"
"os" "os"
"sort" "sort"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -35,6 +36,9 @@ type ObjectGetOptions struct {
XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"` XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
XCosTrafficLimit int `header:"x-cos-traffic-limit,omitempty" url:"-" xml:"-"` XCosTrafficLimit int `header:"x-cos-traffic-limit,omitempty" url:"-" xml:"-"`
// 下载进度, ProgressCompleteEvent不能表示对应API调用成功,API是否调用成功的判断标准为返回err==nil
Listener ProgressListener `header:"-" url:"-" xml:"-"`
} }
// presignedURLTestingOptions is the opt of presigned url // presignedURLTestingOptions is the opt of presigned url
@ -65,6 +69,14 @@ func (s *ObjectService) Get(ctx context.Context, name string, opt *ObjectGetOpti
disableCloseBody: true, disableCloseBody: true,
} }
resp, err := s.client.send(ctx, &sendOpt) 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 return resp, err
} }
@ -152,6 +164,9 @@ type ObjectPutHeaderOptions struct {
//兼容其他自定义头部 //兼容其他自定义头部
XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"` XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
XCosTrafficLimit int `header:"x-cos-traffic-limit,omitempty" url:"-" xml:"-"` XCosTrafficLimit int `header:"x-cos-traffic-limit,omitempty" url:"-" xml:"-"`
// 上传进度, ProgressCompleteEvent不能表示对应API调用成功,API是否调用成功的判断标准为返回err==nil
Listener ProgressListener `header:"-" url:"-" xml:"-"`
} }
// ObjectPutOptions the options of put object // ObjectPutOptions the options of put object
@ -166,6 +181,14 @@ type ObjectPutOptions struct {
// //
// https://www.qcloud.com/document/product/436/7749 // https://www.qcloud.com/document/product/436/7749
func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, opt *ObjectPutOptions) (*Response, error) { func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, opt *ObjectPutOptions) (*Response, error) {
if opt != nil && opt.Listener != nil {
totalBytes, err := GetReaderLen(r)
if err != nil {
return nil, err
}
r = TeeReader(r, nil, totalBytes, opt.Listener)
}
sendOpt := sendOptions{ sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL, baseURL: s.client.BaseURL.BucketURL,
uri: "/" + encodeURIComponent(name), uri: "/" + encodeURIComponent(name),
@ -174,6 +197,7 @@ func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, opt *
optHeader: opt, optHeader: opt,
} }
resp, err := s.client.send(ctx, &sendOpt) resp, err := s.client.send(ctx, &sendOpt)
return resp, err return resp, err
} }
@ -571,27 +595,27 @@ func DividePart(fileSize int64) (int64, int64) {
return partNum, partSize return partNum, partSize
} }
func SplitFileIntoChunks(filePath string, partSize int64) ([]Chunk, int, error) {
func SplitFileIntoChunks(filePath string, partSize int64) (int64, []Chunk, int, error) {
if filePath == "" { if filePath == "" {
return nil, 0, errors.New("filePath invalid")
return 0, nil, 0, errors.New("filePath invalid")
} }
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
return nil, 0, err
return 0, nil, 0, err
} }
defer file.Close() defer file.Close()
stat, err := file.Stat() stat, err := file.Stat()
if err != nil { if err != nil {
return nil, 0, err
return 0, nil, 0, err
} }
var partNum int64 var partNum int64
if partSize > 0 { if partSize > 0 {
partSize = partSize * 1024 * 1024 partSize = partSize * 1024 * 1024
partNum = stat.Size() / partSize partNum = stat.Size() / partSize
if partNum >= 10000 { if partNum >= 10000 {
return nil, 0, errors.New("Too many parts, out of 10000")
return 0, nil, 0, errors.New("Too many parts, out of 10000")
} }
} else { } else {
partNum, partSize = DividePart(stat.Size()) partNum, partSize = DividePart(stat.Size())
@ -614,7 +638,7 @@ func SplitFileIntoChunks(filePath string, partSize int64) ([]Chunk, int, error)
partNum++ partNum++
} }
return chunks, int(partNum), nil
return int64(stat.Size()), chunks, int(partNum), nil
} }
@ -707,7 +731,7 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
opt = &MultiUploadOptions{} opt = &MultiUploadOptions{}
} }
// 1.Get the file chunk // 1.Get the file chunk
chunks, partNum, err := SplitFileIntoChunks(filepath, opt.PartSize)
totalBytes, chunks, partNum, err := SplitFileIntoChunks(filepath, opt.PartSize)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -768,6 +792,15 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
go worker(s, chjobs, chresults) go worker(s, chjobs, chresults)
} }
// progress started event
var listener ProgressListener
var consumedBytes int64
if opt.OptIni != nil {
listener = opt.OptIni.Listener
}
event := newProgressEvent(ProgressStartedEvent, 0, 0, totalBytes)
progressCallback(listener, event)
// 4.Push jobs // 4.Push jobs
for _, chunk := range chunks { for _, chunk := range chunks {
if chunk.Done { if chunk.Done {
@ -798,22 +831,34 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
optcom.Parts = append(optcom.Parts, Object{ optcom.Parts = append(optcom.Parts, Object{
PartNumber: chunks[i].Number, ETag: chunks[i].ETag}, PartNumber: chunks[i].Number, ETag: chunks[i].ETag},
) )
consumedBytes += chunks[i].Size
event = newProgressEvent(ProgressDataEvent, chunks[i].Size, consumedBytes, totalBytes)
progressCallback(listener, event)
continue continue
} }
res := <-chresults res := <-chresults
// Notice one part fail can not get the etag according. // Notice one part fail can not get the etag according.
if res.Resp == nil || res.err != nil { if res.Resp == nil || res.err != nil {
// Some part already fail, can not to get the header inside. // Some part already fail, can not to get the header inside.
return nil, nil, fmt.Errorf("UploadID %s, part %d failed to get resp content. error: %s", uploadID, res.PartNumber, res.err.Error())
err := fmt.Errorf("UploadID %s, part %d failed to get resp content. error: %s", uploadID, res.PartNumber, res.err.Error())
event = newProgressEvent(ProgressFailedEvent, 0, consumedBytes, totalBytes, err)
progressCallback(listener, event)
return nil, nil, err
} }
// Notice one part fail can not get the etag according. // Notice one part fail can not get the etag according.
etag := res.Resp.Header.Get("ETag") etag := res.Resp.Header.Get("ETag")
optcom.Parts = append(optcom.Parts, Object{ optcom.Parts = append(optcom.Parts, Object{
PartNumber: res.PartNumber, ETag: etag}, PartNumber: res.PartNumber, ETag: etag},
) )
consumedBytes += chunks[res.PartNumber-1].Size
event = newProgressEvent(ProgressDataEvent, chunks[res.PartNumber-1].Size, consumedBytes, totalBytes)
progressCallback(listener, event)
} }
sort.Sort(ObjectList(optcom.Parts)) sort.Sort(ObjectList(optcom.Parts))
event = newProgressEvent(ProgressCompletedEvent, 0, consumedBytes, totalBytes)
progressCallback(listener, event)
v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom) v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
return v, resp, err return v, resp, err

10
object_part.go

@ -50,6 +50,9 @@ type ObjectUploadPartOptions struct {
XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"` XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
XCosTrafficLimit int `header:"x-cos-traffic-limit,omitempty" url:"-" xml:"-"` XCosTrafficLimit int `header:"x-cos-traffic-limit,omitempty" url:"-" xml:"-"`
// 上传进度, ProgressCompleteEvent不能表示对应API调用成功,API是否调用成功的判断标准为返回err==nil
Listener ProgressListener `header:"-" url:"-" xml:"-"`
} }
// UploadPart 请求实现在初始化以后的分块上传,支持的块的数量为1到10000,块的大小为1 MB 到5 GB。 // UploadPart 请求实现在初始化以后的分块上传,支持的块的数量为1到10000,块的大小为1 MB 到5 GB。
@ -61,6 +64,13 @@ type ObjectUploadPartOptions struct {
// //
// https://www.qcloud.com/document/product/436/7750 // https://www.qcloud.com/document/product/436/7750
func (s *ObjectService) UploadPart(ctx context.Context, name, uploadID string, partNumber int, r io.Reader, opt *ObjectUploadPartOptions) (*Response, error) { func (s *ObjectService) UploadPart(ctx context.Context, name, uploadID string, partNumber int, r io.Reader, opt *ObjectUploadPartOptions) (*Response, error) {
if opt != nil && opt.Listener != nil {
totalBytes, err := GetReaderLen(r)
if err != nil {
return nil, err
}
r = TeeReader(r, nil, totalBytes, opt.Listener)
}
u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID) u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID)
sendOpt := sendOptions{ sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL, baseURL: s.client.BaseURL.BucketURL,

2
object_test.go

@ -129,7 +129,7 @@ func TestObjectService_Options(t *testing.T) {
}) })
opt := &ObjectOptionsOptions{ opt := &ObjectOptionsOptions{
Origin: "www.qq.com",
Origin: "www.qq.com",
AccessControlRequestMethod: "PUT", AccessControlRequestMethod: "PUT",
} }

135
progress.go

@ -0,0 +1,135 @@
package cos
import (
"fmt"
"io"
)
type ProgressEventType int
const (
// 数据开始传输
ProgressStartedEvent ProgressEventType = iota
// 数据传输中
ProgressDataEvent
// 数据传输完成, 但不能表示对应API调用完成
ProgressCompletedEvent
// 只有在数据传输时发生错误才会返回
ProgressFailedEvent
)
type ProgressEvent struct {
EventType ProgressEventType
RWBytes int64
ConsumedBytes int64
TotalBytes int64
Err error
}
func newProgressEvent(eventType ProgressEventType, rwBytes, consumed, total int64, err ...error) *ProgressEvent {
event := &ProgressEvent{
EventType: eventType,
RWBytes: rwBytes,
ConsumedBytes: consumed,
TotalBytes: total,
}
if len(err) > 0 {
event.Err = err[0]
}
return event
}
// 用户自定义Listener需要实现该方法
type ProgressListener interface {
ProgressChangedCallback(event *ProgressEvent)
}
func progressCallback(listener ProgressListener, event *ProgressEvent) {
if listener != nil && event != nil {
listener.ProgressChangedCallback(event)
}
}
type teeReader struct {
reader io.Reader
writer io.Writer
consumedBytes int64
totalBytes int64
listener ProgressListener
}
func (r *teeReader) Read(p []byte) (int, error) {
if r.consumedBytes == 0 {
event := newProgressEvent(ProgressStartedEvent, 0, r.consumedBytes, r.totalBytes)
progressCallback(r.listener, event)
}
n, err := r.reader.Read(p)
if err != nil && err != io.EOF {
event := newProgressEvent(ProgressFailedEvent, 0, r.consumedBytes, r.totalBytes, err)
progressCallback(r.listener, event)
}
if n > 0 {
r.consumedBytes += int64(n)
if r.writer != nil {
if n, err := r.writer.Write(p[:n]); err != nil {
return n, err
}
}
if r.listener != nil {
event := newProgressEvent(ProgressDataEvent, int64(n), r.consumedBytes, r.totalBytes)
progressCallback(r.listener, event)
}
}
if err == io.EOF {
event := newProgressEvent(ProgressCompletedEvent, int64(n), r.consumedBytes, r.totalBytes)
progressCallback(r.listener, event)
}
return n, err
}
func (r *teeReader) Close() error {
if rc, ok := r.reader.(io.ReadCloser); ok {
return rc.Close()
}
return nil
}
func TeeReader(reader io.Reader, writer io.Writer, total int64, listener ProgressListener) *teeReader {
return &teeReader{
reader: reader,
writer: writer,
consumedBytes: 0,
totalBytes: total,
listener: listener,
}
}
type FixedLengthReader interface {
io.Reader
Size() int64
}
type DefaultProgressListener struct {
}
func (l *DefaultProgressListener) ProgressChangedCallback(event *ProgressEvent) {
switch event.EventType {
case ProgressStartedEvent:
fmt.Printf("Transfer Start [ConsumedBytes/TotalBytes: %d/%d]\n",
event.ConsumedBytes, event.TotalBytes)
case ProgressDataEvent:
fmt.Printf("\rTransfer Data [ConsumedBytes/TotalBytes: %d/%d, %d%%]",
event.ConsumedBytes, event.TotalBytes, event.ConsumedBytes*100/event.TotalBytes)
case ProgressCompletedEvent:
fmt.Printf("\nTransfer Complete [ConsumedBytes/TotalBytes: %d/%d]\n",
event.ConsumedBytes, event.TotalBytes)
case ProgressFailedEvent:
fmt.Printf("\nTransfer Failed [ConsumedBytes/TotalBytes: %d/%d] [Err: %v]\n",
event.ConsumedBytes, event.TotalBytes, event.Err)
default:
fmt.Printf("Progress Changed Error: unknown progress event type\n")
}
}

8
service_test.go

@ -48,13 +48,13 @@ func TestServiceService_Get(t *testing.T) {
}, },
Buckets: []Bucket{ Buckets: []Bucket{
{ {
Name: "huadong-1253846586",
Region: "ap-shanghai",
Name: "huadong-1253846586",
Region: "ap-shanghai",
CreationDate: "2017-06-16T13:08:28Z", CreationDate: "2017-06-16T13:08:28Z",
}, },
{ {
Name: "huanan-1253846586",
Region: "ap-guangzhou",
Name: "huanan-1253846586",
Region: "ap-guangzhou",
CreationDate: "2017-06-10T09:00:07Z", CreationDate: "2017-06-10T09:00:07Z",
}, },
}, },

Loading…
Cancel
Save