Skip to content

Commit be921af

Browse files
author
Shota Sawada
committed
分割ダウンローダーを実装
テストとキャンセル処理は未実装
1 parent 1da3995 commit be921af

File tree

7 files changed

+294
-0
lines changed

7 files changed

+294
-0
lines changed

Diff for: kadai3-2/sawadashota/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v1.0.11.tar.gz

Diff for: kadai3-2/sawadashota/README.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
分割ダウンローダー
2+
===
3+
4+
Download
5+
---
6+
7+
```bash
8+
$ go run main.go ${URL}
9+
```
10+
11+
example
12+
13+
```bash
14+
$ go run main.go https://github.com/sawadashota/gocmd/archive/v1.0.11.tar.gz
15+
```
16+
17+
### Options
18+
19+
- `-p`: ダウンロードプロセス数(デフォルトはコア数)
20+
- `-t`: タイムアウト(デフォルトは10秒)
21+
- `-o`: 出力先(デフォルトはURLのファイル名)

Diff for: kadai3-2/sawadashota/download/download.go

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package download
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"io"
7+
"net/url"
8+
"os"
9+
"time"
10+
)
11+
12+
type Downloader struct {
13+
url *url.URL
14+
Option
15+
Data
16+
}
17+
18+
type Data struct {
19+
filesize uint
20+
data [][]byte
21+
}
22+
23+
type Option interface {
24+
Proc() int
25+
Timeout() time.Duration
26+
Writer() io.Writer
27+
Output() string
28+
}
29+
30+
// New Downloader
31+
func New(u *url.URL, opts Option) *Downloader {
32+
return &Downloader{
33+
url: u,
34+
Option: opts,
35+
Data: Data{},
36+
}
37+
}
38+
39+
// URL getter
40+
func (d *Downloader) URL() *url.URL {
41+
return d.url
42+
}
43+
44+
// Run download
45+
func (d *Downloader) Run() error {
46+
ctx, cancel := context.WithCancel(context.Background())
47+
defer cancel()
48+
49+
filesize, err := d.FetchFileSize(d.url.String())
50+
if err != nil {
51+
return err
52+
}
53+
54+
d.SetFileSize(filesize)
55+
d.data = make([][]byte, d.Proc())
56+
57+
eg := d.get(ctx)
58+
if err := eg.Wait(); err != nil {
59+
return err
60+
}
61+
62+
body := d.Merge()
63+
64+
f, err := os.Create(d.Output())
65+
defer f.Close()
66+
67+
f.Write(body)
68+
69+
return nil
70+
}
71+
72+
// SetFileSize is filesize setter
73+
func (d *Data) SetFileSize(size uint) {
74+
d.filesize = size
75+
}
76+
77+
// Merge data slice
78+
func (d *Data) Merge() []byte {
79+
return bytes.Join(d.data, []byte(""))
80+
}
81+
82+
// String return merged data
83+
func (d *Data) String() string {
84+
return string(d.Merge())
85+
}

Diff for: kadai3-2/sawadashota/download/request.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package download
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io/ioutil"
7+
"net/http"
8+
9+
"golang.org/x/sync/errgroup"
10+
)
11+
12+
type Range struct {
13+
low int
14+
high int
15+
proc int
16+
}
17+
18+
func NewRange(filesize uint, procs, proc int) *Range {
19+
split := int(filesize) / procs
20+
return &Range{
21+
low: split * (proc - 1),
22+
high: split * proc,
23+
}
24+
}
25+
26+
// Low getter
27+
func (r *Range) Low() int {
28+
return r.low
29+
}
30+
31+
// High getter
32+
func (r *Range) High() int {
33+
return r.high
34+
}
35+
36+
// FetchFileSize fetch content length and set filesize
37+
func (d *Data) FetchFileSize(URL string) (uint, error) {
38+
resp, err := http.Head(URL)
39+
40+
if err != nil {
41+
return 0, err
42+
}
43+
44+
return uint(resp.ContentLength), nil
45+
}
46+
47+
// Get contents concurrently
48+
func (d *Downloader) get(ctx context.Context) *errgroup.Group {
49+
eg, ctx := errgroup.WithContext(ctx)
50+
51+
for i := 0; i < d.Proc(); i++ {
52+
i := i
53+
r := NewRange(d.filesize, d.Proc(), i+1)
54+
55+
eg.Go(func() error {
56+
req, err := http.NewRequest(http.MethodGet, d.URL().String(), nil)
57+
if err != nil {
58+
return err
59+
}
60+
61+
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", r.Low(), r.High()))
62+
req = req.WithContext(ctx)
63+
64+
resp, err := http.DefaultClient.Do(req)
65+
if err != nil {
66+
return err
67+
}
68+
69+
body, err := ioutil.ReadAll(resp.Body)
70+
defer resp.Body.Close()
71+
if err != nil {
72+
return err
73+
}
74+
75+
d.data[i] = body
76+
return nil
77+
})
78+
}
79+
80+
return eg
81+
}

