yubin.byb 9 年 前
コミット
7ebf86924c
13 ファイル変更1035 行追加31 行削除
  1. 1 1
      README.md
  2. 13 2
      oss/bucket.go
  3. 2 2
      oss/bucket_test.go
  4. 20 0
      oss/client.go
  5. 41 1
      oss/client_test.go
  6. 1 1
      oss/const.go
  7. 14 4
      oss/download.go
  8. 415 0
      oss/multicopy.go
  9. 481 0
      oss/multicopy_test.go
  10. 3 3
      oss/multipart.go
  11. 12 12
      oss/multipart_test.go
  12. 19 1
      oss/type.go
  13. 13 4
      oss/upload.go

+ 1 - 1
README.md

@@ -6,7 +6,7 @@
 > - 使用此SDK,用户可以方便地在任何应用、任何时间、任何地点上传,下载和管理数据。
 
 ## 版本
-> - 当前版本:0.1.3
+> - 当前版本:0.2.0
 
 ## 运行环境
 > - 推荐使用Go 1.4及以上。

+ 13 - 2
oss/bucket.go

@@ -140,8 +140,9 @@ func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...O
 }
 
 //
-// CopyObjectToBucket bucket间拷贝object。
+// CopyObjectTo bucket间拷贝object。
 //
+// srcBucketName  Copy的源Bucket。
 // srcObjectKey   Copy的源对象。
 // destBucket     Copy的目标Bucket。
 // destObjectKey  Copy的目标Object。
@@ -149,7 +150,17 @@ func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...O
 //
 // error  操作无错误为nil,非nil为错误信息。
 //
