Skip to content

Commit b0b49ec

Browse files
committed
[rget] add parallelDownload
1 parent 8298b44 commit b0b49ec

File tree

6 files changed

+181
-28
lines changed

6 files changed

+181
-28
lines changed

kadai3/imura81gt/rget/.go-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.13.4

kadai3/imura81gt/rget/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,42 @@ Command
77
```
88
go run cmd/rget/main.go https://upload.wikimedia.org/wikipedia/commons/1/16/Notocactus_minimus.jpg
99
```
10+
11+
Theme
12+
-----------------------------------------
13+
14+
分割ダウンロードを行う
15+
16+
元ネタ: https://qiita.com/codehex/items/d0a500ac387d39a34401
17+
18+
- [x]Rangeアクセスを用いる
19+
- [ ]いくつかのゴルーチンでダウンロードしてマージする
20+
- [x]エラー処理を工夫する
21+
- [x]golang.org/x/sync/errgroupパッケージなどを使ってみる
22+
- [x]キャンセルが発生した場合の実装を行う
23+
24+
ref: https://qiita.com/codehex/items/d0a500ac387d39a34401
25+
26+
27+
28+
Note.
29+
------------------------------------------
30+
31+
### Range Request
32+
33+
https://developer.mozilla.org/ja/docs/Web/HTTP/Range_requests
34+
35+
> Accept-Ranges が HTTP レスポンスに存在した場合 (そして値が "none" ではない場合)、サーバーは範囲リクエストに対応しています。これは例えば、 HEAD リクエストを cURL で発行することで確認することができます。
36+
37+
38+
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Accept-Ranges
39+
40+
> Accept-Ranges: bytes
41+
> Accept-Ranges: none
42+
43+
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Range
44+
45+
> Range: <unit>=<range-start>-
46+
> Range: <unit>=<range-start>-<range-end>
47+
> Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
48+
> Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>

kadai3/imura81gt/rget/go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/gopherdojo/dojo7/kadai3/imura81gt/rget
2+
3+
go 1.13
4+
5+
require golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e

kadai3/imura81gt/rget/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
2+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

kadai3/imura81gt/rget/rget.go

Lines changed: 133 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,183 @@
11
package rget
22

33
import (
4+
"context"
45
"fmt"
6+
"io"
57
"net/http"
68
"os"
9+
"path"
10+
11+
"golang.org/x/sync/errgroup"
712
)
813

914
type Option struct {
10-
Concurrency int
11-
Url string
15+
Concurrency int
16+
URL string
17+
OutputDir string
18+
ContentLength int64
19+
Units Units
1220
}
1321

1422
type Unit struct {
15-
RangeStart int64
16-
RangeEnd int64
23+
RangeStart int64
24+
RangeEnd int64
25+
TempFileName string
1726
}
1827

1928
type Units []Unit
2029

2130
func Run(option Option) {
2231
fmt.Printf("%+v\n", option)
23-
cl, err := contentLength(option.Url)
32+
err := option.contentLength()
2433
if err != nil {
25-
fmt.Fprintln(os.Stderr, err)
26-
os.Exit(1)
34+
fmt.Errorf("%s", err)
2735
}
2836

29-
units := divide(cl, option.Concurrency)
37+
option.divide()
3038

31-
//TODO: check errors
32-
download(units)
39+
err = option.parallelDownload()
40+
if err != nil {
41+
fmt.Errorf("%s", err)
42+
}
3343

3444
}
3545

