|  | @ -3,6 +3,7 @@ package cos | 
		
	
		
			
				|  |  | import ( |  |  | import ( | 
		
	
		
			
				|  |  | 	"context" |  |  | 	"context" | 
		
	
		
			
				|  |  | 	"crypto/md5" |  |  | 	"crypto/md5" | 
		
	
		
			
				|  |  |  |  |  | 	"encoding/json" | 
		
	
		
			
				|  |  | 	"encoding/xml" |  |  | 	"encoding/xml" | 
		
	
		
			
				|  |  | 	"errors" |  |  | 	"errors" | 
		
	
		
			
				|  |  | 	"fmt" |  |  | 	"fmt" | 
		
	
	
		
			
				|  | @ -568,6 +569,20 @@ type MultiDownloadOptions struct { | 
		
	
		
			
				|  |  | 	Opt            *ObjectGetOptions |  |  | 	Opt            *ObjectGetOptions | 
		
	
		
			
				|  |  | 	PartSize       int64 |  |  | 	PartSize       int64 | 
		
	
		
			
				|  |  | 	ThreadPoolSize int |  |  | 	ThreadPoolSize int | 
		
	
		
			
				|  |  |  |  |  | 	CheckPoint     bool | 
		
	
		
			
				|  |  |  |  |  | 	CheckPointFile string | 
		
	
		
			
				|  |  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |  | type MultiDownloadCPInfo struct { | 
		
	
		
			
				|  |  |  |  |  | 	Size             int64             `json:"contentLength,omitempty"` | 
		
	
		
			
				|  |  |  |  |  | 	ETag             string            `json:"eTag,omitempty"` | 
		
	
		
			
				|  |  |  |  |  | 	CRC64            string            `json:"crc64ecma,omitempty"` | 
		
	
		
			
				|  |  |  |  |  | 	LastModified     string            `json:"lastModified,omitempty"` | 
		
	
		
			
				|  |  |  |  |  | 	DownloadedBlocks []DownloadedBlock `json:"downloadedBlocks,omitempty"` | 
		
	
		
			
				|  |  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | type DownloadedBlock struct { | 
		
	
		
			
				|  |  |  |  |  | 	From int64 `json:"from,omitempty"` | 
		
	
		
			
				|  |  |  |  |  | 	To   int64 `json:"to,omitempty"` | 
		
	
		
			
				|  |  | } |  |  | } | 
		
	
		
			
				|  |  | 
 |  |  | 
 | 
		
	
		
			
				|  |  | type Chunk struct { |  |  | type Chunk struct { | 
		
	
	
		
			
				|  | @ -584,6 +599,7 @@ type Jobs struct { | 
		
	
		
			
				|  |  | 	UploadId   string |  |  | 	UploadId   string | 
		
	
		
			
				|  |  | 	FilePath   string |  |  | 	FilePath   string | 
		
	
		
			
				|  |  | 	RetryTimes int |  |  | 	RetryTimes int | 
		
	
		
			
				|  |  |  |  |  | 	VersionId  []string | 
		
	
		
			
				|  |  | 	Chunk      Chunk |  |  | 	Chunk      Chunk | 
		
	
		
			
				|  |  | 	Data       io.Reader |  |  | 	Data       io.Reader | 
		
	
		
			
				|  |  | 	Opt        *ObjectUploadPartOptions |  |  | 	Opt        *ObjectUploadPartOptions | 
		
	
	
		
			
				|  | @ -663,7 +679,7 @@ func downloadWorker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results | 
		
	
		
			
				|  |  | 		for { |  |  | 		for { | 
		
	
		
			
				|  |  | 			var res Results |  |  | 			var res Results | 
		
	
		
			
				|  |  | 			res.PartNumber = j.Chunk.Number |  |  | 			res.PartNumber = j.Chunk.Number | 
		
	
		
			
				|  |  | 			resp, err := s.Get(context.Background(), j.Name, j.DownOpt) |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 			resp, err := s.Get(context.Background(), j.Name, j.DownOpt, j.VersionId...) | 
		
	
		
			
				|  |  | 			res.err = err |  |  | 			res.err = err | 
		
	
		
			
				|  |  | 			res.Resp = resp |  |  | 			res.Resp = resp | 
		
	
		
			
				|  |  | 			if err != nil { |  |  | 			if err != nil { | 
		
	
	
		
			
				|  | @ -1046,7 +1062,50 @@ func SplitSizeIntoChunks(totalBytes int64, partSize int64) ([]Chunk, int, error) | 
		
	
		
			
				|  |  | 	return chunks, int(partNum), nil |  |  | 	return chunks, int(partNum), nil | 
		
	
		
			
				|  |  | } |  |  | } | 
		
	
		
			
				|  |  | 
 |  |  | 
 | 
		
	
		
			
				|  |  | func (s *ObjectService) Download(ctx context.Context, name string, filepath string, opt *MultiDownloadOptions) (*Response, error) { |  |  |  | 
		
	
		
			
				|  |  |  |  |  | func (s *ObjectService) checkDownloadedParts(opt *MultiDownloadCPInfo, chfile string, chunks []Chunk) (*MultiDownloadCPInfo, bool) { | 
		
	
		
			
				|  |  |  |  |  | 	var defaultRes MultiDownloadCPInfo | 
		
	
		
			
				|  |  |  |  |  | 	defaultRes = *opt | 
		
	
		
			
				|  |  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |  | 	fd, err := os.Open(chfile) | 
		
	
		
			
				|  |  |  |  |  | 	// checkpoint 文件不存在
 | 
		
	
		
			
				|  |  |  |  |  | 	if err != nil && os.IsNotExist(err) { | 
		
	
		
			
				|  |  |  |  |  | 		// 创建 checkpoint 文件
 | 
		
	
		
			
				|  |  |  |  |  | 		fd, _ = os.OpenFile(chfile, os.O_RDONLY|os.O_CREATE|os.O_TRUNC, 0660) | 
		
	
		
			
				|  |  |  |  |  | 		fd.Close() | 
		
	
		
			
				|  |  |  |  |  | 		return &defaultRes, false | 
		
	
		
			
				|  |  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 	if err != nil { | 
		
	
		
			
				|  |  |  |  |  | 		return &defaultRes, false | 
		
	
		
			
				|  |  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 	defer fd.Close() | 
		
	
		
			
				|  |  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |  | 	var res MultiDownloadCPInfo | 
		
	
		
			
				|  |  |  |  |  | 	err = json.NewDecoder(fd).Decode(&res) | 
		
	
		
			
				|  |  |  |  |  | 	if err != nil { | 
		
	
		
			
				|  |  |  |  |  | 		return &defaultRes, false | 
		
	
		
			
				|  |  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 	// 与COS的文件比较
 | 
		
	
		
			
				|  |  |  |  |  | 	if res.CRC64 != opt.CRC64 || res.ETag != opt.ETag || res.Size != opt.Size || res.LastModified != opt.LastModified || len(res.DownloadedBlocks) == 0 { | 
		
	
		
			
				|  |  |  |  |  | 		return &defaultRes, false | 
		
	
		
			
				|  |  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 	// len(chunks) 大于1,否则为简单下载, chunks[0].Size为partSize
 | 
		
	
		
			
				|  |  |  |  |  | 	partSize := chunks[0].Size | 
		
	
		
			
				|  |  |  |  |  | 	for _, v := range res.DownloadedBlocks { | 
		
	
		
			
				|  |  |  |  |  | 		index := v.From / partSize | 
		
	
		
			
				|  |  |  |  |  | 		to := chunks[index].OffSet + chunks[index].Size - 1 | 
		
	
		
			
				|  |  |  |  |  | 		if chunks[index].OffSet != v.From || to != v.To { | 
		
	
		
			
				|  |  |  |  |  | 			// 重置chunks
 | 
		
	
		
			
				|  |  |  |  |  | 			for i, _ := range chunks { | 
		
	
		
			
				|  |  |  |  |  | 				chunks[i].Done = false | 
		
	
		
			
				|  |  |  |  |  | 			} | 
		
	
		
			
				|  |  |  |  |  | 			return &defaultRes, false | 
		
	
		
			
				|  |  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  |  | 		chunks[index].Done = true | 
		
	
		
			
				|  |  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 	return &res, true | 
		
	
		
			
				|  |  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |  | func (s *ObjectService) Download(ctx context.Context, name string, filepath string, opt *MultiDownloadOptions, id ...string) (*Response, error) { | 
		
	
		
			
				|  |  | 	// 参数校验
 |  |  | 	// 参数校验
 | 
		
	
		
			
				|  |  | 	if opt == nil { |  |  | 	if opt == nil { | 
		
	
		
			
				|  |  | 		opt = &MultiDownloadOptions{} |  |  | 		opt = &MultiDownloadOptions{} | 
		
	
	
		
			
				|  | @ -1056,7 +1115,7 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri | 
		
	
		
			
				|  |  | 	} |  |  | 	} | 
		
	
		
			
				|  |  | 	// 获取文件长度和CRC
 |  |  | 	// 获取文件长度和CRC
 | 
		
	
		
			
				|  |  | 	var coscrc string |  |  | 	var coscrc string | 
		
	
		
			
				|  |  | 	resp, err := s.Head(ctx, name, nil) |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 	resp, err := s.Head(ctx, name, nil, id...) | 
		
	
		
			
				|  |  | 	if err != nil { |  |  | 	if err != nil { | 
		
	
		
			
				|  |  | 		return resp, err |  |  | 		return resp, err | 
		
	
		
			
				|  |  | 	} |  |  | 	} | 
		
	
	
		
			
				|  | @ -1075,7 +1134,7 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri | 
		
	
		
			
				|  |  | 	} |  |  | 	} | 
		
	
		
			
				|  |  | 	// 直接下载到文件
 |  |  | 	// 直接下载到文件
 | 
		
	
		
			
				|  |  | 	if partNum == 0 || partNum == 1 { |  |  | 	if partNum == 0 || partNum == 1 { | 
		
	
		
			
				|  |  | 		rsp, err := s.GetToFile(ctx, name, filepath, opt.Opt) |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 		rsp, err := s.GetToFile(ctx, name, filepath, opt.Opt, id...) | 
		
	
		
			
				|  |  | 		if err != nil { |  |  | 		if err != nil { | 
		
	
		
			
				|  |  | 			return rsp, err |  |  | 			return rsp, err | 
		
	
		
			
				|  |  | 		} |  |  | 		} | 
		
	
	
		
			
				|  | @ -1096,12 +1155,39 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri | 
		
	
		
			
				|  |  | 		} |  |  | 		} | 
		
	
		
			
				|  |  | 		return rsp, err |  |  | 		return rsp, err | 
		
	
		
			
				|  |  | 	} |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 	// 断点续载
 | 
		
	
		
			
				|  |  |  |  |  | 	var resumableFlag bool | 
		
	
		
			
				|  |  |  |  |  | 	var resumableInfo *MultiDownloadCPInfo | 
		
	
		
			
				|  |  |  |  |  | 	var cpfd *os.File | 
		
	
		
			
				|  |  |  |  |  | 	var cpfile string | 
		
	
		
			
				|  |  |  |  |  | 	if opt.CheckPoint { | 
		
	
		
			
				|  |  |  |  |  | 		cpInfo := &MultiDownloadCPInfo{ | 
		
	
		
			
				|  |  |  |  |  | 			LastModified: resp.Header.Get("Last-Modified"), | 
		
	
		
			
				|  |  |  |  |  | 			ETag:         resp.Header.Get("ETag"), | 
		
	
		
			
				|  |  |  |  |  | 			CRC64:        coscrc, | 
		
	
		
			
				|  |  |  |  |  | 			Size:         totalBytes, | 
		
	
		
			
				|  |  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  |  | 		cpfile = opt.CheckPointFile | 
		
	
		
			
				|  |  |  |  |  | 		if cpfile == "" { | 
		
	
		
			
				|  |  |  |  |  | 			cpfile = fmt.Sprintf("%s.cosresumabletask", filepath) | 
		
	
		
			
				|  |  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  |  | 		resumableInfo, resumableFlag = s.checkDownloadedParts(cpInfo, cpfile, chunks) | 
		
	
		
			
				|  |  |  |  |  | 		cpfd, err = os.OpenFile(cpfile, os.O_RDWR, 0660) | 
		
	
		
			
				|  |  |  |  |  | 		if err != nil { | 
		
	
		
			
				|  |  |  |  |  | 			return nil, fmt.Errorf("Open CheckPoint File[%v] Failed:%v", cpfile, err) | 
		
	
		
			
				|  |  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 	if !resumableFlag { | 
		
	
		
			
				|  |  | 		// 创建文件
 |  |  | 		// 创建文件
 | 
		
	
		
			
				|  |  | 		nfile, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660) |  |  | 		nfile, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660) | 
		
	
		
			
				|  |  | 		if err != nil { |  |  | 		if err != nil { | 
		
	
		
			
				|  |  |  |  |  | 			if cpfd != nil { | 
		
	
		
			
				|  |  |  |  |  | 				cpfd.Close() | 
		
	
		
			
				|  |  |  |  |  | 			} | 
		
	
		
			
				|  |  | 			return resp, err |  |  | 			return resp, err | 
		
	
		
			
				|  |  | 		} |  |  | 		} | 
		
	
		
			
				|  |  | 		nfile.Close() |  |  | 		nfile.Close() | 
		
	
		
			
				|  |  |  |  |  | 	} | 
		
	
		
			
				|  |  | 
 |  |  | 
 | 
		
	
		
			
				|  |  | 	var poolSize int |  |  | 	var poolSize int | 
		
	
		
			
				|  |  | 	if opt.ThreadPoolSize > 0 { |  |  | 	if opt.ThreadPoolSize > 0 { | 
		
	
	
		
			
				|  | @ -1117,6 +1203,9 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri | 
		
	
		
			
				|  |  | 
 |  |  | 
 | 
		
	
		
			
				|  |  | 	go func() { |  |  | 	go func() { | 
		
	
		
			
				|  |  | 		for _, chunk := range chunks { |  |  | 		for _, chunk := range chunks { | 
		
	
		
			
				|  |  |  |  |  | 			if chunk.Done { | 
		
	
		
			
				|  |  |  |  |  | 				continue | 
		
	
		
			
				|  |  |  |  |  | 			} | 
		
	
		
			
				|  |  | 			var downOpt ObjectGetOptions |  |  | 			var downOpt ObjectGetOptions | 
		
	
		
			
				|  |  | 			if opt.Opt != nil { |  |  | 			if opt.Opt != nil { | 
		
	
		
			
				|  |  | 				downOpt = *opt.Opt |  |  | 				downOpt = *opt.Opt | 
		
	
	
		
			
				|  | @ -1129,23 +1218,42 @@ func (s *ObjectService) Download(ctx context.Context, name string, filepath stri | 
		
	
		
			
				|  |  | 				Chunk:      chunk, |  |  | 				Chunk:      chunk, | 
		
	
		
			
				|  |  | 				DownOpt:    &downOpt, |  |  | 				DownOpt:    &downOpt, | 
		
	
		
			
				|  |  | 			} |  |  | 			} | 
		
	
		
			
				|  |  |  |  |  | 			if len(id) > 0 { | 
		
	
		
			
				|  |  |  |  |  | 				job.VersionId = append(job.VersionId, id...) | 
		
	
		
			
				|  |  |  |  |  | 			} | 
		
	
		
			
				|  |  | 			chjobs <- job |  |  | 			chjobs <- job | 
		
	
		
			
				|  |  | 		} |  |  | 		} | 
		
	
		
			
				|  |  | 		close(chjobs) |  |  | 		close(chjobs) | 
		
	
		
			
				|  |  | 	}() |  |  | 	}() | 
		
	
		
			
				|  |  | 
 |  |  |  | 
		
	
		
			
				|  |  | 	err = nil |  |  | 	err = nil | 
		
	
		
			
				|  |  | 	for i := 0; i < partNum; i++ { |  |  | 	for i := 0; i < partNum; i++ { | 
		
	
		
			
				|  |  |  |  |  | 		if chunks[i].Done { | 
		
	
		
			
				|  |  |  |  |  | 			continue | 
		
	
		
			
				|  |  |  |  |  | 		} | 
		
	
		
			
				|  |  | 		res := <-chresults |  |  | 		res := <-chresults | 
		
	
		
			
				|  |  | 		if res.Resp == nil || res.err != nil { |  |  | 		if res.Resp == nil || res.err != nil { | 
		
	
		
			
				|  |  | 			err = fmt.Errorf("part %d get resp Content. error: %s", res.PartNumber, res.err.Error()) |  |  | 			err = fmt.Errorf("part %d get resp Content. error: %s", res.PartNumber, res.err.Error()) | 
		
	
		
			
				|  |  | 			continue |  |  | 			continue | 
		
	
		
			
				|  |  | 		} |  |  | 		} | 
		
	
		
			
				|  |  |  |  |  | 		// Dump CheckPoint Info
 | 
		
	
		
			
				|  |  |  |  |  | 		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}) | 
		
	
		
			
				|  |  |  |  |  | 			json.NewEncoder(cpfd).Encode(resumableInfo) | 
		
	
		
			
				|  |  |  |  |  | 		} | 
		
	
		
			
				|  |  | 	} |  |  | 	} | 
		
	
		
			
				|  |  | 	close(chresults) |  |  | 	close(chresults) | 
		
	
		
			
				|  |  |  |  |  | 	if cpfd != nil { | 
		
	
		
			
				|  |  |  |  |  | 		cpfd.Close() | 
		
	
		
			
				|  |  |  |  |  | 	} | 
		
	
		
			
				|  |  | 	if err != nil { |  |  | 	if err != nil { | 
		
	
		
			
				|  |  | 		return nil, err |  |  | 		return nil, err | 
		
	
		
			
				|  |  | 	} |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  |     // 下载成功,删除checkpoint文件
 | 
		
	
		
			
				|  |  |  |  |  | 	if opt.CheckPoint { | 
		
	
		
			
				|  |  |  |  |  | 		os.Remove(cpfile) | 
		
	
		
			
				|  |  |  |  |  | 	} | 
		
	
		
			
				|  |  | 	if coscrc != "" && s.client.Conf.EnableCRC { |  |  | 	if coscrc != "" && s.client.Conf.EnableCRC { | 
		
	
		
			
				|  |  | 		icoscrc, _ := strconv.ParseUint(coscrc, 10, 64) |  |  | 		icoscrc, _ := strconv.ParseUint(coscrc, 10, 64) | 
		
	
		
			
				|  |  | 		fd, err := os.Open(filepath) |  |  | 		fd, err := os.Open(filepath) | 
		
	
	
		
			
				|  | 
 |