-func (bucket Bucket) CopyObjectToBucket(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) {
+func (bucket Bucket) CopyObjectTo(srcBucketName, srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) {
+	var out CopyObjectResult
+	srcBucket, err := bucket.Client.Bucket(srcBucketName)
+	if err != nil {
+		return out, err
+	}
+
+	return srcBucket.copyObjectTo(srcObjectKey, destBucketName, destObjectKey, options...);
+}
+
+func (bucket Bucket) copyObjectTo(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) {
 	var out CopyObjectResult
 	options = append(options, CopySource(bucket.BucketName, srcObjectKey))
 	headers := make(map[string]string)

+ 2 - 2
oss/bucket_test.go

@@ -1135,11 +1135,11 @@ func (s *OssBucketSuite) TestCopyObjectToBucket(c *C) {
 	c.Assert(err, IsNil)
 
 	// copy
-	_, err = s.bucket.CopyObjectToBucket(objectName, descBucket, objectNameDest)
+	_, err = s.bucket.CopyObjectTo(bucketName, objectName, descBucket, objectNameDest)
 	c.Assert(err, IsNil)
 
 	// check
-	body, err := s.bucket.GetObject(objectName)
+	body, err := descBuck.GetObject(objectNameDest)
 	c.Assert(err, IsNil)
 	str, err := readBody(body)
 	c.Assert(err, IsNil)

+ 20 - 0
oss/client.go

@@ -598,6 +598,26 @@ func (client Client) GetBucketCORS(bucketName string) (GetBucketCORSResult, erro
 	return out, err
 }
 
+//
+// GetBucketInfo 获得Bucket的日志设置。
+//
+// bucketName  需要删除访问日志的Bucket。
+// GetBucketInfoResult  操作成功的返回值,error为nil时该返回值有效。
+//
+// error 操作无错误为nil,非nil为错误信息。
+//
+func (client Client) GetBucketInfo(bucketName string) (GetBucketInfoResult, error) {
+	var out GetBucketInfoResult
+	resp, err := client.do("GET", bucketName, "bucketInfo", "bucketInfo", nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.body.Close()
+
+	err = xmlUnmarshal(resp.body, &out)
+	return out, err
+}
+
 //
 // UseCname 设置是否使用CNAME,默认不使用。
 //

+ 41 - 1
oss/client_test.go

@@ -8,6 +8,7 @@ import (
 	"log"
 	"os"
 	"testing"
+	"strings"
 	"time"
 
 	. "gopkg.in/check.v1"
@@ -1092,6 +1093,45 @@ func (s *OssClientSuite) TestSetBucketCORSNegative(c *C) {
 	c.Assert(err, IsNil)
 }
 
+// TestGetBucketInfo
+func (s *OssClientSuite) TestGetBucketInfo(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tgbi"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.CreateBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+
+	res, err := client.GetBucketInfo(bucketNameTest)
+	c.Assert(err, IsNil)
+	c.Assert(res.BucketInfo.Name, Equals, bucketNameTest)
+	c.Assert(strings.HasPrefix(res.BucketInfo.Location, "oss-cn-"), Equals, true)
+	c.Assert(res.BucketInfo.ACL, Equals, "private")
+	c.Assert(strings.HasSuffix(res.BucketInfo.ExtranetEndpoint, ".com"), Equals, true)
+	c.Assert(strings.HasSuffix(res.BucketInfo.IntranetEndpoint, ".com"), Equals, true)
+	c.Assert(res.BucketInfo.CreationDate, NotNil)
+	
+	err = client.DeleteBucket(bucketNameTest)
+	c.Assert(err, IsNil)
+}
+
+// TestGetBucketInfoNegative
+func (s *OssClientSuite) TestGetBucketInfoNegative(c *C) {
+	var bucketNameTest = bucketNamePrefix + "tgbig"
+
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	// not exist
+	_, err = client.GetBucketInfo(bucketNameTest)
+	c.Assert(err, NotNil)
+
+	// not exist
+	_, err = client.GetBucketInfo("InvalidBucketName_")
+	c.Assert(err, NotNil)
+}
+
 // TestEndpointFormat
 func (s *OssClientSuite) TestEndpointFormat(c *C) {
 	var bucketNameTest = bucketNamePrefix + "tef"
@@ -1143,7 +1183,7 @@ func (s *OssClientSuite) _TestCname(c *C) {
 	c.Assert(res.ACL, Equals, string(ACLPrivate))
 }
 
-// TestCname
+// TestCnameNegative
 func (s *OssClientSuite) _TestCnameNegative(c *C) {
 	var bucketNameTest = "<my-bucket-cname>"
 

+ 1 - 1
oss/const.go

@@ -75,5 +75,5 @@ const (
 	MaxPartSize = 5 * 1024 * 1024 * 1024 // 文件片最大值,5GB
 	MinPartSize = 100 * 1024             // 文件片最小值,100KB
 
-	Version = "0.1.1" // Go sdk版本
+	Version = "0.2.0" // Go sdk版本
 )

+ 14 - 4
oss/download.go

@@ -13,7 +13,7 @@ import (
 )
 
 //
-// DownloadFile 分块下载文件,适合加大Object
+// DownloadFile 分片下载文件
 //
 // objectKey  object key。
 // filePath   本地文件。objectKey下载到文件。
@@ -62,7 +62,7 @@ func defaultDownloadPartHook(part downloadPart) error {
 }
 
 // 工作协程
-func downloadWorker(id int, arg downloadWorkerArg, jobs <-chan downloadPart, results chan<- downloadPart, failed chan<- error) {
+func downloadWorker(id int, arg downloadWorkerArg, jobs <-chan downloadPart, results chan<- downloadPart, failed chan<- error, die <- chan bool) {
 	for part := range jobs {
 		if err := arg.hook(part); err != nil {
 			failed <- err
@@ -78,6 +78,12 @@ func downloadWorker(id int, arg downloadWorkerArg, jobs <-chan downloadPart, res
 		}
 		defer rd.Close()
 
+		select {
+			case <-die:
+				return
+			default:
+		}
+
 		fd, err := os.OpenFile(arg.filePath, os.O_WRONLY, 0660)
 		if err != nil {
 			failed <- err
@@ -159,11 +165,12 @@ func (bucket Bucket) downloadFile(objectKey, filePath string, partSize int64, op
 	jobs := make(chan downloadPart, len(parts))
 	results := make(chan downloadPart, len(parts))
 	failed := make(chan error)
+	die := make(chan bool)
 
 	// 启动工作协程
 	arg := downloadWorkerArg{&bucket, objectKey, filePath, options, downloadPartHooker}
 	for w := 1; w <= routines; w++ {
-		go downloadWorker(w, arg, jobs, results, failed)
+		go downloadWorker(w, arg, jobs, results, failed, die)
 	}
 
 	// 并发上传分片
@@ -178,6 +185,7 @@ func (bucket Bucket) downloadFile(objectKey, filePath string, partSize int64, op
 			completed++
 			ps[part.Index] = part
 		case err := <-failed:
+			close(die)
 			return err
 		}
 
@@ -360,11 +368,12 @@ func (bucket Bucket) downloadFileWithCp(objectKey, filePath string, partSize int
 	jobs := make(chan downloadPart, len(parts))
 	results := make(chan downloadPart, len(parts))
 	failed := make(chan error)
+	die := make(chan bool)
 
 	// 启动工作协程
 	arg := downloadWorkerArg{&bucket, objectKey, filePath, options, downloadPartHooker}
 	for w := 1; w <= routines; w++ {
-		go downloadWorker(w, arg, jobs, results, failed)
+		go downloadWorker(w, arg, jobs, results, failed, die)
 	}
 
 	// 并发下载分片
@@ -379,6 +388,7 @@ func (bucket Bucket) downloadFileWithCp(objectKey, filePath string, partSize int
 			dcp.PartStat[part.Index] = true
 			dcp.dump(cpFilePath)
 		case err := <-failed:
+			close(die)
 			return err
 		}
 

+ 415 - 0
oss/multicopy.go

@@ -0,0 +1,415 @@
+package oss
+
+import (
+	"crypto/md5"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+	"sync"
+)
+
+//
+// CopyFile 分片复制文件
+//
+// objectKey  object key。
+// filePath   本地文件。objectKey下载到文件。
+// partSize   本次上传文件片的大小,字节数。比如100 * 1024为每片100KB。
+// options    Object的属性限制项。详见InitiateMultipartUpload。
+//
+// error 操作成功error为nil,非nil为错误信息。
+//
+func (bucket Bucket) CopyFile(srcBucketName, srcObjectKey, destBucketName, destObjectKey string, 
+	partSize int64, options ...Option) error {
+	if partSize < MinPartSize || partSize > MaxPartSize {
+		return errors.New("oss: part size invalid range (1024KB, 5GB]")
+	}
+
+	cpConf, err := getCpConfig(options, filepath.Base(destObjectKey))
+	if err != nil {
+		return err
+	}
+
+	routines := getRoutines(options)
+
+	if cpConf.IsEnable {
+		return bucket.copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, destObjectKey, 
+			partSize, options, cpConf.FilePath, routines)
+	}
+
+	return bucket.copyFile(srcBucketName, srcObjectKey, destBucketName, destObjectKey, 
+		partSize, options, routines)
+}
+
+// ----- 并发无断点的下载  -----
+
+// 工作协程参数
+type copyWorkerArg struct {
+	bucket         *Bucket
+	imur           InitiateMultipartUploadResult
+	srcBucketName  string  
+	srcObjectKey   string  
+	options        []Option
+	hook           copyPartHook
+}
+
+// Hook用于测试
+type copyPartHook func(part copyPart) error
+
+var copyPartHooker copyPartHook = defaultCopyPartHook
+
+func defaultCopyPartHook(part copyPart) error {
+	return nil
+}
+
+// 工作协程
+func copyWorker(id int, arg copyWorkerArg, jobs <-chan copyPart, results chan<- UploadPart, failed chan<- error, die <- chan bool) {	
+	for chunk := range jobs {
+		if err := arg.hook(chunk); err != nil {
+			failed <- err
+			break
+		}
+		chunkSize := chunk.End - chunk.Start + 1
+		part, err := arg.bucket.UploadPartCopy(arg.imur, arg.srcBucketName, arg.srcObjectKey, 
+			chunk.Start, chunkSize, chunk.Number, arg.options...)
+		if err != nil {
+			failed <- err
+			break
+		}
+		select {
+        	case <-die:
+            	return
+			default:
+        }
+		results <- part
+	}
+}
+
+// 调度协程
+func copyScheduler(jobs chan copyPart, parts []copyPart) {
+	for _, part := range parts {
+		jobs <- part
+	}
+	close(jobs)
+}
+
+// 分片
+type copyPart struct {
+	Number int   // 片序号[1, 10000]
+	Start int64 // 片起始位置
+	End   int64 // 片结束位置
+}
+
+// 文件分片
+func getCopyPart(bucket *Bucket, objectKey string, partSize int64) ([]copyPart, error) {
+	meta, err := bucket.GetObjectDetailedMeta(objectKey)
+	if err != nil {
+		return nil, err
+	}
+
+	parts := []copyPart{}
+	objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
+	if err != nil {
+		return nil, err
+	}
+
+	part := copyPart{}
+	i := 0
+	for offset := int64(0); offset < objectSize; offset += partSize {
+		part.Number = i + 1
+		part.Start = offset
+		part.End = GetPartEnd(offset, objectSize, partSize)
+		parts = append(parts, part)
+		i++
+	}
+	return parts, nil
+}
+
+// 并发无断点续传的下载
+func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destObjectKey string, 
+	partSize int64, options []Option, routines int) error {
+	descBucket, err := bucket.Client.Bucket(destBucketName)
+	srcBucket, err := bucket.Client.Bucket(srcBucketName)
+	
+	// 分割文件
+	parts, err := getCopyPart(srcBucket, srcObjectKey, partSize)
+	if err != nil {
+		return err
+	}
+	
+	// 初始化上传任务
+	imur, err := descBucket.InitiateMultipartUpload(destObjectKey, options...)
+	if err != nil {
+		return err
+	}
+
+	jobs := make(chan copyPart, len(parts))
+	results := make(chan UploadPart, len(parts))
+	failed := make(chan error)
+	die := make(chan bool)
+
+	// 启动工作协程
+	arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, options, copyPartHooker}
+	for w := 1; w <= routines; w++ {
+		go copyWorker(w, arg, jobs, results, failed, die)
+	}
+
+	// 并发上传分片
+	go copyScheduler(jobs, parts)
+
+	// 等待分片下载完成
+	completed := 0
+	ups := make([]UploadPart, len(parts))
+	for completed < len(parts) {
+		select {
+		case part := <-results:
+			completed++
+			ups[part.PartNumber-1] = part
+		case err := <-failed:
+			close(die)
+			descBucket.AbortMultipartUpload(imur)
+			return err
+		}
+
+		if completed >= len(parts) {
+			break
+		}
+	}
+
+	// 提交任务
+	_, err = descBucket.CompleteMultipartUpload(imur, ups)
+	if err != nil {
+		bucket.AbortMultipartUpload(imur)
+		return err
+	}
+	return nil
+}
+
+// ----- 并发有断点的下载  -----
+
+const copyCpMagic = "84F1F18C-FF1D-403B-A1D8-9DEB5F65910A"
+
+type copyCheckpoint struct {
+	Magic    string          // magic
+	MD5      string          // cp内容的MD5
+	SrcBucketName  string    // 源Bucket
+	SrcObjectKey   string    // 源Object
+	DestBucketName string    // 目标Bucket
+	DestObjectKey  string    // 目标Bucket
+	CopyID         string    // copy id
+	ObjStat   objectStat     // 文件状态
+	Parts     []copyPart     // 全部分片
+	CopyParts []UploadPart   // 分片上传成功后的返回值
+	PartStat  []bool         // 分片下载是否完成
+	mutex     sync.Mutex     // Lock
+}
+
+// CP数据是否有效,CP有效且Object没有更新时有效
+func (cp copyCheckpoint) isValid(bucket *Bucket, objectKey string) (bool, error) {
+	// 比较CP的Magic及MD5
+	cpb := cp
+	cpb.MD5 = ""
+	js, _ := json.Marshal(cpb)
+	sum := md5.Sum(js)
+	b64 := base64.StdEncoding.EncodeToString(sum[:])
+
+	if cp.Magic != downloadCpMagic || b64 != cp.MD5 {
+		return false, nil
+	}
+
+	// 确认object没有更新
+	meta, err := bucket.GetObjectDetailedMeta(objectKey)
+	if err != nil {
+		return false, err
+	}
+
+	objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
+	if err != nil {
+		return false, err
+	}
+
+	// 比较Object的大小/最后修改时间/etag
+	if cp.ObjStat.Size != objectSize ||
+		cp.ObjStat.LastModified != meta.Get(HTTPHeaderLastModified) ||
+		cp.ObjStat.Etag != meta.Get(HTTPHeaderEtag) {
+		return false, nil
+	}
+
+	return true, nil
+}
+
+// 从文件中load
+func (cp *copyCheckpoint) load(filePath string) error {
+	contents, err := ioutil.ReadFile(filePath)
+	if err != nil {
+		return err
+	}
+
+	err = json.Unmarshal(contents, cp)
+	return err
+}
+
+// 更新分片状态
+func (cp *copyCheckpoint) update(part UploadPart) {
+	cp.CopyParts[part.PartNumber - 1] = part
+	cp.PartStat[part.PartNumber - 1] = true	
+}
+
+// dump到文件
+func (cp *copyCheckpoint) dump(filePath string) error {
+	bcp := *cp
+
+	// 计算MD5
+	bcp.MD5 = ""
+	js, err := json.Marshal(bcp)
+	if err != nil {
+		return err
+	}
+	sum := md5.Sum(js)
+	b64 := base64.StdEncoding.EncodeToString(sum[:])
+	bcp.MD5 = b64
+
+	// 序列化
+	js, err = json.Marshal(bcp)
+	if err != nil {
+		return err
+	}
+
+	// dump
+	return ioutil.WriteFile(filePath, js, 0644)
+}
+
+// 未完成的分片
+func (cp copyCheckpoint) todoParts() []copyPart {
+	dps := []copyPart{}
+	for i, ps := range cp.PartStat {
+		if !ps {
+			dps = append(dps, cp.Parts[i])
+		}
+	}
+	return dps
+}
+
+// 初始化下载任务
+func (cp *copyCheckpoint) prepare(srcBucket *Bucket, srcObjectKey string, destBucket *Bucket, destObjectKey string, 
+	partSize int64, options []Option) error {
+	// cp
+	cp.Magic = copyCpMagic
+	cp.SrcBucketName = srcBucket.BucketName
+	cp.SrcObjectKey = srcObjectKey
+	cp.DestBucketName = destBucket.BucketName
+	cp.DestObjectKey = destObjectKey
+
+	// object
+	meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey)
+	if err != nil {
+		return err
+	}
+
+	objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0)
+	if err != nil {
+		return err
+	}
+
+	cp.ObjStat.Size = objectSize
+	cp.ObjStat.LastModified = meta.Get(HTTPHeaderLastModified)
+	cp.ObjStat.Etag = meta.Get(HTTPHeaderEtag)
+
+	// parts
+	cp.Parts, err = getCopyPart(srcBucket, srcObjectKey, partSize)
+	if err != nil {
+		return err
+	}
+	cp.PartStat = make([]bool, len(cp.Parts))
+	for i := range cp.PartStat {
+		cp.PartStat[i] = false
+	}
+	cp.CopyParts = make([]UploadPart, len(cp.Parts))
+	
+	// init copy
+	imur, err := destBucket.InitiateMultipartUpload(destObjectKey, options...)
+	if err != nil {
+		return err
+	}
+	cp.CopyID = imur.UploadID
+
+	return nil
+}
+
+func (cp *copyCheckpoint) complete(bucket *Bucket, parts []UploadPart, cpFilePath string) error {
+	imur := InitiateMultipartUploadResult{Bucket: cp.DestBucketName,
+		Key: cp.DestObjectKey, UploadID: cp.CopyID}
+	_, err := bucket.CompleteMultipartUpload(imur, parts)
+	if err != nil {
+		return err
+	}
+	os.Remove(cpFilePath)
+	return err
+}
+
+// 并发带断点的下载
+func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, destObjectKey string, 
+	partSize int64, options []Option, cpFilePath string, routines int) error {
+	descBucket, err := bucket.Client.Bucket(destBucketName)
+	srcBucket, err := bucket.Client.Bucket(srcBucketName)
+	
+	// LOAD CP数据
+	ccp := copyCheckpoint{}
+	err = ccp.load(cpFilePath)
+	if err != nil {
+		os.Remove(cpFilePath)
+	}
+
+	// LOAD出错或数据无效重新初始化下载
+	valid, err := ccp.isValid(srcBucket, srcObjectKey)
+	if err != nil || !valid {
+		if err = ccp.prepare(srcBucket, srcObjectKey, descBucket, destObjectKey, partSize, options); err != nil {
+			return err
+		}
+		os.Remove(cpFilePath)
+	}
+
+	// 未完成的分片
+	parts := ccp.todoParts()
+	imur := InitiateMultipartUploadResult{
+		Bucket:   destBucketName,
+		Key:      destObjectKey,
+		UploadID: ccp.CopyID}
+	
+	jobs := make(chan copyPart, len(parts))
+	results := make(chan UploadPart, len(parts))
+	failed := make(chan error)
+	die := make(chan bool)
+
+	// 启动工作协程
+	arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, options, copyPartHooker}
+	for w := 1; w <= routines; w++ {
+		go copyWorker(w, arg, jobs, results, failed, die)
+	}
+
+	// 并发下载分片
+	go copyScheduler(jobs, parts)
+
+	// 等待分片下载完成
+	completed := 0
+	for completed < len(parts) {
+		select {
+		case part := <-results:
+			completed++
+			ccp.update(part);
+			ccp.dump(cpFilePath)
+		case err := <-failed:
+			close(die)
+			return err
+		}
+
+		if completed >= len(parts) {
+			break
+		}
+	}
+	
+	return ccp.complete(descBucket, ccp.CopyParts, cpFilePath)
+}

+ 481 - 0
oss/multicopy_test.go

@@ -0,0 +1,481 @@
+package oss
+
+import (
+	"fmt"
+	"os"
+	"time"
+
+	. "gopkg.in/check.v1"
+)
+
+type OssCopySuite struct {
+	client *Client
+	bucket *Bucket
+}
+
+var _ = Suite(&OssCopySuite{})
+
+// Run once when the suite starts running
+func (s *OssCopySuite) SetUpSuite(c *C) {
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+	s.client = client
+
+	s.client.CreateBucket(bucketName)
+
+	bucket, err := s.client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+	s.bucket = bucket
+
+	testLogger.Println("test copy started")
+}
+
+// Run before each test or benchmark starts running
+func (s *OssCopySuite) TearDownSuite(c *C) {
+	// Delete Part
+	lmur, err := s.bucket.ListMultipartUploads()
+	c.Assert(err, IsNil)
+
+	for _, upload := range lmur.Uploads {
+		var imur = InitiateMultipartUploadResult{Bucket: bucketName,
+			Key: upload.Key, UploadID: upload.UploadID}
+		err = s.bucket.AbortMultipartUpload(imur)
+		c.Assert(err, IsNil)
+	}
+
+	//Delete Objects
+	lor, err := s.bucket.ListObjects()
+	c.Assert(err, IsNil)
+
+	for _, object := range lor.Objects {
+		err = s.bucket.DeleteObject(object.Key)
+		c.Assert(err, IsNil)
+	}
+
+	// delete bucket
+	err = s.client.DeleteBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	testLogger.Println("test copy completed")
+}
+
+// Run after each test or benchmark runs
+func (s *OssCopySuite) SetUpTest(c *C) {
+	err := removeTempFiles("../oss", ".jpg")
+	c.Assert(err, IsNil)
+}
+
+// Run once after all tests or benchmarks have finished running
+func (s *OssCopySuite) TearDownTest(c *C) {
+	err := removeTempFiles("../oss", ".jpg")
+	c.Assert(err, IsNil)
+}
+
+// TestCopyRoutineWithoutRecovery 多线程无断点恢复的复制
+func (s *OssCopySuite) _TestCopyRoutineWithoutRecovery(c *C) {
+	srcObjectName := objectNamePrefix + "tcrwr"
+	destObjectName := srcObjectName + "-copy" 
+	fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+	newFile := "copy-new-file.jpg"
+
+	// 上传源文件
+	err := s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3))
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// 不指定Routines,默认单线程
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 100*1024)
+	c.Assert(err, IsNil)
+	
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err := compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// 指定线程数1
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 100*1024, Routines(1))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// 指定线程数3,小于分片数5
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 100*1024, Routines(3))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// 指定线程数5,等于分片数
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 100*1024, Routines(5))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// 指定线程数10,大于分片数5
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 100*1024, Routines(10))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// 线程值无效自动变成1
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 100*1024, Routines(-1))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// option
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 100*1024, Routines(3), Meta("myprop", "mypropval"))
+
+	meta, err := s.bucket.GetObjectDetailedMeta(destObjectName)
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	err = s.bucket.DeleteObject(srcObjectName)
+	c.Assert(err, IsNil)
+}
+
+// CopyErrorHooker CopyPart请求Hook
+func CopyErrorHooker(part copyPart) error {
+	if part.Number == 5 {
+		time.Sleep(time.Second)
+		return fmt.Errorf("ErrorHooker")
+	}
+	return nil
+}
+
+
+// TestCopyRoutineWithoutRecoveryNegative 多线程无断点恢复的复制
+func (s *OssCopySuite) _TestCopyRoutineWithoutRecoveryNegative(c *C) {
+	srcObjectName := objectNamePrefix + "tcrwrn"
+	destObjectName := srcObjectName + "-copy" 
+	fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+
+	// 上传源文件
+	err := s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3))
+	c.Assert(err, IsNil)
+
+	copyPartHooker = CopyErrorHooker
+	// worker线程错误
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 100*1024, Routines(2))
+
+	c.Assert(err, NotNil)
+	c.Assert(err.Error(), Equals, "ErrorHooker")
+	copyPartHooker = defaultCopyPartHook
+
+	// 目标Bucket不存在
+	err = s.bucket.CopyFile(bucketName, srcObjectName, "NotExist", destObjectName, 100*1024, Routines(2))
+	c.Assert(err, NotNil)
+
+	// 源Bucket不存在
+	err = s.bucket.CopyFile("NotExist", srcObjectName, bucketName, destObjectName, 100*1024, Routines(2))
+	c.Assert(err, NotNil)
+
+	// 源Object不存在
+	err = s.bucket.CopyFile(bucketName, "NotExist", bucketName, destObjectName, 100*1024, Routines(2))
+
+	// 指定的分片大小无效
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024, Routines(2))
+	c.Assert(err, NotNil)
+
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024*1024*1024*100, Routines(2))
+	c.Assert(err, NotNil)
+
+	// 删除源文件
+	err = s.bucket.DeleteObject(srcObjectName)
+	c.Assert(err, IsNil)
+}
+
+// TestCopyRoutineWithRecovery 多线程且有断点恢复的复制
+func (s *OssCopySuite) _TestCopyRoutineWithRecovery(c *C) {
+	srcObjectName := objectNamePrefix + "tcrtr"
+	destObjectName := srcObjectName + "-copy" 
+	fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+	newFile := "copy-new-file.jpg"
+
+	// 上传源文件
+	err := s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3))
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// Routines默认值,CP开启默认路径是destObjectName+.cp
+	// 第一次上传,上传4片
+	copyPartHooker = CopyErrorHooker
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024*100, Checkpoint(true, ""))
+	c.Assert(err, NotNil)
+	c.Assert(err.Error(), Equals, "ErrorHooker")
+	copyPartHooker = defaultCopyPartHook
+
+	// check cp
+	ccp := copyCheckpoint{}
+	err = ccp.load(destObjectName + ".cp")
+	c.Assert(err, IsNil)
+	c.Assert(ccp.Magic, Equals, copyCpMagic)
+	c.Assert(len(ccp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ=="))
+	c.Assert(ccp.SrcBucketName, Equals, bucketName)
+	c.Assert(ccp.SrcObjectKey, Equals, srcObjectName)
+	c.Assert(ccp.DestBucketName, Equals, bucketName)
+	c.Assert(ccp.DestObjectKey, Equals, destObjectName)
+	c.Assert(len(ccp.CopyID), Equals, len("3F79722737D1469980DACEDCA325BB52"))
+	c.Assert(ccp.ObjStat.Size, Equals, int64(482048))
+	c.Assert(len(ccp.ObjStat.LastModified), Equals, len("2015-12-17 18:43:03 +0800 CST"))
+	c.Assert(ccp.ObjStat.Etag, Equals, "\"2351E662233817A7AE974D8C5B0876DD-5\"")
+	c.Assert(len(ccp.Parts), Equals, 5)
+	c.Assert(len(ccp.todoParts()), Equals, 1)
+	c.Assert(ccp.PartStat[4], Equals, false)
+
+	// 第二次上传,完成剩余的一片
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024*100, Checkpoint(true, ""))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err := compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	err = ccp.load(fileName + ".cp")
+	c.Assert(err, NotNil)
+
+	// Routines指定,CP指定
+	copyPartHooker = CopyErrorHooker
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024*100, Routines(2), Checkpoint(true, srcObjectName+".cp"))
+	c.Assert(err, NotNil)
+	c.Assert(err.Error(), Equals, "ErrorHooker")
+	copyPartHooker = defaultCopyPartHook
+
+	// check cp
+	ccp = copyCheckpoint{}
+	err = ccp.load(srcObjectName + ".cp")
+	c.Assert(err, IsNil)
+	c.Assert(ccp.Magic, Equals, copyCpMagic)
+	c.Assert(len(ccp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ=="))
+	c.Assert(ccp.SrcBucketName, Equals, bucketName)
+	c.Assert(ccp.SrcObjectKey, Equals, srcObjectName)
+	c.Assert(ccp.DestBucketName, Equals, bucketName)
+	c.Assert(ccp.DestObjectKey, Equals, destObjectName)
+	c.Assert(len(ccp.CopyID), Equals, len("3F79722737D1469980DACEDCA325BB52"))
+	c.Assert(ccp.ObjStat.Size, Equals, int64(482048))
+	c.Assert(len(ccp.ObjStat.LastModified), Equals, len("2015-12-17 18:43:03 +0800 CST"))
+	c.Assert(ccp.ObjStat.Etag, Equals, "\"2351E662233817A7AE974D8C5B0876DD-5\"")
+	c.Assert(len(ccp.Parts), Equals, 5)
+	c.Assert(len(ccp.todoParts()), Equals, 1)
+	c.Assert(ccp.PartStat[4], Equals, false)
+
+	// 第二次上传,完成剩余的一片
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024*100, Routines(2), Checkpoint(true, srcObjectName+".cp"))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	err = ccp.load(srcObjectName + ".cp")
+	c.Assert(err, NotNil)
+
+	// 一次完成上传,中间没有错误
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024*100, Routines(3), Checkpoint(true, ""))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// 用多协程下载,中间没有错误
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024*100, Routines(10), Checkpoint(true, ""))
+	c.Assert(err, IsNil)
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// option
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024*100, Routines(5), Checkpoint(true, ""), Meta("myprop", "mypropval"))
+	c.Assert(err, IsNil)
+
+	meta, err := s.bucket.GetObjectDetailedMeta(destObjectName)
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval")
+
+	err = s.bucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = s.bucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// 删除源文件
+	err = s.bucket.DeleteObject(srcObjectName)
+	c.Assert(err, IsNil)
+}
+
+// TestCopyRoutineWithRecoveryNegative 多线程无断点恢复的复制
+func (s *OssCopySuite) _TestCopyRoutineWithRecoveryNegative(c *C) {
+	srcObjectName := objectNamePrefix + "tcrwrn"
+	destObjectName := srcObjectName + "-copy" 
+
+	// 目标Bucket不存在
+	err := s.bucket.CopyFile(bucketName, srcObjectName, "NotExist", destObjectName, 100*1024, Checkpoint(true, ""))
+	c.Assert(err, NotNil)
+
+	// 源Bucket不存在
+	err = s.bucket.CopyFile("NotExist", srcObjectName, bucketName, destObjectName, 100*1024, Checkpoint(true, ""))
+	c.Assert(err, NotNil)
+	c.Assert(err, NotNil)
+
+	// 源Object不存在
+	err = s.bucket.CopyFile(bucketName, "NotExist", bucketName, destObjectName, 100*1024, Routines(2), Checkpoint(true, ""))
+	c.Assert(err, NotNil)
+
+	// 指定的分片大小无效
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024,  Checkpoint(true, ""))
+	c.Assert(err, NotNil)
+
+	err = s.bucket.CopyFile(bucketName, srcObjectName, bucketName, destObjectName, 1024*1024*1024*100, Routines(2), Checkpoint(true, ""))
+	c.Assert(err, NotNil)
+}
+
+// TestCopyFileCrossBucket 跨Bucket直接的复制
+func (s *OssCopySuite) TestCopyFileCrossBucket(c *C) {
+	destBucketName := bucketName + "-desc"
+	srcObjectName := objectNamePrefix + "tcrtr"
+	destObjectName := srcObjectName + "-copy" 
+	fileName := "../sample/BingWallpaper-2015-11-07.jpg"
+	newFile := "copy-new-file.jpg"
+	
+	destBucket, err := s.client.Bucket(destBucketName)
+	c.Assert(err, IsNil)
+
+	// 创建目标Bucket
+	err = s.client.CreateBucket(destBucketName)
+	c.Assert(err, IsNil)
+
+	// 上传源文件
+	err = s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3))
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+	
+	// 复制文件
+	err = destBucket.CopyFile(bucketName, srcObjectName, destBucketName, destObjectName, 1024*100, Routines(5), Checkpoint(true, ""))
+	c.Assert(err, IsNil)
+
+	err = destBucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err := compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = destBucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// 带option的复制
+	err = destBucket.CopyFile(bucketName, srcObjectName, destBucketName, destObjectName, 1024*100, Routines(10), Checkpoint(true, "copy.cp"), Meta("myprop", "mypropval"))
+	c.Assert(err, IsNil)
+
+	err = destBucket.GetObjectToFile(destObjectName, newFile)
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = destBucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// 删除目标Bucket
+	err = s.client.DeleteBucket(destBucketName)
+	c.Assert(err, IsNil)
+}

