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.

191 lines
7.5 KiB

  1. package cos
  2. import (
  3. "context"
  4. "encoding/xml"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. )
  10. // InitiateMultipartUploadOptions is the option of InitateMultipartUpload
  11. type InitiateMultipartUploadOptions struct {
  12. *ACLHeaderOptions
  13. *ObjectPutHeaderOptions
  14. }
  15. // InitiateMultipartUploadResult is the result of InitateMultipartUpload
  16. type InitiateMultipartUploadResult struct {
  17. XMLName xml.Name `xml:"InitiateMultipartUploadResult"`
  18. Bucket string
  19. Key string
  20. UploadID string `xml:"UploadId"`
  21. }
  22. // InitiateMultipartUpload 请求实现初始化分片上传,成功执行此请求以后会返回Upload ID用于后续的Upload Part请求。
  23. //
  24. // https://www.qcloud.com/document/product/436/7746
  25. func (s *ObjectService) InitiateMultipartUpload(ctx context.Context, name string, opt *InitiateMultipartUploadOptions) (*InitiateMultipartUploadResult, *Response, error) {
  26. var res InitiateMultipartUploadResult
  27. sendOpt := sendOptions{
  28. baseURL: s.client.BaseURL.BucketURL,
  29. uri: "/" + encodeURIComponent(name) + "?uploads",
  30. method: http.MethodPost,
  31. optHeader: opt,
  32. result: &res,
  33. }
  34. resp, err := s.client.send(ctx, &sendOpt)
  35. return &res, resp, err
  36. }
  37. // ObjectUploadPartOptions is the options of upload-part
  38. type ObjectUploadPartOptions struct {
  39. Expect string `header:"Expect,omitempty" url:"-"`
  40. XCosContentSHA1 string `header:"x-cos-content-sha1" url:"-"`
  41. ContentLength int `header:"Content-Length,omitempty" url:"-"`
  42. }
  43. // UploadPart 请求实现在初始化以后的分块上传,支持的块的数量为1到10000,块的大小为1 MB 到5 GB。
  44. // 在每次请求Upload Part时候,需要携带partNumber和uploadID,partNumber为块的编号,支持乱序上传。
  45. //
  46. // 当传入uploadID和partNumber都相同的时候,后传入的块将覆盖之前传入的块。当uploadID不存在时会返回404错误,NoSuchUpload.
  47. //
  48. // 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ContentLength
  49. //
  50. // https://www.qcloud.com/document/product/436/7750
  51. func (s *ObjectService) UploadPart(ctx context.Context, name, uploadID string, partNumber int, r io.Reader, opt *ObjectUploadPartOptions) (*Response, error) {
  52. u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID)
  53. sendOpt := sendOptions{
  54. baseURL: s.client.BaseURL.BucketURL,
  55. uri: u,
  56. method: http.MethodPut,
  57. optHeader: opt,
  58. body: r,
  59. }
  60. resp, err := s.client.send(ctx, &sendOpt)
  61. return resp, err
  62. }
  63. // ObjectListPartsOptions is the option of ListParts
  64. type ObjectListPartsOptions struct {
  65. EncodingType string `url:"Encoding-type,omitempty"`
  66. MaxParts string `url:"max-parts,omitempty"`
  67. PartNumberMarker string `url:"part-number-marker,omitempty"`
  68. }
  69. // ObjectListPartsResult is the result of ListParts
  70. type ObjectListPartsResult struct {
  71. XMLName xml.Name `xml:"ListPartsResult"`
  72. Bucket string
  73. EncodingType string `xml:"Encoding-type,omitempty"`
  74. Key string
  75. UploadID string `xml:"UploadId"`
  76. Initiator *Initiator `xml:"Initiator,omitempty"`
  77. Owner *Owner `xml:"Owner,omitempty"`
  78. StorageClass string
  79. PartNumberMarker string
  80. NextPartNumberMarker string `xml:"NextPartNumberMarker,omitempty"`
  81. MaxParts string
  82. IsTruncated bool
  83. Parts []Object `xml:"Part,omitempty"`
  84. }
  85. // ListParts 用来查询特定分块上传中的已上传的块。
  86. //
  87. // https://www.qcloud.com/document/product/436/7747
  88. func (s *ObjectService) ListParts(ctx context.Context, name, uploadID string, opt *ObjectListPartsOptions) (*ObjectListPartsResult, *Response, error) {
  89. u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID)
  90. var res ObjectListPartsResult
  91. sendOpt := sendOptions{
  92. baseURL: s.client.BaseURL.BucketURL,
  93. uri: u,
  94. method: http.MethodGet,
  95. result: &res,
  96. optQuery: opt,
  97. }
  98. resp, err := s.client.send(ctx, &sendOpt)
  99. return &res, resp, err
  100. }
  101. // CompleteMultipartUploadOptions is the option of CompleteMultipartUpload
  102. type CompleteMultipartUploadOptions struct {
  103. XMLName xml.Name `xml:"CompleteMultipartUpload"`
  104. Parts []Object `xml:"Part"`
  105. }
  106. // CompleteMultipartUploadResult is the result CompleteMultipartUpload
  107. type CompleteMultipartUploadResult struct {
  108. XMLName xml.Name `xml:"CompleteMultipartUploadResult"`
  109. Location string
  110. Bucket string
  111. Key string
  112. ETag string
  113. }
  114. // ObjectList can used for sort the parts which needs in complete upload part
  115. // sort.Sort(cos.ObjectList(opt.Parts))
  116. type ObjectList []Object
  117. func (o ObjectList) Len() int {
  118. return len(o)
  119. }
  120. func (o ObjectList) Swap(i, j int) {
  121. o[i], o[j] = o[j], o[i]
  122. }
  123. func (o ObjectList) Less(i, j int) bool { // rewrite the Less method from small to big
  124. return o[i].PartNumber < o[j].PartNumber
  125. }
  126. // CompleteMultipartUpload 用来实现完成整个分块上传。当您已经使用Upload Parts上传所有块以后,你可以用该API完成上传。
  127. // 在使用该API时,您必须在Body中给出每一个块的PartNumber和ETag,用来校验块的准确性。
  128. //
  129. // 由于分块上传的合并需要数分钟时间,因而当合并分块开始的时候,COS就立即返回200的状态码,在合并的过程中,
  130. // COS会周期性的返回空格信息来保持连接活跃,直到合并完成,COS会在Body中返回合并后块的内容。
  131. //
  132. // 当上传块小于1 MB的时候,在调用该请求时,会返回400 EntityTooSmall;
  133. // 当上传块编号不连续的时候,在调用该请求时,会返回400 InvalidPart;
  134. // 当请求Body中的块信息没有按序号从小到大排列的时候,在调用该请求时,会返回400 InvalidPartOrder;
  135. // 当UploadId不存在的时候,在调用该请求时,会返回404 NoSuchUpload。
  136. //
  137. // 建议您及时完成分块上传或者舍弃分块上传,因为已上传但是未终止的块会占用存储空间进而产生存储费用。
  138. //
  139. // https://www.qcloud.com/document/product/436/7742
  140. func (s *ObjectService) CompleteMultipartUpload(ctx context.Context, name, uploadID string, opt *CompleteMultipartUploadOptions) (*CompleteMultipartUploadResult, *Response, error) {
  141. u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID)
  142. var res CompleteMultipartUploadResult
  143. sendOpt := sendOptions{
  144. baseURL: s.client.BaseURL.BucketURL,
  145. uri: u,
  146. method: http.MethodPost,
  147. body: opt,
  148. result: &res,
  149. }
  150. resp, err := s.client.send(ctx, &sendOpt)
  151. // 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.
  152. if err == nil && resp.StatusCode == 200 {
  153. if res.ETag == "" {
  154. return &res, resp, errors.New("response 200 OK, but body contains an error")
  155. }
  156. }
  157. return &res, resp, err
  158. }
  159. // AbortMultipartUpload 用来实现舍弃一个分块上传并删除已上传的块。当您调用Abort Multipart Upload时,
  160. // 如果有正在使用这个Upload Parts上传块的请求,则Upload Parts会返回失败。当该UploadID不存在时,会返回404 NoSuchUpload。
  161. //
  162. // 建议您及时完成分块上传或者舍弃分块上传,因为已上传但是未终止的块会占用存储空间进而产生存储费用。
  163. //
  164. // https://www.qcloud.com/document/product/436/7740
  165. func (s *ObjectService) AbortMultipartUpload(ctx context.Context, name, uploadID string) (*Response, error) {
  166. u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID)
  167. sendOpt := sendOptions{
  168. baseURL: s.client.BaseURL.BucketURL,
  169. uri: u,
  170. method: http.MethodDelete,
  171. }
  172. resp, err := s.client.send(ctx, &sendOpt)
  173. return resp, err
  174. }