Skip to content

Commit 2fe1c46

Browse files
authored
Merge pull request #53 from tencentyun/dev/feature-retry_download
Dev/feature retry download
2 parents ed65fa6 + 9f5d5a1 commit 2fe1c46

8 files changed

Lines changed: 87 additions & 33 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
## 下载链接
88

9-
当前版本:v0.19.0-beta
9+
当前版本:v0.20.0-beta
1010

11-
[Linux](https://github.com/tencentyun/coscli/releases/download/v0.19.0-beta/coscli-linux)
11+
[Linux](https://github.com/tencentyun/coscli/releases/download/v0.20.0-beta/coscli-linux)
1212

13-
[Mac](https://github.com/tencentyun/coscli/releases/download/v0.19.0-beta/coscli-mac)
13+
[Mac](https://github.com/tencentyun/coscli/releases/download/v0.20.0-beta/coscli-mac)
1414

15-
[Windows](https://github.com/tencentyun/coscli/releases/download/v0.19.0-beta/coscli-windows.exe)
15+
[Windows](https://github.com/tencentyun/coscli/releases/download/v0.20.0-beta/coscli-windows.exe)
1616

1717
## 使用方法
1818

cmd/cp.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,16 @@ Example:
5151
threadNum, _ := cmd.Flags().GetInt("thread-num")
5252
metaString, _ := cmd.Flags().GetString("meta")
5353
meta, err := util.MetaStringToHeader(metaString)
54+
retryNum, _ := cmd.Flags().GetInt("retry-num")
5455
if err != nil {
5556
logger.Fatalln("Copy invalid meta " + err.Error())
5657
}
58+
59+
if retryNum < 0 || retryNum > 10 {
60+
logger.Fatalln("retry-num must be between 0 and 10 (inclusive)")
61+
return
62+
}
63+
5764
// args[0]: 源地址
5865
// args[1]: 目标地址
5966
if !util.IsCosPath(args[0]) && util.IsCosPath(args[1]) {
@@ -73,7 +80,7 @@ Example:
7380
PartSize: partSize,
7481
ThreadNum: threadNum,
7582
}
76-
download(args, recursive, include, exclude, op)
83+
download(args, recursive, include, exclude, retryNum, op)
7784
} else if util.IsCosPath(args[0]) && util.IsCosPath(args[1]) {
7885
// 拷贝
7986
cosCopy(args, recursive, include, exclude, meta, storageClass)
@@ -96,6 +103,7 @@ func init() {
96103
cpCmd.Flags().String("meta", "",
97104
"Set the meta information of the file, "+
98105
"the format is header:value#header:value, the example is Cache-Control:no-cache#Content-Encoding:gzip")
106+
cpCmd.Flags().Int("retry-num", 0, "Retry download")
99107
}
100108

101109
func upload(args []string, recursive bool, include string, exclude string, op *util.UploadOptions) {
@@ -110,13 +118,13 @@ func upload(args []string, recursive bool, include string, exclude string, op *u
110118
}
111119
}
112120

113-
func download(args []string, recursive bool, include string, exclude string, op *util.DownloadOptions) {
121+
func download(args []string, recursive bool, include string, exclude string, retryNum int, op *util.DownloadOptions) {
114122
bucketName, cosPath := util.ParsePath(args[0])
115123
_, localPath := util.ParsePath(args[1])
116124
c := util.NewClient(&config, &param, bucketName)
117125

118126
if recursive {
119-
util.MultiDownload(c, bucketName, cosPath, localPath, include, exclude, op)
127+
util.MultiDownload(c, bucketName, cosPath, localPath, include, exclude, retryNum, op)
120128
} else {
121129
util.SingleDownload(c, bucketName, cosPath, localPath, op, false)
122130
}
@@ -133,7 +141,7 @@ func cosCopy(args []string, recursive bool, include string, exclude string, meta
133141
// 记录是否是代码添加的路径分隔符
134142
isAddSeparator := false
135143
// 源路径若不以路径分隔符结尾,则添加
136-
if !strings.HasSuffix(cosPath1, "/") && cosPath1 != ""{
144+
if !strings.HasSuffix(cosPath1, "/") && cosPath1 != "" {
137145
isAddSeparator = true
138146
cosPath1 += "/"
139147
}

cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ var rootCmd = &cobra.Command{
2727
Run: func(cmd *cobra.Command, args []string) {
2828
_ = cmd.Help()
2929
},
30-
Version: "v0.19.0-beta",
30+
Version: "v0.20.0-beta",
3131
}
3232

3333
func Execute() {

cmd/sync.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,16 @@ Example:
5353
metaString, _ := cmd.Flags().GetString("meta")
5454
snapshotPath, _ := cmd.Flags().GetString("snapshot-path")
5555
meta, err := util.MetaStringToHeader(metaString)
56+
retryNum, _ := cmd.Flags().GetInt("retry-num")
5657
if err != nil {
5758
logger.Fatalln("Sync invalid meta, reason: " + err.Error())
5859
}
60+
61+
if retryNum < 0 || retryNum > 10 {
62+
logger.Fatalln("retry-num must be between 0 and 10 (inclusive)")
63+
return
64+
}
65+
5966
// args[0]: 源地址
6067
// args[1]: 目标地址
6168
var snapshotDb *leveldb.DB
@@ -86,7 +93,7 @@ Example:
8693
SnapshotPath: snapshotPath,
8794
SnapshotDb: snapshotDb,
8895
}
89-
syncDownload(args, recursive, include, exclude, op)
96+
syncDownload(args, recursive, include, exclude, retryNum, op)
9097
} else if util.IsCosPath(args[0]) && util.IsCosPath(args[1]) {
9198
// 拷贝
9299
syncCopy(args, recursive, include, exclude, meta, storageClass)
@@ -129,6 +136,7 @@ func init() {
129136
"In addition, coscli does not automatically delete snapshot-path snapshot information, "+
130137
"in order to avoid too much snapshot information, when the snapshot information is useless, "+
131138
"please clean up your own snapshot-path on your own immediately.")
139+
syncCmd.Flags().Int("retry-num", 0, "Retry download")
132140
}
133141

134142
func syncUpload(args []string, recursive bool, include string, exclude string, op *util.UploadOptions,
@@ -144,13 +152,13 @@ func syncUpload(args []string, recursive bool, include string, exclude string, o
144152
}
145153
}
146154

147-
func syncDownload(args []string, recursive bool, include string, exclude string, op *util.DownloadOptions) {
155+
func syncDownload(args []string, recursive bool, include string, exclude string, retryNum int, op *util.DownloadOptions) {
148156
bucketName, cosPath := util.ParsePath(args[0])
149157
_, localPath := util.ParsePath(args[1])
150158
c := util.NewClient(&config, &param, bucketName)
151159

152160
if recursive {
153-
util.SyncMultiDownload(c, bucketName, cosPath, localPath, include, exclude, op)
161+
util.SyncMultiDownload(c, bucketName, cosPath, localPath, include, exclude, retryNum, op)
154162
} else {
155163
util.SyncSingleDownload(c, bucketName, cosPath, localPath, op, "", recursive)
156164
}
@@ -167,7 +175,7 @@ func syncCopy(args []string, recursive bool, include string, exclude string, met
167175
// 记录是否是代码添加的路径分隔符
168176
isAddSeparator := false
169177
// 源路径若不以路径分隔符结尾,则添加
170-
if !strings.HasSuffix(cosPath1, "/") && cosPath1 != ""{
178+
if !strings.HasSuffix(cosPath1, "/") && cosPath1 != "" {
171179
isAddSeparator = true
172180
cosPath1 += "/"
173181
}

util/download.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func SingleDownload(c *cos.Client, bucketName, cosPath, localPath string, op *Do
126126
return nil
127127
}
128128

129-
func MultiDownload(c *cos.Client, bucketName, cosDir, localDir, include, exclude string, op *DownloadOptions) {
129+
func MultiDownload(c *cos.Client, bucketName, cosDir, localDir, include, exclude string, retryNum int, op *DownloadOptions) {
130130
if localDir == "" {
131131
logger.Fatalln("localDir is empty")
132132
os.Exit(1)
@@ -136,12 +136,12 @@ func MultiDownload(c *cos.Client, bucketName, cosDir, localDir, include, exclude
136136
// 记录是否是代码添加的路径分隔符
137137
isCosAddSeparator := false
138138
// cos路径若不以路径分隔符结尾,则添加
139-
if !strings.HasSuffix(cosDir, "/") && cosDir != ""{
139+
if !strings.HasSuffix(cosDir, "/") && cosDir != "" {
140140
isCosAddSeparator = true
141141
cosDir += "/"
142142
}
143143
// 判断cosDir是否是文件夹
144-
isDir := CheckCosPathType(c, cosDir, 0)
144+
isDir := CheckCosPathType(c, cosDir, 0, retryNum)
145145

146146
if isDir {
147147
// cosDir是文件夹 且 localDir不以路径分隔符结尾,则添加
@@ -162,13 +162,13 @@ func MultiDownload(c *cos.Client, bucketName, cosDir, localDir, include, exclude
162162
}
163163
}
164164

165-
objects, commonPrefixes := GetObjectsListRecursive(c, cosDir, 0, include, exclude)
165+
objects, commonPrefixes := GetObjectsListRecursive(c, cosDir, 0, include, exclude, retryNum)
166166
listObjects(c, bucketName, objects, cosDir, localDir, op)
167167

168168
if len(commonPrefixes) > 0 {
169169
for i := 0; i < len(commonPrefixes); i++ {
170170
localDirTemp := localDir + commonPrefixes[i]
171-
MultiDownload(c, bucketName, commonPrefixes[i], localDirTemp, include, exclude, op)
171+
MultiDownload(c, bucketName, commonPrefixes[i], localDirTemp, include, exclude, retryNum, op)
172172
}
173173
}
174174

util/list.go

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import (
55
"fmt"
66
"io/fs"
77
"io/ioutil"
8+
"math/rand"
89
"net/url"
910
"os"
1011
"path/filepath"
1112
"regexp"
13+
"time"
1214

1315
logger "github.com/sirupsen/logrus"
1416
"github.com/tencentyun/cos-go-sdk-v5"
@@ -173,7 +175,7 @@ func GetObjectsList(c *cos.Client, prefix string, limit int, include string, exc
173175
isTruncated = false
174176
} else {
175177
isTruncated = res.IsTruncated
176-
marker = res.NextMarker
178+
marker, _ = url.QueryUnescape(res.NextMarker)
177179
}
178180
}
179181

@@ -191,7 +193,7 @@ func GetObjectsList(c *cos.Client, prefix string, limit int, include string, exc
191193

192194
func GetObjectsListForLs(c *cos.Client, prefix string, limit int, include string, exclude string,
193195
marker string) (dirs []string,
194-
objects []cos.Object, isTruncated bool, nextMaker string) {
196+
objects []cos.Object, isTruncated bool, nextMarker string) {
195197
opt := &cos.BucketGetOptions{
196198
Prefix: prefix,
197199
Delimiter: "/",
@@ -220,7 +222,7 @@ func GetObjectsListForLs(c *cos.Client, prefix string, limit int, include string
220222
isTruncated = false
221223
} else {
222224
isTruncated = res.IsTruncated
223-
nextMaker = res.NextMarker
225+
nextMarker, _ = url.QueryUnescape(res.NextMarker)
224226
}
225227

226228
if len(include) > 0 {
@@ -232,13 +234,19 @@ func GetObjectsListForLs(c *cos.Client, prefix string, limit int, include string
232234
dirs = MatchPattern(dirs, exclude, false)
233235
}
234236

235-
return dirs, objects, isTruncated, nextMaker
237+
return dirs, objects, isTruncated, nextMarker
236238
}
237239

238-
func CheckCosPathType(c *cos.Client, prefix string, limit int) (isDir bool) {
240+
func CheckCosPathType(c *cos.Client, prefix string, limit int, retryCount ...int) (isDir bool) {
239241
if prefix == "" {
240242
return true
241243
}
244+
245+
retries := 0
246+
if len(retryCount) > 0 {
247+
retries = retryCount[0]
248+
}
249+
242250
opt := &cos.BucketGetOptions{
243251
Prefix: prefix,
244252
Delimiter: "",
@@ -247,7 +255,7 @@ func CheckCosPathType(c *cos.Client, prefix string, limit int) (isDir bool) {
247255
MaxKeys: limit,
248256
}
249257

250-
res, _, err := c.Bucket.Get(context.Background(), opt)
258+
res, err := tryGetBucket(c, opt, retries)
251259
if err != nil {
252260
logger.Fatalln(err)
253261
os.Exit(1)
@@ -260,8 +268,14 @@ func CheckCosPathType(c *cos.Client, prefix string, limit int) (isDir bool) {
260268
return isDir
261269
}
262270

263-
func GetObjectsListRecursive(c *cos.Client, prefix string, limit int, include string, exclude string) (objects []cos.Object,
271+
func GetObjectsListRecursive(c *cos.Client, prefix string, limit int, include string, exclude string, retryCount ...int) (objects []cos.Object,
264272
commonPrefixes []string) {
273+
274+
retries := 0
275+
if len(retryCount) > 0 {
276+
retries = retryCount[0]
277+
}
278+
265279
opt := &cos.BucketGetOptions{
266280
Prefix: prefix,
267281
Delimiter: "",
@@ -275,7 +289,7 @@ func GetObjectsListRecursive(c *cos.Client, prefix string, limit int, include st
275289
for isTruncated {
276290
opt.Marker = marker
277291

278-
res, _, err := c.Bucket.Get(context.Background(), opt)
292+
res, err := tryGetBucket(c, opt, retries)
279293
if err != nil {
280294
logger.Fatalln(err)
281295
os.Exit(1)
@@ -288,7 +302,7 @@ func GetObjectsListRecursive(c *cos.Client, prefix string, limit int, include st
288302
isTruncated = false
289303
} else {
290304
isTruncated = res.IsTruncated
291-
marker = res.NextMarker
305+
marker, _ = url.QueryUnescape(res.NextMarker)
292306
}
293307
}
294308

@@ -305,6 +319,29 @@ func GetObjectsListRecursive(c *cos.Client, prefix string, limit int, include st
305319
return objects, commonPrefixes
306320
}
307321

322+
func tryGetBucket(c *cos.Client, opt *cos.BucketGetOptions, retryCount int) (*cos.BucketGetResult, error) {
323+
for i := 0; i <= retryCount; i++ {
324+
res, resp, err := c.Bucket.Get(context.Background(), opt)
325+
if err != nil {
326+
if resp.StatusCode == 503 {
327+
if i == retryCount {
328+
return res, err
329+
} else {
330+
fmt.Println("Error 503: Service Unavailable. Retrying...")
331+
waitTime := time.Duration(rand.Intn(10)+1) * time.Second
332+
time.Sleep(waitTime)
333+
continue
334+
}
335+
} else {
336+
return res, err
337+
}
338+
} else {
339+
return res, err
340+
}
341+
}
342+
return nil, fmt.Errorf("Retry limit exceeded")
343+
}
344+
308345
func GetObjectsListRecursiveForLs(c *cos.Client, prefix string, limit int, include string, exclude string,
309346
marker string) (objects []cos.Object, isTruncated bool, nextMarker string, commonPrefixes []string) {
310347
opt := &cos.BucketGetOptions{
@@ -331,7 +368,7 @@ func GetObjectsListRecursiveForLs(c *cos.Client, prefix string, limit int, inclu
331368
isTruncated = false
332369
} else {
333370
isTruncated = res.IsTruncated
334-
nextMarker = res.NextMarker
371+
nextMarker, _ = url.QueryUnescape(res.NextMarker)
335372
}
336373

337374
if len(include) > 0 {

util/list_iterator.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package util
22

33
import (
44
"context"
5+
"net/url"
56
"os"
67

78
logger "github.com/sirupsen/logrus"
@@ -28,7 +29,7 @@ func GetObjectsListIterator(c *cos.Client, prefix, marker string, include, exclu
2829
commonPrefixes = res.CommonPrefixes
2930

3031
isTruncated = res.IsTruncated
31-
nextMarker = res.NextMarker
32+
nextMarker, _ = url.QueryUnescape(res.NextMarker)
3233

3334
if len(include) > 0 {
3435
objects = MatchCosPattern(objects, include, true)

util/synchronize.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,20 +274,20 @@ func getCosLastModified(c *cos.Client, cosPath string) (lmt string, err error) {
274274
}
275275
}
276276

277-
func SyncMultiDownload(c *cos.Client, bucketName, cosDir, localDir, include, exclude string, op *DownloadOptions) {
277+
func SyncMultiDownload(c *cos.Client, bucketName, cosDir, localDir, include, exclude string, retryNum int, op *DownloadOptions) {
278278
if localDir == "" {
279279
logger.Fatalln("localDir is empty")
280280
os.Exit(1)
281281
}
282282
// 记录是否是代码添加的路径分隔符
283283
isCosAddSeparator := false
284284
// cos路径若不以路径分隔符结尾,则添加
285-
if !strings.HasSuffix(cosDir, "/") && cosDir != ""{
285+
if !strings.HasSuffix(cosDir, "/") && cosDir != "" {
286286
isCosAddSeparator = true
287287
cosDir += "/"
288288
}
289289
// 判断cosDir是否是文件夹
290-
isDir := CheckCosPathType(c, cosDir, 0)
290+
isDir := CheckCosPathType(c, cosDir, 0, retryNum)
291291

292292
if isDir {
293293
// cosDir是文件夹
@@ -309,7 +309,7 @@ func SyncMultiDownload(c *cos.Client, bucketName, cosDir, localDir, include, exc
309309
}
310310
}
311311

312-
objects, _ := GetObjectsListRecursive(c, cosDir, 0, include, exclude)
312+
objects, _ := GetObjectsListRecursive(c, cosDir, 0, include, exclude, retryNum)
313313
if len(objects) == 0 {
314314
logger.Warningf("cosDir: cos://%s is empty\n", cosDir)
315315
return

0 commit comments

Comments
 (0)