Skip to content

Kadai3 2 misonog #53

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 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ee9ad8d
Initialize Repository
misonog Apr 8, 2021
cbddf50
Create Makefile
misonog Apr 13, 2021
88b2d43
Add download func
misonog Apr 13, 2021
3af52b8
Add target directory option
misonog Apr 13, 2021
38f84cb
Add Data struct and func
misonog Apr 18, 2021
588096f
Add Utils interface
misonog Apr 18, 2021
8e744fc
Add Check func
misonog Apr 18, 2021
e4ba981
add pdownload constructor
misonog Apr 18, 2021
84fe8b8
Add pdownload test
misonog Apr 20, 2021
09d012a
Change test file name
misonog Apr 20, 2021
3ff17b7
Add Go sync
misonog Apr 21, 2021
72d57bd
Add assignment func
misonog Apr 21, 2021
ad5b30e
Add Download func
misonog Apr 22, 2021
54f2a64
Add MergeFiles method
misonog Apr 23, 2021
0664606
Add parseURL func
misonog Apr 27, 2021
7005d93
CLI setup
misonog Apr 28, 2021
ba7f971
Add SetDirName func
misonog Apr 30, 2021
6f18cc2
Add run test
misonog Apr 30, 2021
4ccd9be
Add tempdir for download
misonog Apr 30, 2021
01395e9
Add termination package for cancel process
misonog Apr 30, 2021
004ee1e
Add context for Ctrl + c
misonog Apr 30, 2021
bdada63
Add timeout setting
misonog Apr 30, 2021
c0d5713
Add testdata and Update README
misonog Apr 30, 2021
230579e
Update flag package parse to main function
misonog May 2, 2021
57fee38
Update timeout from Int type to time.Duration type
misonog May 2, 2021
d34d94e
Fix error handling
misonog May 2, 2021
40aaf93
Update context in main function
misonog May 2, 2021
b2e7766
Update context
misonog May 2, 2021
b82fb95
Fix context handling
misonog May 2, 2021
aa2e3ae
Fix const name from upper case to camel case
misonog May 2, 2021
51d9d48
Reimplemented pdwonload func without Data struct
misonog May 3, 2021
c4b316f
Remove MakeRange
misonog May 3, 2021
e5b722c
Remove MergeFiles
misonog May 3, 2021
7af0bb5
Remove getter
misonog May 3, 2021
25034f2
Remove setter
misonog May 3, 2021
37a3051
Remove Data struct
misonog May 3, 2021
664bf28
Remove Utils interface
misonog May 3, 2021
81a25e8
Remove unnecessary comments
misonog May 3, 2021
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
16 changes: 16 additions & 0 deletions kadai3-2/misonog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
/pdownload

# 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/
9 changes: 9 additions & 0 deletions kadai3-2/misonog/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
BINARY_NAME=pdownload

all: test build

build:
go build -o $(BINARY_NAME)

test:
go test -v ./...
61 changes: 61 additions & 0 deletions kadai3-2/misonog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 分割ダウンローダ

## 仕様

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

## オプション

| オプション | 内容 | デフォルト |
| ---------- | -------------------------------------- | ---------- |
| -d | ファイルをダウンロードするディレクトリ | $PWD |
| -t | タイムアウトするまでの時間(秒) | 10 |

## 利用方法

### setup

```shell
$ make # テスト & ビルド
```

### ダウンロードコマンドの例

```shell
$ ./pdownload https://blog.golang.org/gopher/header.jpg
$ # ディレクトリとタイムアウトまでの時間の指定
$ ./pdownload -d testdata/ -t 30 https://blog.golang.org/gopher/header.jpg
```

## ディレクトリ構造

```
.
├── Makefile
├── README.md
├── go.mod
├── go.sum
├── main.go
├── pdownload
├── pdownload.go
├── pdownload_test.go
├── requests.go
├── requests_test.go
├── termination
│ ├── termination.go
│ └── termination_test.go
├── testdata
│ ├── header.jpg
│ └── test_download
│ └── header.jpg
└── util.go
```

## 参考

[Code-Hex/pget](https://github.com/Code-Hex/pget)と[gopherdojo/dojo3#50](https://github.com/gopherdojo/dojo3/pull/50)を参考にさせていただきました。
8 changes: 8 additions & 0 deletions kadai3-2/misonog/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/misonog/gopherdojo-studyroom/kadai3-2/misonog

go 1.16

require (
golang.org/x/net v0.0.0-20210502030024-e5908800b52b
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
)
9 changes: 9 additions & 0 deletions kadai3-2/misonog/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
golang.org/x/net v0.0.0-20210502030024-e5908800b52b h1:jCRjgm6WJHzM8VQrm/es2wXYqqbq0NZ1yXFHHgzkiVQ=
golang.org/x/net v0.0.0-20210502030024-e5908800b52b/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
32 changes: 32 additions & 0 deletions kadai3-2/misonog/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"context"
"flag"
"log"
"os"
"time"
)

