Skip to content

kadai3-2-en-ken #39

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

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/gopherdojo/dojo6

go 1.12

require (
github.com/google/go-cmp v0.3.1
github.com/pkg/errors v0.8.1
golang.org/x/sync v0.0.0-20190423024810-112230192c58
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
39 changes: 39 additions & 0 deletions kadai3-2/en-ken/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 課題3-2

## 分割ダウンローダを作ろう

- Rangeアクセスを用いる
- いくつかのゴルーチンでダウンロードしてマージする
- エラー処理を工夫する
- golang.org/x/sync/errgroupパッケージなどを使ってみる
- キャンセルが発生した場合の実装を行う

## 使い方

```
go get github.com/gopherdojo/dojo6/kadai3-2/en-ken/dl-mgr
div -n [goroutineの並列数(デフォルト:5)] -o [保存ファイル名(デフォルト:リモート名)] URL
```

## やったこと

1. HEADリクエストしてAccept-Rangesヘッダの有無確認
1. Accept-Rangesがあった場合、
1. 任意の分割数に応じて、goroutineごとの割当範囲を決めて、各goroutineでRange GET
1. 部分ファイルに出力
1. 全部ダウンロードできたら、ファイルをマージして部分ファイル削除

## 工夫した点

- 1つのgoroutineがダウンロードするデータが1MBを超えた場合、1MBごとにファイル出力する。
- Rangeアクセスに対応していなかったら普通にダウンロードする。
- すでにダウンロード済のデータがあった場合(そのパートのファイルがあった場合)、それを再利用する。

## 困っていること

- `go test`のやり方がいまいちわかっていない。依存関係の解決の仕方が理解に至っていない。
- `go test *.go`だと`export_test.go`が解決できない
- `go test ./`だと解決できる
- `go test ./...`すると`utils`以下がimport cycleで失敗してしまう
- `go test ./utils/*`だと`utils`だと問題ない
- `go test -cover ./utils/*`でちゃんとカバレッジがどれない
124 changes: 124 additions & 0 deletions kadai3-2/en-ken/dl-manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package divdl

import (
"fmt"
"os"

"golang.org/x/sync/errgroup"

"github.com/gopherdojo/dojo6/kadai3-2/en-ken/utils"
"github.com/pkg/errors"
)

// DlRange expresses range of Range request
type DlRange struct {
id int
from int64
to int64
}

var maxRangeSize = int64(1024 * 1024) //1MB

func divideIntoRanges(contentLength int64, numOfDivision int) (numOfRanges int, rngs [][]*DlRange) {
rngs = make([][]*DlRange, numOfDivision)

var rngSize int64
if contentLength%int64(numOfDivision) == 0 {
rngSize = contentLength / int64(numOfDivision)
} else {
rngSize = (contentLength + int64(numOfDivision)) / int64(numOfDivision)
}
if maxRangeSize < rngSize {
rngSize = maxRangeSize
}

for j, pos := 0, int64(0); pos < contentLength; j++ {
for i := 0; i < numOfDivision && pos < contentLength; i++ {
id := j*numOfDivision + i
numOfRanges = id + 1
// Last range
if contentLength-pos < rngSize {
rngs[i] = append(rngs[i], &DlRange{
id: id,
from: int64(id) * rngSize,
to: contentLength - 1,
})
pos = contentLength
break
}

rngs[i] = append(rngs[i], &DlRange{
id: id,
from: int64(id) * rngSize,
to: int64(id+1)*rngSize - 1,
})
pos += rngSize
}
}
return
}

// Do manages separately downloading.
func Do(url string, fileName string, numOfDivision int) error {
req, err := utils.NewRequest(url)
if err != nil {
return errors.WithStack(err)
}

// Range request is not accepted
if !req.CanAcceptRangeRequest() {
data, err := req.Download()
if err != nil {
return err
}

return utils.SaveFile(fileName, data)
}

// Range request is accepted
n, rngs := divideIntoRanges(req.GetContentLength(), numOfDivision)

var g errgroup.Group
for _, rList := range rngs {
rList := rList
g.Go(func() error {
for _, r := range rList {
tmpFileName := createPartialFileName(fileName, r.id)

// Pass downloading if tmpFileName exists.
if fileExists(tmpFileName) {
continue
}

data, err := req.DownloadPartially(r.from, r.to)
if err != nil {
return err
}
if err := utils.SaveFile(tmpFileName, data); err != nil {
return err
}
fmt.Printf("%v saved\n", tmpFileName)
}
return nil
})
}

if err := g.Wait(); err != nil {
return errors.WithStack(err)
}

files := make([]string, 0)
for i := 0; i < n; i++ {
files = append(files, createPartialFileName(fileName, i))
}
return utils.MergeFiles(files, fileName)
}

func createPartialFileName(fileName string, suffix int) string {
return fmt.Sprintf("%v.%v", fileName, suffix)
}

func fileExists(fileName string) bool {
_, err := os.Stat(fileName)
return err == nil
}
Loading