-
Notifications
You must be signed in to change notification settings - Fork 0
Kadai3 2 gosagawa #58
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
vendor/* |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# kadai3-2 | ||
|
||
##分割ダウンロードを行う | ||
- Rangeアクセスを用いる | ||
- いくつかのゴルーチンでダウンロードしてマージする | ||
- エラー処理を工夫する | ||
- golang.org/x/sync/errgourpパッケージなどを使ってみる | ||
- キャンセルが発生した場合の実装を行う | ||
|
||
##install | ||
|
||
``` | ||
dep ensure | ||
``` | ||
|
||
##usage | ||
|
||
``` | ||
go run main.go | ||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"runtime" | ||
"strings" | ||
|
||
"github.com/pkg/errors" | ||
"golang.org/x/sync/errgroup" | ||
) | ||
|
||
func main() { | ||
|
||
url := os.Args[1] | ||
fmt.Println(url) | ||
|
||
//TODO check args | ||
|
||
//TODO check RangeAccess available | ||
|
||
d, err := NewDownloader(url) | ||
if err != nil { | ||
fmt.Printf("\ndownload initialize error. %v", err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 通常、お尻に |
||
os.Exit(1) | ||
} | ||
|
||
err = d.Download() | ||
if err != nil { | ||
fmt.Printf("\ndownload error. %v", err) | ||
os.Exit(1) | ||
} | ||
err = d.Merge() | ||
if err != nil { | ||
fmt.Printf("\ndownload merge error. %v", err) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
type Downloader struct { | ||
FileName string | ||
MaxProcess uint | ||
Workers []*worker | ||
} | ||
|
||
type worker struct { | ||
processId uint | ||
bytesToStartReading uint | ||
bytesToFinishReading uint | ||
resourceUrl string | ||
partFilePath string | ||
} | ||
|
||
func NewDownloader(url string) (*Downloader, error) { | ||
d := new(Downloader) | ||
d.FileName = getFileName(url) | ||
d.MaxProcess = uint(runtime.NumCPU()) | ||
|
||
res, err := http.Head(url) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
size := uint(res.ContentLength) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. uint を使うのは賢いですね! |
||
|
||
split := size / d.MaxProcess | ||
for i := uint(0); i < d.MaxProcess; i++ { | ||
w, err := NewWorker(d, size, i, split, url) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "initialize worker error") | ||
} | ||
d.Workers = append(d.Workers, w) | ||
} | ||
fmt.Fprintf(os.Stdout, "Download start from %s\n", url) | ||
return d, nil | ||
} | ||
|
||
func NewWorker(d *Downloader, size uint, i uint, split uint, url string) (*worker, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. この関数はpackage外部にexportするべきかどうか考えてみましょう(しなくてよさそう |
||
bytesToStartReading := split * i | ||
bytesToFinishReading := bytesToStartReading + split - 1 | ||
partFilePath := fmt.Sprintf("%s.%d", d.FileName, i) | ||
|
||
if i == d.MaxProcess-1 { | ||
bytesToFinishReading = size | ||
} | ||
w := &worker{ | ||
processId: i, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. プロセスIDと言われると別のものを想像するので、単に |
||
bytesToStartReading: bytesToStartReading, | ||
bytesToFinishReading: bytesToFinishReading, | ||
resourceUrl: url, | ||
partFilePath: partFilePath, | ||
} | ||
return w, nil | ||
} | ||
|
||
func (d *Downloader) Download() error { | ||
eg := errgroup.Group{} | ||
for _, worker := range d.Workers { | ||
w := worker | ||
eg.Go(func() error { | ||
return w.Request() | ||
}) | ||
} | ||
|
||
if err := eg.Wait(); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (d *Downloader) Merge() error { | ||
outputFilePath := fmt.Sprintf("%s", d.FileName) | ||
outputFile, err := os.Create(outputFilePath) | ||
if err != nil { | ||
return errors.Wrap(err, "failed to create merge file") | ||
} | ||
defer outputFile.Close() | ||
for i := uint(0); i < d.MaxProcess; i++ { | ||
partFilePath := fmt.Sprintf("%s.%d", outputFilePath, i) | ||
partFile, err := os.Open(partFilePath) | ||
if err != nil { | ||
return errors.Wrap(err, "failed to open part file") | ||
} | ||
io.Copy(outputFile, partFile) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. io.Copy はerrを返すので、無視せずにちゃんとチェックするようにしましょう。 errを無視している場合、それを怒ってくれるツールがあった気がします( |
||
partFile.Close() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Openした後に |
||
if err := os.Remove(partFilePath); err != nil { | ||
return errors.Wrap(err, "failed to remove a file") | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (w *worker) Request() error { | ||
res, err := w.MakeResponse() | ||
if err != nil { | ||
return errors.Wrap(err, fmt.Sprintf("failed to split get requests: %d", w.processId)) | ||
} | ||
defer res.Body.Close() | ||
output, err := os.Create(w.partFilePath) | ||
if err != nil { | ||
return errors.Wrap(err, fmt.Sprintf("failed to create file %s", w.partFilePath)) | ||
} | ||
defer output.Close() | ||
io.Copy(output, res.Body) | ||
return nil | ||
} | ||
|
||
func (w *worker) MakeResponse() (*http.Response, error) { | ||
req, err := http.NewRequest("GET", w.resourceUrl, nil) | ||
if err != nil { | ||
return nil, errors.Wrap(err, fmt.Sprintf("failed to split NewRequest for get: %d", w.processId)) | ||
} | ||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", w.bytesToStartReading, w.bytesToFinishReading)) | ||
return http.DefaultClient.Do(req) | ||
} | ||
|
||
func getFileName(resourceUrl string) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 自力で文字列操作するよりも、既存のものが既に準備されていないか調べるようにしましょう。 |
||
token := strings.Split(resourceUrl, "/") | ||
filename := token[len(token)-1] | ||
return filename | ||
} |
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.
この後に
TODO check args
とあるので自覚されている気もしますが、どのような引数が与えられた(与えられなかった)場合でもpanicにならないようにしましょう。