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.

309 lines
13 KiB

4 years ago
4 years ago
4 years ago
  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,omitempty" url:"-"`
  41. ContentLength int `header:"Content-Length,omitempty" url:"-"`
  42. ContentMD5 string `header:"Content-MD5,omitempty" url:"-"`
  43. XCosSSECustomerAglo string `header:"x-cos-server-side-encryption-customer-algorithm,omitempty" url:"-" xml:"-"`
  44. XCosSSECustomerKey string `header:"x-cos-server-side-encryption-customer-key,omitempty" url:"-" xml:"-"`
  45. XCosSSECustomerKeyMD5 string `header:"x-cos-server-side-encryption-customer-key-MD5,omitempty" url:"-" xml:"-"`
  46. XCosTrafficLimit int `header:"x-cos-traffic-limit,omitempty" url:"-" xml:"-"`
  47. XOptionHeader *http.Header `header:"-,omitempty" url:"-" xml:"-"`
  48. // 上传进度, ProgressCompleteEvent不能表示对应API调用成功,API是否调用成功的判断标准为返回err==nil
  49. Listener ProgressListener `header:"-" url:"-" xml:"-"`
  50. }
  51. // UploadPart 请求实现在初始化以后的分块上传,支持的块的数量为1到10000,块的大小为1 MB 到5 GB。
  52. // 在每次请求Upload Part时候,需要携带partNumber和uploadID,partNumber为块的编号,支持乱序上传。
  53. //
  54. // 当传入uploadID和partNumber都相同的时候,后传入的块将覆盖之前传入的块。当uploadID不存在时会返回404错误,NoSuchUpload.
  55. //
  56. // 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ContentLength
  57. //
  58. // https://www.qcloud.com/document/product/436/7750
  59. func (s *ObjectService) UploadPart(ctx context.Context, name, uploadID string, partNumber int, r io.Reader, opt *ObjectUploadPartOptions) (*Response, error) {
  60. if err := CheckReaderLen(r); err != nil {
  61. return nil, err
  62. }
  63. if opt != nil && opt.Listener != nil {
  64. totalBytes, err := GetReaderLen(r)
  65. if err != nil {
  66. return nil, err
  67. }
  68. r = TeeReader(r, nil, totalBytes, opt.Listener)
  69. }
  70. u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID)
  71. sendOpt := sendOptions{
  72. baseURL: s.client.BaseURL.BucketURL,
  73. uri: u,
  74. method: http.MethodPut,
  75. optHeader: opt,
  76. body: r,
  77. }
  78. resp, err := s.client.send(ctx, &sendOpt)
  79. return resp, err
  80. }
  81. // ObjectListPartsOptions is the option of ListParts
  82. type ObjectListPartsOptions struct {
  83. EncodingType string `url:"Encoding-type,omitempty"`
  84. MaxParts string `url:"max-parts,omitempty"`
  85. PartNumberMarker string `url:"part-number-marker,omitempty"`
  86. }
  87. // ObjectListPartsResult is the result of ListParts
  88. type ObjectListPartsResult struct {
  89. XMLName xml.Name `xml:"ListPartsResult"`
  90. Bucket string
  91. EncodingType string `xml:"Encoding-type,omitempty"`
  92. Key string
  93. UploadID string `xml:"UploadId"`
  94. Initiator *Initiator `xml:"Initiator,omitempty"`
  95. Owner *Owner `xml:"Owner,omitempty"`
  96. StorageClass string
  97. PartNumberMarker string
  98. NextPartNumberMarker string `xml:"NextPartNumberMarker,omitempty"`
  99. MaxParts string
  100. IsTruncated bool
  101. Parts []Object `xml:"Part,omitempty"`
  102. }
  103. // ListParts 用来查询特定分块上传中的已上传的块。
  104. //
  105. // https://www.qcloud.com/document/product/436/7747
  106. func (s *ObjectService) ListParts(ctx context.Context, name, uploadID string, opt *ObjectListPartsOptions) (*ObjectListPartsResult, *Response, error) {
  107. u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID)
  108. var res ObjectListPartsResult
  109. sendOpt := sendOptions{
  110. baseURL: s.client.BaseURL.BucketURL,
  111. uri: u,
  112. method: http.MethodGet,
  113. result: &res,
  114. optQuery: opt,
  115. }
  116. resp, err := s.client.send(ctx, &sendOpt)
  117. return &res, resp, err
  118. }
  119. // CompleteMultipartUploadOptions is the option of CompleteMultipartUpload
  120. type CompleteMultipartUploadOptions struct {
  121. XMLName xml.Name `xml:"CompleteMultipartUpload" header:"-" url:"-"`
  122. Parts []Object `xml:"Part" header:"-" url:"-"`
  123. XOptionHeader *http.Header `header:"-,omitempty" xml:"-" url:"-"`
  124. }
  125. // CompleteMultipartUploadResult is the result CompleteMultipartUpload
  126. type CompleteMultipartUploadResult struct {
  127. XMLName xml.Name `xml:"CompleteMultipartUploadResult"`
  128. Location string
  129. Bucket string
  130. Key string
  131. ETag string
  132. }
  133. // ObjectList can used for sort the parts which needs in complete upload part
  134. // sort.Sort(cos.ObjectList(opt.Parts))
  135. type ObjectList []Object
  136. func (o ObjectList) Len() int {
  137. return len(o)
  138. }
  139. func (o ObjectList) Swap(i, j int) {
  140. o[i], o[j] = o[j], o[i]
  141. }
  142. func (o ObjectList) Less(i, j int) bool { // rewrite the Less method from small to big
  143. return o[i].PartNumber < o[j].PartNumber
  144. }
  145. // CompleteMultipartUpload 用来实现完成整个分块上传。当您已经使用Upload Parts上传所有块以后,你可以用该API完成上传。
  146. // 在使用该API时,您必须在Body中给出每一个块的PartNumber和ETag,用来校验块的准确性。
  147. //
  148. // 由于分块上传的合并需要数分钟时间,因而当合并分块开始的时候,COS就立即返回200的状态码,在合并的过程中,
  149. // COS会周期性的返回空格信息来保持连接活跃,直到合并完成,COS会在Body中返回合并后块的内容。
  150. //
  151. // 当上传块小于1 MB的时候,在调用该请求时,会返回400 EntityTooSmall;
  152. // 当上传块编号不连续的时候,在调用该请求时,会返回400 InvalidPart;
  153. // 当请求Body中的块信息没有按序号从小到大排列的时候,在调用该请求时,会返回400 InvalidPartOrder;
  154. // 当UploadId不存在的时候,在调用该请求时,会返回404 NoSuchUpload。
  155. //
  156. // 建议您及时完成分块上传或者舍弃分块上传,因为已上传但是未终止的块会占用存储空间进而产生存储费用。
  157. //
  158. // https://www.qcloud.com/document/product/436/7742
  159. func (s *ObjectService) CompleteMultipartUpload(ctx context.Context, name, uploadID string, opt *CompleteMultipartUploadOptions) (*CompleteMultipartUploadResult, *Response, error) {
  160. u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID)
  161. var res CompleteMultipartUploadResult
  162. sendOpt := sendOptions{
  163. baseURL: s.client.BaseURL.BucketURL,
  164. uri: u,
  165. method: http.MethodPost,
  166. optHeader: opt,
  167. body: opt,
  168. result: &res,
  169. }
  170. resp, err := s.client.send(ctx, &sendOpt)
  171. // 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.
  172. if err == nil && resp.StatusCode == 200 {
  173. if res.ETag == "" {
  174. return &res, resp, errors.New("response 200 OK, but body contains an error")
  175. }
  176. }
  177. return &res, resp, err
  178. }
  179. // AbortMultipartUpload 用来实现舍弃一个分块上传并删除已上传的块。当您调用Abort Multipart Upload时,
  180. // 如果有正在使用这个Upload Parts上传块的请求,则Upload Parts会返回失败。当该UploadID不存在时,会返回404 NoSuchUpload。
  181. //
  182. // 建议您及时完成分块上传或者舍弃分块上传,因为已上传但是未终止的块会占用存储空间进而产生存储费用。
  183. //
  184. // https://www.qcloud.com/document/product/436/7740
  185. func (s *ObjectService) AbortMultipartUpload(ctx context.Context, name, uploadID string) (*Response, error) {
  186. u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID)
  187. sendOpt := sendOptions{
  188. baseURL: s.client.BaseURL.BucketURL,
  189. uri: u,
  190. method: http.MethodDelete,
  191. }
  192. resp, err := s.client.send(ctx, &sendOpt)
  193. return resp, err
  194. }
  195. // ObjectCopyPartOptions is the options of copy-part
  196. type ObjectCopyPartOptions struct {
  197. XCosCopySource string `header:"x-cos-copy-source" url:"-"`
  198. XCosCopySourceRange string `header:"x-cos-copy-source-range,omitempty" url:"-"`
  199. XCosCopySourceIfModifiedSince string `header:"x-cos-copy-source-If-Modified-Since,omitempty" url:"-"`
  200. XCosCopySourceIfUnmodifiedSince string `header:"x-cos-copy-source-If-Unmodified-Since,omitempty" url:"-"`
  201. XCosCopySourceIfMatch string `header:"x-cos-copy-source-If-Match,omitempty" url:"-"`
  202. XCosCopySourceIfNoneMatch string `header:"x-cos-copy-source-If-None-Match,omitempty" url:"-"`
  203. }
  204. // CopyPartResult is the result CopyPart
  205. type CopyPartResult struct {
  206. XMLName xml.Name `xml:"CopyPartResult"`
  207. ETag string
  208. LastModified string
  209. }
  210. // CopyPart 请求实现在初始化以后的分块上传,支持的块的数量为1到10000,块的大小为1 MB 到5 GB。
  211. // 在每次请求Upload Part时候,需要携带partNumber和uploadID,partNumber为块的编号,支持乱序上传。
  212. // ObjectCopyPartOptions的XCosCopySource为必填参数,格式为<bucket-name>-<app-id>.cos.<region-id>.myqcloud.com/<object-key>
  213. // ObjectCopyPartOptions的XCosCopySourceRange指定源的Range,格式为bytes=<start>-<end>
  214. //
  215. // 当传入uploadID和partNumber都相同的时候,后传入的块将覆盖之前传入的块。当uploadID不存在时会返回404错误,NoSuchUpload.
  216. //
  217. // https://www.qcloud.com/document/product/436/7750
  218. func (s *ObjectService) CopyPart(ctx context.Context, name, uploadID string, partNumber int, sourceURL string, opt *ObjectCopyPartOptions) (*CopyPartResult, *Response, error) {
  219. if opt == nil {
  220. opt = &ObjectCopyPartOptions{}
  221. }
  222. opt.XCosCopySource = sourceURL
  223. u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID)
  224. var res CopyPartResult
  225. sendOpt := sendOptions{
  226. baseURL: s.client.BaseURL.BucketURL,
  227. uri: u,
  228. method: http.MethodPut,
  229. optHeader: opt,
  230. result: &res,
  231. }
  232. resp, err := s.client.send(ctx, &sendOpt)
  233. // 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.
  234. if err == nil && resp != nil && resp.StatusCode == 200 {
  235. if res.ETag == "" {
  236. return &res, resp, errors.New("response 200 OK, but body contains an error")
  237. }
  238. }
  239. return &res, resp, err
  240. }
  241. type ObjectListUploadsOptions struct {
  242. Delimiter string `url:"Delimiter,omitempty"`
  243. EncodingType string `url:"EncodingType,omitempty"`
  244. Prefix string `url:"Prefix"`
  245. MaxUploads int `url:"MaxUploads"`
  246. KeyMarker string `url:"KeyMarker"`
  247. UploadIdMarker string `url:"UploadIDMarker"`
  248. }
  249. type ObjectListUploadsResult struct {
  250. XMLName xml.Name `xml:"ListMultipartUploadsResult"`
  251. Bucket string `xml:"Bucket,omitempty"`
  252. EncodingType string `xml:"Encoding-Type,omitempty"`
  253. KeyMarker string `xml:"KeyMarker,omitempty"`
  254. UploadIdMarker string `xml:"UploadIdMarker,omitempty"`
  255. NextKeyMarker string `xml:"NextKeyMarker,omitempty"`
  256. NextUploadIdMarker string `xml:"NextUploadIdMarker,omitempty"`
  257. MaxUploads string `xml:"MaxUploads,omitempty"`
  258. IsTruncated bool `xml:"IsTruncated,omitempty"`
  259. Prefix string `xml:"Prefix,omitempty"`
  260. Delimiter string `xml:"Delimiter,omitempty"`
  261. Upload []ListUploadsResultUpload `xml:"Upload,omitempty"`
  262. CommonPrefixes []string `xml:"CommonPrefixes>Prefix,omitempty"`
  263. }
  264. type ListUploadsResultUpload struct {
  265. Key string `xml:"Key,omitempty"`
  266. UploadID string `xml:"UploadId,omitempty"`
  267. StorageClass string `xml:"StorageClass,omitempty"`
  268. Initiator *Initiator `xml:"Initiator,omitempty"`
  269. Owner *Owner `xml:"Owner,omitempty"`
  270. Initiated string `xml:"Initiated,omitempty"`
  271. }
  272. func (s *ObjectService) ListUploads(ctx context.Context, opt *ObjectListUploadsOptions) (*ObjectListUploadsResult, *Response, error) {
  273. var res ObjectListUploadsResult
  274. sendOpt := &sendOptions{
  275. baseURL: s.client.BaseURL.BucketURL,
  276. uri: "/?uploads",
  277. method: http.MethodGet,
  278. optQuery: opt,
  279. result: &res,
  280. }
  281. resp, err := s.client.send(ctx, sendOpt)
  282. return &res, resp, err
  283. }