+ 3 - 3
oss/multipart.go

@@ -119,12 +119,12 @@ func (bucket Bucket) UploadPartFromFile(imur InitiateMultipartUploadResult, file
 // ETag及上传数据的MD5。error为nil时有效。
 // error 操作成功error为nil,非nil为错误信息。
 //
-func (bucket Bucket) UploadPartCopy(imur InitiateMultipartUploadResult, copySrc string, startPosition,
-	partSize int64, partNumber int, options ...Option) (UploadPart, error) {
+func (bucket Bucket) UploadPartCopy(imur InitiateMultipartUploadResult, srcBucketName, srcObjectKey string,
+	startPosition, partSize int64, partNumber int, options ...Option) (UploadPart, error) {
 	var out UploadPartCopyResult
 	var part UploadPart
 
-	opts := []Option{CopySource(bucket.BucketName, copySrc),
+	opts := []Option{CopySource(srcBucketName, srcObjectKey),
 		CopySourceRange(startPosition, partSize)}
 	opts = append(opts, options...)
 	params := "partNumber=" + strconv.Itoa(partNumber) + "&uploadId=" + imur.UploadID

+ 12 - 12
oss/multipart_test.go

@@ -184,7 +184,7 @@ func (s *OssBucketMultipartSuite) TestUploadPartCopy(c *C) {
 	c.Assert(err, IsNil)
 	var parts []UploadPart
 	for _, chunk := range chunks {
-		part, err := s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		part, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
 		c.Assert(err, IsNil)
 		parts = append(parts, part)
 	}
@@ -235,7 +235,7 @@ func (s *OssBucketMultipartSuite) TestListUploadedParts(c *C) {
 	imurCopy, err := s.bucket.InitiateMultipartUpload(objectDesc)
 	var partsCopy []UploadPart
 	for _, chunk := range chunks {
-		part, err := s.bucket.UploadPartCopy(imurCopy, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		part, err := s.bucket.UploadPartCopy(imurCopy, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
 		c.Assert(err, IsNil)
 		partsCopy = append(partsCopy, part)
 	}
@@ -302,7 +302,7 @@ func (s *OssBucketMultipartSuite) TestAbortMultipartUpload(c *C) {
 	imurCopy, err := s.bucket.InitiateMultipartUpload(objectDesc)
 	var partsCopy []UploadPart
 	for _, chunk := range chunks {
-		part, err := s.bucket.UploadPartCopy(imurCopy, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		part, err := s.bucket.UploadPartCopy(imurCopy, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
 		c.Assert(err, IsNil)
 		partsCopy = append(partsCopy, part)
 	}
@@ -357,13 +357,13 @@ func (s *OssBucketMultipartSuite) TestUploadPartCopyWithConstraints(c *C) {
 	imur, err := s.bucket.InitiateMultipartUpload(objectDesc)
 	var parts []UploadPart
 	for _, chunk := range chunks {
-		_, err = s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+		_, err = s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
 			CopySourceIfModifiedSince(futureDate))
 		c.Assert(err, NotNil)
 	}
 
 	for _, chunk := range chunks {
-		_, err = s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+		_, err = s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
 			CopySourceIfUnmodifiedSince(futureDate))
 		c.Assert(err, IsNil)
 	}
@@ -373,13 +373,13 @@ func (s *OssBucketMultipartSuite) TestUploadPartCopyWithConstraints(c *C) {
 	testLogger.Println("GetObjectDetailedMeta:", meta)
 
 	for _, chunk := range chunks {
-		_, err = s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+		_, err = s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
 			CopySourceIfNoneMatch(meta.Get("Etag")))
 		c.Assert(err, NotNil)
 	}
 
 	for _, chunk := range chunks {
-		part, err := s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
+		part, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number),
 			CopySourceIfMatch(meta.Get("Etag")))
 		c.Assert(err, IsNil)
 		parts = append(parts, part)
@@ -449,12 +449,12 @@ func (s *OssBucketMultipartSuite) TestUploadPartCopyOutofOrder(c *C) {
 	imur, err := s.bucket.InitiateMultipartUpload(objectDesc)
 	var parts []UploadPart
 	for _, chunk := range chunks {
-		_, err := s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		_, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
 		c.Assert(err, IsNil)
 	}
 	//double copy
 	for _, chunk := range chunks {
-		part, err := s.bucket.UploadPartCopy(imur, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
+		part, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number))
 		c.Assert(err, IsNil)
 		parts = append(parts, part)
 	}
@@ -614,7 +614,7 @@ func (s *OssBucketMultipartSuite) TestMultipartNegative(c *C) {
 	_, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 1024, 1)
 	c.Assert(err, NotNil)
 
-	_, err = s.bucket.UploadPartCopy(imur, fileName, 0, 1024, 1)
+	_, err = s.bucket.UploadPartCopy(imur, bucketName, fileName, 0, 1024, 1)
 	c.Assert(err, NotNil)
 
 	err = s.bucket.AbortMultipartUpload(imur)
@@ -639,10 +639,10 @@ func (s *OssBucketMultipartSuite) TestMultipartNegative(c *C) {
 	_, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 102400, 10001)
 	c.Assert(err, NotNil)
 
-	_, err = s.bucket.UploadPartCopy(imur, fileName, 0, 1024, 1)
+	_, err = s.bucket.UploadPartCopy(imur, bucketName, fileName, 0, 1024, 1)
 	c.Assert(err, NotNil)
 
-	_, err = s.bucket.UploadPartCopy(imur, fileName, 0, 1024, 1000)
+	_, err = s.bucket.UploadPartCopy(imur, bucketName, fileName, 0, 1024, 1000)
 	c.Assert(err, NotNil)
 
 	err = s.bucket.AbortMultipartUpload(imur)

+ 19 - 1
oss/type.go

@@ -188,6 +188,24 @@ type CORSRule struct {
 // GetBucketCORSResult GetBucketCORS请求返回的结果
 type GetBucketCORSResult CORSXML
 
+// GetBucketInfoResult GetBucketInfo请求返回结果
+type GetBucketInfoResult struct {
+	XMLName        xml.Name `xml:"BucketInfo"`
+	BucketInfo     BucketInfo   `xml:"Bucket"`
+}
+
+// BucketInfo Bucket信息
+type BucketInfo struct {
+	XMLName          xml.Name  `xml:"Bucket"`
+	Name             string    `xml:"Name"`                    // Bucket名称
+	Location         string    `xml:"Location"`                // Bucket所在的数据中心
+	CreationDate     time.Time `xml:"CreationDate"`            // Bucket创建时间
+	ExtranetEndpoint string    `xml:"ExtranetEndpoint"`        // Bucket访问的外网域名 
+	IntranetEndpoint string    `xml:"IntranetEndpoint"`        // Bucket访问的内网域名
+	ACL              string    `xml:"AccessControlList>Grant"` // Bucket权限
+	Owner            Owner     `xml:"Owner"`                   // Bucket拥有者信息
+}
+
 // ListObjectsResult ListObjects请求返回结果
 type ListObjectsResult struct {
 	XMLName        xml.Name           `xml:"ListBucketResult"`
@@ -206,7 +224,7 @@ type ObjectProperties struct {
 	XMLName      xml.Name  `xml:"Contents"`
 	Key          string    `xml:"Key"`          // Object的Key
 	Type         string    `xml:"Type"`         // Object Type
-	Size         uint      `xml:"Size"`         // Object的长度字节数
+	Size         int64     `xml:"Size"`         // Object的长度字节数
 	ETag         string    `xml:"ETag"`         // 标示Object的内容
 	Owner        Owner     `xml:"Owner"`        // 保存Object拥有者信息的容器
 	LastModified time.Time `xml:"LastModified"` // Object最后修改时间

+ 13 - 4
oss/upload.go

@@ -12,7 +12,7 @@ import (
 )
 
 //
-// UploadFile 分块上传文件,适合加大文件
+// UploadFile 分片上传文件
 //
 // objectKey  object名称。
 // filePath   本地文件。需要上传的文件。
@@ -102,7 +102,7 @@ type workerArg struct {
 }
 
 // 工作协程
-func worker(id int, arg workerArg, jobs <-chan FileChunk, results chan<- UploadPart, failed chan<- error) {
+func worker(id int, arg workerArg, jobs <-chan FileChunk, results chan<- UploadPart, failed chan<- error, die <- chan bool) {
 	for chunk := range jobs {
 		if err := arg.hook(id, chunk); err != nil {
 			failed <- err
@@ -113,6 +113,11 @@ func worker(id int, arg workerArg, jobs <-chan FileChunk, results chan<- UploadP
 			failed <- err
 			break
 		}
+		select {
+			case <-die:
+				return
+			default:
+		}
 		results <- part
 	}
 }
@@ -141,11 +146,12 @@ func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, opti
 	jobs := make(chan FileChunk, len(chunks))
 	results := make(chan UploadPart, len(chunks))
 	failed := make(chan error)
+	die := make(chan bool)
 
 	// 启动工作协程
 	arg := workerArg{&bucket, filePath, imur, uploadPartHooker}
 	for w := 1; w <= routines; w++ {
-		go worker(w, arg, jobs, results, failed)
+		go worker(w, arg, jobs, results, failed, die)
 	}
 
 	// 并发上传分片
@@ -160,6 +166,7 @@ func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, opti
 			completed++
 			parts[part.PartNumber-1] = part
 		case err := <-failed:
+			close(die)
 			bucket.AbortMultipartUpload(imur)
 			return err
 		}
@@ -396,11 +403,12 @@ func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64
 	jobs := make(chan FileChunk, len(chunks))
 	results := make(chan UploadPart, len(chunks))
 	failed := make(chan error)
+	die := make(chan bool)
 
 	// 启动工作协程
 	arg := workerArg{&bucket, filePath, imur, uploadPartHooker}
 	for w := 1; w <= routines; w++ {
-		go worker(w, arg, jobs, results, failed)
+		go worker(w, arg, jobs, results, failed, die)
 	}
 
 	// 并发上传分片
@@ -415,6 +423,7 @@ func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64
 			ucp.updatePart(part)
 			ucp.dump(cpFilePath)
 		case err := <-failed:
+			close(die)
 			return err
 		}