download checkpoint
This commit is contained in:
@@ -48,7 +48,7 @@ func main() {
|
|||||||
Condition: map[string]map[string]interface{}{
|
Condition: map[string]map[string]interface{}{
|
||||||
"ip_not_equal": map[string]interface{}{
|
"ip_not_equal": map[string]interface{}{
|
||||||
"qcs:ip": []string{
|
"qcs:ip": []string{
|
||||||
"192.168.1.1",
|
"<ip>",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
128
object.go
128
object.go
@@ -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
|
||||||
}
|
}
|
||||||
// 创建文件
|
// 断点续载
|
||||||
nfile, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
|
var resumableFlag bool
|
||||||
if err != nil {
|
var resumableInfo *MultiDownloadCPInfo
|
||||||
return resp, err
|
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)
|
||||||
|
if err != nil {
|
||||||
|
if cpfd != nil {
|
||||||
|
cpfd.Close()
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user