diff --git a/cmd/get.go b/cmd/get.go index 35b20b6..e4f3c4e 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -13,6 +13,7 @@ import ( "runtime" "strings" "sync" + "time" "github.com/linuxsuren/http-downloader/pkg/common" "github.com/linuxsuren/http-downloader/pkg/log" @@ -55,7 +56,7 @@ func newGetCmd(ctx context.Context) (cmd *cobra.Command) { flags.IntVarP(&opt.Mod, "mod", "", -1, "The file permission, -1 means using the system default") flags.BoolVarP(&opt.SkipTLS, "skip-tls", "k", false, "Skip the TLS") - flags.IntVarP(&opt.Timeout, "time", "", 10, + flags.DurationVarP(&opt.Timeout, "timeout", "", 15*time.Minute, `The default timeout in seconds with the HTTP request`) flags.IntVarP(&opt.MaxAttempts, "max-attempts", "", 10, `Max times to attempt to download, zero means there's no retry action'`) @@ -100,7 +101,7 @@ type downloadOption struct { Category string Output string ShowProgress bool - Timeout int + Timeout time.Duration NoProxy bool MaxAttempts int AcceptPreRelease bool @@ -311,7 +312,8 @@ func (o *downloadOption) runE(cmd *cobra.Command, args []string) (err error) { suggestedFilenameAware = downloader downloader.WithoutProxy(o.NoProxy). WithRoundTripper(o.RoundTripper). - WithInsecureSkipVerify(o.SkipTLS) + WithInsecureSkipVerify(o.SkipTLS). + WithTimeout(o.Timeout) err = downloader.DownloadWithContinue(targetURL, o.Output, o.ContinueAt, -1, 0, o.ShowProgress) } else { downloader := &net.MultiThreadDownloader{} @@ -320,7 +322,8 @@ func (o *downloadOption) runE(cmd *cobra.Command, args []string) (err error) { WithShowProgress(o.ShowProgress). WithoutProxy(o.NoProxy). WithRoundTripper(o.RoundTripper). - WithInsecureSkipVerify(o.SkipTLS) + WithInsecureSkipVerify(o.SkipTLS). + WithTimeout(o.Timeout) err = downloader.Download(targetURL, o.Output, o.Thread) } diff --git a/cmd/get_test.go b/cmd/get_test.go index 06b3ad0..846d84c 100644 --- a/cmd/get_test.go +++ b/cmd/get_test.go @@ -35,7 +35,7 @@ func Test_newGetCmd(t *testing.T) { }, { name: "pre", }, { - name: "time", + name: "timeout", }, { name: "max-attempts", }, { @@ -63,9 +63,10 @@ func Test_newGetCmd(t *testing.T) { tt := flags[i] t.Run(tt.name, func(t *testing.T) { flag := cmd.Flag(tt.name) - assert.NotNil(t, flag) - assert.NotEmpty(t, flag.Usage) - assert.Equal(t, tt.shorthand, flag.Shorthand) + if assert.NotNil(t, flag, tt.name) { + assert.NotEmpty(t, flag.Usage) + assert.Equal(t, tt.shorthand, flag.Shorthand) + } }) } } diff --git a/go.mod b/go.mod index 877744a..f2fa047 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/go-git/go-git/v5 v5.4.2 github.com/golang/mock v1.6.0 github.com/google/go-github/v29 v29.0.3 - github.com/gosuri/uiprogress v0.0.1 github.com/h2non/gock v1.0.9 github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec github.com/linuxsuren/cobra-extension v0.0.16 @@ -25,6 +24,14 @@ require ( require ( github.com/antonmedv/expr v1.11.1 github.com/creack/pty v1.1.17 + github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 + github.com/schollz/progressbar/v3 v3.13.0 +) + +require ( + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/rivo/uniseg v0.4.3 // indirect ) require ( @@ -40,7 +47,6 @@ require ( github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-querystring v1.0.0 // indirect - github.com/gosuri/uilive v0.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect @@ -49,7 +55,7 @@ require ( github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mattn/go-colorable v0.1.6 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/mapstructure v1.4.2 // indirect github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect @@ -65,8 +71,8 @@ require ( golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect golang.org/x/text v0.3.8 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect diff --git a/go.sum b/go.sum index 7ee1433..c81e7c4 100644 --- a/go.sum +++ b/go.sum @@ -236,9 +236,7 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gosuri/uilive v0.0.3 h1:kvo6aB3pez9Wbudij8srWo4iY6SFTTxTKOkb+uRCE8I= github.com/gosuri/uilive v0.0.3/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8= -github.com/gosuri/uiprogress v0.0.1 h1:0kpv/XY/qTmFWl/SkaJykZXrBBzwwadmW8fRb7RJSxw= github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -299,6 +297,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= @@ -344,8 +344,11 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -353,6 +356,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -419,12 +424,17 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= +github.com/schollz/progressbar/v3 v3.13.0 h1:9TeeWRcjW2qd05I8Kf9knPkW4vLM/hYoa6z9ABvxje8= +github.com/schollz/progressbar/v3 v3.13.0/go.mod h1:ZBYnSuLAX2LU8P8UiKN/KgF2DY58AJC8yfVYLPC8Ly4= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= @@ -715,12 +725,16 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/net/http.go b/pkg/net/http.go index 02930fd..ac2b6d4 100644 --- a/pkg/net/http.go +++ b/pkg/net/http.go @@ -6,6 +6,7 @@ import ( "encoding/base64" "fmt" "io" + "net" "net/http" "net/url" "os" @@ -44,10 +45,10 @@ type HTTPDownloader struct { // PreStart returns false will don't continue PreStart func(*http.Response) bool - Thread int - Title string - Timeout int - MaxAttempts int + Thread int + Title string + Timeout time.Duration + // MaxAttempts int Debug bool RoundTripper http.RoundTripper @@ -121,6 +122,9 @@ func (h *HTTPDownloader) DownloadFile() error { } else { trp := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: h.InsecureSkipVerify}, + DialContext: (&net.Dialer{ + Timeout: h.Timeout, + }).DialContext, } if !h.NoProxy { @@ -136,13 +140,9 @@ func (h *HTTPDownloader) DownloadFile() error { } tr = trp } - client := &RetryClient{ - Client: http.Client{ - Transport: tr, - Timeout: time.Duration(h.Timeout) * time.Second, - }, - MaxAttempts: h.MaxAttempts, - } + client := *NewRetryClient(http.Client{ + Transport: tr, + }) var resp *http.Response if resp, err = client.Do(req); err != nil { @@ -196,10 +196,7 @@ func (h *HTTPDownloader) DownloadFile() error { } h.progressIndicator.Writer = out - - if showProgress { - h.progressIndicator.Init() - } + h.progressIndicator.Init() // Write the body to file _, err = io.Copy(h.progressIndicator, resp.Body) @@ -228,6 +225,7 @@ func DownloadFileWithMultipleThreadKeepParts(targetURL, targetFilePath string, t type ContinueDownloader struct { downloader *HTTPDownloader + Timeout time.Duration Context context.Context roundTripper http.RoundTripper noProxy bool @@ -263,6 +261,12 @@ func (c *ContinueDownloader) WithContext(ctx context.Context) *ContinueDownloade return c } +// WithTimeout sets the timeout +func (c *ContinueDownloader) WithTimeout(timeout time.Duration) *ContinueDownloader { + c.Timeout = timeout + return c +} + // DownloadWithContinue downloads the files continuously func (c *ContinueDownloader) DownloadWithContinue(targetURL, output string, index, continueAt, end int64, showProgress bool) (err error) { c.downloader = &HTTPDownloader{ @@ -273,6 +277,7 @@ func (c *ContinueDownloader) DownloadWithContinue(targetURL, output string, inde RoundTripper: c.roundTripper, InsecureSkipVerify: c.insecureSkipVerify, Context: c.Context, + Timeout: c.Timeout, } if index >= 0 { c.downloader.Title = fmt.Sprintf("Downloading part %d", index) @@ -296,7 +301,7 @@ func (c *ContinueDownloader) DownloadWithContinue(targetURL, output string, inde // DetectSizeWithRoundTripper returns the size of target resource func DetectSizeWithRoundTripper(targetURL, output string, showProgress, noProxy, insecureSkipVerify bool, - roundTripper http.RoundTripper) (total int64, rangeSupport bool, err error) { + roundTripper http.RoundTripper, timeout time.Duration) (total int64, rangeSupport bool, err error) { downloader := HTTPDownloader{ TargetFilePath: output, URL: targetURL, @@ -304,6 +309,7 @@ func DetectSizeWithRoundTripper(targetURL, output string, showProgress, noProxy, RoundTripper: roundTripper, NoProxy: false, // below HTTP request does not need proxy InsecureSkipVerify: insecureSkipVerify, + Timeout: timeout, } var detectOffset int64 diff --git a/pkg/net/http_test.go b/pkg/net/http_test.go index 3cf831e..ebbd252 100644 --- a/pkg/net/http_test.go +++ b/pkg/net/http_test.go @@ -127,7 +127,7 @@ var _ = Describe("http test", func() { request, _ := http.NewRequest(http.MethodGet, "", nil) response := &http.Response{} roundTripper.EXPECT(). - RoundTrip(request).Return(response, fmt.Errorf("fake error")) + RoundTrip(request).Return(response, fmt.Errorf("fake error")).AnyTimes() err := downloader.DownloadFile() Expect(err).To(HaveOccurred()) }) @@ -251,7 +251,7 @@ func TestDetectSize(t *testing.T) { roundTripper.EXPECT(). RoundTrip(mockRequest).Return(mockResponse, nil) - total, rangeSupport, err := net.DetectSizeWithRoundTripper(targetURL, os.TempDir(), false, false, false, roundTripper) + total, rangeSupport, err := net.DetectSizeWithRoundTripper(targetURL, os.TempDir(), false, false, false, roundTripper, 0) assert.Nil(t, err) assert.Equal(t, int64(102), total) assert.True(t, rangeSupport) diff --git a/pkg/net/multi_thread.go b/pkg/net/multi_thread.go index 0098250..2bf8786 100644 --- a/pkg/net/multi_thread.go +++ b/pkg/net/multi_thread.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "sync" + "time" ) // MultiThreadDownloader is a download with multi-thread @@ -17,6 +18,7 @@ type MultiThreadDownloader struct { roundTripper http.RoundTripper suggestedFilename string + timeout time.Duration } // GetSuggestedFilename returns the suggested filename @@ -30,6 +32,12 @@ func (d *MultiThreadDownloader) WithInsecureSkipVerify(insecureSkipVerify bool) return d } +// WithTimeout sets the timeout +func (d *MultiThreadDownloader) WithTimeout(timeout time.Duration) *MultiThreadDownloader { + d.timeout = timeout + return d +} + // WithoutProxy indicates not use HTTP proxy func (d *MultiThreadDownloader) WithoutProxy(noProxy bool) *MultiThreadDownloader { d.noProxy = noProxy @@ -60,7 +68,7 @@ func (d *MultiThreadDownloader) Download(targetURL, targetFilePath string, threa var total int64 var rangeSupport bool if total, rangeSupport, err = DetectSizeWithRoundTripper(targetURL, targetFilePath, d.showProgress, - d.noProxy, d.insecureSkipVerify, d.roundTripper); rangeSupport && err != nil { + d.noProxy, d.insecureSkipVerify, d.roundTripper, d.timeout); rangeSupport && err != nil { return } @@ -111,7 +119,7 @@ func (d *MultiThreadDownloader) Download(targetURL, targetFilePath string, threa downloader.WithoutProxy(d.noProxy). WithRoundTripper(d.roundTripper). WithInsecureSkipVerify(d.insecureSkipVerify). - WithContext(ctx) + WithContext(ctx).WithTimeout(d.timeout) if downloadErr := downloader.DownloadWithContinue(targetURL, output, int64(index), start, end, d.showProgress); downloadErr != nil { fmt.Println(downloadErr) @@ -120,8 +128,9 @@ func (d *MultiThreadDownloader) Download(targetURL, targetFilePath string, threa } wg.Wait() - ProgressIndicator{}.Close() + // ProgressIndicator{}.Close() if canceled { + err = fmt.Errorf("download process canceled") return } @@ -153,6 +162,7 @@ func (d *MultiThreadDownloader) Download(targetURL, targetFilePath string, threa downloader.WithoutProxy(d.noProxy) downloader.WithRoundTripper(d.roundTripper) downloader.WithInsecureSkipVerify(d.insecureSkipVerify) + downloader.WithTimeout(d.timeout) err = downloader.DownloadWithContinue(targetURL, targetFilePath, -1, 0, 0, true) d.suggestedFilename = downloader.GetSuggestedFilename() } diff --git a/pkg/net/progress.go b/pkg/net/progress.go index 712f7b5..3e0ef7b 100644 --- a/pkg/net/progress.go +++ b/pkg/net/progress.go @@ -2,8 +2,12 @@ package net import ( "fmt" - "github.com/gosuri/uiprogress" "io" + "os" + "sync" + + "github.com/k0kubun/go-ansi" + "github.com/schollz/progressbar/v3" ) // ProgressIndicator hold the progress of io operation @@ -14,58 +18,58 @@ type ProgressIndicator struct { // bytes.Buffer Total float64 - count float64 - bar *uiprogress.Bar + line int + bar *progressbar.ProgressBar } -var process *uiprogress.Progress +var line int = 0 +var currentLine int = 0 +var guard sync.Mutex = sync.Mutex{} // Init set the default value for progress indicator func (i *ProgressIndicator) Init() { - // start rendering - if process == nil { - process = uiprogress.New() - process.Start() - } - i.bar = process.AddBar(100) // Add a new bar - - // optionally, append and prepend completion and elapsed time - i.bar.AppendCompleted() - //i.bar.PrependElapsed() - - if i.Title != "" { - i.bar.PrependFunc(func(_ *uiprogress.Bar) string { - return fmt.Sprintf("%s: ", i.Title) - }) - } + i.line = line + line++ + i.bar = progressbar.NewOptions64(int64(i.Total), + progressbar.OptionSetWriter(ansi.NewAnsiStdout()), + progressbar.OptionEnableColorCodes(true), + progressbar.OptionShowBytes(true), + progressbar.OptionSetWidth(10), + progressbar.OptionFullWidth(), + progressbar.OptionSetDescription(fmt.Sprintf("[cyan][reset] %s", i.Title)), + progressbar.OptionSetTheme(progressbar.Theme{ + Saucer: "[green]=[reset]", + SaucerHead: "[green]>[reset]", + SaucerPadding: " ", + BarStart: "[", + BarEnd: "]", + })) } // Close shutdowns the ui process func (i ProgressIndicator) Close() { - if process != nil { - process.Stop() - process = nil - } + _ = i.bar.Close() } // Write writes the progress func (i *ProgressIndicator) Write(p []byte) (n int, err error) { - n, err = i.Writer.Write(p) - i.setBar(n) + guard.Lock() + defer guard.Unlock() + bias := currentLine - i.line + currentLine = i.line + if bias > 0 { + // move up + fmt.Fprintf(os.Stdout, "\r\033[%dA", bias) + } else if bias < 0 { + // move down + fmt.Fprintf(os.Stdout, "\r\033[%dB", -bias) + } + n, err = io.MultiWriter(i.Writer, i.bar).Write(p) return } // Read reads the progress func (i *ProgressIndicator) Read(p []byte) (n int, err error) { - n, err = i.Reader.Read(p) - i.setBar(n) + n, err = io.MultiReader(i.Reader, i.bar).Read(p) return } - -func (i *ProgressIndicator) setBar(n int) { - i.count += float64(n) - - if i.bar != nil { - _ = i.bar.Set((int)(i.count * 100 / i.Total)) - } -} diff --git a/pkg/net/retry_client.go b/pkg/net/retry_client.go index c8e2d02..8013f3f 100644 --- a/pkg/net/retry_client.go +++ b/pkg/net/retry_client.go @@ -3,6 +3,7 @@ package net import ( "net/http" "net/url" + "strings" ) // RetryClient is the wrap of http.Client @@ -12,12 +13,24 @@ type RetryClient struct { currentAttempts int } +// NewRetryClient creates the instance of RetryClient +func NewRetryClient(client http.Client) *RetryClient { + return &RetryClient{ + Client: client, + MaxAttempts: 3, + } +} + // Do is the wrap of http.Client.Do func (c *RetryClient) Do(req *http.Request) (rsp *http.Response, err error) { rsp, err = c.Client.Do(req) - if _, ok := err.(*url.Error); ok { + // fmt.Println("client error", err, c.Client.Timeout, reflect.TypeOf(err)) + + if _, ok := err.(*url.Error); ok && !strings.Contains(err.Error(), "context canceled") { + // fmt.Println("retry", c.currentAttempts, c.MaxAttempts) if c.currentAttempts < c.MaxAttempts { c.currentAttempts++ + // fmt.Println("try", c.currentAttempts) return c.Do(req) } }