mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #10562 from ioito/automated-cherry-pick-of-#10561-upstream-release-3.7
Automated cherry pick of #10561: fix: vendor update
This commit is contained in:
@@ -114,7 +114,7 @@ require (
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.135+incompatible
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.10
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.24
|
||||
github.com/tinylib/msgp v1.1.0 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
|
||||
github.com/tredoe/osutil v0.0.0-20161130133508-7d3ee1afa71c
|
||||
@@ -146,13 +146,13 @@ require (
|
||||
k8s.io/apimachinery v0.19.3
|
||||
k8s.io/client-go v0.19.3
|
||||
k8s.io/cluster-bootstrap v0.19.3
|
||||
yunion.io/x/executor v0.0.0-20201231064744-df32f32165a9
|
||||
yunion.io/x/executor v0.0.0-20210310041834-3fcd1574f68e
|
||||
yunion.io/x/jsonutils v0.0.0-20201110084044-3e4e1cb49769
|
||||
yunion.io/x/log v0.0.0-20201210064738-43181789dc74
|
||||
yunion.io/x/ovsdb v0.0.0-20200526071744-27bf0940cbc7
|
||||
yunion.io/x/pkg v0.0.0-20210218105412-13a69f60034c
|
||||
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e
|
||||
yunion.io/x/sqlchemy v0.0.0-20210204030051-4fb14f92a88f
|
||||
yunion.io/x/sqlchemy v0.0.0-20210329171939-2c520f93aee3
|
||||
yunion.io/x/structarg v0.0.0-20200720093445-9f850fa222ce
|
||||
)
|
||||
|
||||
|
||||
@@ -588,8 +588,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
github.com/syncthing/syncthing v0.14.48-rc.4/go.mod h1:nw3siZwHPA6M8iSfjDCWQ402eqvEIasMQOE8nFOxy7M=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.135+incompatible h1:QIMoFqKCmNp4HPLiTR+couZbHsIZfoOllncHYvtqse8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v3.0.135+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.10 h1:hJPLdR4RPsnubB3M9qXNBeUbcOJMVANnO/L6Att/PZU=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.10/go.mod h1:wQBO5HdAkLjj2q6XQiIfDSP8DXDNrppDRw2Kp/1BODA=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.24 h1:ZsZij764lOaPsj7mEAlyxXvslGt6/m312Tzqj/zeRpo=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.24/go.mod h1:wQBO5HdAkLjj2q6XQiIfDSP8DXDNrppDRw2Kp/1BODA=
|
||||
github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e h1:T5PdfK/M1xyrHwynxMIVMWLS7f/qHwfslZphxtGnw7s=
|
||||
github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e/go.mod h1:XDKHRm5ThF8YJjx001LtgelzsoaEcvnA7lVWz9EeX3g=
|
||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
@@ -910,8 +910,8 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
yunion.io/x/executor v0.0.0-20201231064744-df32f32165a9 h1:TyKy58HHgjkTJYrZgD0haz4bItoC+QwcOQ+GtMuGTjw=
|
||||
yunion.io/x/executor v0.0.0-20201231064744-df32f32165a9/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
|
||||
yunion.io/x/executor v0.0.0-20210310041834-3fcd1574f68e h1:uGmKmmHm4zeLXhc/MEbAFtEDtGtyEYhC0OaBRGFi+fQ=
|
||||
yunion.io/x/executor v0.0.0-20210310041834-3fcd1574f68e/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
|
||||
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634=
|
||||
yunion.io/x/jsonutils v0.0.0-20201110084044-3e4e1cb49769 h1:LIQ4hhLGQuQK+XxlV+8JrKBuL37WUT+5ZTVxBwHOTD4=
|
||||
yunion.io/x/jsonutils v0.0.0-20201110084044-3e4e1cb49769/go.mod h1:p0nyMqGA/apTxxyLIU/o1k4V7Vujl2O6ey30L594sYE=
|
||||
@@ -928,7 +928,7 @@ yunion.io/x/pkg v0.0.0-20210218105412-13a69f60034c h1:J/joqbA1N2mAlOl0Uqd4LpAq3+
|
||||
yunion.io/x/pkg v0.0.0-20210218105412-13a69f60034c/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e h1:v+EzIadodSwkdZ/7bremd7J8J50Cise/HCylsOJngmo=
|
||||
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo=
|
||||
yunion.io/x/sqlchemy v0.0.0-20210204030051-4fb14f92a88f h1:RH3RY/AReFPwASyakFnHyFaHb0N+juxWmwiPlOHZqaY=
|
||||
yunion.io/x/sqlchemy v0.0.0-20210204030051-4fb14f92a88f/go.mod h1:FTdwPdGhMgh4E+UFXc9klI1Ok34fMuybTT+jLhOaIjI=
|
||||
yunion.io/x/sqlchemy v0.0.0-20210329171939-2c520f93aee3 h1:C4yasW3gXronDIlIVUqHSbkA38PM1X9gxUWTYGsuIuI=
|
||||
yunion.io/x/sqlchemy v0.0.0-20210329171939-2c520f93aee3/go.mod h1:FTdwPdGhMgh4E+UFXc9klI1Ok34fMuybTT+jLhOaIjI=
|
||||
yunion.io/x/structarg v0.0.0-20200720093445-9f850fa222ce h1:kU8xE7O5uZ1GSJVMZHoJ+jrNL7csUQHYGyAPW9QfNpE=
|
||||
yunion.io/x/structarg v0.0.0-20200720093445-9f850fa222ce/go.mod h1:EP6NSv2C0zzqBDTKumv8hPWLb3XvgMZDHQRfyuOrQng=
|
||||
|
||||
@@ -268,7 +268,7 @@ func (b *SBucket) PutObject(ctx context.Context, key string, reader io.Reader, s
|
||||
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{},
|
||||
}
|
||||
if sizeBytes > 0 {
|
||||
opts.ContentLength = int(sizeBytes)
|
||||
opts.ContentLength = sizeBytes
|
||||
}
|
||||
if meta != nil {
|
||||
extraHdr := http.Header{}
|
||||
@@ -364,7 +364,7 @@ func (b *SBucket) UploadPart(ctx context.Context, key string, uploadId string, p
|
||||
return "", errors.Wrap(err, "GetCosClient")
|
||||
}
|
||||
opts := &cos.ObjectUploadPartOptions{}
|
||||
opts.ContentLength = int(partSize)
|
||||
opts.ContentLength = partSize
|
||||
resp, err := coscli.Object.UploadPart(ctx, key, uploadId, partIndex, input, opts)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "UploadPart")
|
||||
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
## [v0.7.23](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.22...v0.7.23) - 2021-03-23
|
||||
|
||||
a stable release
|
||||
|
||||
### Merged
|
||||
|
||||
- latest 0.7.23 stable [`#113`](https://github.com/tencentyun/cos-go-sdk-v5/pull/113)
|
||||
|
||||
### Commits
|
||||
|
||||
- update latest 0.7.23 stable [`17c220d`](https://github.com/tencentyun/cos-go-sdk-v5/commit/17c220d588c93afcdfce86154245daf44b196e0c)
|
||||
- Updated CHANGELOG.md [`f42ee97`](https://github.com/tencentyun/cos-go-sdk-v5/commit/f42ee9765b9267fc64345d4bb5ca389ed0528c07)
|
||||
|
||||
## [v0.7.22](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.21...v0.7.22) - 2021-03-12
|
||||
|
||||
update crc
|
||||
|
||||
### Merged
|
||||
|
||||
- Cos v4 dev [`#111`](https://github.com/tencentyun/cos-go-sdk-v5/pull/111)
|
||||
- update [`#109`](https://github.com/tencentyun/cos-go-sdk-v5/pull/109)
|
||||
|
||||
### Commits
|
||||
|
||||
- Updated CHANGELOG.md [`d58178d`](https://github.com/tencentyun/cos-go-sdk-v5/commit/d58178d869994363c70a4e1b846b4f8ff87ccb84)
|
||||
- update crc [`23ad6e2`](https://github.com/tencentyun/cos-go-sdk-v5/commit/23ad6e2c72a815f790345446045db132abdec0dd)
|
||||
|
||||
## [v0.7.21](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.20...v0.7.21) - 2021-03-08
|
||||
|
||||
高级接口增加数据校验功能
|
||||
|
||||
### Merged
|
||||
|
||||
- Cos v4 dev [`#108`](https://github.com/tencentyun/cos-go-sdk-v5/pull/108)
|
||||
- Cos v4 dev [`#107`](https://github.com/tencentyun/cos-go-sdk-v5/pull/107)
|
||||
- Cos v4 dev [`#106`](https://github.com/tencentyun/cos-go-sdk-v5/pull/106)
|
||||
|
||||
### Commits
|
||||
|
||||
- add:支持媒体处理任务接口 [`917abd1`](https://github.com/tencentyun/cos-go-sdk-v5/commit/917abd14626ac6b1dd08c21d46b9a9988f537b1a)
|
||||
- add:redeme里增加链接 [`d795d20`](https://github.com/tencentyun/cos-go-sdk-v5/commit/d795d20dca35ef730c8b58ba8a1e7e27fd9810d3)
|
||||
- update ci [`33b02c0`](https://github.com/tencentyun/cos-go-sdk-v5/commit/33b02c0be26631712f47c410e6d1f32f6fae477b)
|
||||
- update ci put & select [`62fb57e`](https://github.com/tencentyun/cos-go-sdk-v5/commit/62fb57e373a9b86dafbd356820c0e96b20da9ba2)
|
||||
- add upload verification [`2bfb93f`](https://github.com/tencentyun/cos-go-sdk-v5/commit/2bfb93f7d05ffdd4db0a21cbec410f97f2d51cb6)
|
||||
- Updated CHANGELOG.md [`e0e66ae`](https://github.com/tencentyun/cos-go-sdk-v5/commit/e0e66ae920c8611459d505d9f847e16e62cb666f)
|
||||
- add:redeme里增加链接 [`a060fa0`](https://github.com/tencentyun/cos-go-sdk-v5/commit/a060fa03a93c6b7bb2e303e7c270fa0df8895e24)
|
||||
- update [`45e6cb6`](https://github.com/tencentyun/cos-go-sdk-v5/commit/45e6cb65c3d9758743bba26342ec02ad38f75d22)
|
||||
- update [`685c58a`](https://github.com/tencentyun/cos-go-sdk-v5/commit/685c58af29276c23ff69aee377b03ed31c7eb856)
|
||||
- update ci [`deaede4`](https://github.com/tencentyun/cos-go-sdk-v5/commit/deaede440c412277e93e9c0a891089ddb92381f3)
|
||||
|
||||
## [v0.7.20](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.19...v0.7.20) - 2021-02-25
|
||||
|
||||
fix multicopy and multiupload send on closed channel error
|
||||
|
||||
### Merged
|
||||
|
||||
- fix multicopy and multiupload send on closed channel panic [`#104`](https://github.com/tencentyun/cos-go-sdk-v5/pull/104)
|
||||
- update DividePart to 64M [`#103`](https://github.com/tencentyun/cos-go-sdk-v5/pull/103)
|
||||
- update test [`#102`](https://github.com/tencentyun/cos-go-sdk-v5/pull/102)
|
||||
|
||||
### Commits
|
||||
|
||||
- Updated CHANGELOG.md [`eb667d9`](https://github.com/tencentyun/cos-go-sdk-v5/commit/eb667d9ed9fae2a338bee4fffc84381d00dec897)
|
||||
|
||||
## [v0.7.19](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.18...v0.7.19) - 2021-02-01
|
||||
|
||||
Add Get Object Optional Header && Add Test
|
||||
|
||||
### Merged
|
||||
|
||||
- add get object optional and test [`#101`](https://github.com/tencentyun/cos-go-sdk-v5/pull/101)
|
||||
|
||||
### Commits
|
||||
|
||||
- Updated CHANGELOG.md [`d2273eb`](https://github.com/tencentyun/cos-go-sdk-v5/commit/d2273eb6855df9c5db147ae4e1477eefc2490403)
|
||||
|
||||
## [v0.7.18](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.17...v0.7.18) - 2021-01-11
|
||||
|
||||
### Merged
|
||||
|
||||
- update upload [`#100`](https://github.com/tencentyun/cos-go-sdk-v5/pull/100)
|
||||
|
||||
### Commits
|
||||
|
||||
- Updated CHANGELOG.md [`5172396`](https://github.com/tencentyun/cos-go-sdk-v5/commit/51723968313c10a106ecf7fdb5227c41247ec066)
|
||||
|
||||
## [v0.7.17](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.16...v0.7.17) - 2020-12-29
|
||||
|
||||
add multicopy
|
||||
|
||||
### Merged
|
||||
|
||||
- add MultiCopy [`#99`](https://github.com/tencentyun/cos-go-sdk-v5/pull/99)
|
||||
|
||||
### Commits
|
||||
|
||||
- Updated CHANGELOG.md [`91fc87b`](https://github.com/tencentyun/cos-go-sdk-v5/commit/91fc87ba8af042adb0d0bf147f8fe6aa49057976)
|
||||
|
||||
## [v0.7.16](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.15...v0.7.16) - 2020-12-24
|
||||
|
||||
add bucket accelerate && update upload progress
|
||||
|
||||
### Merged
|
||||
|
||||
- Cos v4 dev [`#98`](https://github.com/tencentyun/cos-go-sdk-v5/pull/98)
|
||||
|
||||
### Commits
|
||||
|
||||
- Add Bucket Accelerate [`c07e497`](https://github.com/tencentyun/cos-go-sdk-v5/commit/c07e49771c809fab640ac9f2c31d776de9dea23d)
|
||||
- update upload progress && single object length [`72e7751`](https://github.com/tencentyun/cos-go-sdk-v5/commit/72e77516044f833f60577906800a638ebb31dc83)
|
||||
- Updated CHANGELOG.md [`5057561`](https://github.com/tencentyun/cos-go-sdk-v5/commit/50575619064fbd54e69745b1884756e6f6222a99)
|
||||
- update version [`6da3d40`](https://github.com/tencentyun/cos-go-sdk-v5/commit/6da3d4094cd8ca8e6840dedcb8b540c14e1f4c93)
|
||||
|
||||
## [v0.7.15](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.14...v0.7.15) - 2020-12-11
|
||||
|
||||
update ci & ci document
|
||||
|
||||
### Merged
|
||||
|
||||
- update ci [`#96`](https://github.com/tencentyun/cos-go-sdk-v5/pull/96)
|
||||
|
||||
### Commits
|
||||
|
||||
- Updated CHANGELOG.md [`b8afb3f`](https://github.com/tencentyun/cos-go-sdk-v5/commit/b8afb3f85050cee4884a46c8ed49d26bf76d10a4)
|
||||
|
||||
## [v0.7.14](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.13...v0.7.14) - 2020-12-09
|
||||
|
||||
fix bucket lifecycle
|
||||
|
||||
### Merged
|
||||
|
||||
- update version [`#95`](https://github.com/tencentyun/cos-go-sdk-v5/pull/95)
|
||||
- fix bucket lifecycle [`#94`](https://github.com/tencentyun/cos-go-sdk-v5/pull/94)
|
||||
|
||||
### Commits
|
||||
|
||||
- Updated CHANGELOG.md [`e3a89ee`](https://github.com/tencentyun/cos-go-sdk-v5/commit/e3a89ee58b4f524c7ad5f2b1f0bdc688a2e39c32)
|
||||
|
||||
## [v0.7.13](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.12...v0.7.13) - 2020-12-08
|
||||
|
||||
add ci document && add progress
|
||||
|
||||
### Merged
|
||||
|
||||
- Cos v4 dev [`#93`](https://github.com/tencentyun/cos-go-sdk-v5/pull/93)
|
||||
|
||||
### Commits
|
||||
|
||||
- add ci doc [`dad5b1f`](https://github.com/tencentyun/cos-go-sdk-v5/commit/dad5b1f3fbbf30958d3d8ae930b06abcaf8db5ac)
|
||||
- add progress [`2afc5e1`](https://github.com/tencentyun/cos-go-sdk-v5/commit/2afc5e192cd9bc8d6630fa929e10028ec79bde8e)
|
||||
- Updated CHANGELOG.md [`e39f3e3`](https://github.com/tencentyun/cos-go-sdk-v5/commit/e39f3e3585e0478abe50b33962e1e3981a2d432c)
|
||||
- update test [`4899c22`](https://github.com/tencentyun/cos-go-sdk-v5/commit/4899c226f70ccc1bb4ac8489a2b8ff000003bc97)
|
||||
- add auto changelog workflow [`ca3ea38`](https://github.com/tencentyun/cos-go-sdk-v5/commit/ca3ea38770afcd11e50570a835090c68a377157f)
|
||||
|
||||
## [v0.7.12](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.11...v0.7.12) - 2020-11-25
|
||||
|
||||
update presignedurl && copy
|
||||
|
||||
### Merged
|
||||
|
||||
- fix ci_test [`#89`](https://github.com/tencentyun/cos-go-sdk-v5/pull/89)
|
||||
- update version [`#92`](https://github.com/tencentyun/cos-go-sdk-v5/pull/92)
|
||||
- update presignedurl && copy [`#91`](https://github.com/tencentyun/cos-go-sdk-v5/pull/91)
|
||||
|
||||
## [v0.7.11](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.10...v0.7.11) - 2020-11-15
|
||||
|
||||
断点续传、select、CI内容审核、CI云上处理、单链接限制
|
||||
|
||||
### Merged
|
||||
|
||||
- Cos v4 dev [`#90`](https://github.com/tencentyun/cos-go-sdk-v5/pull/90)
|
||||
|
||||
### Commits
|
||||
|
||||
- add select, and x-cos-traffic-limit [`e6f823c`](https://github.com/tencentyun/cos-go-sdk-v5/commit/e6f823c7c1ee66fe68de4aafd45b009dee4b1ac2)
|
||||
- add ListMultiUploads && 断点续传 [`64d31f3`](https://github.com/tencentyun/cos-go-sdk-v5/commit/64d31f318a223866ac16c64bbe9effcd168b696d)
|
||||
- ci 内容审核 [`695c446`](https://github.com/tencentyun/cos-go-sdk-v5/commit/695c4466f5cec17acd5143777d4eae90f389f119)
|
||||
- add checkpoint multi upload [`287669a`](https://github.com/tencentyun/cos-go-sdk-v5/commit/287669a677af36153d645aabd4b296114aea75fe)
|
||||
- add ci post [`dd7c41c`](https://github.com/tencentyun/cos-go-sdk-v5/commit/dd7c41ca0824b607633e6a7bd0d3a838d52a91fd)
|
||||
- add CI图片审核 [`98eab28`](https://github.com/tencentyun/cos-go-sdk-v5/commit/98eab2886c628a3fbbbf581aeea56759ff38700c)
|
||||
- fix list uploads [`5e69c19`](https://github.com/tencentyun/cos-go-sdk-v5/commit/5e69c19d3450d06c3da270ca12e43647432d0ca4)
|
||||
- add decodeURIComponent [`f6c91c9`](https://github.com/tencentyun/cos-go-sdk-v5/commit/f6c91c92d6e46869c8f7acf000eccce2d351051b)
|
||||
- add checkpoint multi upload: update [`52c110b`](https://github.com/tencentyun/cos-go-sdk-v5/commit/52c110b7ede92af97e3685d4afd73a128ddd443b)
|
||||
|
||||
## [v0.7.10](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.9...v0.7.10) - 2020-09-29
|
||||
|
||||
add delete with versionid & fix MultiUpload when filesize equal 0 & Bucket/Object ACL transform
|
||||
|
||||
### Merged
|
||||
|
||||
- ACL转换 [`#87`](https://github.com/tencentyun/cos-go-sdk-v5/pull/87)
|
||||
- fix bucket encryption & test [`#86`](https://github.com/tencentyun/cos-go-sdk-v5/pull/86)
|
||||
|
||||
### Commits
|
||||
|
||||
- fix MultiUpload when filesize=0 [`cb662cd`](https://github.com/tencentyun/cos-go-sdk-v5/commit/cb662cdad5225cf92be335911d1805a29e445d14)
|
||||
- 多版本删除 [`0e9536d`](https://github.com/tencentyun/cos-go-sdk-v5/commit/0e9536d989b8e47b71fa02d12cb75d8ffda8b5fc)
|
||||
- update travis.yml [`b0a399e`](https://github.com/tencentyun/cos-go-sdk-v5/commit/b0a399e92dd143fb55b1ed173497644e51450835)
|
||||
- update version [`5804e86`](https://github.com/tencentyun/cos-go-sdk-v5/commit/5804e86747f587402b962dcb680ebc0d7c04035d)
|
||||
|
||||
## [v0.7.9](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.8...v0.7.9) - 2020-09-14
|
||||
|
||||
add bucket intelligenttiering
|
||||
|
||||
### Merged
|
||||
|
||||
- add bucket intelligenttiering [`#85`](https://github.com/tencentyun/cos-go-sdk-v5/pull/85)
|
||||
|
||||
## [v0.7.8](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.7...v0.7.8) - 2020-08-31
|
||||
|
||||
x-cos-copy-source urlencode修正
|
||||
|
||||
### Merged
|
||||
|
||||
- x-cos-copy-source urlencode修正 [`#83`](https://github.com/tencentyun/cos-go-sdk-v5/pull/83)
|
||||
- Common dev [`#82`](https://github.com/tencentyun/cos-go-sdk-v5/pull/82)
|
||||
|
||||
### Commits
|
||||
|
||||
- add host to signature [`f9f6178`](https://github.com/tencentyun/cos-go-sdk-v5/commit/f9f617878dad23f915ecea978f065aa70486843c)
|
||||
|
||||
## [v0.7.7](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.6...v0.7.7) - 2020-06-04
|
||||
|
||||
add object tagging && bucket origin && add stsv3 demo
|
||||
|
||||
### Merged
|
||||
|
||||
- add object tagging && bucket origin && add stsv3 demo [`#80`](https://github.com/tencentyun/cos-go-sdk-v5/pull/80)
|
||||
|
||||
## [v0.7.6](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.5...v0.7.6) - 2020-05-20
|
||||
|
||||
Add PutBucketObjectVersions
|
||||
|
||||
### Merged
|
||||
|
||||
- add GetBucketObjectVersions & update tips when failed [`#78`](https://github.com/tencentyun/cos-go-sdk-v5/pull/78)
|
||||
|
||||
### Commits
|
||||
|
||||
- update version [`bcc1ed2`](https://github.com/tencentyun/cos-go-sdk-v5/commit/bcc1ed2b8317cea8a32ab193ffad0a6482d63560)
|
||||
|
||||
## [v0.7.5](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.4...v0.7.5) - 2020-05-14
|
||||
|
||||
add HeaderOptions to Object.Delete
|
||||
|
||||
### Merged
|
||||
|
||||
- update tag 0.7.5 [`#77`](https://github.com/tencentyun/cos-go-sdk-v5/pull/77)
|
||||
- feat: Object.Delete 增加 optHeader 传递 [`#75`](https://github.com/tencentyun/cos-go-sdk-v5/pull/75)
|
||||
|
||||
### Commits
|
||||
|
||||
- feat: 改为可选参数,向下兼容 [`2eee514`](https://github.com/tencentyun/cos-go-sdk-v5/commit/2eee5149d727a045e80d28729bb60c90614e8275)
|
||||
|
||||
## [v0.7.4](https://github.com/tencentyun/cos-go-sdk-v5/compare/v0.7.3...v0.7.4) - 2020-04-12
|
||||
|
||||
add put/get bucket eferer and put/get/delete bucket encryption
|
||||
|
||||
### Merged
|
||||
|
||||
- add encryption and referer [`#74`](https://github.com/tencentyun/cos-go-sdk-v5/pull/74)
|
||||
|
||||
### Commits
|
||||
|
||||
- version 0.7.4 [`da7722b`](https://github.com/tencentyun/cos-go-sdk-v5/commit/da7722b10af59762b8e4f687891850650f0100a9)
|
||||
|
||||
## v0.7.3 - 2020-04-02
|
||||
|
||||
update ci
|
||||
|
||||
### Merged
|
||||
|
||||
- update ci and error [`#73`](https://github.com/tencentyun/cos-go-sdk-v5/pull/73)
|
||||
- add policy and IsNoSuchKeyError function [`#72`](https://github.com/tencentyun/cos-go-sdk-v5/pull/72)
|
||||
- add restore object for batch [`#69`](https://github.com/tencentyun/cos-go-sdk-v5/pull/69)
|
||||
- Addbatch [`#68`](https://github.com/tencentyun/cos-go-sdk-v5/pull/68)
|
||||
- inventory/logging/replication/versioning/tagging [`#66`](https://github.com/tencentyun/cos-go-sdk-v5/pull/66)
|
||||
- add content-language [`#65`](https://github.com/tencentyun/cos-go-sdk-v5/pull/65)
|
||||
- add host [`#64`](https://github.com/tencentyun/cos-go-sdk-v5/pull/64)
|
||||
- update MultiUpload [`#63`](https://github.com/tencentyun/cos-go-sdk-v5/pull/63)
|
||||
- add bucket website and domain interface [`#53`](https://github.com/tencentyun/cos-go-sdk-v5/pull/53)
|
||||
- 改进:增加对Upload part - copy API的支持 [`#54`](https://github.com/tencentyun/cos-go-sdk-v5/pull/54)
|
||||
- add sts policy describe [`#60`](https://github.com/tencentyun/cos-go-sdk-v5/pull/60)
|
||||
- update the sts example [`#59`](https://github.com/tencentyun/cos-go-sdk-v5/pull/59)
|
||||
- Automatic partitioning for MultiUpload [`#58`](https://github.com/tencentyun/cos-go-sdk-v5/pull/58)
|
||||
- add sse-c and optional header for put object [`#57`](https://github.com/tencentyun/cos-go-sdk-v5/pull/57)
|
||||
- fix object upload copy versionid [`#56`](https://github.com/tencentyun/cos-go-sdk-v5/pull/56)
|
||||
- Fix the multiupload property [`#50`](https://github.com/tencentyun/cos-go-sdk-v5/pull/50)
|
||||
- Fix the mulupload out of index panic [`#49`](https://github.com/tencentyun/cos-go-sdk-v5/pull/49)
|
||||
- fix the example of get, reorder the import package [`#48`](https://github.com/tencentyun/cos-go-sdk-v5/pull/48)
|
||||
- spec the test bucket only used for test [`#47`](https://github.com/tencentyun/cos-go-sdk-v5/pull/47)
|
||||
- reopen the coverage report [`#46`](https://github.com/tencentyun/cos-go-sdk-v5/pull/46)
|
||||
- Fix the ci test [`#45`](https://github.com/tencentyun/cos-go-sdk-v5/pull/45)
|
||||
- add Server Side Encryption Option to ObjectPutHeaderOptions and fixed testing [`#44`](https://github.com/tencentyun/cos-go-sdk-v5/pull/44)
|
||||
- Fix the outindex of the resp addr in head object [`#42`](https://github.com/tencentyun/cos-go-sdk-v5/pull/42)
|
||||
- Add the check for anonymous user [`#41`](https://github.com/tencentyun/cos-go-sdk-v5/pull/41)
|
||||
- Change the param type in listParts [`#38`](https://github.com/tencentyun/cos-go-sdk-v5/pull/38)
|
||||
- Fix the options param to listparts interface [`#37`](https://github.com/tencentyun/cos-go-sdk-v5/pull/37)
|
||||
- Add the error when return 200OK but body contains the error [`#34`](https://github.com/tencentyun/cos-go-sdk-v5/pull/34)
|
||||
- cgi already compact, so remove the sprit [`#32`](https://github.com/tencentyun/cos-go-sdk-v5/pull/32)
|
||||
- fix the blink in prefix which lead the wrong auth [`#31`](https://github.com/tencentyun/cos-go-sdk-v5/pull/31)
|
||||
- Support Versioning, Replication, Inventory and Logging API [`#30`](https://github.com/tencentyun/cos-go-sdk-v5/pull/30)
|
||||
- fix the panic of getting out of index with response header [`#28`](https://github.com/tencentyun/cos-go-sdk-v5/pull/28)
|
||||
- According the versionid to head and get object, add the opt header of copy object [`#26`](https://github.com/tencentyun/cos-go-sdk-v5/pull/26)
|
||||
- rm the panic in interface [`#24`](https://github.com/tencentyun/cos-go-sdk-v5/pull/24)
|
||||
- Add presigned url and demo use it to get object [`#23`](https://github.com/tencentyun/cos-go-sdk-v5/pull/23)
|
||||
- Check delete object param and fix restore name [`#22`](https://github.com/tencentyun/cos-go-sdk-v5/pull/22)
|
||||
- Add PostObjectRestore interface [`#21`](https://github.com/tencentyun/cos-go-sdk-v5/pull/21)
|
||||
- Add vendor files [`#19`](https://github.com/tencentyun/cos-go-sdk-v5/pull/19)
|
||||
- Add the comment to declare the out of memory issue when put large loc… [`#17`](https://github.com/tencentyun/cos-go-sdk-v5/pull/17)
|
||||
- Need to improve the interface in the future [`#16`](https://github.com/tencentyun/cos-go-sdk-v5/pull/16)
|
||||
- Add ci test with the testify and travis, Add get/put by localfile. [`#15`](https://github.com/tencentyun/cos-go-sdk-v5/pull/15)
|
||||
- url encode without / [`#14`](https://github.com/tencentyun/cos-go-sdk-v5/pull/14)
|
||||
- solve the oom when send the big file with the debug request header option [`#10`](https://github.com/tencentyun/cos-go-sdk-v5/pull/10)
|
||||
- remove the content-type when the request body is empty [`#9`](https://github.com/tencentyun/cos-go-sdk-v5/pull/9)
|
||||
- fix format [`#6`](https://github.com/tencentyun/cos-go-sdk-v5/pull/6)
|
||||
- fix format of doc [`#5`](https://github.com/tencentyun/cos-go-sdk-v5/pull/5)
|
||||
- update the comment for godoc [`#4`](https://github.com/tencentyun/cos-go-sdk-v5/pull/4)
|
||||
- fix test [`#3`](https://github.com/tencentyun/cos-go-sdk-v5/pull/3)
|
||||
- update rm the append interface [`#2`](https://github.com/tencentyun/cos-go-sdk-v5/pull/2)
|
||||
|
||||
### Commits
|
||||
|
||||
- fix the vendor files [`a5b3ad3`](https://github.com/tencentyun/cos-go-sdk-v5/commit/a5b3ad37d64c5aa82b12a57a6fdb0d2dba5ca1a8)
|
||||
- first to commit project [`6ad265f`](https://github.com/tencentyun/cos-go-sdk-v5/commit/6ad265fc0c275a4b1615c0f4702a7a992d8d299c)
|
||||
- add batch [`c57ac81`](https://github.com/tencentyun/cos-go-sdk-v5/commit/c57ac81a16da82605cc42bf9e0c8fd3af2cf62c5)
|
||||
- Support Versioning, Replication, Inventory and Logging api [`c131870`](https://github.com/tencentyun/cos-go-sdk-v5/commit/c131870916d7c29eb8c4a9512ea59ed7d7b09e18)
|
||||
- fix path [`121a7d8`](https://github.com/tencentyun/cos-go-sdk-v5/commit/121a7d861c8e0fb048aac5a8bc1a644414b16dc3)
|
||||
- fix the vendor file [`830b5c5`](https://github.com/tencentyun/cos-go-sdk-v5/commit/830b5c54a51d38b91e1432c7b216350a5c479ad0)
|
||||
- update bucket inventory/logging/replication/versioning/tagging and test [`d37fd23`](https://github.com/tencentyun/cos-go-sdk-v5/commit/d37fd23cdfe570886024c2c0586a2f14429a10ed)
|
||||
- Add ci test with the testify and travis, test the working [`4cc9e08`](https://github.com/tencentyun/cos-go-sdk-v5/commit/4cc9e08da1a0ce670cf6d20567059b7529b440d5)
|
||||
- Add ci test with the testify and travis, test the working [`c323f1b`](https://github.com/tencentyun/cos-go-sdk-v5/commit/c323f1b90cd97b98708216a72c666d89bdbcad8b)
|
||||
- rm inventory and logging example [`7739e84`](https://github.com/tencentyun/cos-go-sdk-v5/commit/7739e84d42b7a33121b16817f25129050db93db5)
|
||||
- add policy [`b427226`](https://github.com/tencentyun/cos-go-sdk-v5/commit/b427226ae44b1c0f25b8681147e828f4603a9014)
|
||||
- update batch [`156deae`](https://github.com/tencentyun/cos-go-sdk-v5/commit/156deaea3caacc5249050ae1e7378ae8af909c5d)
|
||||
- update bucket domain and website struct [`b97a490`](https://github.com/tencentyun/cos-go-sdk-v5/commit/b97a490e28370fecc475bf8b1a82df0d5a52a424)
|
||||
- presigned url, and demo [`0c2d28c`](https://github.com/tencentyun/cos-go-sdk-v5/commit/0c2d28caf108cff2a95507a9ff286be679d7925b)
|
||||
- fix ci_test [`c17efed`](https://github.com/tencentyun/cos-go-sdk-v5/commit/c17efed0aedee60e819d3e9504720ff47958da14)
|
||||
- fix [`9ebd645`](https://github.com/tencentyun/cos-go-sdk-v5/commit/9ebd6456b6aa788d0d2e97f4a8fb652c1b9a1976)
|
||||
- update batch [`699fef2`](https://github.com/tencentyun/cos-go-sdk-v5/commit/699fef215987bcf7f2459b9843ce8641900135f7)
|
||||
- Add the comment to declare the out of memory issue when put large local file [`6c894b8`](https://github.com/tencentyun/cos-go-sdk-v5/commit/6c894b8e4d5f0bb1ca24d444a9f8e18f95027cde)
|
||||
- go module support [`54101f9`](https://github.com/tencentyun/cos-go-sdk-v5/commit/54101f9739b3e59f201092f79d683a0cf377d7d0)
|
||||
- optional header for complete upload part [`44546a4`](https://github.com/tencentyun/cos-go-sdk-v5/commit/44546a40372260158652ddc6d805ddfc19f13fa3)
|
||||
+11
@@ -93,3 +93,14 @@ Object API:
|
||||
* [x] Complete Multipart Upload(使用示例:[object/completeMultipartUpload.go](./example/object/completeMultipartUpload.go))
|
||||
* [x] Abort Multipart Upload(使用示例:[object/abortMultipartUpload.go](./example/object/abortMultipartUpload.go))
|
||||
* [x] Mutipart Upload(使用示例:[object/MutiUpload.go](./example/object/MutiUpload.go))
|
||||
|
||||
|
||||
数据处理 API:
|
||||
|
||||
* [x] 媒体处理(使用示例:[media_process.go](./example/CI/media_process/media_process.go))
|
||||
* [x] 文档处理(使用示例:[ci_doc_process.go](./example/CI/doc_process/ci_doc_process.go))
|
||||
|
||||
|
||||
内容审核 API:
|
||||
|
||||
* [x] 视频审核(使用示例:[ci_video_auditing_job.go](./example/CI/content_auditing/ci_video_auditing_job.go))
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ type BatchRequestHeaders struct {
|
||||
XCosAppid int `header:"x-cos-appid" xml:"-" url:"-"`
|
||||
ContentLength string `header:"Content-Length,omitempty" xml:"-" url:"-"`
|
||||
ContentType string `header:"Content-Type,omitempty" xml:"-" url:"-"`
|
||||
Headers *http.Header `header:"-" xml:"-", url:"-"`
|
||||
Headers *http.Header `header:"-" xml:"-" url:"-"`
|
||||
}
|
||||
|
||||
// BatchProgressSummary
|
||||
@@ -244,7 +244,7 @@ func (s *BatchService) UpdateJobPriority(ctx context.Context, opt *BatchUpdatePr
|
||||
type BatchUpdateStatusOptions struct {
|
||||
JobId string `header:"-" url:"-" xml:"-"`
|
||||
RequestedJobStatus string `url:"requestedJobStatus" header:"-" xml:"-"`
|
||||
StatusUpdateReason string `url:"statusUpdateReason,omitempty" header:"-", xml:"-"`
|
||||
StatusUpdateReason string `url:"statusUpdateReason,omitempty" header:"-" xml:"-"`
|
||||
}
|
||||
type BatchUpdateStatusResult struct {
|
||||
XMLName xml.Name `xml:"UpdateJobStatusResult"`
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type BucketPutAccelerateOptions struct {
|
||||
XMLName xml.Name `xml:"AccelerateConfiguration"`
|
||||
Status string `xml:"Status,omitempty"`
|
||||
Type string `xml:"Type,omitempty"`
|
||||
}
|
||||
type BucketGetAccelerateResult BucketPutAccelerateOptions
|
||||
|
||||
func (s *BucketService) PutAccelerate(ctx context.Context, opt *BucketPutAccelerateOptions) (*Response, error) {
|
||||
sendOpt := &sendOptions{
|
||||
baseURL: s.client.BaseURL.BucketURL,
|
||||
uri: "/?accelerate",
|
||||
method: http.MethodPut,
|
||||
body: opt,
|
||||
}
|
||||
resp, err := s.client.send(ctx, sendOpt)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *BucketService) GetAccelerate(ctx context.Context) (*BucketGetAccelerateResult, *Response, error) {
|
||||
var res BucketGetAccelerateResult
|
||||
sendOpt := &sendOptions{
|
||||
baseURL: s.client.BaseURL.BucketURL,
|
||||
uri: "/?accelerate",
|
||||
method: http.MethodGet,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
+1
-1
@@ -26,7 +26,7 @@ type BucketLifecycleTransition struct {
|
||||
|
||||
// BucketLifecycleAbortIncompleteMultipartUpload is the param of BucketLifecycleRule
|
||||
type BucketLifecycleAbortIncompleteMultipartUpload struct {
|
||||
DaysAfterInitiation string `xml:"DaysAfterInititation,omitempty"`
|
||||
DaysAfterInitiation int `xml:"DaysAfterInitiation,omitempty"`
|
||||
}
|
||||
|
||||
// BucketLifecycleRule is the rule of BucketLifecycle
|
||||
|
||||
+232
-1
@@ -1,14 +1,21 @@
|
||||
package cos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"hash/crc64"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type CIService service
|
||||
|
||||
type PicOperations struct {
|
||||
IsPicInfo int `json:"is_pic_info,omitempty"`
|
||||
Rules []PicOperationsRules `json:"rules,omitemtpy"`
|
||||
}
|
||||
|
||||
type PicOperationsRules struct {
|
||||
Bucket string `json:"bucket,omitempty"`
|
||||
FileId string `json:"fileid"`
|
||||
@@ -16,9 +23,233 @@ type PicOperationsRules struct {
|
||||
}
|
||||
|
||||
func EncodePicOperations(pic *PicOperations) string {
|
||||
if pic == nil {
|
||||
return ""
|
||||
}
|
||||
bs, err := json.Marshal(pic)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
type ImageProcessResult struct {
|
||||
XMLName xml.Name `xml:"UploadResult"`
|
||||
OriginalInfo *PicOriginalInfo `xml:"OriginalInfo,omitempty"`
|
||||
ProcessResults *PicProcessObject `xml:"ProcessResults>Object,omitempty"`
|
||||
}
|
||||
type PicOriginalInfo struct {
|
||||
Key string `xml:"Key,omitempty"`
|
||||
Location string `xml:"Location,omitempty"`
|
||||
ImageInfo *PicImageInfo `xml:"ImageInfo,omitempty"`
|
||||
ETag string `xml:"ETag,omitempty"`
|
||||
}
|
||||
type PicImageInfo struct {
|
||||
Format string `xml:"Format,omitempty"`
|
||||
Width int `xml:"Width,omitempty"`
|
||||
Height int `xml:"Height,omitempty"`
|
||||
Quality int `xml:"Quality,omitempty"`
|
||||
Ave string `xml:"Ave,omitempty"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type picOperationsHeader struct {
|
||||
PicOperations string `header:"Pic-Operations" xml:"-" url:"-"`
|
||||
}
|
||||
|
||||
type ImageProcessOptions = PicOperations
|
||||
|
||||
// 云上数据处理 https://cloud.tencent.com/document/product/460/18147
|
||||
func (s *CIService) ImageProcess(ctx context.Context, name string, opt *ImageProcessOptions) (*ImageProcessResult, *Response, error) {
|
||||
header := &picOperationsHeader{
|
||||
PicOperations: EncodePicOperations(opt),
|
||||
}
|
||||
var res ImageProcessResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.BucketURL,
|
||||
uri: "/" + encodeURIComponent(name) + "?image_process",
|
||||
method: http.MethodPost,
|
||||
optHeader: header,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
type ImageRecognitionOptions struct {
|
||||
CIProcess string `url:"ci-process,omitempty"`
|
||||
DetectType string `url:"detect-type,omitempty"`
|
||||
}
|
||||
|
||||
type ImageRecognitionResult struct {
|
||||
XMLName xml.Name `xml:"RecognitionResult"`
|
||||
PornInfo *RecognitionInfo `xml:"PornInfo,omitempty"`
|
||||
TerroristInfo *RecognitionInfo `xml:"TerroristInfo,omitempty"`
|
||||
PoliticsInfo *RecognitionInfo `xml:"PoliticsInfo,omitempty"`
|
||||
AdsInfo *RecognitionInfo `xml:"AdsInfo,omitempty"`
|
||||
}
|
||||
type RecognitionInfo struct {
|
||||
Code int `xml:"Code,omitempty"`
|
||||
Msg string `xml:"Msg,omitempty"`
|
||||
HitFlag int `xml:"HitFlag,omitempty"`
|
||||
Score int `xml:"Score,omitempty"`
|
||||
Label string `xml:"Label,omitempty"`
|
||||
Count int `xml:"Count,omitempty"`
|
||||
}
|
||||
|
||||
// 图片审核 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"
|
||||
}
|
||||
var res ImageRecognitionResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.BucketURL,
|
||||
uri: "/" + encodeURIComponent(name),
|
||||
method: http.MethodGet,
|
||||
optQuery: opt,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
type PutVideoAuditingJobOptions struct {
|
||||
XMLName xml.Name `xml:"Request"`
|
||||
InputObject string `xml:"Input>Object"`
|
||||
Conf *VideoAuditingJobConf `xml:"Conf"`
|
||||
}
|
||||
type VideoAuditingJobConf struct {
|
||||
DetectType string `xml:",omitempty"`
|
||||
Snapshot *PutVideoAuditingJobSnapshot `xml:",omitempty"`
|
||||
Callback string `xml:",omitempty"`
|
||||
}
|
||||
type PutVideoAuditingJobSnapshot struct {
|
||||
Mode string `xml:",omitempty"`
|
||||
Count int `xml:",omitempty"`
|
||||
TimeInterval float32 `xml:",omitempty"`
|
||||
Start float32 `xml:",omitempty"`
|
||||
}
|
||||
|
||||
type PutVideoAuditingJobResult struct {
|
||||
XMLName xml.Name `xml:"Response"`
|
||||
JobsDetail struct {
|
||||
JobId string `xml:"JobId,omitempty"`
|
||||
State string `xml:"State,omitempty"`
|
||||
CreationTime string `xml:"CreationTime,omitempty"`
|
||||
Object string `xml:"Object,omitempty"`
|
||||
} `xml:"JobsDetail,omitempty"`
|
||||
}
|
||||
|
||||
func (s *CIService) PutVideoAuditingJob(ctx context.Context, opt *PutVideoAuditingJobOptions) (*PutVideoAuditingJobResult, *Response, error) {
|
||||
var res PutVideoAuditingJobResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.CIURL,
|
||||
uri: "/video/auditing",
|
||||
method: http.MethodPost,
|
||||
body: opt,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
type GetVideoAuditingJobResult struct {
|
||||
XMLName xml.Name `xml:"Response"`
|
||||
JobsDetail *VideoAuditingJobDetail `xml:",omitempty"`
|
||||
NonExistJobIds string `xml:",omitempty"`
|
||||
}
|
||||
type VideoAuditingJobDetail struct {
|
||||
Code string `xml:",omitempty"`
|
||||
Message string `xml:",omitempty"`
|
||||
JobId string `xml:",omitempty"`
|
||||
State string `xml:",omitempty"`
|
||||
CreationTime string `xml:",omitempty"`
|
||||
Object string `xml:",omitempty"`
|
||||
SnapshotCount string `xml:",omitempty"`
|
||||
Result int `xml:",omitempty"`
|
||||
PornInfo *RecognitionInfo `xml:",omitempty"`
|
||||
TerrorismInfo *RecognitionInfo `xml:",omitempty"`
|
||||
PoliticsInfo *RecognitionInfo `xml:",omitempty"`
|
||||
AdsInfo *RecognitionInfo `xml:",omitempty"`
|
||||
Snapshot *GetVideoAuditingJobSnapshot `xml:",omitempty"`
|
||||
}
|
||||
type GetVideoAuditingJobSnapshot struct {
|
||||
Url string `xml:",omitempty"`
|
||||
PornInfo *RecognitionInfo `xml:",omitempty"`
|
||||
TerrorismInfo *RecognitionInfo `xml:",omitempty"`
|
||||
PoliticsInfo *RecognitionInfo `xml:",omitempty"`
|
||||
AdsInfo *RecognitionInfo `xml:",omitempty"`
|
||||
}
|
||||
|
||||
func (s *CIService) GetVideoAuditingJob(ctx context.Context, jobid string) (*GetVideoAuditingJobResult, *Response, error) {
|
||||
var res GetVideoAuditingJobResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.CIURL,
|
||||
uri: "/video/auditing/" + jobid,
|
||||
method: http.MethodGet,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
// ci put https://cloud.tencent.com/document/product/460/18147
|
||||
func (s *CIService) Put(ctx context.Context, name string, r io.Reader, uopt *ObjectPutOptions) (*ImageProcessResult, *Response, error) {
|
||||
if err := CheckReaderLen(r); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt := cloneObjectPutOptions(uopt)
|
||||
totalBytes, err := GetReaderLen(r)
|
||||
if err != nil && opt != nil && opt.Listener != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err == nil {
|
||||
// 与 go http 保持一致, 非bytes.Buffer/bytes.Reader/strings.Reader由用户指定ContentLength, 或使用 Chunk 上传
|
||||
if opt != nil && opt.ContentLength == 0 && IsLenReader(r) {
|
||||
opt.ContentLength = totalBytes
|
||||
}
|
||||
}
|
||||
reader := TeeReader(r, nil, totalBytes, nil)
|
||||
if s.client.Conf.EnableCRC {
|
||||
reader.writer = crc64.New(crc64.MakeTable(crc64.ECMA))
|
||||
}
|
||||
if opt != nil && opt.Listener != nil {
|
||||
reader.listener = opt.Listener
|
||||
}
|
||||
|
||||
var res ImageProcessResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.BucketURL,
|
||||
uri: "/" + encodeURIComponent(name),
|
||||
method: http.MethodPut,
|
||||
body: reader,
|
||||
optHeader: opt,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
// ci put object from local file
|
||||
func (s *CIService) PutFromFile(ctx context.Context, name string, filePath string, opt *ObjectPutOptions) (*ImageProcessResult, *Response, error) {
|
||||
fd, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
return s.Put(ctx, name, fd, opt)
|
||||
}
|
||||
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
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,
|
||||
disableCloseBody: true,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return resp, err
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
package cos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type JobInput struct {
|
||||
Object string `xml:"Object,omitempty"`
|
||||
}
|
||||
|
||||
type JobOutput struct {
|
||||
Region string `xml:"Region,omitempty"`
|
||||
Bucket string `xml:"Bucket,omitempty"`
|
||||
Object string `xml:"Object,omitempty"`
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
Format string `xml:"Format"`
|
||||
}
|
||||
|
||||
type Video struct {
|
||||
Codec string `xml:"Codec"`
|
||||
Width string `xml:"Width"`
|
||||
Height string `xml:"Height"`
|
||||
Fps string `xml:"Fps"`
|
||||
Remove string `xml:"Remove"`
|
||||
Profile string `xml:"Profile"`
|
||||
Bitrate string `xml:"Bitrate"`
|
||||
Crf string `xml:"Crf"`
|
||||
Gop string `xml:"Gop"`
|
||||
Preset string `xml:"Preset"`
|
||||
Bufsize string `xml:"Bufsize"`
|
||||
Maxrate string `xml:"Maxrate"`
|
||||
HlsTsTime string `xml:"HlsTsTime"`
|
||||
Pixfmt string `xml:"Pixfmt"`
|
||||
LongShortMode string `xml:"LongShortMode"`
|
||||
}
|
||||
|
||||
type TimeInterval struct {
|
||||
Start string `xml:"Start"`
|
||||
Duration string `xml:"Duration"`
|
||||
}
|
||||
|
||||
type Audio struct {
|
||||
Codec string `xml:"Codec"`
|
||||
Samplerate string `xml:"Samplerate"`
|
||||
Bitrate string `xml:"Bitrate"`
|
||||
Channels string `xml:"Channels"`
|
||||
Remove string `xml:"Remove"`
|
||||
}
|
||||
|
||||
type TransConfig struct {
|
||||
AdjDarMethod string `xml:"AdjDarMethod"`
|
||||
IsCheckReso string `xml:"IsCheckReso"`
|
||||
ResoAdjMethod string `xml:"ResoAdjMethod"`
|
||||
IsCheckVideoBitrate string `xml:"IsCheckVideoBitrate"`
|
||||
VideoBitrateAdjMethod string `xml:"VideoBitrateAdjMethod"`
|
||||
IsCheckAudioBitrate string `xml:"IsCheckAudioBitrate"`
|
||||
AudioBitrateAdjMethod string `xml:"AudioBitrateAdjMethod"`
|
||||
}
|
||||
|
||||
type Transcode struct {
|
||||
Container *Container `xml:"Container,omitempty"`
|
||||
Video *Video `xml:"Video,omitempty"`
|
||||
TimeInterval *TimeInterval `xml:"TimeInterval,omitempty"`
|
||||
Audio *Audio `xml:"Audio,omitempty"`
|
||||
TransConfig *TransConfig `xml:"TransConfig,omitempty"`
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
Url string `xml:"Url,omitempty"`
|
||||
Mode string `xml:"Mode,omitempty"`
|
||||
Width string `xml:"Width,omitempty"`
|
||||
Height string `xml:"Height,omitempty"`
|
||||
Transparency string `xml:"Transparency,omitempty"`
|
||||
Background string `xml:"Background,omitempty"`
|
||||
}
|
||||
|
||||
type Text struct {
|
||||
FontSize string `xml:"FontSize,omitempty"`
|
||||
FontType string `xml:"FontType,omitempty"`
|
||||
FontColor string `xml:"FontColor,omitempty"`
|
||||
Transparency string `xml:"Transparency,omitempty"`
|
||||
Text string `xml:"Text,omitempty"`
|
||||
}
|
||||
|
||||
type Watermark struct {
|
||||
Type string `xml:"Type,omitempty"`
|
||||
Pos string `xml:"Pos,omitempty"`
|
||||
LocMode string `xml:"LocMode,omitempty"`
|
||||
Dx string `xml:"Dx,omitempty"`
|
||||
Dy string `xml:"Dy,omitempty"`
|
||||
StartTime string `xml:"StartTime,omitempty"`
|
||||
EndTime string `xml:"EndTime,omitempty"`
|
||||
Image *Image `xml:"Image,omitempty"`
|
||||
Text *Text `xml:"Text,omitempty"`
|
||||
}
|
||||
|
||||
type MediaProcessJobOperation struct {
|
||||
Output *JobOutput `xml:"Output,omitempty"`
|
||||
Transcode *Transcode `xml:"Transcode,omitempty"`
|
||||
Watermark *Watermark `xml:"Watermark,omitempty"`
|
||||
TemplateId string `xml:"TemplateId,omitempty"`
|
||||
WatermarkTemplateId []string `xml:"WatermarkTemplateId,omitempty"`
|
||||
}
|
||||
|
||||
type CreateMediaJobsOptions struct {
|
||||
XMLName xml.Name `xml:"Request"`
|
||||
Tag string `xml:"Tag,omitempty"`
|
||||
Input *JobInput `xml:"Input,omitempty"`
|
||||
Operation *MediaProcessJobOperation `xml:"Operation,omitempty"`
|
||||
QueueId string `xml:"QueueId,omitempty"`
|
||||
}
|
||||
|
||||
type MediaProcessJobDetail 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 *JobInput `xml:"Input,omitempty"`
|
||||
Operation *MediaProcessJobOperation `xml:"Operation,omitempty"`
|
||||
}
|
||||
|
||||
type CreateMediaJobsResult struct {
|
||||
XMLName xml.Name `xml:"Response"`
|
||||
JobsDetail MediaProcessJobDetail `xml:"JobsDetail,omitempty"`
|
||||
}
|
||||
|
||||
func (s *CIService) CreateMediaJobs(ctx context.Context, opt *CreateMediaJobsOptions) (*CreateMediaJobsResult, *Response, error) {
|
||||
var res CreateMediaJobsResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.CIURL,
|
||||
uri: "/jobs",
|
||||
method: http.MethodPost,
|
||||
body: opt,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
type DescribeMediaProcessJobResult struct {
|
||||
XMLName xml.Name `xml:"Response"`
|
||||
JobsDetail *MediaProcessJobDetail `xml:"JobsDetail,omitempty"`
|
||||
NonExistJobIds string `xml:"NonExistJobIds,omitempty"`
|
||||
}
|
||||
|
||||
func (s *CIService) DescribeMediaJobs(ctx context.Context, jobid string) (*DescribeMediaProcessJobResult, *Response, error) {
|
||||
var res DescribeMediaProcessJobResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.CIURL,
|
||||
uri: "/jobs/" + jobid,
|
||||
method: http.MethodGet,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
type DescribeMediaProcessJobsOptions 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 DescribeMediaProcessJobsResult struct {
|
||||
XMLName xml.Name `xml:"Response"`
|
||||
JobsDetail []DocProcessJobDetail `xml:"JobsDetail,omitempty"`
|
||||
NextToken string `xml:"NextToken,omitempty"`
|
||||
}
|
||||
|
||||
func (s *CIService) DescribeMediaProcessJobs(ctx context.Context, opt *DescribeMediaProcessJobsOptions) (*DescribeMediaProcessJobsResult, *Response, error) {
|
||||
var res DescribeMediaProcessJobsResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.CIURL,
|
||||
uri: "/jobs",
|
||||
optQuery: opt,
|
||||
method: http.MethodGet,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
type DescribeMediaProcessQueuesOptions struct {
|
||||
QueueIds string `url:"queueIds,omitempty"`
|
||||
State string `url:"state,omitempty"`
|
||||
PageNumber int `url:"pageNumber,omitempty"`
|
||||
PageSize int `url:"pageSize,omitempty"`
|
||||
}
|
||||
|
||||
type DescribeMediaProcessQueuesResult 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 []MediaProcessQueue `xml:"QueueList,omitempty"`
|
||||
NonExistPIDs []string `xml:"NonExistPIDs,omitempty"`
|
||||
}
|
||||
|
||||
type MediaProcessQueue 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 *MediaProcessQueueNotifyConfig `xml:"NotifyConfig,omitempty"`
|
||||
}
|
||||
|
||||
type MediaProcessQueueNotifyConfig struct {
|
||||
Url string `xml:"Url,omitempty"`
|
||||
State string `xml:"State,omitempty"`
|
||||
Type string `xml:"Type,omitempty"`
|
||||
Event string `xml:"Event,omitempty"`
|
||||
}
|
||||
|
||||
func (s *CIService) DescribeMediaProcessQueues(ctx context.Context, opt *DescribeMediaProcessQueuesOptions) (*DescribeMediaProcessQueuesResult, *Response, error) {
|
||||
var res DescribeMediaProcessQueuesResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.CIURL,
|
||||
uri: "/queue",
|
||||
optQuery: opt,
|
||||
method: http.MethodGet,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
type UpdateMediaProcessQueueOptions struct {
|
||||
XMLName xml.Name `xml:"Request"`
|
||||
Name string `xml:"Name,omitempty"`
|
||||
QueueID string `xml:"QueueID,omitempty"`
|
||||
State string `xml:"State,omitempty"`
|
||||
NotifyConfig *MediaProcessQueueNotifyConfig `xml:"NotifyConfig,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateMediaProcessQueueResult struct {
|
||||
XMLName xml.Name `xml:"Response"`
|
||||
RequestId string `xml:"RequestId"`
|
||||
Queue *MediaProcessQueue `xml:"Queue"`
|
||||
}
|
||||
|
||||
func (s *CIService) UpdateMediaProcessQueue(ctx context.Context, opt *UpdateMediaProcessQueueOptions) (*UpdateMediaProcessQueueResult, *Response, error) {
|
||||
var res UpdateMediaProcessQueueResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.CIURL,
|
||||
uri: "/queue/" + opt.QueueID,
|
||||
body: opt,
|
||||
method: http.MethodPut,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
type DescribeMediaProcessBucketsOptions 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 DescribeMediaProcessBucketsResult 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"`
|
||||
MediaBucketList []MediaProcessBucket `xml:"MediaBucketList,omitempty"`
|
||||
}
|
||||
type MediaProcessBucket struct {
|
||||
BucketId string `xml:"BucketId,omitempty"`
|
||||
Region string `xml:"Region,omitempty"`
|
||||
CreateTime string `xml:"CreateTime,omitempty"`
|
||||
}
|
||||
|
||||
func (s *CIService) DescribeMediaProcessBuckets(ctx context.Context, opt *DescribeMediaProcessBucketsOptions) (*DescribeMediaProcessBucketsResult, *Response, error) {
|
||||
var res DescribeMediaProcessBucketsResult
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.CIURL,
|
||||
uri: "/mediabucket",
|
||||
optQuery: opt,
|
||||
method: http.MethodGet,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
+32
-4
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version current go sdk version
|
||||
Version = "0.7.10"
|
||||
Version = "0.7.24"
|
||||
userAgent = "cos-go-sdk-v5/" + Version
|
||||
contentTypeXML = "application/xml"
|
||||
defaultServiceBaseURL = "http://service.cos.myqcloud.com"
|
||||
@@ -42,6 +42,8 @@ type BaseURL struct {
|
||||
ServiceURL *url.URL
|
||||
// 访问 job API 的基础 URL (不包含 path 部分): http://example.com
|
||||
BatchURL *url.URL
|
||||
// 访问 CI 的基础 URL
|
||||
CIURL *url.URL
|
||||
}
|
||||
|
||||
// NewBucketURL 生成 BaseURL 所需的 BucketURL
|
||||
@@ -68,6 +70,11 @@ func NewBucketURL(bucketName, region string, secure bool) *url.URL {
|
||||
return u
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
EnableCRC bool
|
||||
RequestBodyClose bool
|
||||
}
|
||||
|
||||
// Client is a client manages communication with the COS API.
|
||||
type Client struct {
|
||||
client *http.Client
|
||||
@@ -82,6 +89,9 @@ type Client struct {
|
||||
Bucket *BucketService
|
||||
Object *ObjectService
|
||||
Batch *BatchService
|
||||
CI *CIService
|
||||
|
||||
Conf *Config
|
||||
}
|
||||
|
||||
type service struct {
|
||||
@@ -99,6 +109,7 @@ func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
|
||||
baseURL.BucketURL = uri.BucketURL
|
||||
baseURL.ServiceURL = uri.ServiceURL
|
||||
baseURL.BatchURL = uri.BatchURL
|
||||
baseURL.CIURL = uri.CIURL
|
||||
}
|
||||
if baseURL.ServiceURL == nil {
|
||||
baseURL.ServiceURL, _ = url.Parse(defaultServiceBaseURL)
|
||||
@@ -108,12 +119,17 @@ func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
|
||||
client: httpClient,
|
||||
UserAgent: userAgent,
|
||||
BaseURL: baseURL,
|
||||
Conf: &Config{
|
||||
EnableCRC: true,
|
||||
RequestBodyClose: false,
|
||||
},
|
||||
}
|
||||
c.common.client = c
|
||||
c.Service = (*ServiceService)(&c.common)
|
||||
c.Bucket = (*BucketService)(&c.common)
|
||||
c.Object = (*ObjectService)(&c.common)
|
||||
c.Batch = (*BatchService)(&c.common)
|
||||
c.CI = (*CIService)(&c.common)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -168,6 +184,9 @@ func (c *Client) newRequest(ctx context.Context, baseURL *url.URL, uri, method s
|
||||
if c.Host != "" {
|
||||
req.Host = c.Host
|
||||
}
|
||||
if c.Conf.RequestBodyClose {
|
||||
req.Close = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -203,6 +222,18 @@ func (c *Client) doAPI(ctx context.Context, req *http.Request, result interface{
|
||||
return response, err
|
||||
}
|
||||
|
||||
// need CRC64 verification
|
||||
if reader, ok := req.Body.(*teeReader); ok {
|
||||
if c.Conf.EnableCRC && reader.writer != nil {
|
||||
localcrc := reader.Crc64()
|
||||
scoscrc := response.Header.Get("x-cos-hash-crc64ecma")
|
||||
icoscrc, _ := strconv.ParseUint(scoscrc, 10, 64)
|
||||
if icoscrc != localcrc {
|
||||
return response, fmt.Errorf("verification failed, want:%v, return:%v", localcrc, icoscrc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
if w, ok := result.(io.Writer); ok {
|
||||
io.Copy(w, resp.Body)
|
||||
@@ -244,9 +275,6 @@ func (c *Client) send(ctx context.Context, opt *sendOptions) (resp *Response, er
|
||||
}
|
||||
|
||||
resp, err = c.doAPI(ctx, req, opt.result, !opt.disableCloseBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+155
-1
@@ -4,10 +4,20 @@ import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc64"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 单次上传文件最大为5GB
|
||||
const singleUploadMaxLength = 5 * 1024 * 1024 * 1024
|
||||
const singleUploadThreshold = 32 * 1024 * 1024
|
||||
|
||||
// 计算 md5 或 sha1 时的分块大小
|
||||
const calDigestBlockSize = 1024 * 1024 * 10
|
||||
|
||||
@@ -25,6 +35,17 @@ func calSHA1Digest(msg []byte) []byte {
|
||||
return m.Sum(nil)
|
||||
}
|
||||
|
||||
func calCRC64(fd io.Reader) (uint64, error) {
|
||||
tb := crc64.MakeTable(crc64.ECMA)
|
||||
hash := crc64.New(tb)
|
||||
_, err := io.Copy(hash, fd)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sum := hash.Sum64()
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
// cloneRequest returns a clone of the provided *http.Request. The clone is a
|
||||
// shallow copy of the struct and its Header map.
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
@@ -44,7 +65,7 @@ func cloneRequest(r *http.Request) *http.Request {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
||||
//
|
||||
// http://www.ecma-international.org/ecma-262/6.0/#sec-uri-syntax-and-semantics
|
||||
func encodeURIComponent(s string) string {
|
||||
func encodeURIComponent(s string, excluded ...[]byte) string {
|
||||
var b bytes.Buffer
|
||||
written := 0
|
||||
|
||||
@@ -70,6 +91,18 @@ func encodeURIComponent(s string) string {
|
||||
|
||||
continue
|
||||
}
|
||||
if len(excluded) > 0 {
|
||||
conti := false
|
||||
for _, ch := range excluded[0] {
|
||||
if ch == c {
|
||||
conti = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if conti {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString(s[written:i])
|
||||
@@ -83,3 +116,124 @@ func encodeURIComponent(s string) string {
|
||||
b.WriteString(s[written:])
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func decodeURIComponent(s string) (string, error) {
|
||||
decodeStr, err := url.QueryUnescape(s)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
return decodeStr, err
|
||||
}
|
||||
|
||||
func DecodeURIComponent(s string) (string, error) {
|
||||
return decodeURIComponent(s)
|
||||
}
|
||||
|
||||
func EncodeURIComponent(s string) string {
|
||||
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 *LimitedReadCloser:
|
||||
length = int64(v.N)
|
||||
case FixedLengthReader:
|
||||
length = v.Size()
|
||||
default:
|
||||
err = fmt.Errorf("can't get reader content length, unkown reader type")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func IsLenReader(reader io.Reader) bool {
|
||||
switch reader.(type) {
|
||||
case *bytes.Buffer:
|
||||
return true
|
||||
case *bytes.Reader:
|
||||
return true
|
||||
case *strings.Reader:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func CheckReaderLen(reader io.Reader) error {
|
||||
nlen, err := GetReaderLen(reader)
|
||||
if err != nil || nlen < singleUploadMaxLength {
|
||||
return nil
|
||||
}
|
||||
return errors.New("The single object size you upload can not be larger than 5GB")
|
||||
}
|
||||
|
||||
func CopyOptionsToMulti(opt *ObjectCopyOptions) *InitiateMultipartUploadOptions {
|
||||
if opt == nil {
|
||||
return nil
|
||||
}
|
||||
optini := &InitiateMultipartUploadOptions{
|
||||
opt.ACLHeaderOptions,
|
||||
&ObjectPutHeaderOptions{},
|
||||
}
|
||||
if opt.ObjectCopyHeaderOptions == nil {
|
||||
return optini
|
||||
}
|
||||
optini.ObjectPutHeaderOptions = &ObjectPutHeaderOptions{
|
||||
CacheControl: opt.ObjectCopyHeaderOptions.CacheControl,
|
||||
ContentDisposition: opt.ObjectCopyHeaderOptions.ContentDisposition,
|
||||
ContentEncoding: opt.ObjectCopyHeaderOptions.ContentEncoding,
|
||||
ContentType: opt.ObjectCopyHeaderOptions.ContentType,
|
||||
ContentLanguage: opt.ObjectCopyHeaderOptions.ContentLanguage,
|
||||
Expect: opt.ObjectCopyHeaderOptions.Expect,
|
||||
Expires: opt.ObjectCopyHeaderOptions.Expires,
|
||||
XCosMetaXXX: opt.ObjectCopyHeaderOptions.XCosMetaXXX,
|
||||
XCosStorageClass: opt.ObjectCopyHeaderOptions.XCosStorageClass,
|
||||
XCosServerSideEncryption: opt.ObjectCopyHeaderOptions.XCosServerSideEncryption,
|
||||
XCosSSECustomerAglo: opt.ObjectCopyHeaderOptions.XCosSSECustomerAglo,
|
||||
XCosSSECustomerKey: opt.ObjectCopyHeaderOptions.XCosSSECustomerKey,
|
||||
XCosSSECustomerKeyMD5: opt.ObjectCopyHeaderOptions.XCosSSECustomerKeyMD5,
|
||||
XOptionHeader: opt.ObjectCopyHeaderOptions.XOptionHeader,
|
||||
}
|
||||
return optini
|
||||
}
|
||||
|
||||
// 浅拷贝ObjectPutOptions
|
||||
func cloneObjectPutOptions(opt *ObjectPutOptions) *ObjectPutOptions {
|
||||
res := &ObjectPutOptions{
|
||||
&ACLHeaderOptions{},
|
||||
&ObjectPutHeaderOptions{},
|
||||
}
|
||||
if opt != nil {
|
||||
if opt.ACLHeaderOptions != nil {
|
||||
*res.ACLHeaderOptions = *opt.ACLHeaderOptions
|
||||
}
|
||||
if opt.ObjectPutHeaderOptions != nil {
|
||||
*res.ObjectPutHeaderOptions = *opt.ObjectPutHeaderOptions
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 浅拷贝ObjectUploadPartOptions
|
||||
func cloneObjectUploadPartOptions(opt *ObjectUploadPartOptions) *ObjectUploadPartOptions {
|
||||
var res ObjectUploadPartOptions
|
||||
if opt != nil {
|
||||
res = *opt
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
||||
+326
-80
@@ -2,14 +2,18 @@ package cos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -31,6 +35,13 @@ type ObjectGetOptions struct {
|
||||
XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
|
||||
XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
|
||||
XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
|
||||
|
||||
//兼容其他自定义头部
|
||||
XOptionHeader *http.Header `header:"-,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
|
||||
@@ -61,6 +72,14 @@ func (s *ObjectService) Get(ctx context.Context, name string, opt *ObjectGetOpti
|
||||
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
|
||||
}
|
||||
|
||||
@@ -111,12 +130,12 @@ func (s *ObjectService) GetPresignedURL(ctx context.Context, httpMethod, name, a
|
||||
authTime = NewAuthTime(expired)
|
||||
}
|
||||
authorization := newAuthorization(ak, sk, req, authTime)
|
||||
sign := encodeURIComponent(authorization)
|
||||
sign := encodeURIComponent(authorization, []byte{'&', '='})
|
||||
|
||||
if req.URL.RawQuery == "" {
|
||||
req.URL.RawQuery = fmt.Sprintf("sign=%s", sign)
|
||||
req.URL.RawQuery = fmt.Sprintf("%s", sign)
|
||||
} else {
|
||||
req.URL.RawQuery = fmt.Sprintf("%s&sign=%s", req.URL.RawQuery, sign)
|
||||
req.URL.RawQuery = fmt.Sprintf("%s&%s", req.URL.RawQuery, sign)
|
||||
}
|
||||
return req.URL, nil
|
||||
|
||||
@@ -129,7 +148,7 @@ type ObjectPutHeaderOptions struct {
|
||||
ContentEncoding string `header:"Content-Encoding,omitempty" url:"-"`
|
||||
ContentType string `header:"Content-Type,omitempty" url:"-"`
|
||||
ContentMD5 string `header:"Content-MD5,omitempty" url:"-"`
|
||||
ContentLength int `header:"Content-Length,omitempty" url:"-"`
|
||||
ContentLength int64 `header:"Content-Length,omitempty" url:"-"`
|
||||
ContentLanguage string `header:"Content-Language,omitempty" url:"-"`
|
||||
Expect string `header:"Expect,omitempty" url:"-"`
|
||||
Expires string `header:"Expires,omitempty" url:"-"`
|
||||
@@ -146,7 +165,11 @@ type ObjectPutHeaderOptions struct {
|
||||
XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
|
||||
XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
|
||||
//兼容其他自定义头部
|
||||
XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
|
||||
XOptionHeader *http.Header `header:"-,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
|
||||
@@ -157,31 +180,61 @@ type ObjectPutOptions struct {
|
||||
|
||||
// Put Object请求可以将一个文件(Oject)上传至指定Bucket。
|
||||
//
|
||||
// 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ObjectPutHeaderOptions.ContentLength
|
||||
//
|
||||
// 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, uopt *ObjectPutOptions) (*Response, error) {
|
||||
if err := CheckReaderLen(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opt := cloneObjectPutOptions(uopt)
|
||||
totalBytes, err := GetReaderLen(r)
|
||||
if err != nil && opt != nil && opt.Listener != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err == nil {
|
||||
// 与 go http 保持一致, 非bytes.Buffer/bytes.Reader/strings.Reader由用户指定ContentLength, 或使用 Chunk 上传
|
||||
if opt != nil && opt.ContentLength == 0 && IsLenReader(r) {
|
||||
opt.ContentLength = totalBytes
|
||||
}
|
||||
}
|
||||
reader := TeeReader(r, nil, totalBytes, nil)
|
||||
if s.client.Conf.EnableCRC {
|
||||
reader.writer = crc64.New(crc64.MakeTable(crc64.ECMA))
|
||||
}
|
||||
if opt != nil && opt.Listener != nil {
|
||||
reader.listener = opt.Listener
|
||||
}
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.BucketURL,
|
||||
uri: "/" + encodeURIComponent(name),
|
||||
method: http.MethodPut,
|
||||
body: r,
|
||||
body: reader,
|
||||
optHeader: opt,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// PutFromFile put object from local file
|
||||
// Notice that when use this put large file need set non-body of debug req/resp, otherwise will out of memory
|
||||
func (s *ObjectService) PutFromFile(ctx context.Context, name string, filePath string, opt *ObjectPutOptions) (*Response, error) {
|
||||
fd, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (s *ObjectService) PutFromFile(ctx context.Context, name string, filePath string, opt *ObjectPutOptions) (resp *Response, err error) {
|
||||
nr := 0
|
||||
for nr < 3 {
|
||||
fd, e := os.Open(filePath)
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
resp, err = s.Put(ctx, name, fd, opt)
|
||||
if err != nil {
|
||||
nr++
|
||||
fd.Close()
|
||||
continue
|
||||
}
|
||||
fd.Close()
|
||||
break
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
return s.Put(ctx, name, fd, opt)
|
||||
return
|
||||
}
|
||||
|
||||
// ObjectCopyHeaderOptions is the head option of the Copy
|
||||
@@ -226,6 +279,8 @@ type ObjectCopyResult struct {
|
||||
XMLName xml.Name `xml:"CopyObjectResult"`
|
||||
ETag string `xml:"ETag,omitempty"`
|
||||
LastModified string `xml:"LastModified,omitempty"`
|
||||
CRC64 string `xml:"CRC64,omitempty"`
|
||||
VersionId string `xml:"VersionId,omitempty"`
|
||||
}
|
||||
|
||||
// Copy 调用 PutObjectCopy 请求实现将一个文件从源路径复制到目标路径。建议文件大小 1M 到 5G,
|
||||
@@ -251,20 +306,26 @@ func (s *ObjectService) Copy(ctx context.Context, name, sourceURL string, opt *O
|
||||
}
|
||||
|
||||
var res ObjectCopyResult
|
||||
if opt == nil {
|
||||
opt = new(ObjectCopyOptions)
|
||||
copyOpt := &ObjectCopyOptions{
|
||||
&ObjectCopyHeaderOptions{},
|
||||
&ACLHeaderOptions{},
|
||||
}
|
||||
if opt.ObjectCopyHeaderOptions == nil {
|
||||
opt.ObjectCopyHeaderOptions = new(ObjectCopyHeaderOptions)
|
||||
if opt != nil {
|
||||
if opt.ObjectCopyHeaderOptions != nil {
|
||||
*copyOpt.ObjectCopyHeaderOptions = *opt.ObjectCopyHeaderOptions
|
||||
}
|
||||
if opt.ACLHeaderOptions != nil {
|
||||
*copyOpt.ACLHeaderOptions = *opt.ACLHeaderOptions
|
||||
}
|
||||
}
|
||||
opt.XCosCopySource = u
|
||||
copyOpt.XCosCopySource = u
|
||||
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.BucketURL,
|
||||
uri: "/" + encodeURIComponent(name),
|
||||
method: http.MethodPut,
|
||||
body: nil,
|
||||
optHeader: opt,
|
||||
optHeader: copyOpt,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
@@ -315,9 +376,10 @@ func (s *ObjectService) Delete(ctx context.Context, name string, opt ...*ObjectD
|
||||
type ObjectHeadOptions struct {
|
||||
IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"`
|
||||
// SSE-C
|
||||
XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
|
||||
XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
|
||||
XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
|
||||
XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
|
||||
XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
|
||||
XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
|
||||
XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
|
||||
}
|
||||
|
||||
// Head Object请求可以取回对应Object的元数据,Head的权限与Get的权限一致
|
||||
@@ -470,7 +532,7 @@ func (s *ObjectService) DeleteMulti(ctx context.Context, opt *ObjectDeleteMultiO
|
||||
type Object struct {
|
||||
Key string `xml:",omitempty"`
|
||||
ETag string `xml:",omitempty"`
|
||||
Size int `xml:",omitempty"`
|
||||
Size int64 `xml:",omitempty"`
|
||||
PartNumber int `xml:",omitempty"`
|
||||
LastModified string `xml:",omitempty"`
|
||||
StorageClass string `xml:",omitempty"`
|
||||
@@ -481,15 +543,19 @@ type Object struct {
|
||||
// MultiUploadOptions is the option of the multiupload,
|
||||
// ThreadPoolSize default is one
|
||||
type MultiUploadOptions struct {
|
||||
OptIni *InitiateMultipartUploadOptions
|
||||
PartSize int64
|
||||
ThreadPoolSize int
|
||||
OptIni *InitiateMultipartUploadOptions
|
||||
PartSize int64
|
||||
ThreadPoolSize int
|
||||
CheckPoint bool
|
||||
EnableVerification bool
|
||||
}
|
||||
|
||||
type Chunk struct {
|
||||
Number int
|
||||
OffSet int64
|
||||
Size int64
|
||||
Done bool
|
||||
ETag string
|
||||
}
|
||||
|
||||
// jobs
|
||||
@@ -506,46 +572,65 @@ type Jobs struct {
|
||||
type Results struct {
|
||||
PartNumber int
|
||||
Resp *Response
|
||||
err error
|
||||
}
|
||||
|
||||
func LimitReadCloser(r io.Reader, n int64) io.Reader {
|
||||
var lc LimitedReadCloser
|
||||
lc.R = r
|
||||
lc.N = n
|
||||
return &lc
|
||||
}
|
||||
|
||||
type LimitedReadCloser struct {
|
||||
io.LimitedReader
|
||||
}
|
||||
|
||||
func (lc *LimitedReadCloser) Close() error {
|
||||
if r, ok := lc.R.(io.ReadCloser); ok {
|
||||
return r.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func worker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results) {
|
||||
for j := range jobs {
|
||||
fd, err := os.Open(j.FilePath)
|
||||
var res Results
|
||||
if err != nil {
|
||||
res.PartNumber = j.Chunk.Number
|
||||
res.Resp = nil
|
||||
results <- &res
|
||||
}
|
||||
|
||||
fd.Seek(j.Chunk.OffSet, os.SEEK_SET)
|
||||
// UploadPart do not support the chunk trsf, so need to add the content-length
|
||||
j.Opt.ContentLength = int(j.Chunk.Size)
|
||||
j.Opt.ContentLength = j.Chunk.Size
|
||||
|
||||
rt := j.RetryTimes
|
||||
for {
|
||||
// http.Request.Body can be Closed in request
|
||||
fd, err := os.Open(j.FilePath)
|
||||
var res Results
|
||||
if err != nil {
|
||||
res.err = err
|
||||
res.PartNumber = j.Chunk.Number
|
||||
res.Resp = nil
|
||||
results <- &res
|
||||
break
|
||||
}
|
||||
fd.Seek(j.Chunk.OffSet, os.SEEK_SET)
|
||||
resp, err := s.UploadPart(context.Background(), j.Name, j.UploadId, j.Chunk.Number,
|
||||
&io.LimitedReader{R: fd, N: j.Chunk.Size}, j.Opt)
|
||||
LimitReadCloser(fd, j.Chunk.Size), j.Opt)
|
||||
res.PartNumber = j.Chunk.Number
|
||||
res.Resp = resp
|
||||
res.err = err
|
||||
if err != nil {
|
||||
rt--
|
||||
if rt == 0 {
|
||||
fd.Close()
|
||||
results <- &res
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
fd.Close()
|
||||
results <- &res
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DividePart(fileSize int64) (int64, int64) {
|
||||
partSize := int64(1 * 1024 * 1024)
|
||||
func DividePart(fileSize int64, last int) (int64, int64) {
|
||||
partSize := int64(last * 1024 * 1024)
|
||||
partNum := fileSize / partSize
|
||||
for partNum >= 10000 {
|
||||
partSize = partSize * 2
|
||||
@@ -554,30 +639,30 @@ func DividePart(fileSize int64) (int64, int64) {
|
||||
return partNum, partSize
|
||||
}
|
||||
|
||||
func SplitFileIntoChunks(filePath string, partSize int64) ([]Chunk, int, error) {
|
||||
func SplitFileIntoChunks(filePath string, partSize int64) (int64, []Chunk, int, error) {
|
||||
if filePath == "" {
|
||||
return nil, 0, errors.New("filePath invalid")
|
||||
return 0, nil, 0, errors.New("filePath invalid")
|
||||
}
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return 0, nil, 0, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return 0, nil, 0, err
|
||||
}
|
||||
var partNum int64
|
||||
if partSize > 0 {
|
||||
partSize = partSize * 1024 * 1024
|
||||
partNum = stat.Size() / partSize
|
||||
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 {
|
||||
partNum, partSize = DividePart(stat.Size())
|
||||
partNum, partSize = DividePart(stat.Size(), 64)
|
||||
}
|
||||
|
||||
var chunks []Chunk
|
||||
@@ -597,10 +682,84 @@ func SplitFileIntoChunks(filePath string, partSize int64) ([]Chunk, int, error)
|
||||
partNum++
|
||||
}
|
||||
|
||||
return chunks, int(partNum), nil
|
||||
return int64(stat.Size()), chunks, int(partNum), nil
|
||||
|
||||
}
|
||||
|
||||
func (s *ObjectService) getResumableUploadID(ctx context.Context, name string) (string, error) {
|
||||
opt := &ObjectListUploadsOptions{
|
||||
Prefix: name,
|
||||
EncodingType: "url",
|
||||
}
|
||||
res, _, err := s.ListUploads(ctx, opt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(res.Upload) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
last := len(res.Upload) - 1
|
||||
for last >= 0 {
|
||||
decodeKey, _ := decodeURIComponent(res.Upload[last].Key)
|
||||
if decodeKey == name {
|
||||
return decodeURIComponent(res.Upload[last].UploadID)
|
||||
}
|
||||
last = last - 1
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *ObjectService) checkUploadedParts(ctx context.Context, name, UploadID, filepath string, chunks []Chunk, partNum int) error {
|
||||
var uploadedParts []Object
|
||||
isTruncated := true
|
||||
opt := &ObjectListPartsOptions{
|
||||
EncodingType: "url",
|
||||
}
|
||||
for isTruncated {
|
||||
res, _, err := s.ListParts(ctx, name, UploadID, opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(res.Parts) > 0 {
|
||||
uploadedParts = append(uploadedParts, res.Parts...)
|
||||
}
|
||||
isTruncated = res.IsTruncated
|
||||
opt.PartNumberMarker = res.NextPartNumberMarker
|
||||
}
|
||||
fd, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
// 某个分块出错, 重置chunks
|
||||
ret := func(e error) error {
|
||||
for i, _ := range chunks {
|
||||
chunks[i].Done = false
|
||||
chunks[i].ETag = ""
|
||||
}
|
||||
return e
|
||||
}
|
||||
for _, part := range uploadedParts {
|
||||
partNumber := part.PartNumber
|
||||
if partNumber > partNum {
|
||||
return ret(errors.New("Part Number is not consistent"))
|
||||
}
|
||||
partNumber = partNumber - 1
|
||||
fd.Seek(chunks[partNumber].OffSet, os.SEEK_SET)
|
||||
bs, err := ioutil.ReadAll(io.LimitReader(fd, chunks[partNumber].Size))
|
||||
if err != nil {
|
||||
return ret(err)
|
||||
}
|
||||
localMD5 := fmt.Sprintf("\"%x\"", md5.Sum(bs))
|
||||
if localMD5 != part.ETag {
|
||||
return ret(errors.New(fmt.Sprintf("CheckSum Failed in Part[%d]", part.PartNumber)))
|
||||
}
|
||||
chunks[partNumber].Done = true
|
||||
chunks[partNumber].ETag = part.ETag
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MultiUpload/Upload 为高级upload接口,并发分块上传
|
||||
// 注意该接口目前只供参考
|
||||
//
|
||||
@@ -615,12 +774,26 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
|
||||
if opt == nil {
|
||||
opt = &MultiUploadOptions{}
|
||||
}
|
||||
var localcrc uint64
|
||||
// 1.Get the file chunk
|
||||
chunks, partNum, err := SplitFileIntoChunks(filepath, opt.PartSize)
|
||||
totalBytes, chunks, partNum, err := SplitFileIntoChunks(filepath, opt.PartSize)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if partNum == 0 {
|
||||
// 校验
|
||||
if s.client.Conf.EnableCRC {
|
||||
fd, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
localcrc, err = calCRC64(fd)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
// filesize=0 , use simple upload
|
||||
if partNum == 0 || partNum == 1 {
|
||||
var opt0 *ObjectPutOptions
|
||||
if opt.OptIni != nil {
|
||||
opt0 = &ObjectPutOptions{
|
||||
@@ -636,16 +809,36 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
|
||||
Key: name,
|
||||
ETag: rsp.Header.Get("ETag"),
|
||||
}
|
||||
if rsp != nil && s.client.Conf.EnableCRC {
|
||||
scoscrc := rsp.Header.Get("x-cos-hash-crc64ecma")
|
||||
icoscrc, _ := strconv.ParseUint(scoscrc, 10, 64)
|
||||
if icoscrc != localcrc {
|
||||
return result, rsp, fmt.Errorf("verification failed, want:%v, return:%v", localcrc, icoscrc)
|
||||
}
|
||||
}
|
||||
return result, rsp, nil
|
||||
}
|
||||
|
||||
var uploadID string
|
||||
resumableFlag := false
|
||||
if opt.CheckPoint {
|
||||
var err error
|
||||
uploadID, err = s.getResumableUploadID(ctx, name)
|
||||
if err == nil && uploadID != "" {
|
||||
err = s.checkUploadedParts(ctx, name, uploadID, filepath, chunks, partNum)
|
||||
resumableFlag = (err == nil)
|
||||
}
|
||||
}
|
||||
|
||||
// 2.Init
|
||||
optini := opt.OptIni
|
||||
res, _, err := s.InitiateMultipartUpload(ctx, name, optini)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
if !resumableFlag {
|
||||
res, _, err := s.InitiateMultipartUpload(ctx, name, optini)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
uploadID = res.UploadID
|
||||
}
|
||||
uploadID := res.UploadID
|
||||
var poolSize int
|
||||
if opt.ThreadPoolSize > 0 {
|
||||
poolSize = opt.ThreadPoolSize
|
||||
@@ -663,44 +856,97 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
|
||||
go worker(s, chjobs, chresults)
|
||||
}
|
||||
|
||||
// 4.Push jobs
|
||||
for _, chunk := range chunks {
|
||||
partOpt := &ObjectUploadPartOptions{}
|
||||
if optini != nil && optini.ObjectPutHeaderOptions != nil {
|
||||
partOpt.XCosSSECustomerAglo = optini.XCosSSECustomerAglo
|
||||
partOpt.XCosSSECustomerKey = optini.XCosSSECustomerKey
|
||||
partOpt.XCosSSECustomerKeyMD5 = optini.XCosSSECustomerKeyMD5
|
||||
}
|
||||
job := &Jobs{
|
||||
Name: name,
|
||||
RetryTimes: 3,
|
||||
FilePath: filepath,
|
||||
UploadId: uploadID,
|
||||
Chunk: chunk,
|
||||
Opt: partOpt,
|
||||
}
|
||||
chjobs <- job
|
||||
// progress started event
|
||||
var listener ProgressListener
|
||||
var consumedBytes int64
|
||||
if opt.OptIni != nil {
|
||||
listener = opt.OptIni.Listener
|
||||
}
|
||||
close(chjobs)
|
||||
event := newProgressEvent(ProgressStartedEvent, 0, 0, totalBytes)
|
||||
progressCallback(listener, event)
|
||||
|
||||
// 4.Push jobs
|
||||
go func() {
|
||||
for _, chunk := range chunks {
|
||||
if chunk.Done {
|
||||
continue
|
||||
}
|
||||
partOpt := &ObjectUploadPartOptions{}
|
||||
if optini != nil && optini.ObjectPutHeaderOptions != nil {
|
||||
partOpt.XCosSSECustomerAglo = optini.XCosSSECustomerAglo
|
||||
partOpt.XCosSSECustomerKey = optini.XCosSSECustomerKey
|
||||
partOpt.XCosSSECustomerKeyMD5 = optini.XCosSSECustomerKeyMD5
|
||||
partOpt.XCosTrafficLimit = optini.XCosTrafficLimit
|
||||
}
|
||||
job := &Jobs{
|
||||
Name: name,
|
||||
RetryTimes: 3,
|
||||
FilePath: filepath,
|
||||
UploadId: uploadID,
|
||||
Chunk: chunk,
|
||||
Opt: partOpt,
|
||||
}
|
||||
chjobs <- job
|
||||
}
|
||||
close(chjobs)
|
||||
}()
|
||||
|
||||
// 5.Recv the resp etag to complete
|
||||
for i := 1; i <= partNum; i++ {
|
||||
err = nil
|
||||
for i := 0; i < partNum; i++ {
|
||||
if chunks[i].Done {
|
||||
optcom.Parts = append(optcom.Parts, Object{
|
||||
PartNumber: chunks[i].Number, ETag: chunks[i].ETag},
|
||||
)
|
||||
if err == nil {
|
||||
consumedBytes += chunks[i].Size
|
||||
event = newProgressEvent(ProgressDataEvent, chunks[i].Size, consumedBytes, totalBytes)
|
||||
progressCallback(listener, event)
|
||||
}
|
||||
continue
|
||||
}
|
||||
res := <-chresults
|
||||
// Notice one part fail can not get the etag according.
|
||||
if res.Resp == nil {
|
||||
if res.Resp == nil || res.err != nil {
|
||||
// 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.", uploadID, res.PartNumber)
|
||||
err = fmt.Errorf("UploadID %s, part %d failed to get resp content. error: %s", uploadID, res.PartNumber, res.err.Error())
|
||||
continue
|
||||
}
|
||||
// Notice one part fail can not get the etag according.
|
||||
etag := res.Resp.Header.Get("ETag")
|
||||
optcom.Parts = append(optcom.Parts, Object{
|
||||
PartNumber: res.PartNumber, ETag: etag},
|
||||
)
|
||||
if err == nil {
|
||||
consumedBytes += chunks[res.PartNumber-1].Size
|
||||
event = newProgressEvent(ProgressDataEvent, chunks[res.PartNumber-1].Size, consumedBytes, totalBytes)
|
||||
progressCallback(listener, event)
|
||||
}
|
||||
}
|
||||
close(chresults)
|
||||
if err != nil {
|
||||
event = newProgressEvent(ProgressFailedEvent, 0, consumedBytes, totalBytes, err)
|
||||
progressCallback(listener, event)
|
||||
return nil, nil, err
|
||||
}
|
||||
sort.Sort(ObjectList(optcom.Parts))
|
||||
|
||||
v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
|
||||
event = newProgressEvent(ProgressCompletedEvent, 0, consumedBytes, totalBytes)
|
||||
progressCallback(listener, event)
|
||||
|
||||
v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
|
||||
if err != nil {
|
||||
s.AbortMultipartUpload(ctx, name, uploadID)
|
||||
return v, resp, err
|
||||
}
|
||||
|
||||
if resp != nil && s.client.Conf.EnableCRC {
|
||||
scoscrc := resp.Header.Get("x-cos-hash-crc64ecma")
|
||||
icoscrc, _ := strconv.ParseUint(scoscrc, 10, 64)
|
||||
if icoscrc != localcrc {
|
||||
return v, resp, fmt.Errorf("verification failed, want:%v, return:%v", localcrc, icoscrc)
|
||||
}
|
||||
}
|
||||
return v, resp, err
|
||||
}
|
||||
|
||||
|
||||
+290
-6
@@ -5,8 +5,13 @@ import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc64"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InitiateMultipartUploadOptions is the option of InitateMultipartUpload
|
||||
@@ -41,13 +46,19 @@ func (s *ObjectService) InitiateMultipartUpload(ctx context.Context, name string
|
||||
|
||||
// ObjectUploadPartOptions is the options of upload-part
|
||||
type ObjectUploadPartOptions struct {
|
||||
Expect string `header:"Expect,omitempty" url:"-"`
|
||||
XCosContentSHA1 string `header:"x-cos-content-sha1" url:"-"`
|
||||
ContentLength int `header:"Content-Length,omitempty" url:"-"`
|
||||
|
||||
Expect string `header:"Expect,omitempty" url:"-"`
|
||||
XCosContentSHA1 string `header:"x-cos-content-sha1,omitempty" url:"-"`
|
||||
ContentLength int64 `header:"Content-Length,omitempty" url:"-"`
|
||||
ContentMD5 string `header:"Content-MD5,omitempty" url:"-"`
|
||||
XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
|
||||
XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,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:"-"`
|
||||
|
||||
XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
|
||||
// 上传进度, ProgressCompleteEvent不能表示对应API调用成功,API是否调用成功的判断标准为返回err==nil
|
||||
Listener ProgressListener `header:"-" url:"-" xml:"-"`
|
||||
}
|
||||
|
||||
// UploadPart 请求实现在初始化以后的分块上传,支持的块的数量为1到10000,块的大小为1 MB 到5 GB。
|
||||
@@ -58,14 +69,37 @@ type ObjectUploadPartOptions struct {
|
||||
// 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ContentLength
|
||||
//
|
||||
// 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, uopt *ObjectUploadPartOptions) (*Response, error) {
|
||||
if err := CheckReaderLen(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// opt 不为 nil
|
||||
opt := cloneObjectUploadPartOptions(uopt)
|
||||
totalBytes, err := GetReaderLen(r)
|
||||
if err != nil && opt.Listener != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 分块上传不支持 Chunk 上传
|
||||
if err == nil {
|
||||
// 与 go http 保持一致, 非bytes.Buffer/bytes.Reader/strings.Reader需用户指定ContentLength
|
||||
if opt != nil && opt.ContentLength == 0 && IsLenReader(r) {
|
||||
opt.ContentLength = totalBytes
|
||||
}
|
||||
}
|
||||
reader := TeeReader(r, nil, totalBytes, nil)
|
||||
if s.client.Conf.EnableCRC {
|
||||
reader.writer = crc64.New(crc64.MakeTable(crc64.ECMA))
|
||||
}
|
||||
if opt != nil && opt.Listener != nil {
|
||||
reader.listener = opt.Listener
|
||||
}
|
||||
u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID)
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.BucketURL,
|
||||
uri: u,
|
||||
method: http.MethodPut,
|
||||
optHeader: opt,
|
||||
body: r,
|
||||
body: reader,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
return resp, err
|
||||
@@ -244,3 +278,253 @@ func (s *ObjectService) CopyPart(ctx context.Context, name, uploadID string, par
|
||||
}
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
type ObjectListUploadsOptions struct {
|
||||
Delimiter string `url:"Delimiter,omitempty"`
|
||||
EncodingType string `url:"EncodingType,omitempty"`
|
||||
Prefix string `url:"Prefix"`
|
||||
MaxUploads int `url:"MaxUploads"`
|
||||
KeyMarker string `url:"KeyMarker"`
|
||||
UploadIdMarker string `url:"UploadIDMarker"`
|
||||
}
|
||||
|
||||
type ObjectListUploadsResult struct {
|
||||
XMLName xml.Name `xml:"ListMultipartUploadsResult"`
|
||||
Bucket string `xml:"Bucket,omitempty"`
|
||||
EncodingType string `xml:"Encoding-Type,omitempty"`
|
||||
KeyMarker string `xml:"KeyMarker,omitempty"`
|
||||
UploadIdMarker string `xml:"UploadIdMarker,omitempty"`
|
||||
NextKeyMarker string `xml:"NextKeyMarker,omitempty"`
|
||||
NextUploadIdMarker string `xml:"NextUploadIdMarker,omitempty"`
|
||||
MaxUploads string `xml:"MaxUploads,omitempty"`
|
||||
IsTruncated bool `xml:"IsTruncated,omitempty"`
|
||||
Prefix string `xml:"Prefix,omitempty"`
|
||||
Delimiter string `xml:"Delimiter,omitempty"`
|
||||
Upload []ListUploadsResultUpload `xml:"Upload,omitempty"`
|
||||
CommonPrefixes []string `xml:"CommonPrefixes>Prefix,omitempty"`
|
||||
}
|
||||
|
||||
type ListUploadsResultUpload struct {
|
||||
Key string `xml:"Key,omitempty"`
|
||||
UploadID string `xml:"UploadId,omitempty"`
|
||||
StorageClass string `xml:"StorageClass,omitempty"`
|
||||
Initiator *Initiator `xml:"Initiator,omitempty"`
|
||||
Owner *Owner `xml:"Owner,omitempty"`
|
||||
Initiated string `xml:"Initiated,omitempty"`
|
||||
}
|
||||
|
||||
func (s *ObjectService) ListUploads(ctx context.Context, opt *ObjectListUploadsOptions) (*ObjectListUploadsResult, *Response, error) {
|
||||
var res ObjectListUploadsResult
|
||||
sendOpt := &sendOptions{
|
||||
baseURL: s.client.BaseURL.BucketURL,
|
||||
uri: "/?uploads",
|
||||
method: http.MethodGet,
|
||||
optQuery: opt,
|
||||
result: &res,
|
||||
}
|
||||
resp, err := s.client.send(ctx, sendOpt)
|
||||
return &res, resp, err
|
||||
}
|
||||
|
||||
type MultiCopyOptions struct {
|
||||
OptCopy *ObjectCopyOptions
|
||||
PartSize int64
|
||||
ThreadPoolSize int
|
||||
}
|
||||
|
||||
type CopyJobs struct {
|
||||
Name string
|
||||
UploadId string
|
||||
RetryTimes int
|
||||
Chunk Chunk
|
||||
Opt *ObjectCopyPartOptions
|
||||
}
|
||||
|
||||
type CopyResults struct {
|
||||
PartNumber int
|
||||
Resp *Response
|
||||
err error
|
||||
res *CopyPartResult
|
||||
}
|
||||
|
||||
func copyworker(s *ObjectService, jobs <-chan *CopyJobs, results chan<- *CopyResults) {
|
||||
for j := range jobs {
|
||||
var copyres CopyResults
|
||||
j.Opt.XCosCopySourceRange = fmt.Sprintf("bytes=%d-%d", j.Chunk.OffSet, j.Chunk.OffSet+j.Chunk.Size-1)
|
||||
rt := j.RetryTimes
|
||||
for {
|
||||
res, resp, err := s.CopyPart(context.Background(), j.Name, j.UploadId, j.Chunk.Number, j.Opt.XCosCopySource, j.Opt)
|
||||
copyres.PartNumber = j.Chunk.Number
|
||||
copyres.Resp = resp
|
||||
copyres.err = err
|
||||
copyres.res = res
|
||||
if err != nil {
|
||||
rt--
|
||||
if rt == 0 {
|
||||
results <- ©res
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
results <- ©res
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ObjectService) innerHead(ctx context.Context, sourceURL string, opt *ObjectHeadOptions, id []string) (resp *Response, err error) {
|
||||
surl := strings.SplitN(sourceURL, "/", 2)
|
||||
if len(surl) < 2 {
|
||||
err = errors.New(fmt.Sprintf("sourceURL format error: %s", sourceURL))
|
||||
return
|
||||
}
|
||||
|
||||
u, err := url.Parse(fmt.Sprintf("https://%s", surl[0]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b := &BaseURL{BucketURL: u}
|
||||
client := NewClient(b, &http.Client{
|
||||
Transport: s.client.client.Transport,
|
||||
})
|
||||
if len(id) > 0 {
|
||||
resp, err = client.Object.Head(ctx, surl[1], nil, id[0])
|
||||
} else {
|
||||
resp, err = client.Object.Head(ctx, surl[1], nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SplitCopyFileIntoChunks(totalBytes int64, partSize int64) ([]Chunk, int, error) {
|
||||
var partNum int64
|
||||
if partSize > 0 {
|
||||
partSize = partSize * 1024 * 1024
|
||||
partNum = totalBytes / partSize
|
||||
if partNum >= 10000 {
|
||||
return nil, 0, errors.New("Too many parts, out of 10000")
|
||||
}
|
||||
} else {
|
||||
partNum, partSize = DividePart(totalBytes, 128)
|
||||
}
|
||||
|
||||
var chunks []Chunk
|
||||
var chunk = Chunk{}
|
||||
for i := int64(0); i < partNum; i++ {
|
||||
chunk.Number = int(i + 1)
|
||||
chunk.OffSet = i * partSize
|
||||
chunk.Size = partSize
|
||||
chunks = append(chunks, chunk)
|
||||
}
|
||||
|
||||
if totalBytes%partSize > 0 {
|
||||
chunk.Number = len(chunks) + 1
|
||||
chunk.OffSet = int64(len(chunks)) * partSize
|
||||
chunk.Size = totalBytes % partSize
|
||||
chunks = append(chunks, chunk)
|
||||
partNum++
|
||||
}
|
||||
return chunks, int(partNum), nil
|
||||
}
|
||||
|
||||
func (s *ObjectService) MultiCopy(ctx context.Context, name string, sourceURL string, opt *MultiCopyOptions, id ...string) (*ObjectCopyResult, *Response, error) {
|
||||
resp, err := s.innerHead(ctx, sourceURL, nil, id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
totalBytes := resp.ContentLength
|
||||
surl := strings.SplitN(sourceURL, "/", 2)
|
||||
if len(surl) < 2 {
|
||||
return nil, nil, errors.New(fmt.Sprintf("x-cos-copy-source format error: %s", sourceURL))
|
||||
}
|
||||
var u string
|
||||
if len(id) == 1 {
|
||||
u = fmt.Sprintf("%s/%s?versionId=%s", surl[0], encodeURIComponent(surl[1]), id[0])
|
||||
} else if len(id) == 0 {
|
||||
u = fmt.Sprintf("%s/%s", surl[0], encodeURIComponent(surl[1]))
|
||||
} else {
|
||||
return nil, nil, errors.New("wrong params")
|
||||
}
|
||||
|
||||
if opt == nil {
|
||||
opt = &MultiCopyOptions{}
|
||||
}
|
||||
chunks, partNum, err := SplitCopyFileIntoChunks(totalBytes, opt.PartSize)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if partNum == 0 || totalBytes < singleUploadMaxLength {
|
||||
if len(id) > 0 {
|
||||
return s.Copy(ctx, name, sourceURL, opt.OptCopy, id[0])
|
||||
} else {
|
||||
return s.Copy(ctx, name, sourceURL, opt.OptCopy)
|
||||
}
|
||||
}
|
||||
optini := CopyOptionsToMulti(opt.OptCopy)
|
||||
var uploadID string
|
||||
res, _, err := s.InitiateMultipartUpload(ctx, name, optini)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
uploadID = res.UploadID
|
||||
|
||||
var poolSize int
|
||||
if opt.ThreadPoolSize > 0 {
|
||||
poolSize = opt.ThreadPoolSize
|
||||
} else {
|
||||
poolSize = 1
|
||||
}
|
||||
|
||||
chjobs := make(chan *CopyJobs, 100)
|
||||
chresults := make(chan *CopyResults, 10000)
|
||||
optcom := &CompleteMultipartUploadOptions{}
|
||||
|
||||
for w := 1; w <= poolSize; w++ {
|
||||
go copyworker(s, chjobs, chresults)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for _, chunk := range chunks {
|
||||
partOpt := &ObjectCopyPartOptions{
|
||||
XCosCopySource: u,
|
||||
}
|
||||
job := &CopyJobs{
|
||||
Name: name,
|
||||
RetryTimes: 3,
|
||||
UploadId: uploadID,
|
||||
Chunk: chunk,
|
||||
Opt: partOpt,
|
||||
}
|
||||
chjobs <- job
|
||||
}
|
||||
close(chjobs)
|
||||
}()
|
||||
err = nil
|
||||
for i := 0; i < partNum; i++ {
|
||||
res := <-chresults
|
||||
if res.res == nil || res.err != nil {
|
||||
err = fmt.Errorf("UploadID %s, part %d failed to get resp content. error: %s", uploadID, res.PartNumber, res.err.Error())
|
||||
continue
|
||||
}
|
||||
etag := res.res.ETag
|
||||
optcom.Parts = append(optcom.Parts, Object{
|
||||
PartNumber: res.PartNumber, ETag: etag},
|
||||
)
|
||||
}
|
||||
close(chresults)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sort.Sort(ObjectList(optcom.Parts))
|
||||
|
||||
v, resp, err := s.CompleteMultipartUpload(ctx, name, uploadID, optcom)
|
||||
if err != nil {
|
||||
s.AbortMultipartUpload(ctx, name, uploadID)
|
||||
}
|
||||
cpres := &ObjectCopyResult{
|
||||
ETag: v.ETag,
|
||||
CRC64: resp.Header.Get("x-cos-hash-crc64ecma"),
|
||||
VersionId: resp.Header.Get("x-cos-version-id"),
|
||||
}
|
||||
return cpres, resp, err
|
||||
}
|
||||
|
||||
+444
@@ -0,0 +1,444 @@
|
||||
package cos
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JSONInputSerialization struct {
|
||||
Type string `xml:"Type,omitempty"`
|
||||
}
|
||||
|
||||
type CSVInputSerialization struct {
|
||||
RecordDelimiter string `xml:"RecordDelimiter,omitempty"`
|
||||
FieldDelimiter string `xml:"FieldDelimiter,omitempty"`
|
||||
QuoteCharacter string `xml:"QuoteCharacter,omitempty"`
|
||||
QuoteEscapeCharacter string `xml:"QuoteEscapeCharacter,omitempty"`
|
||||
AllowQuotedRecordDelimiter string `xml:"AllowQuotedRecordDelimiter,omitempty"`
|
||||
FileHeaderInfo string `xml:"FileHeaderInfo,omitempty"`
|
||||
Comments string `xml:"Comments,omitempty"`
|
||||
}
|
||||
|
||||
type SelectInputSerialization struct {
|
||||
CompressionType string `xml:"CompressionType,omitempty"`
|
||||
CSV *CSVInputSerialization `xml:"CSV,omitempty"`
|
||||
JSON *JSONInputSerialization `xml:"JSON,omitempty"`
|
||||
}
|
||||
|
||||
type JSONOutputSerialization struct {
|
||||
RecordDelimiter string `xml:"RecordDelimiter,omitempty"`
|
||||
}
|
||||
|
||||
type CSVOutputSerialization struct {
|
||||
QuoteFields string `xml:"QuoteFields,omitempty"`
|
||||
RecordDelimiter string `xml:"RecordDelimiter,omitempty"`
|
||||
FieldDelimiter string `xml:"FieldDelimiter,omitempty"`
|
||||
QuoteCharacter string `xml:"QuoteCharacter,omitempty"`
|
||||
QuoteEscapeCharacter string `xml:"QuoteEscapeCharacter,omitempty"`
|
||||
}
|
||||
|
||||
type SelectOutputSerialization struct {
|
||||
CSV *CSVOutputSerialization `xml:"CSV,omitempty"`
|
||||
JSON *JSONOutputSerialization `xml:"JSON,omitempty"`
|
||||
}
|
||||
|
||||
type ObjectSelectOptions struct {
|
||||
XMLName xml.Name `xml:"SelectRequest"`
|
||||
Expression string `xml:"Expression"`
|
||||
ExpressionType string `xml:"ExpressionType"`
|
||||
InputSerialization *SelectInputSerialization `xml:"InputSerialization"`
|
||||
OutputSerialization *SelectOutputSerialization `xml:"OutputSerialization"`
|
||||
RequestProgress string `xml:"RequestProgress>Enabled,omitempty"`
|
||||
}
|
||||
|
||||
func (s *ObjectService) Select(ctx context.Context, name string, opt *ObjectSelectOptions) (io.ReadCloser, error) {
|
||||
u := fmt.Sprintf("/%s?select&select-type=2", encodeURIComponent(name))
|
||||
sendOpt := sendOptions{
|
||||
baseURL: s.client.BaseURL.BucketURL,
|
||||
uri: u,
|
||||
method: http.MethodPost,
|
||||
body: opt,
|
||||
disableCloseBody: true,
|
||||
}
|
||||
resp, err := s.client.send(ctx, &sendOpt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &ObjectSelectResponse{
|
||||
Headers: resp.Header,
|
||||
Body: resp.Body,
|
||||
StatusCode: resp.StatusCode,
|
||||
Frame: &ObjectSelectResult{
|
||||
NextFrame: true,
|
||||
Payload: []byte{},
|
||||
},
|
||||
Finish: false,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *ObjectService) SelectToFile(ctx context.Context, name, file string, opt *ObjectSelectOptions) (*ObjectSelectResponse, error) {
|
||||
resp, err := s.Select(ctx, name, opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, _ := resp.(*ObjectSelectResponse)
|
||||
defer func() {
|
||||
io.Copy(ioutil.Discard, resp)
|
||||
resp.Close()
|
||||
}()
|
||||
|
||||
fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(0664))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
_, err = io.Copy(fd, resp)
|
||||
fd.Close()
|
||||
res.Finish = true
|
||||
return res, err
|
||||
}
|
||||
|
||||
const (
|
||||
kReadTimeout = 3
|
||||
kMessageType = ":message-type"
|
||||
kEventType = ":event-type"
|
||||
kContentType = ":content-type"
|
||||
|
||||
kRecordsFrameType = iota
|
||||
kContinuationFrameType
|
||||
kProgressFrameType
|
||||
kStatsFrameType
|
||||
kEndFrameType
|
||||
kErrorFrameType
|
||||
)
|
||||
|
||||
type ProgressFrame struct {
|
||||
XMLName xml.Name `xml:"Progress"`
|
||||
BytesScanned int `xml:"BytesScanned"`
|
||||
BytesProcessed int `xml:"BytesProcessed"`
|
||||
BytesReturned int `xml:"BytesReturned"`
|
||||
}
|
||||
|
||||
type StatsFrame struct {
|
||||
XMLName xml.Name `xml:"Stats"`
|
||||
BytesScanned int `xml:"BytesScanned"`
|
||||
BytesProcessed int `xml:"BytesProcessed"`
|
||||
BytesReturned int `xml:"BytesReturned"`
|
||||
}
|
||||
|
||||
type DataFrame struct {
|
||||
ContentType string
|
||||
ConsumedBytesLength int32
|
||||
LeftBytesLength int32
|
||||
}
|
||||
|
||||
type ErrorFrame struct {
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *ErrorFrame) Error() string {
|
||||
return fmt.Sprintf("Error Code: %s, Error Message: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
type ObjectSelectResult struct {
|
||||
TotalFrameLength int32
|
||||
TotalHeaderLength int32
|
||||
NextFrame bool
|
||||
FrameType int
|
||||
Payload []byte
|
||||
DataFrame DataFrame
|
||||
ProgressFrame ProgressFrame
|
||||
StatsFrame StatsFrame
|
||||
ErrorFrame *ErrorFrame
|
||||
}
|
||||
|
||||
type ObjectSelectResponse struct {
|
||||
StatusCode int
|
||||
Headers http.Header
|
||||
Body io.ReadCloser
|
||||
Frame *ObjectSelectResult
|
||||
Finish bool
|
||||
}
|
||||
|
||||
func (osr *ObjectSelectResponse) Read(p []byte) (n int, err error) {
|
||||
n, err = osr.readFrames(p)
|
||||
return
|
||||
}
|
||||
func (osr *ObjectSelectResponse) Close() error {
|
||||
return osr.Body.Close()
|
||||
}
|
||||
|
||||
func (osr *ObjectSelectResponse) readFrames(p []byte) (int, error) {
|
||||
if osr.Finish {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if osr.Frame.ErrorFrame != nil {
|
||||
return 0, osr.Frame.ErrorFrame
|
||||
}
|
||||
|
||||
var err error
|
||||
var nlen int
|
||||
dlen := len(p)
|
||||
|
||||
for nlen < dlen {
|
||||
if osr.Frame.NextFrame == true {
|
||||
osr.Frame.NextFrame = false
|
||||
err := osr.analysisPrelude()
|
||||
if err != nil {
|
||||
return nlen, err
|
||||
}
|
||||
err = osr.analysisHeader()
|
||||
if err != nil {
|
||||
return nlen, err
|
||||
}
|
||||
}
|
||||
switch osr.Frame.FrameType {
|
||||
case kRecordsFrameType:
|
||||
n, err := osr.analysisRecords(p[nlen:])
|
||||
if err != nil {
|
||||
return nlen, err
|
||||
}
|
||||
nlen += n
|
||||
case kContinuationFrameType:
|
||||
err = osr.payloadChecksum("ContinuationFrame")
|
||||
if err != nil {
|
||||
return nlen, err
|
||||
}
|
||||
case kProgressFrameType:
|
||||
err := osr.analysisXml(&osr.Frame.ProgressFrame)
|
||||
if err != nil {
|
||||
return nlen, err
|
||||
}
|
||||
case kStatsFrameType:
|
||||
err := osr.analysisXml(&osr.Frame.StatsFrame)
|
||||
if err != nil {
|
||||
return nlen, err
|
||||
}
|
||||
case kEndFrameType:
|
||||
err = osr.payloadChecksum("EndFrame")
|
||||
if err != nil {
|
||||
return nlen, err
|
||||
}
|
||||
osr.Finish = true
|
||||
return nlen, io.EOF
|
||||
case kErrorFrameType:
|
||||
return nlen, osr.Frame.ErrorFrame
|
||||
}
|
||||
}
|
||||
return nlen, err
|
||||
}
|
||||
|
||||
func (osr *ObjectSelectResponse) analysisPrelude() error {
|
||||
frame := make([]byte, 12)
|
||||
_, err := osr.fixedLengthRead(frame, kReadTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var preludeCRC uint32
|
||||
bytesToInt(frame[0:4], &osr.Frame.TotalFrameLength)
|
||||
bytesToInt(frame[4:8], &osr.Frame.TotalHeaderLength)
|
||||
bytesToInt(frame[8:12], &preludeCRC)
|
||||
osr.Frame.Payload = append(osr.Frame.Payload, frame...)
|
||||
|
||||
return checksum(frame[0:8], preludeCRC, "Prelude")
|
||||
}
|
||||
|
||||
func (osr *ObjectSelectResponse) analysisHeader() error {
|
||||
var nlen int32
|
||||
headers := make(map[string]string)
|
||||
for nlen < osr.Frame.TotalHeaderLength {
|
||||
var headerNameLen int8
|
||||
var headerValueLen int16
|
||||
bHeaderNameLen := make([]byte, 1)
|
||||
_, err := osr.fixedLengthRead(bHeaderNameLen, kReadTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nlen += 1
|
||||
bytesToInt(bHeaderNameLen, &headerNameLen)
|
||||
osr.Frame.Payload = append(osr.Frame.Payload, bHeaderNameLen...)
|
||||
|
||||
bHeaderName := make([]byte, headerNameLen)
|
||||
_, err = osr.fixedLengthRead(bHeaderName, kReadTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nlen += int32(headerNameLen)
|
||||
headerName := string(bHeaderName)
|
||||
osr.Frame.Payload = append(osr.Frame.Payload, bHeaderName...)
|
||||
|
||||
bValueTypeLen := make([]byte, 3)
|
||||
_, err = osr.fixedLengthRead(bValueTypeLen, kReadTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nlen += 3
|
||||
bytesToInt(bValueTypeLen[1:], &headerValueLen)
|
||||
osr.Frame.Payload = append(osr.Frame.Payload, bValueTypeLen...)
|
||||
|
||||
bHeaderValue := make([]byte, headerValueLen)
|
||||
_, err = osr.fixedLengthRead(bHeaderValue, kReadTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nlen += int32(headerValueLen)
|
||||
headers[headerName] = string(bHeaderValue)
|
||||
osr.Frame.Payload = append(osr.Frame.Payload, bHeaderValue...)
|
||||
}
|
||||
htype, ok := headers[kMessageType]
|
||||
if !ok {
|
||||
return fmt.Errorf("header parse failed, no message-type, headers: %+v\n", headers)
|
||||
}
|
||||
switch {
|
||||
case htype == "error":
|
||||
osr.Frame.FrameType = kErrorFrameType
|
||||
osr.Frame.ErrorFrame = &ErrorFrame{}
|
||||
osr.Frame.ErrorFrame.Code, _ = headers[":error-code"]
|
||||
osr.Frame.ErrorFrame.Message, _ = headers[":error-message"]
|
||||
case htype == "event":
|
||||
hevent, ok := headers[kEventType]
|
||||
if !ok {
|
||||
return fmt.Errorf("header parse failed, no event-type, headers: %+v\n", headers)
|
||||
}
|
||||
switch {
|
||||
case hevent == "Records":
|
||||
hContentType, ok := headers[kContentType]
|
||||
if ok {
|
||||
osr.Frame.DataFrame.ContentType = hContentType
|
||||
}
|
||||
osr.Frame.FrameType = kRecordsFrameType
|
||||
case hevent == "Cont":
|
||||
osr.Frame.FrameType = kContinuationFrameType
|
||||
case hevent == "Progress":
|
||||
osr.Frame.FrameType = kProgressFrameType
|
||||
case hevent == "Stats":
|
||||
osr.Frame.FrameType = kStatsFrameType
|
||||
case hevent == "End":
|
||||
osr.Frame.FrameType = kEndFrameType
|
||||
default:
|
||||
return fmt.Errorf("header parse failed, invalid event-type, headers: %+v\n", headers)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("header parse failed, invalid message-type: headers: %+v\n", headers)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (osr *ObjectSelectResponse) analysisRecords(data []byte) (int, error) {
|
||||
var needReadLength int32
|
||||
dlen := int32(len(data))
|
||||
restLen := osr.Frame.TotalFrameLength - 16 - osr.Frame.TotalHeaderLength - osr.Frame.DataFrame.ConsumedBytesLength
|
||||
if dlen <= restLen {
|
||||
needReadLength = dlen
|
||||
} else {
|
||||
needReadLength = restLen
|
||||
}
|
||||
n, err := osr.fixedLengthRead(data[:needReadLength], kReadTimeout)
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("read data frame error: %s", err.Error())
|
||||
}
|
||||
osr.Frame.DataFrame.ConsumedBytesLength += int32(n)
|
||||
osr.Frame.Payload = append(osr.Frame.Payload, data[:needReadLength]...)
|
||||
// 读完了一帧数据并填充到data中了
|
||||
if osr.Frame.DataFrame.ConsumedBytesLength == osr.Frame.TotalFrameLength-16-osr.Frame.TotalHeaderLength {
|
||||
osr.Frame.DataFrame.ConsumedBytesLength = 0
|
||||
err = osr.payloadChecksum("RecordFrame")
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (osr *ObjectSelectResponse) analysisXml(frame interface{}) error {
|
||||
payloadLength := osr.Frame.TotalFrameLength - 16 - osr.Frame.TotalHeaderLength
|
||||
bFrame := make([]byte, payloadLength)
|
||||
_, err := osr.fixedLengthRead(bFrame, kReadTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = xml.Unmarshal(bFrame, frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
osr.Frame.Payload = append(osr.Frame.Payload, bFrame...)
|
||||
return osr.payloadChecksum("XmlFrame")
|
||||
}
|
||||
|
||||
// 调用payloadChecksum时,表示该帧已读完,开始读取下一帧内容
|
||||
func (osr *ObjectSelectResponse) payloadChecksum(ftype string) error {
|
||||
bcrc := make([]byte, 4)
|
||||
_, err := osr.fixedLengthRead(bcrc, kReadTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var res uint32
|
||||
bytesToInt(bcrc, &res)
|
||||
err = checksum(osr.Frame.Payload, res, ftype)
|
||||
|
||||
osr.Frame.NextFrame = true
|
||||
osr.Frame.Payload = []byte{}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type chanReadIO struct {
|
||||
readLen int
|
||||
err error
|
||||
}
|
||||
|
||||
func (osr *ObjectSelectResponse) fixedLengthRead(p []byte, read_timeout int64) (int, error) {
|
||||
timeout := time.Duration(read_timeout)
|
||||
r := osr.Body
|
||||
ch := make(chan chanReadIO, 1)
|
||||
defer close(ch)
|
||||
go func(p []byte) {
|
||||
var needLen int
|
||||
readChan := chanReadIO{}
|
||||
needLen = len(p)
|
||||
for {
|
||||
n, err := r.Read(p[readChan.readLen:needLen])
|
||||
readChan.readLen += n
|
||||
if err != nil {
|
||||
readChan.err = err
|
||||
ch <- readChan
|
||||
return
|
||||
}
|
||||
|
||||
if readChan.readLen == needLen {
|
||||
break
|
||||
}
|
||||
}
|
||||
ch <- readChan
|
||||
}(p)
|
||||
|
||||
select {
|
||||
case <-time.After(time.Second * timeout):
|
||||
return 0, fmt.Errorf("requestId: %s, readLen timeout, timeout is %d(second),need read:%d", "sr.Headers.Get(HTTPHeaderOssRequestID)", timeout, len(p))
|
||||
case result := <-ch:
|
||||
return result.readLen, result.err
|
||||
}
|
||||
}
|
||||
|
||||
func bytesToInt(b []byte, ret interface{}) {
|
||||
binBuf := bytes.NewBuffer(b)
|
||||
binary.Read(binBuf, binary.BigEndian, ret)
|
||||
}
|
||||
|
||||
func checksum(b []byte, rec uint32, ftype string) error {
|
||||
c := crc32.ChecksumIEEE(b)
|
||||
if c != rec {
|
||||
return fmt.Errorf("parse type: %v, checksum failed, cal: %v, rec: %v\n", ftype, c, rec)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package cos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"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 (r *teeReader) Size() int64 {
|
||||
return r.totalBytes
|
||||
}
|
||||
|
||||
func (r *teeReader) Crc64() uint64 {
|
||||
if r.writer != nil {
|
||||
if th, ok := r.writer.(hash.Hash64); ok {
|
||||
return th.Sum64()
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
Vendored
+3
-3
@@ -610,7 +610,7 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312
|
||||
# github.com/tencentyun/cos-go-sdk-v5 v0.7.10
|
||||
# github.com/tencentyun/cos-go-sdk-v5 v0.7.24
|
||||
github.com/tencentyun/cos-go-sdk-v5
|
||||
github.com/tencentyun/cos-go-sdk-v5/debug
|
||||
# github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e
|
||||
@@ -1130,7 +1130,7 @@ k8s.io/utils/integer
|
||||
sigs.k8s.io/structured-merge-diff/v4/value
|
||||
# sigs.k8s.io/yaml v1.2.0
|
||||
sigs.k8s.io/yaml
|
||||
# yunion.io/x/executor v0.0.0-20201231064744-df32f32165a9
|
||||
# yunion.io/x/executor v0.0.0-20210310041834-3fcd1574f68e
|
||||
yunion.io/x/executor/apis
|
||||
yunion.io/x/executor/client
|
||||
yunion.io/x/executor/server
|
||||
@@ -1177,7 +1177,7 @@ yunion.io/x/pkg/util/workqueue
|
||||
yunion.io/x/pkg/utils
|
||||
# yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e
|
||||
yunion.io/x/s3cli
|
||||
# yunion.io/x/sqlchemy v0.0.0-20210204030051-4fb14f92a88f
|
||||
# yunion.io/x/sqlchemy v0.0.0-20210329171939-2c520f93aee3
|
||||
yunion.io/x/sqlchemy
|
||||
# yunion.io/x/structarg v0.0.0-20200720093445-9f850fa222ce
|
||||
yunion.io/x/structarg
|
||||
|
||||
+11
-1
@@ -575,7 +575,17 @@ func (c *STextColumn) IsSupportDefault() bool {
|
||||
}
|
||||
|
||||
func (c *STextColumn) ColType() string {
|
||||
return fmt.Sprintf("%s CHARACTER SET '%s'", c.SBaseWidthColumn.ColType(), c.Charset)
|
||||
var charset string
|
||||
var collate string
|
||||
switch c.Charset {
|
||||
case "ascii":
|
||||
charset = "ascii"
|
||||
collate = "ascii_general_ci"
|
||||
default:
|
||||
charset = "utf8mb4"
|
||||
collate = "utf8mb4_unicode_ci"
|
||||
}
|
||||
return fmt.Sprintf("%s CHARACTER SET '%s' COLLATE '%s'", c.SBaseWidthColumn.ColType(), charset, collate)
|
||||
}
|
||||
|
||||
func (c *STextColumn) IsText() bool {
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ func (ts *STableSpec) updateFields(dt interface{}, fields map[string]interface{}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if aCnt != 1 {
|
||||
if aCnt > 1 {
|
||||
return errors.Wrapf(ErrUnexpectRowCount, "affected rows %d != 1", aCnt)
|
||||
}
|
||||
return nil
|
||||
|
||||
+1
-1
@@ -179,7 +179,7 @@ func (t *STableSpec) insert(data interface{}, update bool, debug bool) error {
|
||||
// for insertOrUpdate cases, if no duplication, targetCnt=1, else targetCnt=2
|
||||
targetCnt = 2
|
||||
}
|
||||
if affectCnt < 1 || affectCnt > targetCnt {
|
||||
if (!update && affectCnt < 1) || affectCnt > targetCnt {
|
||||
return errors.Wrapf(ErrUnexpectRowCount, "Insert affected cnt %d != (1, %d)", affectCnt, targetCnt)
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -75,8 +75,10 @@ func (info *SSqlColumnInfo) toColumnSpec() IColumnSpec {
|
||||
charset := ""
|
||||
if info.Collation == "ascii_general_ci" {
|
||||
charset = "ascii"
|
||||
} else if info.Collation == "utf8_general_ci" {
|
||||
} else if info.Collation == "utf8_general_ci" || info.Collation == "utf8mb4_unicode_ci" {
|
||||
charset = "utf8"
|
||||
} else {
|
||||
charset = "ascii"
|
||||
}
|
||||
if len(charset) > 0 {
|
||||
tagmap[TAG_CHARSET] = charset
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@ func (ts *STableSpec) CreateSQL() string {
|
||||
if len(indexes) > 0 {
|
||||
cols = append(cols, indexes...)
|
||||
}
|
||||
return fmt.Sprintf("CREATE TABLE IF NOT EXISTS `%s` (\n%s\n) ENGINE=InnoDB DEFAULT CHARSET=utf8%s", ts.name, strings.Join(cols, ",\n"), autoInc)
|
||||
return fmt.Sprintf("CREATE TABLE IF NOT EXISTS `%s` (\n%s\n) ENGINE=InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci%s", ts.name, strings.Join(cols, ",\n"), autoInc)
|
||||
}
|
||||
|
||||
func NewTableInstance(ts ITableSpec) *STable {
|
||||
|
||||
+2
-7
@@ -16,7 +16,6 @@ package sqlchemy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -193,12 +192,8 @@ func (us *SUpdateSession) saveUpdate(dt interface{}) (UpdateDiffs, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if aCnt != 1 {
|
||||
if aCnt == 0 {
|
||||
return nil, sql.ErrNoRows
|
||||
} else {
|
||||
return nil, errors.Wrapf(ErrUnexpectRowCount, "affected rows %d != 1", aCnt)
|
||||
}
|
||||
if aCnt > 1 {
|
||||
return nil, errors.Wrapf(ErrUnexpectRowCount, "affected rows %d != 1", aCnt)
|
||||
}
|
||||
q := us.tableSpec.Query()
|
||||
for k, v := range primaries {
|
||||
|
||||
Reference in New Issue
Block a user