generated from gopherdojo/template
-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Kadai3 2 kimuson13 #50
Open
kimuson13
wants to merge
21
commits into
gopherdojo:master
Choose a base branch
from
kimuson13:kadai3-2-kimuson13
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
6c80e24
Added .gitignore
75223bc
Added main.go as a goal of this task
a142292
Added options and struct parse
5d654d7
Added option and fundmental
4d3ec17
added
4888c26
Reset
22bcb8f
Change TODO
c7ab1c3
tryed to send GET request with golang
0c3dac6
Tryed to confirm filesize by HEAD
c21abf4
Added some experiment
31aaa09
Create foundation of parallel download
01c3e4e
Added options and struct and fix bags
4c36882
Remove downloaded file for operation check
63179c1
Added interrupt package
57737fd
Changed some detail
41e581c
Added descriptions
28092ba
Added option_test and changed option.go to testable
e530f54
Added working for removing tempdir when error happens
bc416d7
Added README
e5a2020
Refactoring the code that is pointed
5fce93a
Added Test and testdata
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# 分割ダウンローダ | ||
Rangeアクセスを用いて、ダウンロードする。 | ||
## 使用方法 | ||
まずはkimuson13のディレクトリに移動する。 | ||
その後、 | ||
```go run main.go`` | ||
もしくは、 | ||
```go build main.go``` | ||
```./main``` | ||
で実行可能。 | ||
## オプション | ||
```-p <number>``` | ||
分割数を指定できる。 | ||
```-t <number of second>``` | ||
タイムアウトが起きる時間を指定できる。 | ||
```-f <file name>``` | ||
ダウンロード後のファイル名を指定できる。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
TODO | ||
=== | ||
- [x] GET requestを送ってみる | ||
- [x] ダウンロードしたものをファイルにする | ||
- [x] HEADでコンテンツのサイズを確認する | ||
- [x] range requestの実装 | ||
- [x] 分割ダウンロードの実装 | ||
- [x] timeoutの実装 | ||
|
||
- [x] エラー処理を工夫する。(道場の資料39を参考にする。) | ||
- [x] キャンセルが発生した場合の実装(道場の資料48を参考にする。) | ||
- [x] 型とメソッドにまとめる | ||
- [x] 入力された情報が正しいか判断する(govalidateのIsURLが使えそう。) | ||
|
||
- [x] オプションのstructを作る(分割数、拡張子、タイムアウト、ダウンロードしたファイルの名前) | ||
- [x] オプションに入力された引数が正しいか判断する。 | ||
|
||
- [x] 各関数の説明を書く | ||
- [] レビューで指摘してもらった箇所の修正 | ||
- [] テスト書く |
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,297 @@ | ||
package download | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"log" | ||
"net/http" | ||
"os" | ||
"time" | ||
|
||
"golang.org/x/sync/errgroup" | ||
) | ||
|
||
var ( | ||
ErrValidateParallelism = errors.New("the parallel number needs to be bigger than 1") | ||
ErrValidateTimeout = errors.New("the timeout needs to be bigeer than 0") | ||
ErrNotIncludeRangeAccess = errors.New("the response does not include Accept-Range header") | ||
ErrNotContent = errors.New("it is not content") | ||
ErrNoContentLength = errors.New("it does not have Content-Length") | ||
) | ||
|
||
// Donwnloader struct | ||
type Downloader struct { | ||
parallel int | ||
timeout int | ||
filename string | ||
url string | ||
} | ||
|
||
// Rnage struct | ||
type Range struct { | ||
low int | ||
high int | ||
number int | ||
} | ||
|
||
// New for download package | ||
func New(opts *Options) *Downloader { | ||
return &Downloader{ | ||
parallel: opts.Parallel, | ||
timeout: opts.Timeout, | ||
filename: opts.Filename, | ||
url: opts.URL, | ||
} | ||
} | ||
|
||
func CreateTempdir() error { | ||
if err := os.Mkdir("tempdir", 0755); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func DeleteTempdir() { | ||
err := os.RemoveAll("tempdir") | ||
if err != nil { | ||
log.Println("can't remove the tempdir", err) | ||
} | ||
} | ||
|
||
// Run excecute method in download package | ||
func (d *Downloader) Run(ctx context.Context) error { | ||
if err := d.Validation(); err != nil { | ||
return err | ||
} | ||
|
||
if err := CreateTempdir(); err != nil { | ||
return err | ||
} | ||
defer DeleteTempdir() | ||
|
||
contentLength, err := d.CheckContentLength(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := d.Download(contentLength, ctx); err != nil { | ||
return err | ||
} | ||
|
||
if err := d.MergeFile(d.parallel, contentLength); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
//Preparate method define the variables to Donwload | ||
func (d *Downloader) Validation() error { | ||
if d.parallel <= 1 { | ||
return ErrValidateParallelism | ||
} | ||
|
||
if d.timeout < 1 { | ||
return ErrValidateTimeout | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// CheckContentLength method gets the Content-Length want to download | ||
func (d *Downloader) CheckContentLength(ctx context.Context) (int, error) { | ||
if _, err := fmt.Fprintf(os.Stdout, "Start HEAD request to check Content-Length\n"); err != nil { | ||
return 0, err | ||
} | ||
|
||
req, err := http.NewRequest("HEAD", d.url, nil) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
req = req.WithContext(ctx) | ||
|
||
res, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
acceptRange := res.Header.Get("Accept-Ranges") | ||
if _, err := fmt.Fprintf(os.Stdout, "got: Accept-Ranges: %s\n", acceptRange); err != nil { | ||
return 0, err | ||
} | ||
|
||
if acceptRange == "" { | ||
return 0, ErrNotIncludeRangeAccess | ||
} | ||
|
||
if acceptRange != "bytes" { | ||
return 0, ErrNotContent | ||
} | ||
|
||
contentLength := int(res.ContentLength) | ||
if _, err := fmt.Fprintf(os.Stdout, "got: Content-Length: %v\n", contentLength); err != nil { | ||
return 0, err | ||
} | ||
|
||
if contentLength < 1 { | ||
return 0, ErrNoContentLength | ||
} | ||
|
||
return contentLength, nil | ||
} | ||
|
||
// Download method does split-download with goroutine | ||
func (d *Downloader) Download(contentLength int, ctx context.Context) error { | ||
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.timeout)*time.Second) | ||
defer cancel() | ||
|
||
parallel := d.parallel | ||
split := contentLength / parallel | ||
grp, ctx := errgroup.WithContext(ctx) | ||
for i := 0; i < parallel; i++ { | ||
r := MakeRange(i, split, parallel, contentLength) | ||
tempfile := fmt.Sprintf("tempdir/tempfile.%d.%d", parallel, r.number) | ||
filename, err := CreateTempfile(tempfile) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
grp.Go(func() error { | ||
err := Requests(r, d.url, filename) | ||
return err | ||
}) | ||
} | ||
|
||
if err := grp.Wait(); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func CreateTempfile(name string) (string, error) { | ||
file, err := os.Create(name) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
defer func() { | ||
err := file.Close() | ||
if err != nil { | ||
log.Println("can't close the "+name, err) | ||
} | ||
}() | ||
|
||
return file.Name(), nil | ||
} | ||
|
||
// MakeRange function distributes Content-Length for split-download | ||
func MakeRange(i, split, parallel, contentLength int) Range { | ||
low := split * i | ||
high := low + split - 1 | ||
if i == parallel-1 { | ||
high = contentLength | ||
} | ||
|
||
return Range{ | ||
low: low, | ||
high: high, | ||
number: i, | ||
} | ||
} | ||
|
||
// Requests function sends GET request | ||
func Requests(r Range, url, filename string) error { | ||
req, err := http.NewRequest("GET", url, nil) | ||
if err != nil { | ||
return err | ||
} | ||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", r.low, r.high)) | ||
if _, err := fmt.Fprintf(os.Stdout, "start GET request: bytes=%d-%d\n", r.low, r.high); err != nil { | ||
return err | ||
} | ||
|
||
res, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer func() { | ||
err := res.Body.Close() | ||
if err != nil { | ||
log.Println("the response body can't close", err) | ||
} | ||
}() | ||
|
||
if res.StatusCode != http.StatusPartialContent { | ||
return fmt.Errorf("unexpected status code: %d", res.StatusCode) | ||
} | ||
|
||
output, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { | ||
err := output.Close() | ||
if err != nil { | ||
log.Println("can't close the tempfile", err) | ||
} | ||
}() | ||
|
||
_, err = io.Copy(output, res.Body) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// MergeFile method merges tempfiles to new file | ||
func (d *Downloader) MergeFile(parallel, contentLength int) error { | ||
fmt.Println("\nmerging files...") | ||
filename := d.filename | ||
fh, err := os.Create(filename) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer func() { | ||
err := fh.Close() | ||
if err != nil { | ||
log.Println("can't close the download file!", err) | ||
} | ||
}() | ||
|
||
for i := 0; i < parallel; i++ { | ||
if err := Merger(parallel, i, fh); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
fmt.Println("complete parallel donwload") | ||
return nil | ||
} | ||
|
||
func Merger(parallel, i int, fh *os.File) error { | ||
f := fmt.Sprintf("tempdir/tempfile.%d.%d", parallel, i) | ||
sub, err := os.Open(f) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = io.Copy(fh, sub) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer func() { | ||
err := sub.Close() | ||
if err != nil { | ||
log.Println("can't close the "+f, err) | ||
} | ||
}() | ||
return nil | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Downloadでtempdirの作成、MergeFileでtempdirの削除を行なっていますが、エラーになったときにtmpdirが消されないケースがあるので、ここで以下のように作成の関数と削除の関数を作り削除の関数をdeferを使って呼ぶと良いです