36-
func contentLength(url string) (int64, error) {
37-
resp, err := http.Head(url)
46+
func (o *Option) contentLength() error {
47+
//resp, err := http.Head(url)
48+
resp, err := http.Head(o.URL)
3849
if err != nil {
3950
fmt.Fprintln(os.Stderr, err)
40-
return 0, err
51+
//return 0, err
52+
return err
4153
}
4254

4355
if resp.Header.Get("Accept-Ranges") == "" {
44-
err := fmt.Errorf("This URL cannot support Ranges Requests")
56+
err := fmt.Errorf("%s URL cannot support Ranges Requests", o.URL)
4557
// fmt.Fprintln(os.Stderr, err)
46-
return resp.ContentLength, err
58+
//return resp.ContentLength, err
59+
return err
4760
}
4861
if resp.Header["Accept-Ranges"][0] == "none" {
49-
err := fmt.Errorf("This URL cannot support Ranges Requests")
62+
err := fmt.Errorf("%s cannot support Ranges Requests", o.URL)
5063
// fmt.Fprintln(os.Stderr, err)
51-
return resp.ContentLength, err
64+
//return resp.ContentLength, err
65+
return err
5266
}
5367
if resp.ContentLength == 0 {
54-
err := fmt.Errorf("This URL size is %i", resp.Header["Content-Length"][0])
68+
err := fmt.Errorf("%s size is %s", o.URL, resp.Header["Content-Length"][0])
5569
// fmt.Fprintln(os.Stderr, err)
56-
return resp.ContentLength, err
70+
//return resp.ContentLength, err
71+
return err
5772
}
5873

59-
return resp.ContentLength, nil
74+
o.ContentLength = resp.ContentLength
75+
//return resp.ContentLength, nil
76+
return err
6077
}
6178

62-
func divide(contentLength int64, concurrency int) Units {
79+
//func divide(contentLength int64, concurrency int) Units {
80+
func (o *Option) divide() {
6381
var units []Unit
6482

65-
sbyte := contentLength / int64(concurrency)
66-
for i := 0; i < concurrency; i++ {
83+
//sbyte := contentLength / int64(concurrency)
84+
sbyte := o.ContentLength / int64(o.Concurrency)
85+
86+
// for i := 0; i < concurrency; i++ {
87+
for i := 0; i < o.Concurrency; i++ {
6788
units = append(units, Unit{
68-
RangeStart: int64(i) * sbyte,
69-
RangeEnd: int64((i+1))*sbyte - 1,
89+
RangeStart: int64(i) * sbyte,
90+
RangeEnd: int64((i+1))*sbyte - 1,
91+
TempFileName: fmt.Sprintf("%d_%s", i, path.Base(o.URL)),
92+
})
93+
}
94+
95+
o.Units = units
96+
//return units
97+
}
98+
99+
// func download(units Units) {
100+
// filepath.Split()
101+
// fmt.Println(units)
102+
// }
103+
104+
func (o *Option) parallelDownload() error {
105+
fmt.Println("parallelDownload", o.Units)
106+
107+
eg, ctx := errgroup.WithContext(context.Background())
108+
for i := range o.Units {
109+
// https://godoc.org/golang.org/x/sync/errgroup#example-Group--Parallel
110+
// https://golang.org/doc/faq#closures_and_goroutines
111+
i := i
112+
eg.Go(func() error {
113+
return o.downloadWithContext(ctx, i)
70114
})
71115
}
72116

73-
return units
117+
if err := eg.Wait(); err != nil {
118+
o.clearCache()
119+
return err
120+
}
121+
122+
return nil
123+
}
124+
125+
func (o *Option) downloadWithContext(ctx context.Context, i int) error {
126+
ctx, cancel := context.WithCancel(ctx)
127+
defer cancel()
128+
129+
fmt.Printf("Downloading: %v %+v\n", i, o.Units[i])
130+
131+
//v1.13
132+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, o.URL, nil)
133+
if err != nil {
134+
return fmt.Errorf("Error: %v", err)
135+
}
136+
137+
// add range header
138+
fmt.Printf(fmt.Sprintf("bytes=%d-%d", o.Units[i].RangeStart, o.Units[i].RangeEnd))
139+
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", o.Units[i].RangeStart, o.Units[i].RangeEnd))
140+
141+
client := &http.Client{}
142+
resp, err := client.Do(req)
143+
defer resp.Body.Close()
144+
if err != nil {
145+
return fmt.Errorf("Error: %v", err)
146+
}
147+
148+
select {
149+
case <-ctx.Done():
150+
fmt.Printf("Done: %v %+v\n", i, o.Units[i])
151+
return nil
152+
default:
153+
fmt.Println("default:", i, o.Units[i])
154+
//return fmt.Errorf("Error: %v %+v", i, o.Units[i])
155+
}
156+
157+
w, err := os.Create(o.Units[i].TempFileName)
158+
if err != nil {
159+
return fmt.Errorf("Error: %v", err)
160+
}
161+
defer func() error {
162+
if err := w.Close(); err != nil {
163+
return fmt.Errorf("Error: %v", err)
164+
}
165+
return nil
166+
}()
167+
168+
_, err = io.Copy(w, resp.Body)
169+
if err != nil {
170+
return fmt.Errorf("Error: %v", err)
171+
}
172+
173+
return nil
174+
}
175+
176+
func (o *Option) conbine() error {
177+
return nil
74178
}
75179

76-
func download(units Units) {
77-
fmt.Println(units)
180+
func (o *Option) clearCache() error {
181+
//TODO: remove temporary files
182+
return nil
78183
}

kadai3/imura81gt/rget/rget_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package rget

0 commit comments

Comments
 (0)