You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1017 lines
33 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. package cos
  2. import (
  3. "context"
  4. "crypto/md5"
  5. "encoding/xml"
  6. "errors"
  7. "fmt"
  8. "hash/crc64"
  9. "io"
  10. "io/ioutil"
  11. "net/http"
  12. "net/url"
  13. "os"
  14. "sort"
  15. "strconv"
  16. "strings"
  17. "time"
  18. )
  19. // ObjectService 相关 API
  20. type ObjectService service
  21. // ObjectGetOptions is the option of GetObject
  22. type ObjectGetOptions struct {
  23. ResponseContentType string `url:"response-content-type,omitempty" header:"-"`
  24. ResponseContentLanguage string `url:"response-content-language,omitempty" header:"-"`
  25. ResponseExpires string `url:"response-expires,omitempty" header:"-"`
  26. ResponseCacheControl string `url:"response-cache-control,omitempty" header:"-"`
  27. ResponseContentDisposition string `url:"response-content-disposition,omitempty" header:"-"`
  28. ResponseContentEncoding string `url:"response-content-encoding,omitempty" header:"-"`
  29. Range string `url:"-" header:"Range,omitempty"`
  30. IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"`
  31. // SSE-C
  32. XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
  33. XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
  34. XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
  35. //兼容其他自定义头部
  36. XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
  37. XCosTrafficLimit int `header:"x-cos-traffic-limit,omitempty" url:"-" xml:"-"`
  38. // 下载进度, ProgressCompleteEvent不能表示对应API调用成功,API是否调用成功的判断标准为返回err==nil
  39. Listener ProgressListener `header:"-" url:"-" xml:"-"`
  40. }
  41. // presignedURLTestingOptions is the opt of presigned url
  42. type presignedURLTestingOptions struct {
  43. authTime *AuthTime
  44. }
  45. // Get Object 请求可以将一个文件(Object)下载至本地。
  46. // 该操作需要对目标 Object 具有读权限或目标 Object 对所有人都开放了读权限(公有读)。
  47. //
  48. // https://www.qcloud.com/document/product/436/7753
  49. func (s *ObjectService) Get(ctx context.Context, name string, opt *ObjectGetOptions, id ...string) (*Response, error) {
  50. var u string
  51. if len(id) == 1 {
  52. u = fmt.Sprintf("/%s?versionId=%s", encodeURIComponent(name), id[0])
  53. } else if len(id) == 0 {
  54. u = "/" + encodeURIComponent(name)
  55. } else {
  56. return nil, errors.New("wrong params")
  57. }
  58. sendOpt := sendOptions{
  59. baseURL: s.client.BaseURL.BucketURL,
  60. uri: u,
  61. method: http.MethodGet,
  62. optQuery: opt,
  63. optHeader: opt,
  64. disableCloseBody: true,
  65. }
  66. resp, err := s.client.send(ctx, &sendOpt)
  67. if opt != nil && opt.Listener != nil {
  68. if err == nil && resp != nil {
  69. if totalBytes, e := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64); e == nil {
  70. resp.Body = TeeReader(resp.Body, nil, totalBytes, opt.Listener)
  71. }
  72. }
  73. }
  74. return resp, err
  75. }
  76. // GetToFile download the object to local file
  77. func (s *ObjectService) GetToFile(ctx context.Context, name, localpath string, opt *ObjectGetOptions, id ...string) (*Response, error) {
  78. resp, err := s.Get(ctx, name, opt, id...)
  79. if err != nil {
  80. return resp, err
  81. }
  82. defer resp.Body.Close()
  83. // If file exist, overwrite it
  84. fd, err := os.OpenFile(localpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
  85. if err != nil {
  86. return resp, err
  87. }
  88. _, err = io.Copy(fd, resp.Body)
  89. fd.Close()
  90. if err != nil {
  91. return resp, err
  92. }
  93. return resp, nil
  94. }
  95. // GetPresignedURL get the object presigned to down or upload file by url
  96. func (s *ObjectService) GetPresignedURL(ctx context.Context, httpMethod, name, ak, sk string, expired time.Duration, opt interface{}) (*url.URL, error) {
  97. sendOpt := sendOptions{
  98. baseURL: s.client.BaseURL.BucketURL,
  99. uri: "/" + encodeURIComponent(name),
  100. method: httpMethod,
  101. optQuery: opt,
  102. optHeader: opt,
  103. }
  104. req, err := s.client.newRequest(ctx, sendOpt.baseURL, sendOpt.uri, sendOpt.method, sendOpt.body, sendOpt.optQuery, sendOpt.optHeader)
  105. if err != nil {
  106. return nil, err
  107. }
  108. var authTime *AuthTime
  109. if opt != nil {
  110. if opt, ok := opt.(*presignedURLTestingOptions); ok {
  111. authTime = opt.authTime
  112. }
  113. }
  114. if authTime == nil {
  115. authTime = NewAuthTime(expired)
  116. }
  117. authorization := newAuthorization(ak, sk, req, authTime)
  118. sign := encodeURIComponent(authorization, []byte{'&', '='})
  119. if req.URL.RawQuery == "" {
  120. req.URL.RawQuery = fmt.Sprintf("%s", sign)
  121. } else {
  122. req.URL.RawQuery = fmt.Sprintf("%s&%s", req.URL.RawQuery, sign)
  123. }
  124. return req.URL, nil
  125. }
  126. // ObjectPutHeaderOptions the options of header of the put object
  127. type ObjectPutHeaderOptions struct {
  128. CacheControl string `header:"Cache-Control,omitempty" url:"-"`
  129. ContentDisposition string `header:"Content-Disposition,omitempty" url:"-"`
  130. ContentEncoding string `header:"Content-Encoding,omitempty" url:"-"`
  131. ContentType string `header:"Content-Type,omitempty" url:"-"`
  132. ContentMD5 string `header:"Content-MD5,omitempty" url:"-"`
  133. ContentLength int64 `header:"Content-Length,omitempty" url:"-"`
  134. ContentLanguage string `header:"Content-Language,omitempty" url:"-"`
  135. Expect string `header:"Expect,omitempty" url:"-"`
  136. Expires string `header:"Expires,omitempty" url:"-"`
  137. XCosContentSHA1 string `header:"x-cos-content-sha1,omitempty" url:"-"`
  138. // 自定义的 x-cos-meta-* header
  139. XCosMetaXXX *http.Header `header:"x-cos-meta-*,omitempty" url:"-"`
  140. XCosStorageClass string `header:"x-cos-storage-class,omitempty" url:"-"`
  141. // 可选值: Normal, Appendable
  142. //XCosObjectType string `header:"x-cos-object-type,omitempty" url:"-"`
  143. // Enable Server Side Encryption, Only supported: AES256
  144. XCosServerSideEncryption string `header:"x-cos-server-side-encryption,omitempty" url:"-" xml:"-"`
  145. // SSE-C
  146. XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
  147. XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
  148. XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
  149. //兼容其他自定义头部
  150. XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
  151. XCosTrafficLimit int `header:"x-cos-traffic-limit,omitempty" url:"-" xml:"-"`
  152. // 上传进度, ProgressCompleteEvent不能表示对应API调用成功,API是否调用成功的判断标准为返回err==nil
  153. Listener ProgressListener `header:"-" url:"-" xml:"-"`
  154. }
  155. // ObjectPutOptions the options of put object
  156. type ObjectPutOptions struct {
  157. *ACLHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  158. *ObjectPutHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  159. }
  160. // Put Object请求可以将一个文件(Oject)上传至指定Bucket。
  161. //
  162. // https://www.qcloud.com/document/product/436/7749
  163. func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, uopt *ObjectPutOptions) (*Response, error) {
  164. if err := CheckReaderLen(r); err != nil {
  165. return nil, err
  166. }
  167. opt := cloneObjectPutOptions(uopt)
  168. totalBytes, err := GetReaderLen(r)
  169. if err != nil && opt != nil && opt.Listener != nil {
  170. return nil, err
  171. }
  172. if err == nil {
  173. // 与 go http 保持一致, 非bytes.Buffer/bytes.Reader/strings.Reader由用户指定ContentLength, 或使用 Chunk 上传
  174. if opt != nil && opt.ContentLength == 0 && IsLenReader(r) {
  175. opt.ContentLength = totalBytes
  176. }
  177. }
  178. reader := TeeReader(r, nil, totalBytes, nil)
  179. if s.client.Conf.EnableCRC {
  180. reader.writer = crc64.New(crc64.MakeTable(crc64.ECMA))
  181. }
  182. if opt != nil && opt.Listener != nil {
  183. reader.listener = opt.Listener
  184. }
  185. sendOpt := sendOptions{
  186. baseURL: s.client.BaseURL.BucketURL,
  187. uri: "/" + encodeURIComponent(name),
  188. method: http.MethodPut,
  189. body: reader,
  190. optHeader: opt,
  191. }
  192. resp, err := s.client.send(ctx, &sendOpt)
  193. return resp, err
  194. }
  195. // PutFromFile put object from local file
  196. // Notice that when use this put large file need set non-body of debug req/resp, otherwise will out of memory
  197. func (s *ObjectService) PutFromFile(ctx context.Context, name string, filePath string, opt *ObjectPutOptions) (resp *Response, err error) {
  198. nr := 0
  199. for nr < 3 {
  200. fd, e := os.Open(filePath)
  201. if e != nil {
  202. err = e
  203. return
  204. }
  205. resp, err = s.Put(ctx, name, fd, opt)
  206. if err != nil {
  207. nr++
  208. fd.Close()
  209. continue
  210. }
  211. fd.Close()
  212. break
  213. }
  214. return
  215. }
  216. // ObjectCopyHeaderOptions is the head option of the Copy
  217. type ObjectCopyHeaderOptions struct {
  218. // When use replace directive to update meta infos
  219. CacheControl string `header:"Cache-Control,omitempty" url:"-"`
  220. ContentDisposition string `header:"Content-Disposition,omitempty" url:"-"`
  221. ContentEncoding string `header:"Content-Encoding,omitempty" url:"-"`
  222. ContentLanguage string `header:"Content-Language,omitempty" url:"-"`
  223. ContentType string `header:"Content-Type,omitempty" url:"-"`
  224. Expires string `header:"Expires,omitempty" url:"-"`
  225. Expect string `header:"Expect,omitempty" url:"-"`
  226. XCosMetadataDirective string `header:"x-cos-metadata-directive,omitempty" url:"-" xml:"-"`
  227. XCosCopySourceIfModifiedSince string `header:"x-cos-copy-source-If-Modified-Since,omitempty" url:"-" xml:"-"`
  228. XCosCopySourceIfUnmodifiedSince string `header:"x-cos-copy-source-If-Unmodified-Since,omitempty" url:"-" xml:"-"`
  229. XCosCopySourceIfMatch string `header:"x-cos-copy-source-If-Match,omitempty" url:"-" xml:"-"`
  230. XCosCopySourceIfNoneMatch string `header:"x-cos-copy-source-If-None-Match,omitempty" url:"-" xml:"-"`
  231. XCosStorageClass string `header:"x-cos-storage-class,omitempty" url:"-" xml:"-"`
  232. // 自定义的 x-cos-meta-* header
  233. XCosMetaXXX *http.Header `header:"x-cos-meta-*,omitempty" url:"-"`
  234. XCosCopySource string `header:"x-cos-copy-source" url:"-" xml:"-"`
  235. XCosServerSideEncryption string `header:"x-cos-server-side-encryption,omitempty" url:"-" xml:"-"`
  236. // SSE-C
  237. XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
  238. XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
  239. XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
  240. XCosCopySourceSSECustomerAglo string `header:"x-cos-copy-source-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
  241. XCosCopySourceSSECustomerKey string `header:"x-cos-copy-source-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
  242. XCosCopySourceSSECustomerKeyMD5 string `header:"x-cos-copy-source-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
  243. //兼容其他自定义头部
  244. XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
  245. }
  246. // ObjectCopyOptions is the option of Copy, choose header or body
  247. type ObjectCopyOptions struct {
  248. *ObjectCopyHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  249. *ACLHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  250. }
  251. // ObjectCopyResult is the result of Copy
  252. type ObjectCopyResult struct {
  253. XMLName xml.Name `xml:"CopyObjectResult"`
  254. ETag string `xml:"ETag,omitempty"`
  255. LastModified string `xml:"LastModified,omitempty"`
  256. CRC64 string `xml:"CRC64,omitempty"`
  257. VersionId string `xml:"VersionId,omitempty"`
  258. }
  259. // Copy 调用 PutObjectCopy 请求实现将一个文件从源路径复制到目标路径。建议文件大小 1M 到 5G,
  260. // 超过 5G 的文件请使用分块上传 Upload - Copy。在拷贝的过程中,文件元属性和 ACL 可以被修改。
  261. //
  262. // 用户可以通过该接口实现文件移动,文件重命名,修改文件属性和创建副本。
  263. //
  264. // 注意:在跨帐号复制的时候,需要先设置被复制文件的权限为公有读,或者对目标帐号赋权,同帐号则不需要。
  265. //
  266. // https://cloud.tencent.com/document/product/436/10881
  267. func (s *ObjectService) Copy(ctx context.Context, name, sourceURL string, opt *ObjectCopyOptions, id ...string) (*ObjectCopyResult, *Response, error) {
  268. surl := strings.SplitN(sourceURL, "/", 2)
  269. if len(surl) < 2 {
  270. return nil, nil, errors.New(fmt.Sprintf("x-cos-copy-source format error: %s", sourceURL))
  271. }
  272. var u string
  273. if len(id) == 1 {
  274. u = fmt.Sprintf("%s/%s?versionId=%s", surl[0], encodeURIComponent(surl[1]), id[0])
  275. } else if len(id) == 0 {
  276. u = fmt.Sprintf("%s/%s", surl[0], encodeURIComponent(surl[1]))
  277. } else {
  278. return nil, nil, errors.New("wrong params")
  279. }
  280. var res ObjectCopyResult
  281. copyOpt := &ObjectCopyOptions{
  282. &ObjectCopyHeaderOptions{},
  283. &ACLHeaderOptions{},
  284. }
  285. if opt != nil {
  286. if opt.ObjectCopyHeaderOptions != nil {
  287. *copyOpt.ObjectCopyHeaderOptions = *opt.ObjectCopyHeaderOptions
  288. }
  289. if opt.ACLHeaderOptions != nil {
  290. *copyOpt.ACLHeaderOptions = *opt.ACLHeaderOptions
  291. }
  292. }
  293. copyOpt.XCosCopySource = u
  294. sendOpt := sendOptions{
  295. baseURL: s.client.BaseURL.BucketURL,
  296. uri: "/" + encodeURIComponent(name),
  297. method: http.MethodPut,
  298. body: nil,
  299. optHeader: copyOpt,
  300. result: &res,
  301. }
  302. resp, err := s.client.send(ctx, &sendOpt)
  303. // If the error occurs during the copy operation, the error response is embedded in the 200 OK response. This means that a 200 OK response can contain either a success or an error.
  304. if err == nil && resp.StatusCode == 200 {
  305. if res.ETag == "" {
  306. return &res, resp, errors.New("response 200 OK, but body contains an error")
  307. }
  308. }
  309. return &res, resp, err
  310. }
  311. type ObjectDeleteOptions struct {
  312. // SSE-C
  313. XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
  314. XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
  315. XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
  316. //兼容其他自定义头部
  317. XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
  318. VersionId string `header:"-" url:"VersionId,omitempty" xml:"-"`
  319. }
  320. // Delete Object请求可以将一个文件(Object)删除。
  321. //
  322. // https://www.qcloud.com/document/product/436/7743
  323. func (s *ObjectService) Delete(ctx context.Context, name string, opt ...*ObjectDeleteOptions) (*Response, error) {
  324. var optHeader *ObjectDeleteOptions
  325. // When use "" string might call the delete bucket interface
  326. if len(name) == 0 {
  327. return nil, errors.New("empty object name")
  328. }
  329. if len(opt) > 0 {
  330. optHeader = opt[0]
  331. }
  332. sendOpt := sendOptions{
  333. baseURL: s.client.BaseURL.BucketURL,
  334. uri: "/" + encodeURIComponent(name),
  335. method: http.MethodDelete,
  336. optHeader: optHeader,
  337. optQuery: optHeader,
  338. }
  339. resp, err := s.client.send(ctx, &sendOpt)
  340. return resp, err
  341. }
  342. // ObjectHeadOptions is the option of HeadObject
  343. type ObjectHeadOptions struct {
  344. IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"`
  345. // SSE-C
  346. XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
  347. XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
  348. XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
  349. XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
  350. }
  351. // Head Object请求可以取回对应Object的元数据,Head的权限与Get的权限一致
  352. //
  353. // https://www.qcloud.com/document/product/436/7745
  354. func (s *ObjectService) Head(ctx context.Context, name string, opt *ObjectHeadOptions, id ...string) (*Response, error) {
  355. var u string
  356. if len(id) == 1 {
  357. u = fmt.Sprintf("/%s?versionId=%s", encodeURIComponent(name), id[0])
  358. } else if len(id) == 0 {
  359. u = "/" + encodeURIComponent(name)
  360. } else {
  361. return nil, errors.New("wrong params")
  362. }
  363. sendOpt := sendOptions{
  364. baseURL: s.client.BaseURL.BucketURL,
  365. uri: u,
  366. method: http.MethodHead,
  367. optHeader: opt,
  368. }
  369. resp, err := s.client.send(ctx, &sendOpt)
  370. if resp != nil && resp.Header["X-Cos-Object-Type"] != nil && resp.Header["X-Cos-Object-Type"][0] == "appendable" {
  371. resp.Header.Add("x-cos-next-append-position", resp.Header["Content-Length"][0])
  372. }
  373. return resp, err
  374. }
  375. // ObjectOptionsOptions is the option of object options
  376. type ObjectOptionsOptions struct {
  377. Origin string `url:"-" header:"Origin"`
  378. AccessControlRequestMethod string `url:"-" header:"Access-Control-Request-Method"`
  379. AccessControlRequestHeaders string `url:"-" header:"Access-Control-Request-Headers,omitempty"`
  380. }
  381. // Options Object请求实现跨域访问的预请求。即发出一个 OPTIONS 请求给服务器以确认是否可以进行跨域操作。
  382. //
  383. // 当CORS配置不存在时,请求返回403 Forbidden。
  384. //
  385. // https://www.qcloud.com/document/product/436/8288
  386. func (s *ObjectService) Options(ctx context.Context, name string, opt *ObjectOptionsOptions) (*Response, error) {
  387. sendOpt := sendOptions{
  388. baseURL: s.client.BaseURL.BucketURL,
  389. uri: "/" + encodeURIComponent(name),
  390. method: http.MethodOptions,
  391. optHeader: opt,
  392. }
  393. resp, err := s.client.send(ctx, &sendOpt)
  394. return resp, err
  395. }
  396. // CASJobParameters support three way: Standard(in 35 hours), Expedited(quick way, in 15 mins), Bulk(in 5-12 hours_
  397. type CASJobParameters struct {
  398. Tier string `xml:"Tier"`
  399. }
  400. // ObjectRestoreOptions is the option of object restore
  401. type ObjectRestoreOptions struct {
  402. XMLName xml.Name `xml:"RestoreRequest"`
  403. Days int `xml:"Days"`
  404. Tier *CASJobParameters `xml:"CASJobParameters"`
  405. }
  406. // PutRestore API can recover an object of type archived by COS archive.
  407. //
  408. // https://cloud.tencent.com/document/product/436/12633
  409. func (s *ObjectService) PostRestore(ctx context.Context, name string, opt *ObjectRestoreOptions) (*Response, error) {
  410. u := fmt.Sprintf("/%s?restore", encodeURIComponent(name))
  411. sendOpt := sendOptions{
  412. baseURL: s.client.BaseURL.BucketURL,
  413. uri: u,
  414. method: http.MethodPost,
  415. body: opt,
  416. }
  417. resp, err := s.client.send(ctx, &sendOpt)
  418. return resp, err
  419. }
  420. // TODO Append 接口在优化未开放使用
  421. //
  422. // Append请求可以将一个文件(Object)以分块追加的方式上传至 Bucket 中。使用Append Upload的文件必须事前被设定为Appendable。
  423. // 当Appendable的文件被执行Put Object的操作以后,文件被覆盖,属性改变为Normal。
  424. //
  425. // 文件属性可以在Head Object操作中被查询到,当您发起Head Object请求时,会返回自定义Header『x-cos-object-type』,该Header只有两个枚举值:Normal或者Appendable。
  426. //
  427. // 追加上传建议文件大小1M - 5G。如果position的值和当前Object的长度不致,COS会返回409错误。
  428. // 如果Append一个Normal的Object,COS会返回409 ObjectNotAppendable。
  429. //
  430. // Appendable的文件不可以被复制,不参与版本管理,不参与生命周期管理,不可跨区域复制。
  431. //
  432. // 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ObjectPutHeaderOptions.ContentLength
  433. //
  434. // https://www.qcloud.com/document/product/436/7741
  435. // func (s *ObjectService) Append(ctx context.Context, name string, position int, r io.Reader, opt *ObjectPutOptions) (*Response, error) {
  436. // u := fmt.Sprintf("/%s?append&position=%d", encodeURIComponent(name), position)
  437. // if position != 0{
  438. // opt = nil
  439. // }
  440. // sendOpt := sendOptions{
  441. // baseURL: s.client.BaseURL.BucketURL,
  442. // uri: u,
  443. // method: http.MethodPost,
  444. // optHeader: opt,
  445. // body: r,
  446. // }
  447. // resp, err := s.client.send(ctx, &sendOpt)
  448. // return resp, err
  449. // }
  450. // ObjectDeleteMultiOptions is the option of DeleteMulti
  451. type ObjectDeleteMultiOptions struct {
  452. XMLName xml.Name `xml:"Delete" header:"-"`
  453. Quiet bool `xml:"Quiet" header:"-"`
  454. Objects []Object `xml:"Object" header:"-"`
  455. //XCosSha1 string `xml:"-" header:"x-cos-sha1"`
  456. }
  457. // ObjectDeleteMultiResult is the result of DeleteMulti
  458. type ObjectDeleteMultiResult struct {
  459. XMLName xml.Name `xml:"DeleteResult"`
  460. DeletedObjects []Object `xml:"Deleted,omitempty"`
  461. Errors []struct {
  462. Key string `xml:",omitempty"`
  463. Code string `xml:",omitempty"`
  464. Message string `xml:",omitempty"`
  465. VersionId string `xml:",omitempty"`
  466. } `xml:"Error,omitempty"`
  467. }
  468. // DeleteMulti 请求实现批量删除文件,最大支持单次删除1000个文件。
  469. // 对于返回结果,COS提供Verbose和Quiet两种结果模式。Verbose模式将返回每个Object的删除结果;
  470. // Quiet模式只返回报错的Object信息。
  471. // https://www.qcloud.com/document/product/436/8289
  472. func (s *ObjectService) DeleteMulti(ctx context.Context, opt *ObjectDeleteMultiOptions) (*ObjectDeleteMultiResult, *Response, error) {
  473. var res ObjectDeleteMultiResult
  474. sendOpt := sendOptions{
  475. baseURL: s.client.BaseURL.BucketURL,
  476. uri: "/?delete",
  477. method: http.MethodPost,
  478. body: opt,
  479. result: &res,
  480. }
  481. resp, err := s.client.send(ctx, &sendOpt)
  482. return &res, resp, err
  483. }
  484. // Object is the meta info of the object
  485. type Object struct {
  486. Key string `xml:",omitempty"`
  487. ETag string `xml:",omitempty"`
  488. Size int64 `xml:",omitempty"`
  489. PartNumber int `xml:",omitempty"`
  490. LastModified string `xml:",omitempty"`
  491. StorageClass string `xml:",omitempty"`
  492. Owner *Owner `xml:",omitempty"`
  493. VersionId string `xml:",omitempty"`
  494. }
  495. // MultiUploadOptions is the option of the multiupload,
  496. // ThreadPoolSize default is one
  497. type MultiUploadOptions struct {
  498. OptIni *InitiateMultipartUploadOptions
  499. PartSize int64
  500. ThreadPoolSize int
  501. CheckPoint bool
  502. EnableVerification bool
  503. }
  504. type Chunk struct {
  505. Number int
  506. OffSet int64
  507. Size int64
  508. Done bool
  509. ETag string
  510. }
  511. // jobs
  512. type Jobs struct {
  513. Name string
  514. UploadId string
  515. FilePath string
  516. RetryTimes int
  517. Chunk Chunk
  518. Data io.Reader
  519. Opt *ObjectUploadPartOptions
  520. }
  521. type Results struct {
  522. PartNumber int
  523. Resp *Response
  524. err error
  525. }
  526. func LimitReadCloser(r io.Reader, n int64) io.Reader {
  527. var lc LimitedReadCloser
  528. lc.R = r
  529. lc.N = n
  530. return &lc
  531. }
  532. type LimitedReadCloser struct {
  533. io.LimitedReader
  534. }
  535. func (lc *LimitedReadCloser) Close() error {
  536. if r, ok := lc.R.(io.ReadCloser); ok {
  537. return r.Close()
  538. }
  539. return nil
  540. }
  541. func worker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results) {
  542. for j := range jobs {
  543. j.Opt.ContentLength = j.Chunk.Size
  544. rt := j.RetryTimes
  545. for {
  546. // http.Request.Body can be Closed in request
  547. fd, err := os.Open(j.FilePath)
  548. var res Results
  549. if err != nil {
  550. res.err = err
  551. res.PartNumber = j.Chunk.Number
  552. res.Resp = nil
  553. results <- &res
  554. break
  555. }
  556. fd.Seek(j.Chunk.OffSet, os.SEEK_SET)
  557. resp, err := s.UploadPart(context.Background(), j.Name, j.UploadId, j.Chunk.Number,
  558. LimitReadCloser(fd, j.Chunk.Size), j.Opt)
  559. res.PartNumber = j.Chunk.Number
  560. res.Resp = resp
  561. res.err = err
  562. if err != nil {
  563. rt--
  564. if rt == 0 {
  565. results <- &res
  566. break
  567. }
  568. continue
  569. }
  570. results <- &res
  571. break
  572. }
  573. }
  574. }
  575. func DividePart(fileSize int64, last int) (int64, int64) {
  576. partSize := int64(last * 1024 * 1024)
  577. partNum := fileSize / partSize
  578. for partNum >= 10000 {
  579. partSize = partSize * 2
  580. partNum = fileSize / partSize
  581. }
  582. return partNum, partSize
  583. }
  584. func SplitFileIntoChunks(filePath string, partSize int64) (int64, []Chunk, int, error) {
  585. if filePath == "" {
  586. return 0, nil, 0, errors.New("filePath invalid")
  587. }
  588. file, err := os.Open(filePath)
  589. if err != nil {
  590. return 0, nil, 0, err
  591. }
  592. defer file.Close()
  593. stat, err := file.Stat()
  594. if err != nil {
  595. return 0, nil, 0, err
  596. }
  597. var partNum int64
  598. if partSize > 0 {
  599. partSize = partSize * 1024 * 1024
  600. partNum = stat.Size() / partSize
  601. if partNum >= 10000 {
  602. return 0, nil, 0, errors.New("Too many parts, out of 10000")
  603. }
  604. } else {
  605. partNum, partSize = DividePart(stat.Size(), 64)
  606. }
  607. var chunks []Chunk
  608. var chunk = Chunk{}
  609. for i := int64(0); i < partNum; i++ {
  610. chunk.Number = int(i + 1)
  611. chunk.OffSet = i * partSize
  612. chunk.Size = partSize
  613. chunks = append(chunks, chunk)
  614. }
  615. if stat.Size()%partSize > 0 {
  616. chunk.Number = len(chunks) + 1
  617. chunk.OffSet = int64(len(chunks)) * partSize
  618. chunk.Size = stat.Size() % partSize
  619. chunks = append(chunks, chunk)
  620. partNum++
  621. }
  622. return int64(stat.Size()), chunks, int(partNum), nil
  623. }
  624. func (s *ObjectService) getResumableUploadID(ctx context.Context, name string) (string, error) {
  625. opt := &ObjectListUploadsOptions{
  626. Prefix: name,
  627. EncodingType: "url",
  628. }
  629. res, _, err := s.ListUploads(ctx, opt)
  630. if err != nil {
  631. return "", err
  632. }
  633. if len(res.Upload) == 0 {
  634. return "", nil
  635. }
  636. last := len(res.Upload) - 1
  637. for last >= 0 {
  638. decodeKey, _ := decodeURIComponent(res.Upload[last].Key)
  639. if decodeKey == name {
  640. return decodeURIComponent(res.Upload[last].UploadID)
  641. }
  642. last = last - 1
  643. }
  644. return "", nil
  645. }
  646. func (s *ObjectService) checkUploadedParts(ctx context.Context, name, UploadID, filepath string, chunks []Chunk, partNum int) error {
  647. var uploadedParts []Object
  648. isTruncated := true
  649. opt := &ObjectListPartsOptions{
  650. EncodingType: "url",
  651. }
  652. for isTruncated {
  653. res, _, err := s.ListParts(ctx, name, UploadID, opt)
  654. if err != nil {
  655. return err
  656. }
  657. if len(res.Parts) > 0 {
  658. uploadedParts = append(uploadedParts, res.Parts...)
  659. }
  660. isTruncated = res.IsTruncated
  661. opt.PartNumberMarker = res.NextPartNumberMarker
  662. }
  663. fd, err := os.Open(filepath)
  664. if err != nil {
  665. return err
  666. }
  667. defer fd.Close()
  668. // 某个分块出错, 重置chunks
  669. ret := func(e error) error {
  670. for i, _ := range chunks {
  671. chunks[i].Done = false
  672. chunks[i].ETag = ""
  673. }
  674. return e
  675. }
  676. for _, part := range uploadedParts {
  677. partNumber := part.PartNumber
  678. if partNumber > partNum {
  679. return ret(errors.New("Part Number is not consistent"))
  680. }
  681. partNumber = partNumber - 1
  682. fd.Seek(chunks[partNumber].OffSet, os.SEEK_SET)
  683. bs, err := ioutil.ReadAll(io.LimitReader(fd, chunks[partNumber].Size))
  684. if err != nil {
  685. return ret(err)
  686. }
  687. localMD5 := fmt.Sprintf("\"%x\"", md5.Sum(bs))
  688. if localMD5 != part.ETag {
  689. return ret(errors.New(fmt.Sprintf("CheckSum Failed in Part[%d]", part.PartNumber)))
  690. }
  691. chunks[partNumber].Done = true
  692. chunks[partNumber].ETag = part.ETag
  693. }
  694. return nil
  695. }
  696. // MultiUpload/Upload 为高级upload接口,并发分块上传
  697. // 注意该接口目前只供参考
  698. //
  699. // 当 partSize > 0 时,由调用者指定分块大小,否则由 SDK 自动切分,单位为MB
  700. // 由调用者指定分块大小时,请确认分块数量不超过10000
  701. //
  702. func (s *ObjectService) MultiUpload(ctx context.Context, name string, filepath string, opt *MultiUploadOptions) (*CompleteMultipartUploadResult, *Response, error) {
  703. return s.Upload(ctx, name, filepath, opt)
  704. }
  705. func (s *ObjectService) Upload(ctx context.Context, name string, filepath string, opt *MultiUploadOptions) (*CompleteMultipartUploadResult, *Response, error) {
  706. if opt == nil {
  707. opt = &MultiUploadOptions{}
  708. }
  709. var localcrc uint64
  710. // 1.Get the file chunk
  711. totalBytes, chunks, partNum, err := SplitFileIntoChunks(filepath, opt.PartSize)
  712. if err != nil {
  713. return nil, nil, err
  714. }
  715. // 校验
  716. if s.client.Conf.EnableCRC {
  717. fd, err := os.Open(filepath)
  718. if err != nil {
  719. return nil, nil, err
  720. }
  721. defer fd.Close()
  722. localcrc, err = calCRC64(fd)
  723. if err != nil {
  724. return nil, nil, err
  725. }
  726. }
  727. // filesize=0 , use simple upload
  728. if partNum == 0 || partNum == 1 {
  729. var opt0 *ObjectPutOptions
  730. if opt.OptIni != nil {
  731. opt0 = &ObjectPutOptions{
  732. opt.OptIni.ACLHeaderOptions,
  733. opt.OptIni.ObjectPutHeaderOptions,
  734. }
  735. }
  736. rsp, err := s.PutFromFile(ctx, name, filepath, opt0)
  737. if err != nil {
  738. return nil, rsp, err
  739. }
  740. result := &CompleteMultipartUploadResult{
  741. Key: name,
  742. ETag: rsp.Header.Get("ETag"),
  743. }
  744. if rsp != nil && s.client.Conf.EnableCRC {
  745. scoscrc := rsp.Header.Get("x-cos-hash-crc64ecma")
  746. icoscrc, _ := strconv.ParseUint(scoscrc, 10, 64)
  747. if icoscrc != localcrc {
  748. return result, rsp, fmt.Errorf("verification failed, want:%v, return:%v", localcrc, icoscrc)
  749. }
  750. }
  751. return result, rsp, nil
  752. }
  753. var uploadID string
  754. resumableFlag := false
  755. if opt.CheckPoint {
  756. var err error
  757. uploadID, err = s.getResumableUploadID(ctx, name)
  758. if err == nil && uploadID != "" {
  759. err = s.checkUploadedParts(ctx, name, uploadID, filepath, chunks, partNum)
  760. resumableFlag = (err == nil)
  761. }
  762. }
  763. // 2.Init
  764. optini := opt.OptIni
  765. if !resumableFlag {
  766. res, _, err := s.InitiateMultipartUpload(ctx, name, optini)
  767. if err != nil {
  768. return nil, nil, err
  769. }
  770. uploadID = res.UploadID
  771. }
  772. var poolSize int
  773. if opt.ThreadPoolSize > 0 {
  774. poolSize = opt.ThreadPoolSize
  775. } else {
  776. // Default is one
  777. poolSize = 1
  778. }
  779. chjobs := make(chan *Jobs, 100)
  780. chresults := make(chan *Results, 10000)
  781. optcom := &CompleteMultipartUploadOptions{}
  782. // 3.Start worker
  783. for w := 1; w <= poolSize; w++ {
  784. go worker(s, chjobs, chresults)
  785. }
  786. // progress started event
  787. var listener ProgressListener
  788. var consumedBytes int64
  789. if opt.OptIni != nil {
  790. listener = opt.OptIni.Listener
  791. }
  792. event := newProgressEvent(ProgressStartedEvent, 0, 0, totalBytes)
  793. progressCallback(listener, event)
  794. // 4.Push jobs
  795. go func() {
  796. for _, chunk := range chunks {
  797. if chunk.Done {
  798. continue
  799. }
  800. partOpt := &ObjectUploadPartOptions{}
  801. if optini != nil && optini.ObjectPutHeaderOptions != nil {
  802. partOpt.XCosSSECustomerAglo = optini.XCosSSECustomerAglo
  803. partOpt.XCosSSECustomerKey = optini.XCosSSECustomerKey
  804. partOpt.XCosSSECustomerKeyMD5 = optini.XCosSSECustomerKeyMD5
  805. partOpt.XCosTrafficLimit = optini.XCosTrafficLimit
  806. }
  807. job := &Jobs{
  808. Name: name,
  809. RetryTimes: 3,
  810. FilePath: filepath,
  811. UploadId: uploadID,
  812. Chunk: chunk,
  813. Opt: partOpt,
  814. }
  815. chjobs <- job
  816. }
  817. close(chjobs)
  818. }()
  819. // 5.Recv the resp etag to complete
  820. err = nil
  821. for i := 0; i < partNum; i++ {
  822. if chunks[i].Done {
  823. optcom.Parts = append(optcom.Parts, Object{
  824. PartNumber: chunks[i].Number, ETag: chunks[i].ETag},
  825. )
  826. if err == nil {
  827. consumedBytes += chunks[i].Size
  828. event = newProgressEvent(ProgressDataEvent, chunks[i].Size, consumedBytes, totalBytes)
  829. progressCallback(listener, event)
  830. }
  831. continue
  832. }
  833. res := <-chresults
  834. // Notice one part fail can not get the etag according.
  835. if res.Resp == nil || res.err != nil {
  836. // Some part already fail, can not to get the header inside.
  837. err = fmt.Errorf("UploadID %s, part %d failed to get resp content. error: %s", uploadID, res.PartNumber, res.err.Error())
  838. continue
  839. }
  840. // Notice one part fail can not get the etag according.
  841. etag := res.Resp.Header.Get("ETag")
  842. optcom.Parts = append(optcom.Parts, Object{
  843. PartNumber: res.PartNumber, ETag: etag},
  844. )
  845. if err == nil {
  846. consumedBytes += chunks[res.PartNumber-1].Size
  847. event = newProgressEvent(ProgressDataEvent, chunks[res.PartNumber-1].Size, consumedBytes, totalBytes)
  848. progressCallback(listener, event)
  849. }
  850. }
  851. close(chresults)
  852. if err != nil {
  853. event = newProgressEvent(ProgressFailedEvent, 0, consumedBytes, totalBytes, err)
  854. progressCallback(listener, event)
  855. return nil, nil, err
  856. }
  857. sort.Sort(ObjectList(optcom.Parts))
  858. event = newProgressEvent(ProgressCompletedEvent, 0, consumedBytes, totalBytes)
  859. progressCallback(listener, event)
  860. v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
  861. if err != nil {
  862. s.AbortMultipartUpload(ctx, name, uploadID)
  863. return v, resp, err
  864. }
  865. if resp != nil && s.client.Conf.EnableCRC {
  866. scoscrc := resp.Header.Get("x-cos-hash-crc64ecma")
  867. icoscrc, _ := strconv.ParseUint(scoscrc, 10, 64)
  868. if icoscrc != localcrc {
  869. return v, resp, fmt.Errorf("verification failed, want:%v, return:%v", localcrc, icoscrc)
  870. }
  871. }
  872. return v, resp, err
  873. }
  874. type ObjectPutTaggingOptions struct {
  875. XMLName xml.Name `xml:"Tagging"`
  876. TagSet []ObjectTaggingTag `xml:"TagSet>Tag,omitempty"`
  877. }
  878. type ObjectTaggingTag BucketTaggingTag
  879. type ObjectGetTaggingResult ObjectPutTaggingOptions
  880. func (s *ObjectService) PutTagging(ctx context.Context, name string, opt *ObjectPutTaggingOptions, id ...string) (*Response, error) {
  881. var u string
  882. if len(id) == 1 {
  883. u = fmt.Sprintf("/%s?tagging&versionId=%s", encodeURIComponent(name), id[0])
  884. } else if len(id) == 0 {
  885. u = fmt.Sprintf("/%s?tagging", encodeURIComponent(name))
  886. } else {
  887. return nil, errors.New("wrong params")
  888. }
  889. sendOpt := &sendOptions{
  890. baseURL: s.client.BaseURL.BucketURL,
  891. uri: u,
  892. method: http.MethodPut,
  893. body: opt,
  894. }
  895. resp, err := s.client.send(ctx, sendOpt)
  896. return resp, err
  897. }
  898. func (s *ObjectService) GetTagging(ctx context.Context, name string, id ...string) (*ObjectGetTaggingResult, *Response, error) {
  899. var u string
  900. if len(id) == 1 {
  901. u = fmt.Sprintf("/%s?tagging&versionId=%s", encodeURIComponent(name), id[0])
  902. } else if len(id) == 0 {
  903. u = fmt.Sprintf("/%s?tagging", encodeURIComponent(name))
  904. } else {
  905. return nil, nil, errors.New("wrong params")
  906. }
  907. var res ObjectGetTaggingResult
  908. sendOpt := &sendOptions{
  909. baseURL: s.client.BaseURL.BucketURL,
  910. uri: u,
  911. method: http.MethodGet,
  912. result: &res,
  913. }
  914. resp, err := s.client.send(ctx, sendOpt)
  915. return &res, resp, err
  916. }
  917. func (s *ObjectService) DeleteTagging(ctx context.Context, name string, id ...string) (*Response, error) {
  918. var u string
  919. if len(id) == 1 {
  920. u = fmt.Sprintf("/%s?tagging&versionId=%s", encodeURIComponent(name), id[0])
  921. } else if len(id) == 0 {
  922. u = fmt.Sprintf("/%s?tagging", encodeURIComponent(name))
  923. } else {
  924. return nil, errors.New("wrong params")
  925. }
  926. sendOpt := &sendOptions{
  927. baseURL: s.client.BaseURL.BucketURL,
  928. uri: u,
  929. method: http.MethodDelete,
  930. }
  931. resp, err := s.client.send(ctx, sendOpt)
  932. return resp, err
  933. }