Skip to content

Commit ededca3

Browse files
committed
kadai3-2-nKumaya
1 parent 1153a9d commit ededca3

File tree

3 files changed

+244
-0
lines changed

3 files changed

+244
-0
lines changed

kadai3-2/nKumaya/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# 課題3-2 分割ダウンロードを行う
2+
3+
## 分割ダウンロードの説明
4+
5+
```
6+
go run main.go [ターゲットURL]
7+
```
8+
対象URLをCPU数に応じて分割ダウンロードを行う。
9+
対象URLのステータスコードが200以外のときはエラーを返す。
10+

kadai3-2/nKumaya/kget/kget.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package kget
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"net/url"
9+
"os"
10+
"runtime"
11+
"strconv"
12+
"strings"
13+
14+
"golang.org/x/sync/errgroup"
15+
)
16+
17+
type Client struct {
18+
URL *url.URL
19+
HTTPClient *http.Client
20+
ContentLength, RangeSize int
21+
Filename string
22+
}
23+
24+
func NewClient(urlString string) (*Client, error) {
25+
var err error
26+
client := &Client{}
27+
client.URL, err = url.Parse(urlString)
28+
splitedPath := strings.Split(client.URL.Path, "/")
29+
client.Filename = splitedPath[len(splitedPath)-1]
30+
if err != nil {
31+
return nil, err
32+
}
33+
client.HTTPClient = &http.Client{}
34+
35+
if err = client.setHeadContent(); err != nil {
36+
return nil, err
37+
}
38+
return client, nil
39+
}
40+
41+
// レスポンスヘッダに Accept-Ranges が含まれている場合は分割, ない場合は分割しない
42+
func (c *Client) setHeadContent() error {
43+
req, err := http.NewRequest("HEAD", c.URL.String(), nil)
44+
if err != nil {
45+
return err
46+
}
47+
client := &http.Client{}
48+
res, err := client.Do(req)
49+
if err != nil {
50+
return err
51+
}
52+
defer res.Body.Close()
53+
c.ContentLength, err = strconv.Atoi(res.Header["Content-Length"][0])
54+
if err != nil {
55+
return err
56+
}
57+
if strings.Contains("bytes", res.Header["Accept-Ranges"][0]) {
58+
// CPU数に応じて並行処理を行う
59+
c.RangeSize = runtime.NumCPU()
60+
} else {
61+
c.RangeSize = 1
62+
}
63+
return nil
64+
}
65+
66+
func (c *Client) newRequest(ctx context.Context, index int) (*http.Request, error) {
67+
req, err := http.NewRequest("GET", c.URL.String(), nil)
68+
if err != nil {
69+
return req, nil
70+
}
71+
72+
req = req.WithContext(ctx)
73+
req.Header.Add("User-Agent", "kget")
74+
var chank int
75+
chank = c.ContentLength / c.RangeSize
76+
low := chank * index
77+
high := chank*(index+1) - 1
78+
if index+1 == c.RangeSize && high+1 != c.ContentLength {
79+
high = c.ContentLength - 1
80+
}
81+
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", low, high))
82+
return req, nil
83+
}
84+
85+
func (c *Client) save(ctx context.Context, index int) error {
86+
req, err := c.newRequest(ctx, index)
87+
if err != nil {
88+
return err
89+
}
90+
file, err := os.Create(c.Filename + "_" + strconv.Itoa(index))
91+
if err != nil {
92+
return err
93+
}
94+
res, err := c.HTTPClient.Do(req)
95+
if err != nil {
96+
return err
97+
}
98+
_, err = io.Copy(file, res.Body)
99+
if err != nil {
100+
return err
101+
}
102+
file.Close()
103+
return nil
104+
}
105+
106+
func (c *Client) merge(ctx context.Context) error {
107+
file, err := os.Create(c.Filename)
108+
if err != nil {
109+
return err
110+
}
111+
for i := 0; i < c.RangeSize; i++ {
112+
subFileName := c.Filename + "_" + strconv.Itoa(i)
113+
subFile, err := os.Open(subFileName)
114+
if err != nil {
115+
return err
116+
}
117+
_, err = io.Copy(file, subFile)
118+
subFile.Close()
119+
if err != nil {
120+
return err
121+
}
122+
}
123+
124+
return nil
125+
}
126+
127+
func (c *Client) Download(ctx context.Context) error {
128+
eg := errgroup.Group{}
129+
for i := 0; i < c.RangeSize; i++ {
130+
i := i
131+
eg.Go(func() error {
132+
return c.save(ctx, i)
133+
})
134+
}
135+
if err := eg.Wait(); err != nil {
136+
c.DeleteFiles()
137+
return err
138+
}
139+
err := c.merge(ctx)
140+
c.DeleteFiles()
141+
if err != nil {
142+
return err
143+
}
144+
145+
return nil
146+
}
147+
148+
func (c *Client) DeleteFiles() error {
149+
for i := 0; i < c.RangeSize; i++ {
150+
subFileName := c.Filename + "_" + strconv.Itoa(i)
151+
if fi, _ := os.Stat(subFileName); fi != nil {
152+
if err := os.Remove(subFileName); err != nil {
153+
return err
154+
}
155+
}
156+
}
157+
return nil
158+
}

kadai3-2/nKumaya/main.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"os"
10+
"os/signal"
11+
12+
"github.com/dojo3/kadai3-2/nKumaya/kget"
13+
)
14+
15+
const (
16+
ExitCodeOK = iota
17+
ExitCodeParseFlagError
18+
ExitCodeInvalidUrlError
19+
ExitCodeCreateHTTPClient
20+
ExitCodeErrorDownload
21+
ExitCodeErrorCansel
22+
)
23+
24+
type CLI struct {
25+
outStream, errStream io.Writer
26+
}
27+
28+
func (c *CLI) Run(args []string) int {
29+
flags := flag.NewFlagSet("kget", flag.ContinueOnError)
30+
if err := flags.Parse(args[1:]); err != nil {
31+
return ExitCodeParseFlagError
32+
}
33+
url := args[1]
34+
fmt.Fprintln(c.outStream, "Checking now", url)
35+
response, err := http.Get(url)
36+
fmt.Println(response.StatusCode)
37+
if response.StatusCode != 200 {
38+
return ExitCodeInvalidUrlError
39+
}
40+
client, err := kget.NewClient(url)
41+
if err != nil {
42+
return ExitCodeCreateHTTPClient
43+
}
44+
bc := context.Background()
45+
ctx, cancel := context.WithCancel(bc)
46+
ch := make(chan os.Signal, 1)
47+
signal.Notify(ch, os.Interrupt)
48+
defer func() {
49+
signal.Stop(ch)
50+
cancel()
51+
}()
52+
fmt.Fprintln(c.outStream, "Download start", url)
53+
go func() error {
54+
select {
55+
case <-ch:
56+
cancel()
57+
return nil
58+
case <-ctx.Done():
59+
if err = client.DeleteFiles(); err != nil {
60+
return err
61+
}
62+
return nil
63+
}
64+
}()
65+
err = client.Download(ctx)
66+
if err != nil {
67+
return ExitCodeErrorDownload
68+
}
69+
fmt.Fprintln(c.outStream, "Complite")
70+
return ExitCodeOK
71+
}
72+
73+
func main() {
74+
cli := &CLI{os.Stdout, os.Stderr}
75+
os.Exit(cli.Run(os.Args))
76+
}

0 commit comments

Comments
 (0)