const timeout = 10 * time.Second

func main() {
ctx := context.Background()

var targetDir string
var timeout time.Duration

pwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}

flag.StringVar(&targetDir, "d", pwd, "path to the directory to save the downloaded file, filename will be taken from url")
flag.DurationVar(&timeout, "t", timeout, "timeout of checking request in seconds")
flag.Parse()

cli := New()
if err := cli.Run(ctx, flag.Args(), targetDir, timeout); err != nil {
log.Fatal(err)
}
}
127 changes: 127 additions & 0 deletions kadai3-2/misonog/pdownload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package main

import (
"context"
"errors"
"fmt"
"net/url"
"os"
"runtime"
"time"

"github.com/misonog/gopherdojo-studyroom/kadai3-2/misonog/termination"
)

// Pdownload structs
type Pdownload struct {
URL string
TargetDir string
Procs int
timeout time.Duration
useragent string
referer string
filename string
filesize uint
dirname string
fullfilename string
}

func New() *Pdownload {
return &Pdownload{
Procs: runtime.NumCPU(), // default
timeout: timeout,
}
}

func (pdownload *Pdownload) Run(ctx context.Context, args []string, targetDir string, timeout time.Duration) error {
var cancel context.CancelFunc

ctx, clean := termination.Listen(ctx, os.Stdout)
defer clean()

if err := pdownload.Ready(args, targetDir, timeout); err != nil {
return err
}

dir, err := os.MkdirTemp(pdownload.TargetDir, "")
if err != nil {
return err
}
clean = func() { os.RemoveAll(dir) }
defer clean()
termination.CleanFunc(clean)

ctx, cancel = context.WithTimeout(ctx, pdownload.timeout)
defer cancel()

err = pdownload.Check(ctx, dir)
if err != nil {
return err
}

if err := pdownload.Download(ctx); err != nil {
return err
}

if err := mergeFiles(pdownload.Procs, pdownload.filename, pdownload.dirname, pdownload.fullfilename); err != nil {
return err
}

return nil
}

func (pdownload *Pdownload) Ready(args []string, targetDir string, timeout time.Duration) error {
if err := pdownload.parseURL(args); err != nil {
return err
}

if _, err := os.Stat(targetDir); os.IsNotExist(err) {
return fmt.Errorf("target directory is not exist: %w", err)
}
pdownload.TargetDir = targetDir
pdownload.timeout = timeout

return nil
}

func (pdownload *Pdownload) parseURL(args []string) error {
if len(args) > 1 {
return errors.New("URL must be a single")
}
if len(args) < 1 {
return errors.New("urls not found in the arguments passed")
}

for _, arg := range args {
_, err := url.ParseRequestURI(arg)
if err != nil {
return err
}
pdownload.URL = arg
}

return nil
}

func (pdownload *Pdownload) setFullFileName(dir, filename string) {
if dir == "" {
pdownload.fullfilename = filename
} else {
pdownload.fullfilename = fmt.Sprintf("%s/%s", dir, filename)
}
}

// makeRange will return Range struct to download function
func (pdownload *Pdownload) makeRange(i, split, procs uint) Range {
low := split * i
high := low + split - 1
if i == procs-1 {
high = pdownload.filesize
}

return Range{
low: low,
high: high,
woker: i,
}
}
52 changes: 52 additions & 0 deletions kadai3-2/misonog/pdownload_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"context"
"fmt"
"os"
"testing"
"time"
)

// requests_test.goで作成しているテストサーバを利用してテストを行う
func TestRun(t *testing.T) {
ctx := context.Background()

url := ts.URL
args := []string{fmt.Sprintf("%s/%s", url, "header.jpg")}
targetDir := "testdata/test_download"
timeout := 30 * time.Second

p := New()
if err := p.Run(ctx, args, targetDir, timeout); err != nil {
t.Errorf("failed to Run: %s", err)
}

if err := os.Remove(p.fullfilename); err != nil {
t.Errorf("failed to remove of result file: %s", err)
}
}

func TestParseURL(t *testing.T) {
cases := []struct {
name string
input []string
expected string
}{
{name: "an URL", input: []string{"https://www.google.com/"}, expected: "https://www.google.com/"},
{name: "URLs", input: []string{"https://www.google.com/", "https://golang.org/"}, expected: ""},
{name: "invalid URL", input: []string{"invalid_url"}, expected: ""},
}

for _, c := range cases {
c := c
p := New()
t.Run(c.name, func(t *testing.T) {
_ = p.parseURL(c.input)
if actual := p.URL; c.expected != actual {
t.Errorf("want p.URL = %v, got %v",
c.expected, actual)
}
})
}
}
Loading