Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
957a6e1b9c | ||
|
|
efead0ccc2 | ||
|
|
91fc87ba8a | ||
|
|
6a2da87a06 | ||
|
|
c07e49771c | ||
|
|
6da3d4094c | ||
|
|
72e7751604 | ||
|
|
5057561906 | ||
|
|
3b45c0ae0e | ||
|
|
ba2e64b62e | ||
|
|
b8afb3f850 | ||
|
|
b44176ed7c | ||
|
|
757ad28a32 | ||
|
|
f7881abec6 | ||
|
|
0c4356fe8e | ||
|
|
e3a89ee58b | ||
|
|
08b8ff8ae7 | ||
|
|
4899c226f7 | ||
|
|
dad5b1f3fb | ||
|
|
2afc5e192c | ||
|
|
e39f3e3585 | ||
|
|
ca3ea38770 | ||
|
|
6bc7b5256c | ||
|
|
95b127211f | ||
|
|
e4b5ad8337 | ||
|
|
1942f2303e | ||
|
|
9a672cde0b | ||
|
|
deaefed41a | ||
|
|
d18e8cfac2 | ||
|
|
695c4466f5 | ||
|
|
98eab2886c | ||
|
|
5e69c19d34 | ||
|
|
f6c91c92d6 | ||
|
|
64d31f318a | ||
|
|
dd7c41ca08 | ||
|
|
e6f823c7c1 | ||
|
|
52c110b7ed | ||
|
|
287669a677 | ||
|
|
c88b73871d | ||
|
|
b0a399e92d | ||
|
|
5804e86747 | ||
|
|
cb662cdad5 | ||
|
|
0e9536d989 | ||
|
|
0206a7d026 | ||
|
|
d5130075f0 | ||
|
|
eb4e1ac4c9 | ||
|
|
649bd027d2 | ||
|
|
14683910e1 |
55
.github/workflows/auto-changelog.yml
vendored
Normal file
55
.github/workflows/auto-changelog.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: ChangeLog
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/setup-node@v2-beta
|
||||||
|
with:
|
||||||
|
node-version: '12'
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Checkout Tool
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: konakonall/auto-changelog
|
||||||
|
path: 'auto-changelog'
|
||||||
|
- name: Build Tool
|
||||||
|
run: |
|
||||||
|
cd auto-changelog
|
||||||
|
npm install
|
||||||
|
npm link
|
||||||
|
|
||||||
|
- name: Generate ChangeLog
|
||||||
|
env: # Or as an environment variable
|
||||||
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
auto-changelog --token $TOKEN
|
||||||
|
- name: Cat ChangeLog
|
||||||
|
run: cat CHANGELOG.md
|
||||||
|
|
||||||
|
- name: Commit files
|
||||||
|
env:
|
||||||
|
CI_USER: "gouki0123"
|
||||||
|
CI_EMAIL: "gouki0123@gmail.com"
|
||||||
|
run: |
|
||||||
|
git config --local user.email "$CI_EMAIL"
|
||||||
|
git config --local user.name "$CI_USER"
|
||||||
|
git add CHANGELOG.md && git commit -m 'Updated CHANGELOG.md' && echo "push=1" >> $GITHUB_ENV || echo "No changes to CHANGELOG.md"
|
||||||
|
|
||||||
|
- name: Push changes
|
||||||
|
if: env.push == 1
|
||||||
|
env:
|
||||||
|
CI_USER: "gouki0123"
|
||||||
|
CI_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
git push "https://$CI_USER:$CI_TOKEN@github.com/$GITHUB_REPOSITORY.git" HEAD:master
|
||||||
@@ -1,11 +1,5 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- '1.7'
|
|
||||||
- '1.8'
|
|
||||||
- '1.9'
|
|
||||||
- 1.10.x
|
|
||||||
- 1.11.x
|
|
||||||
- 1.12.x
|
|
||||||
- master
|
- master
|
||||||
sudo: false
|
sudo: false
|
||||||
before_install:
|
before_install:
|
||||||
|
|||||||
254
CHANGELOG.md
Normal file
254
CHANGELOG.md
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# 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.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)
|
||||||
4
batch.go
4
batch.go
@@ -13,7 +13,7 @@ type BatchRequestHeaders struct {
|
|||||||
XCosAppid int `header:"x-cos-appid" xml:"-" url:"-"`
|
XCosAppid int `header:"x-cos-appid" xml:"-" url:"-"`
|
||||||
ContentLength string `header:"Content-Length,omitempty" xml:"-" url:"-"`
|
ContentLength string `header:"Content-Length,omitempty" xml:"-" url:"-"`
|
||||||
ContentType string `header:"Content-Type,omitempty" xml:"-" url:"-"`
|
ContentType string `header:"Content-Type,omitempty" xml:"-" url:"-"`
|
||||||
Headers *http.Header `header:"-" xml:"-", url:"-"`
|
Headers *http.Header `header:"-" xml:"-" url:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BatchProgressSummary
|
// BatchProgressSummary
|
||||||
@@ -244,7 +244,7 @@ func (s *BatchService) UpdateJobPriority(ctx context.Context, opt *BatchUpdatePr
|
|||||||
type BatchUpdateStatusOptions struct {
|
type BatchUpdateStatusOptions struct {
|
||||||
JobId string `header:"-" url:"-" xml:"-"`
|
JobId string `header:"-" url:"-" xml:"-"`
|
||||||
RequestedJobStatus string `url:"requestedJobStatus" header:"-" xml:"-"`
|
RequestedJobStatus string `url:"requestedJobStatus" header:"-" xml:"-"`
|
||||||
StatusUpdateReason string `url:"statusUpdateReason,omitempty" header:"-", xml:"-"`
|
StatusUpdateReason string `url:"statusUpdateReason,omitempty" header:"-" xml:"-"`
|
||||||
}
|
}
|
||||||
type BatchUpdateStatusResult struct {
|
type BatchUpdateStatusResult struct {
|
||||||
XMLName xml.Name `xml:"UpdateJobStatusResult"`
|
XMLName xml.Name `xml:"UpdateJobStatusResult"`
|
||||||
|
|||||||
37
bucket_accelerate.go
Normal file
37
bucket_accelerate.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
74
bucket_accelerate_test.go
Normal file
74
bucket_accelerate_test.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package cos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBucketService_GetAccelerate(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "GET")
|
||||||
|
vs := values{
|
||||||
|
"accelerate": "",
|
||||||
|
}
|
||||||
|
testFormValues(t, r, vs)
|
||||||
|
fmt.Fprint(w, `<AccelerateConfiguration>
|
||||||
|
<Status>Enabled</Status>
|
||||||
|
<Type>COS</Type>
|
||||||
|
</AccelerateConfiguration>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
res, _, err := client.Bucket.GetAccelerate(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Bucket.GetAccelerate returned error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &BucketGetAccelerateResult{
|
||||||
|
XMLName: xml.Name{Local: "AccelerateConfiguration"},
|
||||||
|
Status: "Enabled",
|
||||||
|
Type: "COS",
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(res, want) {
|
||||||
|
t.Errorf("Bucket.GetAccelerate returned %+v, want %+v", res, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBucketService_PutAccelerate(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
opt := &BucketPutAccelerateOptions{
|
||||||
|
XMLName: xml.Name{Local: "AccelerateConfiguration"},
|
||||||
|
Status: "Enabled",
|
||||||
|
Type: "COS",
|
||||||
|
}
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "PUT")
|
||||||
|
vs := values{
|
||||||
|
"accelerate": "",
|
||||||
|
}
|
||||||
|
testFormValues(t, r, vs)
|
||||||
|
|
||||||
|
body := new(BucketPutAccelerateOptions)
|
||||||
|
xml.NewDecoder(r.Body).Decode(body)
|
||||||
|
want := opt
|
||||||
|
want.XMLName = xml.Name{Local: "AccelerateConfiguration"}
|
||||||
|
if !reflect.DeepEqual(body, want) {
|
||||||
|
t.Errorf("Bucket.PutAccelerate request\n body: %+v\n, want %+v\n", body, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := client.Bucket.PutAccelerate(context.Background(), opt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Bucket.PutAccelerate returned error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// BucketGetACLResult is same to the ACLXml
|
// BucketGetACLResult is same to the ACLXml
|
||||||
type BucketGetACLResult ACLXml
|
type BucketGetACLResult = ACLXml
|
||||||
|
|
||||||
// GetACL 使用API读取Bucket的ACL表,只有所有者有权操作。
|
// GetACL 使用API读取Bucket的ACL表,只有所有者有权操作。
|
||||||
//
|
//
|
||||||
@@ -20,6 +20,9 @@ func (s *BucketService) GetACL(ctx context.Context) (*BucketGetACLResult, *Respo
|
|||||||
result: &res,
|
result: &res,
|
||||||
}
|
}
|
||||||
resp, err := s.client.send(ctx, &sendOpt)
|
resp, err := s.client.send(ctx, &sendOpt)
|
||||||
|
if err == nil {
|
||||||
|
decodeACL(resp, &res)
|
||||||
|
}
|
||||||
return &res, resp, err
|
return &res, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type BucketEncryptionConfiguration struct {
|
|||||||
|
|
||||||
type BucketPutEncryptionOptions struct {
|
type BucketPutEncryptionOptions struct {
|
||||||
XMLName xml.Name `xml:"ServerSideEncryptionConfiguration"`
|
XMLName xml.Name `xml:"ServerSideEncryptionConfiguration"`
|
||||||
Rule *BucketEncryptionConfiguration `xml:"Rule>ApplySideEncryptionConfiguration"`
|
Rule *BucketEncryptionConfiguration `xml:"Rule>ApplyServerSideEncryptionByDefault"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BucketGetEncryptionResult BucketPutEncryptionOptions
|
type BucketGetEncryptionResult BucketPutEncryptionOptions
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ func TestBucketService_GetEncryption(t *testing.T) {
|
|||||||
testFormValues(t, r, vs)
|
testFormValues(t, r, vs)
|
||||||
fmt.Fprint(w, `<ServerSideEncryptionConfiguration>
|
fmt.Fprint(w, `<ServerSideEncryptionConfiguration>
|
||||||
<Rule>
|
<Rule>
|
||||||
<ApplySideEncryptionConfiguration>
|
<ApplyServerSideEncryptionByDefault>
|
||||||
<SSEAlgorithm>AES256</SSEAlgorithm>
|
<SSEAlgorithm>AES256</SSEAlgorithm>
|
||||||
</ApplySideEncryptionConfiguration>
|
</ApplyServerSideEncryptionByDefault>
|
||||||
</Rule>
|
</Rule>
|
||||||
</ServerSideEncryptionConfiguration>`)
|
</ServerSideEncryptionConfiguration>`)
|
||||||
|
|
||||||
|
|||||||
47
bucket_intelligenttiering.go
Normal file
47
bucket_intelligenttiering.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package cos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BucketIntelligentTieringTransition struct {
|
||||||
|
Days int `xml:"Days,omitempty"`
|
||||||
|
RequestFrequent int `xml:"RequestFrequent,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BucketPutIntelligentTieringOptions struct {
|
||||||
|
XMLName xml.Name `xml:"IntelligentTieringConfiguration"`
|
||||||
|
Status string `xml:"Status,omitempty"`
|
||||||
|
Transition *BucketIntelligentTieringTransition `xml:"Transition,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BucketGetIntelligentTieringResult BucketPutIntelligentTieringOptions
|
||||||
|
|
||||||
|
func (s *BucketService) PutIntelligentTiering(ctx context.Context, opt *BucketPutIntelligentTieringOptions) (*Response, error) {
|
||||||
|
if opt != nil && opt.Transition != nil {
|
||||||
|
opt.Transition.RequestFrequent = 1
|
||||||
|
}
|
||||||
|
sendOpt := sendOptions{
|
||||||
|
baseURL: s.client.BaseURL.BucketURL,
|
||||||
|
uri: "/?intelligenttiering",
|
||||||
|
method: http.MethodPut,
|
||||||
|
body: opt,
|
||||||
|
}
|
||||||
|
resp, err := s.client.send(ctx, &sendOpt)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BucketService) GetIntelligentTiering(ctx context.Context) (*BucketGetIntelligentTieringResult, *Response, error) {
|
||||||
|
var res BucketGetIntelligentTieringResult
|
||||||
|
sendOpt := sendOptions{
|
||||||
|
baseURL: s.client.BaseURL.BucketURL,
|
||||||
|
uri: "/?intelligenttiering",
|
||||||
|
method: http.MethodGet,
|
||||||
|
result: &res,
|
||||||
|
}
|
||||||
|
resp, err := s.client.send(ctx, &sendOpt)
|
||||||
|
return &res, resp, err
|
||||||
|
|
||||||
|
}
|
||||||
76
bucket_intelligenttiering_test.go
Normal file
76
bucket_intelligenttiering_test.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package cos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBucketService_PutIntelligentTiering(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
opt := &BucketPutIntelligentTieringOptions{
|
||||||
|
Status: "Enabled",
|
||||||
|
Transition: &BucketIntelligentTieringTransition{
|
||||||
|
Days: 30,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, http.MethodPut)
|
||||||
|
vs := values{
|
||||||
|
"intelligenttiering": "",
|
||||||
|
}
|
||||||
|
testFormValues(t, r, vs)
|
||||||
|
|
||||||
|
body := &BucketPutIntelligentTieringOptions{}
|
||||||
|
xml.NewDecoder(r.Body).Decode(body)
|
||||||
|
want := opt
|
||||||
|
want.XMLName = xml.Name{Local: "IntelligentTieringConfiguration"}
|
||||||
|
if !reflect.DeepEqual(want, body) {
|
||||||
|
t.Fatalf("Bucket.PutIntelligentTiering request\n body: %+v\n, want %+v\n", body, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := client.Bucket.PutIntelligentTiering(context.Background(), opt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Bucket.PutIntelligentTiering failed, error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBucketService_GetIntelligentTiering(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, http.MethodGet)
|
||||||
|
vs := values{
|
||||||
|
"intelligenttiering": "",
|
||||||
|
}
|
||||||
|
testFormValues(t, r, vs)
|
||||||
|
|
||||||
|
fmt.Fprint(w, `<IntelligentTieringConfiguration>
|
||||||
|
<Status>Enabled</Status>
|
||||||
|
<Transition>
|
||||||
|
<Days>30</Days>
|
||||||
|
</Transition>
|
||||||
|
</IntelligentTieringConfiguration>`)
|
||||||
|
})
|
||||||
|
res, _, err := client.Bucket.GetIntelligentTiering(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Bucket.GetIntelligentTiering failed, error: %v", err)
|
||||||
|
}
|
||||||
|
want := &BucketGetIntelligentTieringResult{
|
||||||
|
XMLName: xml.Name{Local: "IntelligentTieringConfiguration"},
|
||||||
|
Status: "Enabled",
|
||||||
|
Transition: &BucketIntelligentTieringTransition{
|
||||||
|
Days: 30,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(res, want) {
|
||||||
|
t.Errorf("Bucket.GetIntelligentTiering returned\n%+v, want\n%+v", res, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ type BucketLifecycleTransition struct {
|
|||||||
|
|
||||||
// BucketLifecycleAbortIncompleteMultipartUpload is the param of BucketLifecycleRule
|
// BucketLifecycleAbortIncompleteMultipartUpload is the param of BucketLifecycleRule
|
||||||
type BucketLifecycleAbortIncompleteMultipartUpload struct {
|
type BucketLifecycleAbortIncompleteMultipartUpload struct {
|
||||||
DaysAfterInitiation string `xml:"DaysAfterInititation,omitempty"`
|
DaysAfterInitiation int `xml:"DaysAfterInitiation,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BucketLifecycleRule is the rule of BucketLifecycle
|
// BucketLifecycleRule is the rule of BucketLifecycle
|
||||||
|
|||||||
102
bucket_test.go
102
bucket_test.go
@@ -152,14 +152,14 @@ func TestBucketService_Head(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBucketService_GetObjectVersions(t *testing.T) {
|
func TestBucketService_GetObjectVersions(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
testMethod(t, r, http.MethodGet)
|
testMethod(t, r, http.MethodGet)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
vs := values{
|
vs := values{
|
||||||
"versions": "",
|
"versions": "",
|
||||||
"delimiter": "/",
|
"delimiter": "/",
|
||||||
}
|
}
|
||||||
testFormValues(t, r, vs)
|
testFormValues(t, r, vs)
|
||||||
@@ -203,51 +203,51 @@ func TestBucketService_GetObjectVersions(t *testing.T) {
|
|||||||
</Owner>
|
</Owner>
|
||||||
</DeleteMarker>
|
</DeleteMarker>
|
||||||
</ListVersionsResult>`)
|
</ListVersionsResult>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
want := &BucketGetObjectVersionsResult {
|
want := &BucketGetObjectVersionsResult{
|
||||||
XMLName: xml.Name { Local: "ListVersionsResult" },
|
XMLName: xml.Name{Local: "ListVersionsResult"},
|
||||||
Name: "examplebucket-1250000000",
|
Name: "examplebucket-1250000000",
|
||||||
MaxKeys: 1000,
|
MaxKeys: 1000,
|
||||||
IsTruncated: false,
|
IsTruncated: false,
|
||||||
Delimiter: "/",
|
Delimiter: "/",
|
||||||
CommonPrefixes: []string {
|
CommonPrefixes: []string{
|
||||||
"example-folder-1/",
|
"example-folder-1/",
|
||||||
"example-folder-2/",
|
"example-folder-2/",
|
||||||
},
|
},
|
||||||
Version: []ListVersionsResultVersion {
|
Version: []ListVersionsResultVersion{
|
||||||
{
|
{
|
||||||
Key: "example-object-1.jpg",
|
Key: "example-object-1.jpg",
|
||||||
VersionId: "MTg0NDUxNzgxMjEzNTU3NTk1Mjg",
|
VersionId: "MTg0NDUxNzgxMjEzNTU3NTk1Mjg",
|
||||||
IsLatest: true,
|
IsLatest: true,
|
||||||
LastModified: "2019-08-16T10:45:53.000Z",
|
LastModified: "2019-08-16T10:45:53.000Z",
|
||||||
ETag: "\"5d1143df07a17b23320d0da161e2819e\"",
|
ETag: "\"5d1143df07a17b23320d0da161e2819e\"",
|
||||||
Size: 30,
|
Size: 30,
|
||||||
StorageClass: "STANDARD",
|
StorageClass: "STANDARD",
|
||||||
Owner: &Owner {
|
Owner: &Owner{
|
||||||
ID: "1250000000",
|
ID: "1250000000",
|
||||||
DisplayName: "1250000000",
|
DisplayName: "1250000000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DeleteMarker: []ListVersionsResultDeleteMarker {
|
DeleteMarker: []ListVersionsResultDeleteMarker{
|
||||||
{
|
{
|
||||||
Key: "example-object-1.jpg",
|
Key: "example-object-1.jpg",
|
||||||
VersionId: "MTg0NDUxNzgxMjEzNjE1OTcxMzM",
|
VersionId: "MTg0NDUxNzgxMjEzNjE1OTcxMzM",
|
||||||
IsLatest: false,
|
IsLatest: false,
|
||||||
LastModified: "2019-08-16T10:45:47.000Z",
|
LastModified: "2019-08-16T10:45:47.000Z",
|
||||||
Owner: &Owner {
|
Owner: &Owner{
|
||||||
ID: "1250000000",
|
ID: "1250000000",
|
||||||
DisplayName: "1250000000",
|
DisplayName: "1250000000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
opt := &BucketGetObjectVersionsOptions {
|
opt := &BucketGetObjectVersionsOptions{
|
||||||
Delimiter: "/",
|
Delimiter: "/",
|
||||||
}
|
}
|
||||||
res, _, err := client.Bucket.GetObjectVersions(context.Background(), opt)
|
res, _, err := client.Bucket.GetObjectVersions(context.Background(), opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Bucket.GetObjectVersions returned error: %v", err)
|
t.Fatalf("Bucket.GetObjectVersions returned error: %v", err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(res, want) {
|
if !reflect.DeepEqual(res, want) {
|
||||||
|
|||||||
181
ci.go
181
ci.go
@@ -1,14 +1,18 @@
|
|||||||
package cos
|
package cos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CIService service
|
||||||
|
|
||||||
type PicOperations struct {
|
type PicOperations struct {
|
||||||
IsPicInfo int `json:"is_pic_info,omitempty"`
|
IsPicInfo int `json:"is_pic_info,omitempty"`
|
||||||
Rules []PicOperationsRules `json:"rules,omitemtpy"`
|
Rules []PicOperationsRules `json:"rules,omitemtpy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PicOperationsRules struct {
|
type PicOperationsRules struct {
|
||||||
Bucket string `json:"bucket,omitempty"`
|
Bucket string `json:"bucket,omitempty"`
|
||||||
FileId string `json:"fileid"`
|
FileId string `json:"fileid"`
|
||||||
@@ -16,9 +20,184 @@ type PicOperationsRules struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EncodePicOperations(pic *PicOperations) string {
|
func EncodePicOperations(pic *PicOperations) string {
|
||||||
|
if pic == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
bs, err := json.Marshal(pic)
|
bs, err := json.Marshal(pic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return string(bs)
|
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
|
||||||
|
}
|
||||||
|
|||||||
272
ci_doc.go
Normal file
272
ci_doc.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
61
cos.go
61
cos.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -21,7 +22,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Version current go sdk version
|
// Version current go sdk version
|
||||||
Version = "0.7.8"
|
Version = "0.7.17"
|
||||||
userAgent = "cos-go-sdk-v5/" + Version
|
userAgent = "cos-go-sdk-v5/" + Version
|
||||||
contentTypeXML = "application/xml"
|
contentTypeXML = "application/xml"
|
||||||
defaultServiceBaseURL = "http://service.cos.myqcloud.com"
|
defaultServiceBaseURL = "http://service.cos.myqcloud.com"
|
||||||
@@ -41,6 +42,8 @@ type BaseURL struct {
|
|||||||
ServiceURL *url.URL
|
ServiceURL *url.URL
|
||||||
// 访问 job API 的基础 URL (不包含 path 部分): http://example.com
|
// 访问 job API 的基础 URL (不包含 path 部分): http://example.com
|
||||||
BatchURL *url.URL
|
BatchURL *url.URL
|
||||||
|
// 访问 CI 的基础 URL
|
||||||
|
CIURL *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBucketURL 生成 BaseURL 所需的 BucketURL
|
// NewBucketURL 生成 BaseURL 所需的 BucketURL
|
||||||
@@ -81,6 +84,7 @@ type Client struct {
|
|||||||
Bucket *BucketService
|
Bucket *BucketService
|
||||||
Object *ObjectService
|
Object *ObjectService
|
||||||
Batch *BatchService
|
Batch *BatchService
|
||||||
|
CI *CIService
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
@@ -98,6 +102,7 @@ func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
|
|||||||
baseURL.BucketURL = uri.BucketURL
|
baseURL.BucketURL = uri.BucketURL
|
||||||
baseURL.ServiceURL = uri.ServiceURL
|
baseURL.ServiceURL = uri.ServiceURL
|
||||||
baseURL.BatchURL = uri.BatchURL
|
baseURL.BatchURL = uri.BatchURL
|
||||||
|
baseURL.CIURL = uri.CIURL
|
||||||
}
|
}
|
||||||
if baseURL.ServiceURL == nil {
|
if baseURL.ServiceURL == nil {
|
||||||
baseURL.ServiceURL, _ = url.Parse(defaultServiceBaseURL)
|
baseURL.ServiceURL, _ = url.Parse(defaultServiceBaseURL)
|
||||||
@@ -113,6 +118,7 @@ func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
|
|||||||
c.Bucket = (*BucketService)(&c.common)
|
c.Bucket = (*BucketService)(&c.common)
|
||||||
c.Object = (*ObjectService)(&c.common)
|
c.Object = (*ObjectService)(&c.common)
|
||||||
c.Batch = (*BatchService)(&c.common)
|
c.Batch = (*BatchService)(&c.common)
|
||||||
|
c.CI = (*CIService)(&c.common)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,9 +249,6 @@ func (c *Client) send(ctx context.Context, opt *sendOptions) (resp *Response, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp, err = c.doAPI(ctx, req, opt.result, !opt.disableCloseBody)
|
resp, err = c.doAPI(ctx, req, opt.result, !opt.disableCloseBody)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,3 +358,53 @@ type ACLXml struct {
|
|||||||
Owner *Owner
|
Owner *Owner
|
||||||
AccessControlList []ACLGrant `xml:"AccessControlList>Grant,omitempty"`
|
AccessControlList []ACLGrant `xml:"AccessControlList>Grant,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeACL(resp *Response, res *ACLXml) {
|
||||||
|
ItemMap := map[string]string{
|
||||||
|
"ACL": "x-cos-acl",
|
||||||
|
"READ": "x-cos-grant-read",
|
||||||
|
"WRITE": "x-cos-grant-write",
|
||||||
|
"READ_ACP": "x-cos-grant-read-acp",
|
||||||
|
"WRITE_ACP": "x-cos-grant-write-acp",
|
||||||
|
"FULL_CONTROL": "x-cos-grant-full-control",
|
||||||
|
}
|
||||||
|
publicACL := make(map[string]int)
|
||||||
|
resACL := make(map[string][]string)
|
||||||
|
for _, item := range res.AccessControlList {
|
||||||
|
if item.Grantee == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if item.Grantee.ID == "qcs::cam::anyone:anyone" || item.Grantee.URI == "http://cam.qcloud.com/groups/global/AllUsers" {
|
||||||
|
publicACL[item.Permission] = 1
|
||||||
|
} else if item.Grantee.ID != res.Owner.ID {
|
||||||
|
resACL[item.Permission] = append(resACL[item.Permission], "id=\""+item.Grantee.ID+"\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if publicACL["FULL_CONTROL"] == 1 || (publicACL["READ"] == 1 && publicACL["WRITE"] == 1) {
|
||||||
|
resACL["ACL"] = []string{"public-read-write"}
|
||||||
|
} else if publicACL["READ"] == 1 {
|
||||||
|
resACL["ACL"] = []string{"public-read"}
|
||||||
|
} else {
|
||||||
|
resACL["ACL"] = []string{"private"}
|
||||||
|
}
|
||||||
|
|
||||||
|
for item, header := range ItemMap {
|
||||||
|
if len(resp.Header.Get(header)) > 0 || len(resACL[item]) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resp.Header.Set(header, uniqueGrantID(resACL[item]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniqueGrantID(grantIDs []string) string {
|
||||||
|
res := []string{}
|
||||||
|
filter := make(map[string]int)
|
||||||
|
for _, id := range grantIDs {
|
||||||
|
if filter[id] != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filter[id] = 1
|
||||||
|
res = append(res, id)
|
||||||
|
}
|
||||||
|
return strings.Join(res, ",")
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func setup() {
|
|||||||
server = httptest.NewServer(mux)
|
server = httptest.NewServer(mux)
|
||||||
|
|
||||||
u, _ := url.Parse(server.URL)
|
u, _ := url.Parse(server.URL)
|
||||||
client = NewClient(&BaseURL{u, u, u}, nil)
|
client = NewClient(&BaseURL{u, u, u, u}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// teardown closes the test HTTP server.
|
// teardown closes the test HTTP server.
|
||||||
|
|||||||
@@ -56,14 +56,14 @@ const (
|
|||||||
kBucket = "cosgosdktest-1259654469"
|
kBucket = "cosgosdktest-1259654469"
|
||||||
kRegion = "ap-guangzhou"
|
kRegion = "ap-guangzhou"
|
||||||
|
|
||||||
// 跨区域复制需要的目标存储桶,地域不能与kBucket存储桶相同。
|
// 跨区域复制需要的目标存储桶,地域不能与kBucket存储桶相同, 目的存储桶需要开启多版本
|
||||||
kRepBucket = "cosgosdkreptest"
|
kRepBucket = "cosgosdkreptest"
|
||||||
kRepRegion = "ap-chengdu"
|
kRepRegion = "ap-chengdu"
|
||||||
|
|
||||||
// Batch测试需要的源存储桶和目标存储桶,目前只在成都、重庆地域公测
|
// Batch测试需要的源存储桶和目标存储桶,目前只在成都、重庆地域公测
|
||||||
kBatchBucket = "testcd-1259654469"
|
kBatchBucket = "cosgosdktest-1259654469"
|
||||||
kTargetBatchBucket = "cosgosdkreptest-1259654469" //复用了存储桶
|
kTargetBatchBucket = "cosgosdktest-1259654469" //复用了存储桶
|
||||||
kBatchRegion = "ap-chengdu"
|
kBatchRegion = "ap-guangzhou"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *CosTestSuite) SetupSuite() {
|
func (s *CosTestSuite) SetupSuite() {
|
||||||
@@ -85,9 +85,9 @@ func (s *CosTestSuite) SetupSuite() {
|
|||||||
s.Region = p[2]
|
s.Region = p[2]
|
||||||
|
|
||||||
// Bucket name
|
// Bucket name
|
||||||
pp := strings.Split(p[0], "-")
|
pi := strings.LastIndex(p[0], "-")
|
||||||
s.Bucket = pp[0]
|
s.Bucket = p[0][:pi]
|
||||||
s.Appid = pp[1]
|
s.Appid = p[0][pi+1:]
|
||||||
|
|
||||||
ib := &cos.BaseURL{BucketURL: bucketurl, BatchURL: batchurl}
|
ib := &cos.BaseURL{BucketURL: bucketurl, BatchURL: batchurl}
|
||||||
s.Client = cos.NewClient(ib, &http.Client{
|
s.Client = cos.NewClient(ib, &http.Client{
|
||||||
@@ -185,12 +185,12 @@ func (s *CosTestSuite) TestGetBucket() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CosTestSuite) TestGetObjectVersions() {
|
func (s *CosTestSuite) TestGetObjectVersions() {
|
||||||
opt := &cos.BucketGetObjectVersionsOptions {
|
opt := &cos.BucketGetObjectVersionsOptions{
|
||||||
Prefix: "中文",
|
Prefix: "中文",
|
||||||
MaxKeys: 3,
|
MaxKeys: 3,
|
||||||
}
|
}
|
||||||
_, _, err := s.Client.Bucket.GetObjectVersions(context.Background(), opt)
|
_, _, err := s.Client.Bucket.GetObjectVersions(context.Background(), opt)
|
||||||
assert.Nil(s.T(), err, "GetObjectVersions Failed")
|
assert.Nil(s.T(), err, "GetObjectVersions Failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CosTestSuite) TestGetBucketLocation() {
|
func (s *CosTestSuite) TestGetBucketLocation() {
|
||||||
@@ -226,6 +226,7 @@ func (s *CosTestSuite) TestVersionAndReplication() {
|
|||||||
}
|
}
|
||||||
_, err := s.Client.Bucket.PutVersioning(context.Background(), opt)
|
_, err := s.Client.Bucket.PutVersioning(context.Background(), opt)
|
||||||
assert.Nil(s.T(), err, "PutVersioning Failed")
|
assert.Nil(s.T(), err, "PutVersioning Failed")
|
||||||
|
time.Sleep(time.Second)
|
||||||
v, _, err := s.Client.Bucket.GetVersioning(context.Background())
|
v, _, err := s.Client.Bucket.GetVersioning(context.Background())
|
||||||
assert.Nil(s.T(), err, "GetVersioning Failed")
|
assert.Nil(s.T(), err, "GetVersioning Failed")
|
||||||
assert.Equal(s.T(), "Enabled", v.Status, "Get Wrong Version status")
|
assert.Equal(s.T(), "Enabled", v.Status, "Get Wrong Version status")
|
||||||
@@ -248,6 +249,7 @@ func (s *CosTestSuite) TestVersionAndReplication() {
|
|||||||
|
|
||||||
_, err = s.Client.Bucket.PutBucketReplication(context.Background(), repOpt)
|
_, err = s.Client.Bucket.PutBucketReplication(context.Background(), repOpt)
|
||||||
assert.Nil(s.T(), err, "PutBucketReplication Failed")
|
assert.Nil(s.T(), err, "PutBucketReplication Failed")
|
||||||
|
time.Sleep(time.Second)
|
||||||
vr, _, err := s.Client.Bucket.GetBucketReplication(context.Background())
|
vr, _, err := s.Client.Bucket.GetBucketReplication(context.Background())
|
||||||
assert.Nil(s.T(), err, "GetBucketReplication Failed")
|
assert.Nil(s.T(), err, "GetBucketReplication Failed")
|
||||||
for _, r := range vr.Rule {
|
for _, r := range vr.Rule {
|
||||||
@@ -812,7 +814,7 @@ func (s *CosTestSuite) TestBatch() {
|
|||||||
assert.Equal(s.T(), res3.Priority, 3, "priority not right")
|
assert.Equal(s.T(), res3.Priority, 3, "priority not right")
|
||||||
|
|
||||||
// 等待状态变成Suspended
|
// 等待状态变成Suspended
|
||||||
for i := 0; i < 10; i = i + 1 {
|
for i := 0; i < 50; i = i + 1 {
|
||||||
res, _, err := client.Batch.DescribeJob(context.Background(), jobid, headers)
|
res, _, err := client.Batch.DescribeJob(context.Background(), jobid, headers)
|
||||||
assert.Nil(s.T(), err, "describe job Failed")
|
assert.Nil(s.T(), err, "describe job Failed")
|
||||||
assert.Equal(s.T(), res2.Job.ConfirmationRequired, "true", "ConfirmationRequired not right")
|
assert.Equal(s.T(), res2.Job.ConfirmationRequired, "true", "ConfirmationRequired not right")
|
||||||
@@ -880,6 +882,31 @@ func (s *CosTestSuite) TestReferer() {
|
|||||||
assert.Equal(s.T(), opt.EmptyReferConfiguration, res.EmptyReferConfiguration, "GetReferer Failed")
|
assert.Equal(s.T(), opt.EmptyReferConfiguration, res.EmptyReferConfiguration, "GetReferer Failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *CosTestSuite) TestAccelerate() {
|
||||||
|
opt := &cos.BucketPutAccelerateOptions{
|
||||||
|
Status: "Enabled",
|
||||||
|
Type: "COS",
|
||||||
|
}
|
||||||
|
_, err := s.Client.Bucket.PutAccelerate(context.Background(), opt)
|
||||||
|
assert.Nil(s.T(), err, "PutAccelerate Failed")
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
res, _, err := s.Client.Bucket.GetAccelerate(context.Background())
|
||||||
|
assert.Nil(s.T(), err, "GetAccelerate Failed")
|
||||||
|
assert.Equal(s.T(), opt.Status, res.Status, "GetAccelerate Failed")
|
||||||
|
assert.Equal(s.T(), opt.Type, res.Type, "GetAccelerate Failed")
|
||||||
|
|
||||||
|
opt.Status = "Suspended"
|
||||||
|
_, err = s.Client.Bucket.PutAccelerate(context.Background(), opt)
|
||||||
|
assert.Nil(s.T(), err, "PutAccelerate Failed")
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
res, _, err = s.Client.Bucket.GetAccelerate(context.Background())
|
||||||
|
assert.Nil(s.T(), err, "GetAccelerate Failed")
|
||||||
|
assert.Equal(s.T(), opt.Status, res.Status, "GetAccelerate Failed")
|
||||||
|
assert.Equal(s.T(), opt.Type, res.Type, "GetAccelerate Failed")
|
||||||
|
}
|
||||||
|
|
||||||
// End of api test
|
// End of api test
|
||||||
|
|
||||||
// All methods that begin with "Test" are run as tests within a
|
// All methods that begin with "Test" are run as tests within a
|
||||||
|
|||||||
72
example/bucket/accelerate.go
Normal file
72
example/bucket/accelerate.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func log_status(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cos.IsNotFoundError(err) {
|
||||||
|
// WARN
|
||||||
|
fmt.Println("Resource is not existed")
|
||||||
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
|
fmt.Printf("Code: %v\n", e.Code)
|
||||||
|
fmt.Printf("Message: %v\n", e.Message)
|
||||||
|
fmt.Printf("Resource: %v\n", e.Resource)
|
||||||
|
fmt.Printf("RequestId: %v\n", e.RequestID)
|
||||||
|
// ERROR
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
// ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{
|
||||||
|
BucketURL: u,
|
||||||
|
}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
RequestBody: true,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
res, _, err := c.Bucket.GetAccelerate(context.Background())
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", res)
|
||||||
|
|
||||||
|
opt := &cos.BucketPutAccelerateOptions{
|
||||||
|
Status: "Enabled",
|
||||||
|
Type: "COS",
|
||||||
|
}
|
||||||
|
_, err = c.Bucket.PutAccelerate(context.Background(), opt)
|
||||||
|
log_status(err)
|
||||||
|
|
||||||
|
res, _, err = c.Bucket.GetAccelerate(context.Background())
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", res)
|
||||||
|
|
||||||
|
opt.Status = "Suspended"
|
||||||
|
_, err = c.Bucket.PutAccelerate(context.Background(), opt)
|
||||||
|
log_status(err)
|
||||||
|
|
||||||
|
res, _, err = c.Bucket.GetAccelerate(context.Background())
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", res)
|
||||||
|
}
|
||||||
64
example/bucket/intelligenttiering.go
Normal file
64
example/bucket/intelligenttiering.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func log_status(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cos.IsNotFoundError(err) {
|
||||||
|
// WARN
|
||||||
|
fmt.Println("Resource is not existed")
|
||||||
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
|
fmt.Printf("Code: %v\n", e.Code)
|
||||||
|
fmt.Printf("Message: %v\n", e.Message)
|
||||||
|
fmt.Printf("Resource: %v\n", e.Resource)
|
||||||
|
fmt.Printf("RequestId: %v\n", e.RequestID)
|
||||||
|
// ERROR
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
// ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{
|
||||||
|
BucketURL: u,
|
||||||
|
}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
RequestBody: false,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
opt := &cos.BucketPutIntelligentTieringOptions{
|
||||||
|
Status: "Enabled",
|
||||||
|
Transition: &cos.BucketIntelligentTieringTransition{
|
||||||
|
Days: 30,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := c.Bucket.PutIntelligentTiering(context.Background(), opt)
|
||||||
|
log_status(err)
|
||||||
|
res, _, err := c.Bucket.GetIntelligentTiering(context.Background())
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", res)
|
||||||
|
fmt.Printf("%+v\n", res.Status)
|
||||||
|
fmt.Printf("%+v\n", res.Transition.Days)
|
||||||
|
}
|
||||||
132
example/object/ci_doc_process.go
Normal file
132
example/object/ci_doc_process.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func log_status(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cos.IsNotFoundError(err) {
|
||||||
|
// WARN
|
||||||
|
fmt.Println("WARN: Resource is not existed")
|
||||||
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
|
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
||||||
|
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
||||||
|
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
|
||||||
|
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
|
||||||
|
// ERROR
|
||||||
|
} else {
|
||||||
|
fmt.Printf("ERROR: %v\n", err)
|
||||||
|
// ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
cu, _ := url.Parse("https://test-1259654469.ci.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{BucketURL: u, CIURL: cu}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
// Notice when put a large file and set need the request body, might happend out of memory error.
|
||||||
|
RequestBody: true,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 1、UpdateDocProcessQueue
|
||||||
|
updateQueueOpt := &cos.UpdateDocProcessQueueOptions{
|
||||||
|
Name: "queue-doc-process-1",
|
||||||
|
QueueID: "p111a8dd208104ce3b11c78398f658ca8",
|
||||||
|
State: "Active",
|
||||||
|
NotifyConfig: &cos.DocProcessQueueNotifyConfig{
|
||||||
|
State: "Off",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
updateQueueRes, _, err := c.CI.UpdateDocProcessQueue(context.Background(), updateQueueOpt)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", updateQueueRes)
|
||||||
|
|
||||||
|
// 2、DescribeDocProcessQueues
|
||||||
|
DescribeQueueOpt := &cos.DescribeDocProcessQueuesOptions{
|
||||||
|
QueueIds: "p111a8dd208104ce3b11c78398f658ca8,p4318f85d2aa14c43b1dba6f9b78be9b3,aacb2bb066e9c4478834d4196e76c49d3",
|
||||||
|
PageNumber: 1,
|
||||||
|
PageSize: 2,
|
||||||
|
}
|
||||||
|
DescribeQueueRes, _, err := c.CI.DescribeDocProcessQueues(context.Background(), DescribeQueueOpt)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", DescribeQueueRes)
|
||||||
|
|
||||||
|
// 3、DescribeDocProcessBuckets
|
||||||
|
BucketsOpt := &cos.DescribeDocProcessBucketsOptions{
|
||||||
|
Regions: "All",
|
||||||
|
}
|
||||||
|
BucketsRes, _, err := c.CI.DescribeDocProcessBuckets(context.Background(), BucketsOpt)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", BucketsRes)
|
||||||
|
|
||||||
|
// 4、CreateDocProcessJobs
|
||||||
|
createJobOpt := &cos.CreateDocProcessJobsOptions{
|
||||||
|
Tag: "DocProcess",
|
||||||
|
Input: &cos.DocProcessJobInput{
|
||||||
|
Object: "form.pdf",
|
||||||
|
},
|
||||||
|
Operation: &cos.DocProcessJobOperation{
|
||||||
|
Output: &cos.DocProcessJobOutput{
|
||||||
|
Region: "ap-guangzhou",
|
||||||
|
Object: "test-doc${Number}",
|
||||||
|
Bucket: "test-1259654469",
|
||||||
|
},
|
||||||
|
DocProcess: &cos.DocProcessJobDocProcess{
|
||||||
|
TgtType: "png",
|
||||||
|
StartPage: 1,
|
||||||
|
EndPage: -1,
|
||||||
|
ImageParams: "watermark/1/image/aHR0cDovL3Rlc3QwMDUtMTI1MTcwNDcwOC5jb3MuYXAtY2hvbmdxaW5nLm15cWNsb3VkLmNvbS8xLmpwZw==/gravity/southeast",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
QueueId: "p111a8dd208104ce3b11c78398f658ca8",
|
||||||
|
}
|
||||||
|
createJobRes, _, err := c.CI.CreateDocProcessJobs(context.Background(), createJobOpt)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", createJobRes.JobsDetail)
|
||||||
|
|
||||||
|
// 5、DescribeDocProcessJob
|
||||||
|
DescribeJobRes, _, err := c.CI.DescribeDocProcessJob(context.Background(), createJobRes.JobsDetail.JobId)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", DescribeJobRes.JobsDetail)
|
||||||
|
|
||||||
|
// 6、DescribeDocProcessJobs
|
||||||
|
DescribeJobsOpt := &cos.DescribeDocProcessJobsOptions{
|
||||||
|
QueueId: "p111a8dd208104ce3b11c78398f658ca8",
|
||||||
|
Tag: "DocProcess",
|
||||||
|
}
|
||||||
|
DescribeJobsRes, _, err := c.CI.DescribeDocProcessJobs(context.Background(), DescribeJobsOpt)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", DescribeJobsRes)
|
||||||
|
|
||||||
|
// 7、doc-preview
|
||||||
|
opt := &cos.DocPreviewOptions{
|
||||||
|
Page: 1,
|
||||||
|
}
|
||||||
|
resp, err := c.CI.DocPreview(context.Background(), "form.pdf", opt)
|
||||||
|
log_status(err)
|
||||||
|
fd, _ := os.OpenFile("form.pdf", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
|
||||||
|
io.Copy(fd, resp.Body)
|
||||||
|
fd.Close()
|
||||||
|
|
||||||
|
}
|
||||||
63
example/object/ci_image_process.go
Normal file
63
example/object/ci_image_process.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func log_status(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cos.IsNotFoundError(err) {
|
||||||
|
// WARN
|
||||||
|
fmt.Println("WARN: Resource is not existed")
|
||||||
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
|
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
||||||
|
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
||||||
|
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
|
||||||
|
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
|
||||||
|
// ERROR
|
||||||
|
} else {
|
||||||
|
fmt.Printf("ERROR: %v\n", err)
|
||||||
|
// ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{BucketURL: u}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
// Notice when put a large file and set need the request body, might happend out of memory error.
|
||||||
|
RequestBody: false,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
opt := &cos.ImageProcessOptions{
|
||||||
|
IsPicInfo: 1,
|
||||||
|
Rules: []cos.PicOperationsRules{
|
||||||
|
{
|
||||||
|
FileId: "format.jpg",
|
||||||
|
Rule: "imageView2/format/png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
name := "test.jpg"
|
||||||
|
res, _, err := c.CI.ImageProcess(context.Background(), name, opt)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", res)
|
||||||
|
}
|
||||||
56
example/object/ci_image_recognition.go
Normal file
56
example/object/ci_image_recognition.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func log_status(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cos.IsNotFoundError(err) {
|
||||||
|
// WARN
|
||||||
|
fmt.Println("WARN: Resource is not existed")
|
||||||
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
|
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
||||||
|
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
||||||
|
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
|
||||||
|
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
|
||||||
|
// ERROR
|
||||||
|
} else {
|
||||||
|
fmt.Printf("ERROR: %v\n", err)
|
||||||
|
// ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{BucketURL: u}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
RequestBody: true,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
opt := &cos.ImageRecognitionOptions{
|
||||||
|
DetectType: "porn,terrorist,politics",
|
||||||
|
}
|
||||||
|
|
||||||
|
name := "test.jpg"
|
||||||
|
res, _, err := c.CI.ImageRecognition(context.Background(), name, opt)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", res)
|
||||||
|
}
|
||||||
71
example/object/ci_video_auditing_job.go
Normal file
71
example/object/ci_video_auditing_job.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func log_status(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cos.IsNotFoundError(err) {
|
||||||
|
// WARN
|
||||||
|
fmt.Println("WARN: Resource is not existed")
|
||||||
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
|
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
||||||
|
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
||||||
|
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
|
||||||
|
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
|
||||||
|
// ERROR
|
||||||
|
} else {
|
||||||
|
fmt.Printf("ERROR: %v\n", err)
|
||||||
|
// ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bu, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
cu, _ := url.Parse("https://test-1259654469.ci.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{BucketURL: bu, CIURL: cu}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
RequestBody: true,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
opt := &cos.PutVideoAuditingJobOptions{
|
||||||
|
InputObject: "demo.mp4",
|
||||||
|
Conf: &cos.VideoAuditingJobConf{
|
||||||
|
DetectType: "Porn,Terrorism,Politics,Ads",
|
||||||
|
Snapshot: &cos.PutVideoAuditingJobSnapshot{
|
||||||
|
Mode: "Interval",
|
||||||
|
Start: 0.5,
|
||||||
|
TimeInterval: 50.5,
|
||||||
|
Count: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _, err := c.CI.PutVideoAuditingJob(context.Background(), opt)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", res)
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
res2, _, err := c.CI.GetVideoAuditingJob(context.Background(), res.JobsDetail.JobId)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%+v\n", res2)
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ func log_status(err error) {
|
|||||||
}
|
}
|
||||||
if cos.IsNotFoundError(err) {
|
if cos.IsNotFoundError(err) {
|
||||||
// WARN
|
// WARN
|
||||||
fmt.Println("WARN: Resource is not existed")
|
fmt.Println("WARN: Resource is not existed")
|
||||||
} else if e, ok := cos.IsCOSError(err); ok {
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
||||||
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
||||||
@@ -33,7 +33,7 @@ func log_status(err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com")
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
b := &cos.BaseURL{BucketURL: u}
|
b := &cos.BaseURL{BucketURL: u}
|
||||||
c := cos.NewClient(b, &http.Client{
|
c := cos.NewClient(b, &http.Client{
|
||||||
Transport: &cos.AuthorizationTransport{
|
Transport: &cos.AuthorizationTransport{
|
||||||
@@ -48,8 +48,8 @@ func main() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Case1 Download object into ReadCloser(). the body needs to be closed
|
// Case1 通过resp.Body下载对象,Body需要关闭
|
||||||
name := "test/hello.txt"
|
name := "test/example"
|
||||||
resp, err := c.Object.Get(context.Background(), name, nil)
|
resp, err := c.Object.Get(context.Background(), name, nil)
|
||||||
log_status(err)
|
log_status(err)
|
||||||
|
|
||||||
@@ -57,8 +57,8 @@ func main() {
|
|||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
fmt.Printf("%s\n", string(bs))
|
fmt.Printf("%s\n", string(bs))
|
||||||
|
|
||||||
// Case2 Download object to local file. the body needs to be closed
|
// Case2 下载对象到文件. Body需要关闭
|
||||||
fd, err := os.OpenFile("hello.txt", os.O_WRONLY|os.O_CREATE, 0660)
|
fd, err := os.OpenFile("test", os.O_WRONLY|os.O_CREATE, 0660)
|
||||||
log_status(err)
|
log_status(err)
|
||||||
|
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
@@ -68,11 +68,11 @@ func main() {
|
|||||||
io.Copy(fd, resp.Body)
|
io.Copy(fd, resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
||||||
// Case3 Download object to local file path
|
// Case3 下载对象到文件
|
||||||
_, err = c.Object.GetToFile(context.Background(), name, "hello_1.txt", nil)
|
_, err = c.Object.GetToFile(context.Background(), name, "test", nil)
|
||||||
log_status(err)
|
log_status(err)
|
||||||
|
|
||||||
// Case4 Download object with range header, can used to concurrent download
|
// Case4 range下载对象,可以根据range实现并发下载
|
||||||
opt := &cos.ObjectGetOptions{
|
opt := &cos.ObjectGetOptions{
|
||||||
ResponseContentType: "text/html",
|
ResponseContentType: "text/html",
|
||||||
Range: "bytes=0-3",
|
Range: "bytes=0-3",
|
||||||
@@ -82,4 +82,11 @@ func main() {
|
|||||||
bs, _ = ioutil.ReadAll(resp.Body)
|
bs, _ = ioutil.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
fmt.Printf("%s\n", string(bs))
|
fmt.Printf("%s\n", string(bs))
|
||||||
|
|
||||||
|
// Case5 下载对象到文件,查看下载进度
|
||||||
|
opt = &cos.ObjectGetOptions{
|
||||||
|
Listener: &cos.DefaultProgressListener{},
|
||||||
|
}
|
||||||
|
_, err = c.Object.GetToFile(context.Background(), name, "test", opt)
|
||||||
|
log_status(err)
|
||||||
}
|
}
|
||||||
|
|||||||
98
example/object/list_uploads.go
Normal file
98
example/object/list_uploads.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func log_status(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cos.IsNotFoundError(err) {
|
||||||
|
// WARN
|
||||||
|
fmt.Println("WARN: Resource is not existed")
|
||||||
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
|
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
||||||
|
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
||||||
|
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
|
||||||
|
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
|
||||||
|
// ERROR
|
||||||
|
} else {
|
||||||
|
fmt.Printf("ERROR: %v\n", err)
|
||||||
|
// ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initUpload(c *cos.Client, name string) *cos.InitiateMultipartUploadResult {
|
||||||
|
v, _, err := c.Object.InitiateMultipartUpload(context.Background(), name, nil)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%#v\n", v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func uploadPart(c *cos.Client, name string, uploadID string, blockSize, n int) string {
|
||||||
|
|
||||||
|
b := make([]byte, blockSize)
|
||||||
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
log_status(err)
|
||||||
|
}
|
||||||
|
s := fmt.Sprintf("%X", b)
|
||||||
|
f := strings.NewReader(s)
|
||||||
|
|
||||||
|
resp, err := c.Object.UploadPart(
|
||||||
|
context.Background(), name, uploadID, n, f, nil,
|
||||||
|
)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("%s\n", resp.Status)
|
||||||
|
return resp.Header.Get("Etag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u, _ := url.Parse("http://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{BucketURL: u}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
RequestBody: false,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
name := "test/test_list_parts.go"
|
||||||
|
up := initUpload(c, name)
|
||||||
|
uploadID := up.UploadID
|
||||||
|
blockSize := 1024 * 1024 * 3
|
||||||
|
|
||||||
|
for i := 1; i < 5; i++ {
|
||||||
|
uploadPart(c, name, uploadID, blockSize, i)
|
||||||
|
}
|
||||||
|
opt := &cos.ObjectListUploadsOptions{
|
||||||
|
Prefix: cos.EncodeURIComponent("test/test_list_parts"),
|
||||||
|
MaxUploads: 100,
|
||||||
|
}
|
||||||
|
v, _, err := c.Object.ListUploads(context.Background(), opt)
|
||||||
|
if err != nil {
|
||||||
|
log_status(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%+v\n", v)
|
||||||
|
for _, p := range v.Upload {
|
||||||
|
fmt.Printf("%+v\n", p)
|
||||||
|
fmt.Printf("%v, %v, %v\n", p.Key, p.UploadID, p.Initiated)
|
||||||
|
}
|
||||||
|
}
|
||||||
66
example/object/multicopy.go
Normal file
66
example/object/multicopy.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func log_status(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cos.IsNotFoundError(err) {
|
||||||
|
// WARN
|
||||||
|
fmt.Println("WARN: Resource is not existed")
|
||||||
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
|
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
||||||
|
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
||||||
|
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
|
||||||
|
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
|
||||||
|
// ERROR
|
||||||
|
} else {
|
||||||
|
fmt.Printf("ERROR: %v\n", err)
|
||||||
|
// ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{BucketURL: u}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
RequestBody: true,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
opt := &cos.MultiCopyOptions{
|
||||||
|
OptCopy: &cos.ObjectCopyOptions{
|
||||||
|
&cos.ObjectCopyHeaderOptions{
|
||||||
|
XCosStorageClass: "Archive",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
ThreadPoolSize: 10,
|
||||||
|
}
|
||||||
|
source := "exampleobject"
|
||||||
|
soruceURL := fmt.Sprintf("%s/%s", u.Host, source)
|
||||||
|
dest := fmt.Sprintf("destobject")
|
||||||
|
res, _, err := c.Object.MultiCopy(context.Background(), dest, soruceURL, opt)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("res:%+v\n", res)
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ func log_status(err error) {
|
|||||||
}
|
}
|
||||||
if cos.IsNotFoundError(err) {
|
if cos.IsNotFoundError(err) {
|
||||||
// WARN
|
// WARN
|
||||||
fmt.Println("WARN: Resource is not existed")
|
fmt.Println("WARN: Resource is not existed")
|
||||||
} else if e, ok := cos.IsCOSError(err); ok {
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
||||||
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
||||||
@@ -44,20 +44,19 @@ func main() {
|
|||||||
// Notice when put a large file and set need the request body, might happend out of memory error.
|
// Notice when put a large file and set need the request body, might happend out of memory error.
|
||||||
RequestBody: false,
|
RequestBody: false,
|
||||||
ResponseHeader: true,
|
ResponseHeader: true,
|
||||||
ResponseBody: true,
|
ResponseBody: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Case1 normal put object
|
// Case1 上传对象
|
||||||
name := "test/objectPut.go"
|
name := "test/example"
|
||||||
f := strings.NewReader("test")
|
f := strings.NewReader("test")
|
||||||
|
|
||||||
_, err := c.Object.Put(context.Background(), name, f, nil)
|
_, err := c.Object.Put(context.Background(), name, f, nil)
|
||||||
log_status(err)
|
log_status(err)
|
||||||
|
|
||||||
// Case2 put object with the options
|
// Case2 使用options上传对象
|
||||||
name = "test/put_option.go"
|
|
||||||
f = strings.NewReader("test xxx")
|
f = strings.NewReader("test xxx")
|
||||||
opt := &cos.ObjectPutOptions{
|
opt := &cos.ObjectPutOptions{
|
||||||
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
|
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
|
||||||
@@ -71,7 +70,11 @@ func main() {
|
|||||||
_, err = c.Object.Put(context.Background(), name, f, opt)
|
_, err = c.Object.Put(context.Background(), name, f, opt)
|
||||||
log_status(err)
|
log_status(err)
|
||||||
|
|
||||||
// Case3 put object by local file path
|
// Case3 通过本地文件上传对象
|
||||||
_, err = c.Object.PutFromFile(context.Background(), name, "./test", nil)
|
_, err = c.Object.PutFromFile(context.Background(), name, "./test", nil)
|
||||||
log_status(err)
|
log_status(err)
|
||||||
|
|
||||||
|
// Case4 查看上传进度
|
||||||
|
opt.ObjectPutHeaderOptions.Listener = &cos.DefaultProgressListener{}
|
||||||
|
_, err = c.Object.PutFromFile(context.Background(), name, "./test", opt)
|
||||||
}
|
}
|
||||||
|
|||||||
59
example/object/put_with_timeout.go
Normal file
59
example/object/put_with_timeout.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func log_status(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cos.IsNotFoundError(err) {
|
||||||
|
// WARN
|
||||||
|
fmt.Println("WARN: Resource is not existed")
|
||||||
|
} else if e, ok := cos.IsCOSError(err); ok {
|
||||||
|
fmt.Printf("ERROR: Code: %v\n", e.Code)
|
||||||
|
fmt.Printf("ERROR: Message: %v\n", e.Message)
|
||||||
|
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
|
||||||
|
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
|
||||||
|
// ERROR
|
||||||
|
} else {
|
||||||
|
fmt.Printf("ERROR: %v\n", err)
|
||||||
|
// ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{BucketURL: u}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
// Notice when put a large file and set need the request body, might happend out of memory error.
|
||||||
|
RequestBody: false,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: 5 * time.Second, // HTTP超时时间
|
||||||
|
})
|
||||||
|
|
||||||
|
// Case1 上传对象
|
||||||
|
name := "test/example"
|
||||||
|
// Case3 通过本地文件上传对象
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), 2*time.Second) // context超时时间
|
||||||
|
_, err := c.Object.PutFromFile(ctx, name, "./test", nil) // 请求的超时时间为 min{context超时时间, HTTP超时时间}
|
||||||
|
log_status(err)
|
||||||
|
}
|
||||||
66
example/object/select.go
Normal file
66
example/object/select.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{BucketURL: u}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
// Notice when put a large file and set need the request body, might happend out of memory error.
|
||||||
|
RequestBody: false,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
opt := &cos.ObjectSelectOptions{
|
||||||
|
Expression: "Select * from COSObject",
|
||||||
|
ExpressionType: "SQL",
|
||||||
|
InputSerialization: &cos.SelectInputSerialization{
|
||||||
|
JSON: &cos.JSONInputSerialization{
|
||||||
|
Type: "DOCUMENT",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
OutputSerialization: &cos.SelectOutputSerialization{
|
||||||
|
JSON: &cos.JSONOutputSerialization{
|
||||||
|
RecordDelimiter: "\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RequestProgress: "TRUE",
|
||||||
|
}
|
||||||
|
res, err := c.Object.Select(context.Background(), "test.json", opt)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer res.Close()
|
||||||
|
data, err := ioutil.ReadAll(res)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("data: %v\n", string(data))
|
||||||
|
resp, _ := res.(*cos.ObjectSelectResponse)
|
||||||
|
fmt.Printf("data: %+v\n", resp.Frame)
|
||||||
|
|
||||||
|
// Select to File
|
||||||
|
_, err = c.Object.SelectToFile(context.Background(), "test.json", "./test.json", opt)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
66
example/object/select_csv.go
Normal file
66
example/object/select_csv.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5"
|
||||||
|
"github.com/tencentyun/cos-go-sdk-v5/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
|
b := &cos.BaseURL{BucketURL: u}
|
||||||
|
c := cos.NewClient(b, &http.Client{
|
||||||
|
Transport: &cos.AuthorizationTransport{
|
||||||
|
SecretID: os.Getenv("COS_SECRETID"),
|
||||||
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
|
Transport: &debug.DebugRequestTransport{
|
||||||
|
RequestHeader: true,
|
||||||
|
// Notice when put a large file and set need the request body, might happend out of memory error.
|
||||||
|
RequestBody: false,
|
||||||
|
ResponseHeader: true,
|
||||||
|
ResponseBody: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
opt := &cos.ObjectSelectOptions{
|
||||||
|
Expression: "Select * from COSObject",
|
||||||
|
ExpressionType: "SQL",
|
||||||
|
InputSerialization: &cos.SelectInputSerialization{
|
||||||
|
CSV: &cos.CSVInputSerialization{
|
||||||
|
FileHeaderInfo: "IGNORE",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
OutputSerialization: &cos.SelectOutputSerialization{
|
||||||
|
CSV: &cos.CSVOutputSerialization{
|
||||||
|
RecordDelimiter: "\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RequestProgress: "TRUE",
|
||||||
|
}
|
||||||
|
res, err := c.Object.Select(context.Background(), "test.csv", opt)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer res.Close()
|
||||||
|
data, err := ioutil.ReadAll(res)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("data: %v\n", string(data))
|
||||||
|
resp, _ := res.(*cos.ObjectSelectResponse)
|
||||||
|
fmt.Printf("data: %+v\n", resp.Frame)
|
||||||
|
|
||||||
|
// Select To File
|
||||||
|
_, err = c.Object.SelectToFile(context.Background(), "test.csv", "./test.csv", opt)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,9 +49,26 @@ func main() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Case1 多线程上传对象
|
||||||
|
opt := &cos.MultiUploadOptions{
|
||||||
|
ThreadPoolSize: 3,
|
||||||
|
}
|
||||||
v, _, err := c.Object.Upload(
|
v, _, err := c.Object.Upload(
|
||||||
context.Background(), "gomulput1G", "./test1G", nil,
|
context.Background(), "gomulput1G", "./test1G", opt,
|
||||||
)
|
)
|
||||||
log_status(err)
|
log_status(err)
|
||||||
fmt.Println(v)
|
fmt.Printf("Case1 done, %v\n", v)
|
||||||
|
|
||||||
|
// Case2 多线程上传对象,查看上传进度
|
||||||
|
opt.OptIni = &cos.InitiateMultipartUploadOptions{
|
||||||
|
nil,
|
||||||
|
&cos.ObjectPutHeaderOptions{
|
||||||
|
Listener: &cos.DefaultProgressListener{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v, _, err = c.Object.Upload(
|
||||||
|
context.Background(), "gomulput1G", "./test1G", opt,
|
||||||
|
)
|
||||||
|
log_status(err)
|
||||||
|
fmt.Printf("Case2 done, %v\n", v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func initUpload(c *cos.Client, name string) *cos.InitiateMultipartUploadResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com")
|
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
|
||||||
b := &cos.BaseURL{BucketURL: u}
|
b := &cos.BaseURL{BucketURL: u}
|
||||||
c := cos.NewClient(b, &http.Client{
|
c := cos.NewClient(b, &http.Client{
|
||||||
Transport: &cos.AuthorizationTransport{
|
Transport: &cos.AuthorizationTransport{
|
||||||
@@ -49,20 +49,50 @@ func main() {
|
|||||||
SecretKey: os.Getenv("COS_SECRETKEY"),
|
SecretKey: os.Getenv("COS_SECRETKEY"),
|
||||||
Transport: &debug.DebugRequestTransport{
|
Transport: &debug.DebugRequestTransport{
|
||||||
RequestHeader: true,
|
RequestHeader: true,
|
||||||
RequestBody: true,
|
RequestBody: false,
|
||||||
ResponseHeader: true,
|
ResponseHeader: true,
|
||||||
ResponseBody: true,
|
ResponseBody: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
optcom := &cos.CompleteMultipartUploadOptions{}
|
||||||
name := "test/test_multi_upload.go"
|
name := "test/test_multi_upload.go"
|
||||||
up := initUpload(c, name)
|
up := initUpload(c, name)
|
||||||
uploadID := up.UploadID
|
uploadID := up.UploadID
|
||||||
|
|
||||||
f := strings.NewReader("test heoo")
|
fd, err := os.Open("test")
|
||||||
_, err := c.Object.UploadPart(
|
if err != nil {
|
||||||
context.Background(), name, uploadID, 1, f, nil,
|
fmt.Printf("Open File Error: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
stat, err := fd.Stat()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Stat File Error: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opt := &cos.ObjectUploadPartOptions{
|
||||||
|
Listener: &cos.DefaultProgressListener{},
|
||||||
|
ContentLength: int(stat.Size()),
|
||||||
|
}
|
||||||
|
resp, err := c.Object.UploadPart(
|
||||||
|
context.Background(), name, uploadID, 1, fd, opt,
|
||||||
)
|
)
|
||||||
log_status(err)
|
log_status(err)
|
||||||
|
optcom.Parts = append(optcom.Parts, cos.Object{
|
||||||
|
PartNumber: 1, ETag: resp.Header.Get("ETag"),
|
||||||
|
})
|
||||||
|
|
||||||
|
f := strings.NewReader("test heoo")
|
||||||
|
resp, err = c.Object.UploadPart(
|
||||||
|
context.Background(), name, uploadID, 2, f, nil,
|
||||||
|
)
|
||||||
|
log_status(err)
|
||||||
|
optcom.Parts = append(optcom.Parts, cos.Object{
|
||||||
|
PartNumber: 2, ETag: resp.Header.Get("ETag"),
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err = c.Object.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
|
||||||
|
log_status(err)
|
||||||
}
|
}
|
||||||
|
|||||||
101
helper.go
101
helper.go
@@ -4,10 +4,18 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 单次上传文件最大为5GB
|
||||||
|
const singleUploadMaxLength = 5 * 1024 * 1024 * 1024
|
||||||
|
|
||||||
// 计算 md5 或 sha1 时的分块大小
|
// 计算 md5 或 sha1 时的分块大小
|
||||||
const calDigestBlockSize = 1024 * 1024 * 10
|
const calDigestBlockSize = 1024 * 1024 * 10
|
||||||
|
|
||||||
@@ -44,7 +52,7 @@ func cloneRequest(r *http.Request) *http.Request {
|
|||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
// 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
|
// 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
|
var b bytes.Buffer
|
||||||
written := 0
|
written := 0
|
||||||
|
|
||||||
@@ -70,6 +78,18 @@ func encodeURIComponent(s string) string {
|
|||||||
|
|
||||||
continue
|
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])
|
b.WriteString(s[written:i])
|
||||||
@@ -83,3 +103,82 @@ func encodeURIComponent(s string) string {
|
|||||||
b.WriteString(s[written:])
|
b.WriteString(s[written:])
|
||||||
return b.String()
|
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 FixedLengthReader:
|
||||||
|
length = v.Size()
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("can't get reader content length, unkown reader type")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
289
object.go
289
object.go
@@ -2,14 +2,17 @@ package cos
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -31,6 +34,11 @@ type ObjectGetOptions struct {
|
|||||||
XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,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:"-"`
|
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:"-"`
|
XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
|
||||||
|
|
||||||
|
XCosTrafficLimit int `header:"x-cos-traffic-limit,omitempty" url:"-" xml:"-"`
|
||||||
|
|
||||||
|
// 下载进度, ProgressCompleteEvent不能表示对应API调用成功,API是否调用成功的判断标准为返回err==nil
|
||||||
|
Listener ProgressListener `header:"-" url:"-" xml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// presignedURLTestingOptions is the opt of presigned url
|
// presignedURLTestingOptions is the opt of presigned url
|
||||||
@@ -61,6 +69,14 @@ func (s *ObjectService) Get(ctx context.Context, name string, opt *ObjectGetOpti
|
|||||||
disableCloseBody: true,
|
disableCloseBody: true,
|
||||||
}
|
}
|
||||||
resp, err := s.client.send(ctx, &sendOpt)
|
resp, err := s.client.send(ctx, &sendOpt)
|
||||||
|
|
||||||
|
if opt != nil && opt.Listener != nil {
|
||||||
|
if err == nil && resp != nil {
|
||||||
|
if totalBytes, e := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64); e == nil {
|
||||||
|
resp.Body = TeeReader(resp.Body, nil, totalBytes, opt.Listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,12 +127,12 @@ func (s *ObjectService) GetPresignedURL(ctx context.Context, httpMethod, name, a
|
|||||||
authTime = NewAuthTime(expired)
|
authTime = NewAuthTime(expired)
|
||||||
}
|
}
|
||||||
authorization := newAuthorization(ak, sk, req, authTime)
|
authorization := newAuthorization(ak, sk, req, authTime)
|
||||||
sign := encodeURIComponent(authorization)
|
sign := encodeURIComponent(authorization, []byte{'&', '='})
|
||||||
|
|
||||||
if req.URL.RawQuery == "" {
|
if req.URL.RawQuery == "" {
|
||||||
req.URL.RawQuery = fmt.Sprintf("sign=%s", sign)
|
req.URL.RawQuery = fmt.Sprintf("%s", sign)
|
||||||
} else {
|
} 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
|
return req.URL, nil
|
||||||
|
|
||||||
@@ -146,7 +162,11 @@ type ObjectPutHeaderOptions struct {
|
|||||||
XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,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:"-"`
|
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
|
// ObjectPutOptions the options of put object
|
||||||
@@ -161,6 +181,17 @@ type ObjectPutOptions struct {
|
|||||||
//
|
//
|
||||||
// https://www.qcloud.com/document/product/436/7749
|
// https://www.qcloud.com/document/product/436/7749
|
||||||
func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, opt *ObjectPutOptions) (*Response, error) {
|
func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, opt *ObjectPutOptions) (*Response, error) {
|
||||||
|
if err := CheckReaderLen(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if opt != nil && opt.Listener != nil {
|
||||||
|
totalBytes, err := GetReaderLen(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r = TeeReader(r, nil, totalBytes, opt.Listener)
|
||||||
|
}
|
||||||
|
|
||||||
sendOpt := sendOptions{
|
sendOpt := sendOptions{
|
||||||
baseURL: s.client.BaseURL.BucketURL,
|
baseURL: s.client.BaseURL.BucketURL,
|
||||||
uri: "/" + encodeURIComponent(name),
|
uri: "/" + encodeURIComponent(name),
|
||||||
@@ -169,6 +200,7 @@ func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, opt *
|
|||||||
optHeader: opt,
|
optHeader: opt,
|
||||||
}
|
}
|
||||||
resp, err := s.client.send(ctx, &sendOpt)
|
resp, err := s.client.send(ctx, &sendOpt)
|
||||||
|
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +258,8 @@ type ObjectCopyResult struct {
|
|||||||
XMLName xml.Name `xml:"CopyObjectResult"`
|
XMLName xml.Name `xml:"CopyObjectResult"`
|
||||||
ETag string `xml:"ETag,omitempty"`
|
ETag string `xml:"ETag,omitempty"`
|
||||||
LastModified string `xml:"LastModified,omitempty"`
|
LastModified string `xml:"LastModified,omitempty"`
|
||||||
|
CRC64 string `xml:"CRC64,omitempty"`
|
||||||
|
VersionId string `xml:"VersionId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy 调用 PutObjectCopy 请求实现将一个文件从源路径复制到目标路径。建议文件大小 1M 到 5G,
|
// Copy 调用 PutObjectCopy 请求实现将一个文件从源路径复制到目标路径。建议文件大小 1M 到 5G,
|
||||||
@@ -251,20 +285,26 @@ func (s *ObjectService) Copy(ctx context.Context, name, sourceURL string, opt *O
|
|||||||
}
|
}
|
||||||
|
|
||||||
var res ObjectCopyResult
|
var res ObjectCopyResult
|
||||||
if opt == nil {
|
copyOpt := &ObjectCopyOptions{
|
||||||
opt = new(ObjectCopyOptions)
|
&ObjectCopyHeaderOptions{},
|
||||||
|
&ACLHeaderOptions{},
|
||||||
}
|
}
|
||||||
if opt.ObjectCopyHeaderOptions == nil {
|
if opt != nil {
|
||||||
opt.ObjectCopyHeaderOptions = new(ObjectCopyHeaderOptions)
|
if opt.ObjectCopyHeaderOptions != nil {
|
||||||
|
*copyOpt.ObjectCopyHeaderOptions = *opt.ObjectCopyHeaderOptions
|
||||||
|
}
|
||||||
|
if opt.ACLHeaderOptions != nil {
|
||||||
|
*copyOpt.ACLHeaderOptions = *opt.ACLHeaderOptions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
opt.XCosCopySource = u
|
copyOpt.XCosCopySource = u
|
||||||
|
|
||||||
sendOpt := sendOptions{
|
sendOpt := sendOptions{
|
||||||
baseURL: s.client.BaseURL.BucketURL,
|
baseURL: s.client.BaseURL.BucketURL,
|
||||||
uri: "/" + encodeURIComponent(name),
|
uri: "/" + encodeURIComponent(name),
|
||||||
method: http.MethodPut,
|
method: http.MethodPut,
|
||||||
body: nil,
|
body: nil,
|
||||||
optHeader: opt,
|
optHeader: copyOpt,
|
||||||
result: &res,
|
result: &res,
|
||||||
}
|
}
|
||||||
resp, err := s.client.send(ctx, &sendOpt)
|
resp, err := s.client.send(ctx, &sendOpt)
|
||||||
@@ -284,6 +324,7 @@ type ObjectDeleteOptions struct {
|
|||||||
XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
|
XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
|
||||||
//兼容其他自定义头部
|
//兼容其他自定义头部
|
||||||
XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
|
XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
|
||||||
|
VersionId string `header:"-" url:"VersionId,omitempty" xml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete Object请求可以将一个文件(Object)删除。
|
// Delete Object请求可以将一个文件(Object)删除。
|
||||||
@@ -304,6 +345,7 @@ func (s *ObjectService) Delete(ctx context.Context, name string, opt ...*ObjectD
|
|||||||
uri: "/" + encodeURIComponent(name),
|
uri: "/" + encodeURIComponent(name),
|
||||||
method: http.MethodDelete,
|
method: http.MethodDelete,
|
||||||
optHeader: optHeader,
|
optHeader: optHeader,
|
||||||
|
optQuery: optHeader,
|
||||||
}
|
}
|
||||||
resp, err := s.client.send(ctx, &sendOpt)
|
resp, err := s.client.send(ctx, &sendOpt)
|
||||||
return resp, err
|
return resp, err
|
||||||
@@ -440,9 +482,10 @@ type ObjectDeleteMultiResult struct {
|
|||||||
XMLName xml.Name `xml:"DeleteResult"`
|
XMLName xml.Name `xml:"DeleteResult"`
|
||||||
DeletedObjects []Object `xml:"Deleted,omitempty"`
|
DeletedObjects []Object `xml:"Deleted,omitempty"`
|
||||||
Errors []struct {
|
Errors []struct {
|
||||||
Key string
|
Key string `xml:",omitempty"`
|
||||||
Code string
|
Code string `xml:",omitempty"`
|
||||||
Message string
|
Message string `xml:",omitempty"`
|
||||||
|
VersionId string `xml:",omitempty"`
|
||||||
} `xml:"Error,omitempty"`
|
} `xml:"Error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,6 +515,7 @@ type Object struct {
|
|||||||
LastModified string `xml:",omitempty"`
|
LastModified string `xml:",omitempty"`
|
||||||
StorageClass string `xml:",omitempty"`
|
StorageClass string `xml:",omitempty"`
|
||||||
Owner *Owner `xml:",omitempty"`
|
Owner *Owner `xml:",omitempty"`
|
||||||
|
VersionId string `xml:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultiUploadOptions is the option of the multiupload,
|
// MultiUploadOptions is the option of the multiupload,
|
||||||
@@ -480,12 +524,15 @@ type MultiUploadOptions struct {
|
|||||||
OptIni *InitiateMultipartUploadOptions
|
OptIni *InitiateMultipartUploadOptions
|
||||||
PartSize int64
|
PartSize int64
|
||||||
ThreadPoolSize int
|
ThreadPoolSize int
|
||||||
|
CheckPoint bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Chunk struct {
|
type Chunk struct {
|
||||||
Number int
|
Number int
|
||||||
OffSet int64
|
OffSet int64
|
||||||
Size int64
|
Size int64
|
||||||
|
Done bool
|
||||||
|
ETag string
|
||||||
}
|
}
|
||||||
|
|
||||||
// jobs
|
// jobs
|
||||||
@@ -502,6 +549,7 @@ type Jobs struct {
|
|||||||
type Results struct {
|
type Results struct {
|
||||||
PartNumber int
|
PartNumber int
|
||||||
Resp *Response
|
Resp *Response
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func worker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results) {
|
func worker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results) {
|
||||||
@@ -509,6 +557,7 @@ func worker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results) {
|
|||||||
fd, err := os.Open(j.FilePath)
|
fd, err := os.Open(j.FilePath)
|
||||||
var res Results
|
var res Results
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
res.err = err
|
||||||
res.PartNumber = j.Chunk.Number
|
res.PartNumber = j.Chunk.Number
|
||||||
res.Resp = nil
|
res.Resp = nil
|
||||||
results <- &res
|
results <- &res
|
||||||
@@ -524,6 +573,7 @@ func worker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results) {
|
|||||||
&io.LimitedReader{R: fd, N: j.Chunk.Size}, j.Opt)
|
&io.LimitedReader{R: fd, N: j.Chunk.Size}, j.Opt)
|
||||||
res.PartNumber = j.Chunk.Number
|
res.PartNumber = j.Chunk.Number
|
||||||
res.Resp = resp
|
res.Resp = resp
|
||||||
|
res.err = err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rt--
|
rt--
|
||||||
if rt == 0 {
|
if rt == 0 {
|
||||||
@@ -540,8 +590,8 @@ func worker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DividePart(fileSize int64) (int64, int64) {
|
func DividePart(fileSize int64, last int) (int64, int64) {
|
||||||
partSize := int64(1 * 1024 * 1024)
|
partSize := int64(last * 1024 * 1024)
|
||||||
partNum := fileSize / partSize
|
partNum := fileSize / partSize
|
||||||
for partNum >= 10000 {
|
for partNum >= 10000 {
|
||||||
partSize = partSize * 2
|
partSize = partSize * 2
|
||||||
@@ -550,30 +600,30 @@ func DividePart(fileSize int64) (int64, int64) {
|
|||||||
return partNum, partSize
|
return partNum, partSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func SplitFileIntoChunks(filePath string, partSize int64) ([]Chunk, int, error) {
|
func SplitFileIntoChunks(filePath string, partSize int64) (int64, []Chunk, int, error) {
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return nil, 0, errors.New("filePath invalid")
|
return 0, nil, 0, errors.New("filePath invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return 0, nil, 0, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
stat, err := file.Stat()
|
stat, err := file.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return 0, nil, 0, err
|
||||||
}
|
}
|
||||||
var partNum int64
|
var partNum int64
|
||||||
if partSize > 0 {
|
if partSize > 0 {
|
||||||
partSize = partSize * 1024 * 1024
|
partSize = partSize * 1024 * 1024
|
||||||
partNum = stat.Size() / partSize
|
partNum = stat.Size() / partSize
|
||||||
if partNum >= 10000 {
|
if partNum >= 10000 {
|
||||||
return nil, 0, errors.New("Too many parts, out of 10000")
|
return 0, nil, 0, errors.New("Too many parts, out of 10000")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
partNum, partSize = DividePart(stat.Size())
|
partNum, partSize = DividePart(stat.Size(), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var chunks []Chunk
|
var chunks []Chunk
|
||||||
@@ -593,10 +643,84 @@ func SplitFileIntoChunks(filePath string, partSize int64) ([]Chunk, int, error)
|
|||||||
partNum++
|
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接口,并发分块上传
|
// MultiUpload/Upload 为高级upload接口,并发分块上传
|
||||||
// 注意该接口目前只供参考
|
// 注意该接口目前只供参考
|
||||||
//
|
//
|
||||||
@@ -612,18 +736,50 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
|
|||||||
opt = &MultiUploadOptions{}
|
opt = &MultiUploadOptions{}
|
||||||
}
|
}
|
||||||
// 1.Get the file chunk
|
// 1.Get the file chunk
|
||||||
chunks, partNum, err := SplitFileIntoChunks(filepath, opt.PartSize)
|
totalBytes, chunks, partNum, err := SplitFileIntoChunks(filepath, opt.PartSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
// filesize=0 , use simple upload
|
||||||
|
if partNum == 0 {
|
||||||
|
var opt0 *ObjectPutOptions
|
||||||
|
if opt.OptIni != nil {
|
||||||
|
opt0 = &ObjectPutOptions{
|
||||||
|
opt.OptIni.ACLHeaderOptions,
|
||||||
|
opt.OptIni.ObjectPutHeaderOptions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rsp, err := s.PutFromFile(ctx, name, filepath, opt0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, rsp, err
|
||||||
|
}
|
||||||
|
result := &CompleteMultipartUploadResult{
|
||||||
|
Key: name,
|
||||||
|
ETag: rsp.Header.Get("ETag"),
|
||||||
|
}
|
||||||
|
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
|
// 2.Init
|
||||||
optini := opt.OptIni
|
optini := opt.OptIni
|
||||||
res, _, err := s.InitiateMultipartUpload(ctx, name, optini)
|
if !resumableFlag {
|
||||||
if err != nil {
|
res, _, err := s.InitiateMultipartUpload(ctx, name, optini)
|
||||||
return nil, nil, err
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
uploadID = res.UploadID
|
||||||
}
|
}
|
||||||
uploadID := res.UploadID
|
|
||||||
var poolSize int
|
var poolSize int
|
||||||
if opt.ThreadPoolSize > 0 {
|
if opt.ThreadPoolSize > 0 {
|
||||||
poolSize = opt.ThreadPoolSize
|
poolSize = opt.ThreadPoolSize
|
||||||
@@ -641,43 +797,80 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
|
|||||||
go worker(s, chjobs, chresults)
|
go worker(s, chjobs, chresults)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4.Push jobs
|
// progress started event
|
||||||
for _, chunk := range chunks {
|
var listener ProgressListener
|
||||||
partOpt := &ObjectUploadPartOptions{}
|
var consumedBytes int64
|
||||||
if optini != nil && optini.ObjectPutHeaderOptions != nil {
|
if opt.OptIni != nil {
|
||||||
partOpt.XCosSSECustomerAglo = optini.XCosSSECustomerAglo
|
listener = opt.OptIni.Listener
|
||||||
partOpt.XCosSSECustomerKey = optini.XCosSSECustomerKey
|
|
||||||
partOpt.XCosSSECustomerKeyMD5 = optini.XCosSSECustomerKeyMD5
|
|
||||||
}
|
|
||||||
job := &Jobs{
|
|
||||||
Name: name,
|
|
||||||
RetryTimes: 3,
|
|
||||||
FilePath: filepath,
|
|
||||||
UploadId: uploadID,
|
|
||||||
Chunk: chunk,
|
|
||||||
Opt: partOpt,
|
|
||||||
}
|
|
||||||
chjobs <- job
|
|
||||||
}
|
}
|
||||||
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
|
// 5.Recv the resp etag to complete
|
||||||
for i := 1; i <= partNum; i++ {
|
for i := 0; i < partNum; i++ {
|
||||||
|
if chunks[i].Done {
|
||||||
|
optcom.Parts = append(optcom.Parts, Object{
|
||||||
|
PartNumber: chunks[i].Number, ETag: chunks[i].ETag},
|
||||||
|
)
|
||||||
|
consumedBytes += chunks[i].Size
|
||||||
|
event = newProgressEvent(ProgressDataEvent, chunks[i].Size, consumedBytes, totalBytes)
|
||||||
|
progressCallback(listener, event)
|
||||||
|
continue
|
||||||
|
}
|
||||||
res := <-chresults
|
res := <-chresults
|
||||||
// Notice one part fail can not get the etag according.
|
// Notice one part fail can not get the etag according.
|
||||||
if res.Resp == nil {
|
if res.Resp == nil || res.err != nil {
|
||||||
// Some part already fail, can not to get the header inside.
|
// Some part already fail, can not to get the header inside.
|
||||||
return nil, nil, fmt.Errorf("UploadID %s, part %d failed to get resp content.", uploadID, res.PartNumber)
|
err := fmt.Errorf("UploadID %s, part %d failed to get resp content. error: %s", uploadID, res.PartNumber, res.err.Error())
|
||||||
|
event = newProgressEvent(ProgressFailedEvent, 0, consumedBytes, totalBytes, err)
|
||||||
|
progressCallback(listener, event)
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
// Notice one part fail can not get the etag according.
|
// Notice one part fail can not get the etag according.
|
||||||
etag := res.Resp.Header.Get("ETag")
|
etag := res.Resp.Header.Get("ETag")
|
||||||
optcom.Parts = append(optcom.Parts, Object{
|
optcom.Parts = append(optcom.Parts, Object{
|
||||||
PartNumber: res.PartNumber, ETag: etag},
|
PartNumber: res.PartNumber, ETag: etag},
|
||||||
)
|
)
|
||||||
|
consumedBytes += chunks[res.PartNumber-1].Size
|
||||||
|
event = newProgressEvent(ProgressDataEvent, chunks[res.PartNumber-1].Size, consumedBytes, totalBytes)
|
||||||
|
progressCallback(listener, event)
|
||||||
}
|
}
|
||||||
|
close(chresults)
|
||||||
sort.Sort(ObjectList(optcom.Parts))
|
sort.Sort(ObjectList(optcom.Parts))
|
||||||
|
|
||||||
|
event = newProgressEvent(ProgressCompletedEvent, 0, consumedBytes, totalBytes)
|
||||||
|
progressCallback(listener, event)
|
||||||
|
|
||||||
v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
|
v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
|
||||||
|
if err != nil {
|
||||||
|
s.AbortMultipartUpload(ctx, name, uploadID)
|
||||||
|
}
|
||||||
|
|
||||||
return v, resp, err
|
return v, resp, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ObjectGetACLResult is the result of GetObjectACL
|
// ObjectGetACLResult is the result of GetObjectACL
|
||||||
type ObjectGetACLResult ACLXml
|
type ObjectGetACLResult = ACLXml
|
||||||
|
|
||||||
// GetACL Get Object ACL接口实现使用API读取Object的ACL表,只有所有者有权操作。
|
// GetACL Get Object ACL接口实现使用API读取Object的ACL表,只有所有者有权操作。
|
||||||
//
|
//
|
||||||
@@ -20,6 +20,9 @@ func (s *ObjectService) GetACL(ctx context.Context, name string) (*ObjectGetACLR
|
|||||||
result: &res,
|
result: &res,
|
||||||
}
|
}
|
||||||
resp, err := s.client.send(ctx, &sendOpt)
|
resp, err := s.client.send(ctx, &sendOpt)
|
||||||
|
if err == nil {
|
||||||
|
decodeACL(resp, &res)
|
||||||
|
}
|
||||||
return &res, resp, err
|
return &res, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
277
object_part.go
277
object_part.go
@@ -7,6 +7,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitiateMultipartUploadOptions is the option of InitateMultipartUpload
|
// InitiateMultipartUploadOptions is the option of InitateMultipartUpload
|
||||||
@@ -41,13 +44,19 @@ func (s *ObjectService) InitiateMultipartUpload(ctx context.Context, name string
|
|||||||
|
|
||||||
// ObjectUploadPartOptions is the options of upload-part
|
// ObjectUploadPartOptions is the options of upload-part
|
||||||
type ObjectUploadPartOptions struct {
|
type ObjectUploadPartOptions struct {
|
||||||
Expect string `header:"Expect,omitempty" url:"-"`
|
Expect string `header:"Expect,omitempty" url:"-"`
|
||||||
XCosContentSHA1 string `header:"x-cos-content-sha1" url:"-"`
|
XCosContentSHA1 string `header:"x-cos-content-sha1,omitempty" url:"-"`
|
||||||
ContentLength int `header:"Content-Length,omitempty" url:"-"`
|
ContentLength int `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:"-"`
|
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:"-"`
|
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:"-"`
|
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。
|
// UploadPart 请求实现在初始化以后的分块上传,支持的块的数量为1到10000,块的大小为1 MB 到5 GB。
|
||||||
@@ -59,6 +68,16 @@ type ObjectUploadPartOptions struct {
|
|||||||
//
|
//
|
||||||
// https://www.qcloud.com/document/product/436/7750
|
// https://www.qcloud.com/document/product/436/7750
|
||||||
func (s *ObjectService) UploadPart(ctx context.Context, name, uploadID string, partNumber int, r io.Reader, opt *ObjectUploadPartOptions) (*Response, error) {
|
func (s *ObjectService) UploadPart(ctx context.Context, name, uploadID string, partNumber int, r io.Reader, opt *ObjectUploadPartOptions) (*Response, error) {
|
||||||
|
if err := CheckReaderLen(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if opt != nil && opt.Listener != nil {
|
||||||
|
totalBytes, err := GetReaderLen(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r = TeeReader(r, nil, totalBytes, opt.Listener)
|
||||||
|
}
|
||||||
u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID)
|
u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID)
|
||||||
sendOpt := sendOptions{
|
sendOpt := sendOptions{
|
||||||
baseURL: s.client.BaseURL.BucketURL,
|
baseURL: s.client.BaseURL.BucketURL,
|
||||||
@@ -244,3 +263,253 @@ func (s *ObjectService) CopyPart(ctx context.Context, name, uploadID string, par
|
|||||||
}
|
}
|
||||||
return &res, resp, err
|
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
|
||||||
|
}
|
||||||
|
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, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
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
object_select.go
Normal file
444
object_select.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
QuoteFileds string `xml:"QuoteFileds,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
|
||||||
|
}
|
||||||
@@ -129,7 +129,7 @@ func TestObjectService_Options(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
opt := &ObjectOptionsOptions{
|
opt := &ObjectOptionsOptions{
|
||||||
Origin: "www.qq.com",
|
Origin: "www.qq.com",
|
||||||
AccessControlRequestMethod: "PUT",
|
AccessControlRequestMethod: "PUT",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
139
progress.go
Normal file
139
progress.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package cos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProgressEventType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 数据开始传输
|
||||||
|
ProgressStartedEvent ProgressEventType = iota
|
||||||
|
// 数据传输中
|
||||||
|
ProgressDataEvent
|
||||||
|
// 数据传输完成, 但不能表示对应API调用完成
|
||||||
|
ProgressCompletedEvent
|
||||||
|
// 只有在数据传输时发生错误才会返回
|
||||||
|
ProgressFailedEvent
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProgressEvent struct {
|
||||||
|
EventType ProgressEventType
|
||||||
|
RWBytes int64
|
||||||
|
ConsumedBytes int64
|
||||||
|
TotalBytes int64
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProgressEvent(eventType ProgressEventType, rwBytes, consumed, total int64, err ...error) *ProgressEvent {
|
||||||
|
event := &ProgressEvent{
|
||||||
|
EventType: eventType,
|
||||||
|
RWBytes: rwBytes,
|
||||||
|
ConsumedBytes: consumed,
|
||||||
|
TotalBytes: total,
|
||||||
|
}
|
||||||
|
if len(err) > 0 {
|
||||||
|
event.Err = err[0]
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户自定义Listener需要实现该方法
|
||||||
|
type ProgressListener interface {
|
||||||
|
ProgressChangedCallback(event *ProgressEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func progressCallback(listener ProgressListener, event *ProgressEvent) {
|
||||||
|
if listener != nil && event != nil {
|
||||||
|
listener.ProgressChangedCallback(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type teeReader struct {
|
||||||
|
reader io.Reader
|
||||||
|
writer io.Writer
|
||||||
|
consumedBytes int64
|
||||||
|
totalBytes int64
|
||||||
|
listener ProgressListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *teeReader) Read(p []byte) (int, error) {
|
||||||
|
if r.consumedBytes == 0 {
|
||||||
|
event := newProgressEvent(ProgressStartedEvent, 0, r.consumedBytes, r.totalBytes)
|
||||||
|
progressCallback(r.listener, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := r.reader.Read(p)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
event := newProgressEvent(ProgressFailedEvent, 0, r.consumedBytes, r.totalBytes, err)
|
||||||
|
progressCallback(r.listener, event)
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
r.consumedBytes += int64(n)
|
||||||
|
if r.writer != nil {
|
||||||
|
if n, err := r.writer.Write(p[:n]); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.listener != nil {
|
||||||
|
event := newProgressEvent(ProgressDataEvent, int64(n), r.consumedBytes, r.totalBytes)
|
||||||
|
progressCallback(r.listener, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
event := newProgressEvent(ProgressCompletedEvent, int64(n), r.consumedBytes, r.totalBytes)
|
||||||
|
progressCallback(r.listener, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *teeReader) Close() error {
|
||||||
|
if rc, ok := r.reader.(io.ReadCloser); ok {
|
||||||
|
return rc.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *teeReader) Size() int64 {
|
||||||
|
return r.totalBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,13 +48,13 @@ func TestServiceService_Get(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Buckets: []Bucket{
|
Buckets: []Bucket{
|
||||||
{
|
{
|
||||||
Name: "huadong-1253846586",
|
Name: "huadong-1253846586",
|
||||||
Region: "ap-shanghai",
|
Region: "ap-shanghai",
|
||||||
CreationDate: "2017-06-16T13:08:28Z",
|
CreationDate: "2017-06-16T13:08:28Z",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "huanan-1253846586",
|
Name: "huanan-1253846586",
|
||||||
Region: "ap-guangzhou",
|
Region: "ap-guangzhou",
|
||||||
CreationDate: "2017-06-10T09:00:07Z",
|
CreationDate: "2017-06-10T09:00:07Z",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user