Diff for: kadai3-2/sawadashota/main.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import (
4+
"io"
5+
"log"
6+
"os"
7+
8+
"fmt"
9+
"time"
10+
11+
"github.com/gopherdojo/dojo2/kadai3-2/sawadashota/download"
12+
"github.com/gopherdojo/dojo2/kadai3-2/sawadashota/option"
13+
)
14+
15+
var writer io.Writer
16+
17+
func init() {
18+
writer = os.Stdout
19+
}
20+
21+
func main() {
22+
opts, u, err := option.Parse(writer)
23+
24+
if err != nil {
25+
log.Fatal(err)
26+
}
27+
28+
d := download.New(u, opts)
29+
start := time.Now()
30+
d.Run()
31+
fmt.Fprintf(d.Writer(), "Duration %f seconds\n", time.Since(start).Seconds())
32+
}

Diff for: kadai3-2/sawadashota/makefile

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
run:
2+
go run main.go https://github.com/sawadashota/gocmd/archive/v1.0.11.tar.gz
3+
unzip-gz:
4+
tar -zxvf v1.0.11.tar.gz

Diff for: kadai3-2/sawadashota/option/option.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package option
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"io"
7+
"net/url"
8+
"path/filepath"
9+
"runtime"
10+
"time"
11+
)
12+
13+
type Option struct {
14+
proc int
15+
timeout int
16+
output string
17+
writer io.Writer
18+
}
19+
20+
// DefaultTimeout is default value for request timeout
21+
const DefaultTimeout = 10
22+
23+
// Parse CLI options
24+
func Parse(w io.Writer) (*Option, *url.URL, error) {
25+
opts := &Option{
26+
writer: w,
27+
}
28+
29+
flag.IntVar(&opts.proc, "p", runtime.NumCPU(), "Parallelism")
30+
flag.IntVar(&opts.timeout, "t", DefaultTimeout, "Timeout")
31+
flag.StringVar(&opts.output, "o", "", "output")
32+
33+
flag.Parse()
34+
35+
if len(flag.Args()) < 1 {
36+
return nil, nil, fmt.Errorf("args need url")
37+
}
38+
39+
u, err := url.Parse(flag.Arg(0))
40+
41+
if err != nil {
42+
return nil, nil, err
43+
}
44+
45+
if opts.output == "" {
46+
opts.output = filepath.Base(u.Path)
47+
}
48+
49+
return opts, u, nil
50+
}
51+
52+
// Proc is proc getter
53+
func (o *Option) Proc() int {
54+
return o.proc
55+
}
56+
57+
// Timeout is timeout getter
58+
func (o *Option) Timeout() time.Duration {
59+
return time.Duration(o.timeout) * time.Second
60+
}
61+
62+
// Writer
63+
func (o *Option) Writer() io.Writer {
64+
return o.writer
65+
}
66+
67+
// Output path
68+
func (o *Option) Output() string {
69+
return o.output
70+
}

0 commit comments

Comments
 (0)