diff --git a/bucket.go b/bucket.go index 67f991b..0a7de34 100644 --- a/bucket.go +++ b/bucket.go @@ -50,7 +50,19 @@ func (s *BucketService) Get(ctx context.Context, opt *BucketGetOptions) (*Bucket } // BucketPutOptions is same to the ACLHeaderOptions -type BucketPutOptions ACLHeaderOptions +type BucketPutOptions struct { + XCosACL string `header:"x-cos-acl,omitempty" url:"-" xml:"-"` + XCosGrantRead string `header:"x-cos-grant-read,omitempty" url:"-" xml:"-"` + XCosGrantWrite string `header:"x-cos-grant-write,omitempty" url:"-" xml:"-"` + XCosGrantFullControl string `header:"x-cos-grant-full-control,omitempty" url:"-" xml:"-"` + XCosGrantReadACP string `header:"x-cos-grant-read-acp,omitempty" url:"-" xml:"-"` + XCosGrantWriteACP string `header:"x-cos-grant-write-acp,omitempty" url:"-" xml:"-"` + CreateBucketConfiguration *CreateBucketConfiguration `header:"-" url:"-" xml:"-"` +} +type CreateBucketConfiguration struct { + XMLName xml.Name `xml:"CreateBucketConfiguration"` + BucketAZConfig string `xml:"BucketAZConfig,omitempty"` +} // Put Bucket请求可以在指定账号下创建一个Bucket。 // @@ -62,6 +74,9 @@ func (s *BucketService) Put(ctx context.Context, opt *BucketPutOptions) (*Respon method: http.MethodPut, optHeader: opt, } + if opt != nil && opt.CreateBucketConfiguration != nil { + sendOpt.body = opt.CreateBucketConfiguration + } resp, err := s.client.send(ctx, &sendOpt) return resp, err } diff --git a/object.go b/object.go index a7cbe4a..9d75d99 100644 --- a/object.go +++ b/object.go @@ -1239,7 +1239,10 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri if opt.CheckPoint { cpfd.Truncate(0) cpfd.Seek(0, os.SEEK_SET) - resumableInfo.DownloadedBlocks = append(resumableInfo.DownloadedBlocks, DownloadedBlock{From: chunks[i].OffSet, To: chunks[i].OffSet + chunks[i].Size - 1}) + resumableInfo.DownloadedBlocks = append(resumableInfo.DownloadedBlocks, DownloadedBlock{ + From: chunks[res.PartNumber-1].OffSet, + To: chunks[res.PartNumber-1].OffSet + chunks[res.PartNumber-1].Size - 1, + }) json.NewEncoder(cpfd).Encode(resumableInfo) } } @@ -1250,7 +1253,7 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri if err != nil { return nil, err } - // 下载成功,删除checkpoint文件 + // 下载成功,删除checkpoint文件 if opt.CheckPoint { os.Remove(cpfile) } diff --git a/object_test.go b/object_test.go index 45fc28a..8c7b703 100644 --- a/object_test.go +++ b/object_test.go @@ -564,6 +564,7 @@ func TestObjectService_Upload2(t *testing.T) { } } +/* func TestObjectService_Download(t *testing.T) { setup() defer teardown() @@ -636,7 +637,97 @@ func TestObjectService_Download(t *testing.T) { t.Fatalf("Object.Upload returned error: %v", err) } } +*/ +func TestObjectService_DownloadWithCheckPoint(t *testing.T) { + setup() + defer teardown() + + filePath := "rsp.file" + time.Now().Format(time.RFC3339) + newfile, err := os.Create(filePath) + if err != nil { + t.Fatalf("create tmp file failed") + } + defer os.Remove(filePath) + // 源文件内容 + totalBytes := int64(1024*1024*9 + 123) + partSize := 1024 * 1024 + b := make([]byte, totalBytes) + _, err = rand.Read(b) + newfile.Write(b) + newfile.Close() + tb := crc64.MakeTable(crc64.ECMA) + localcrc := strconv.FormatUint(crc64.Update(0, tb, b), 10) + oddok := false + var oddcount, evencount int + mux.HandleFunc("/test.go.download", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodHead { + w.Header().Add("Content-Length", strconv.FormatInt(totalBytes, 10)) + w.Header().Add("x-cos-hash-crc64ecma", localcrc) + return + } + strRange := r.Header.Get("Range") + slice1 := strings.Split(strRange, "=") + slice2 := strings.Split(slice1[1], "-") + start, _ := strconv.ParseInt(slice2[0], 10, 64) + end, _ := strconv.ParseInt(slice2[1], 10, 64) + if (start/int64(partSize))%2 == 1 { + if oddok { + io.Copy(w, bytes.NewBuffer(b[start:end+1])) + } else { + // 数据校验失败, Download不会做重试 + io.Copy(w, bytes.NewBuffer(b[start:end])) + } + oddcount++ + } else { + io.Copy(w, bytes.NewBuffer(b[start:end+1])) + evencount++ + } + }) + + opt := &MultiDownloadOptions{ + ThreadPoolSize: 3, + PartSize: 1, + CheckPoint: true, + } + downPath := "down.file" + time.Now().Format(time.RFC3339) + defer os.Remove(downPath) + _, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt) + if err == nil { + // 偶数块下载完成,奇数块下载失败 + t.Fatalf("Object.Download returned error: %v", err) + } + fd, err := os.Open(downPath) + if err != nil { + t.Fatalf("Object Download Open File Failed:%v", err) + } + offset := 0 + for i := 0; i < 10; i++ { + bs, _ := ioutil.ReadAll(io.LimitReader(fd, int64(partSize))) + offset += len(bs) + if i%2 == 1 { + bs[len(bs)-1] = b[offset-1] + } + if bytes.Compare(bs, b[i*partSize:offset]) != 0 { + t.Fatalf("Compare Error, index:%v, len:%v, offset:%v", i, len(bs), offset) + } + } + fd.Close() + + if oddcount != 5 || evencount != 5 { + t.Fatalf("Object.Download failed, odd:%v, even:%v", oddcount, evencount) + } + // 设置奇数块OK + oddok = true + _, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt) + if err != nil { + // 下载成功 + t.Fatalf("Object.Download returned error: %v", err) + } + if oddcount != 10 || evencount != 5 { + t.Fatalf("Object.Download failed, odd:%v, even:%v", oddcount, evencount) + } +} func TestObjectService_GetTagging(t *testing.T) { setup() defer teardown() @@ -666,7 +757,7 @@ func TestObjectService_GetTagging(t *testing.T) { t.Fatalf("Object.GetTagging returned error %v", err) } - want := &ObjectGetTaggingResult{ + want := &ObjectGetTaggingResult{ XMLName: xml.Name{Local: "Tagging"}, TagSet: []ObjectTaggingTag{ {"test_k2", "test_v2"}, @@ -735,7 +826,7 @@ func TestObjectService_DeleteTagging(t *testing.T) { w.WriteHeader(http.StatusNoContent) }) - _, err := client.Object.DeleteTagging(context.Background(), "test") + _, err := client.Object.DeleteTagging(context.Background(), "test") if err != nil { t.Fatalf("Object.DeleteTagging returned error: %v", err) }