Skip to content
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

【課題3】タイピングゲームと分割ダウンローダー #31

Open
wants to merge 6 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
188 changes: 188 additions & 0 deletions kadai3/su-san/spread_downloader/cmd/spget/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package main

import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"strconv"

errgroup "golang.org/x/sync/errgroup"
)

var COUNT = 4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

定数であれば、constで先頭を大文字にするとパッケージ外に公開されます


func main() {

url := "https://misc.laboradian.com/test/003"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const?


header, err := HeaderInfo(url)

if err != nil {
fmt.Println(err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

エラーであれば、os.Stderrに出して、os.Exit(1)とかで終了する

}

var byteRanges []string
if canRangeAccess(header) {
// 分割ダウンロードする
byte_num, err := strconv.Atoi(header["Content-Length"][0])
if err != nil {
fmt.Println("normal download")
return
}
byteRanges = ByteRanges(byte_num, 2)
} else {
fmt.Println("normal download")
// TODO: 分岐として通常ダウンロード処理を入れる(やるかどうかY/nで聞いてから?)
return
}

// TODO: tmpディレクトリ作成

eg := errgroup.Group{}
for _, range_suffix := range byteRanges {
// 分割ダウンロードする
range_suffix := range_suffix
eg.Go(func() error {
if downloadedFile(url, range_suffix) {
return nil
}
return Download(url, range_suffix)
})
}

if err = eg.Wait(); err != nil {
// TODO:標準エラー出力使う
fmt.Fprintf(os.Stderr, "ERROR: %v", err)
return
}

_, fileName := path.Split(url)
fmt.Println(fileName)
var filePaths []string
for _, suffix := range byteRanges {
filePaths = append(filePaths, path.Join("./tmp/", fileName+"_"+suffix))
}
// そろっていればtmpファイルを結合する
err = concatFiles(filePaths, "./"+fileName)

if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %v", err)
}

}

func HeaderInfo(url string) (map[string][]string, error) {
req, _ := http.NewRequest("HEAD", "https://misc.laboradian.com/test/003/", nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

エラー処理


client := new(http.Client)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この用途ならhttp.DefaultClientで十分。

resp, err := client.Do(req)
defer resp.Body.Close()

if err != nil {
fmt.Printf("ioutil err: %v", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

エラーの表示も立派なエラーハンドリングなので、2重にハンドリングしない。
表示するなら表示する、returnするならreturnする。

return nil, err
}

// fmt.Println(resp.Header)
return resp.Header, nil
}

func canRangeAccess(header map[string][]string) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http.Headerを使った方が良さそう

v, ok := header["Accept-Ranges"]

if ok {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

早めにreturnしたほうがよい

for _, val := range v {
if val == "bytes" {
if _, ok := header["Content-Length"]; ok {
return true
}
}
}
}
return false
}

func downloadedFile(url, suffix string) bool {
_, fileName := path.Split(url)
_, err := os.Stat(path.Join("./tmp/", fileName+"_"+suffix))
return err == nil
}

func Download(url, byteRange string) error {

req, _ := http.NewRequest("GET", url, nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error処理

req.Header.Set("Range", "bytes="+byteRange)

client := new(http.Client)
resp, err := client.Do(req)
// TODO: エラー処理する?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

エラー処理は必須です

defer resp.Body.Close()

_, fileName := path.Split(url)
fmt.Println(url, byteRange, fileName)

file, err := os.Create(path.Join("./tmp/", fileName+"_"+byteRange))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ファイルにpathパッケージは使わない。filepathを使う。
一時ファイルはioutilとかを使う https://golang.org/pkg/io/ioutil/#TempDir

if err != nil {
return err
}
_, err = io.Copy(file, resp.Body)
if closeErr := file.Close(); err == nil {
err = closeErr
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そのままreturnしないのはなぜ?

}

return err
}

// ByteRanges は分割したバイト範囲を返す関数です
// ex) 100 で 2分割 0-50, 51-100
func ByteRanges(length int, parallel_num int) []string {
var byteRanges []string

for i := 1; i < parallel_num+1; i++ {
byteRanges = append(byteRanges, NthRange(length, parallel_num, i))
}
return byteRanges
}

func NthRange(length, parallel_num, n int) string {
bytes_per_file := length / parallel_num
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

識別子(変数名など)はスネークケースを使わない。キャメルケースを使う。

if n == 1 {
return "0-" + strconv.Itoa(bytes_per_file)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returnするならelseやelse if はいらない

} else if n < parallel_num {
return strconv.Itoa(bytes_per_file*(n-1)+1) + "-" + strconv.Itoa(bytes_per_file*n)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fmt.Sprintfを使ったほうがわかりやすそう

} else {
return strconv.Itoa(bytes_per_file*(n-1)+1) + "-" + strconv.Itoa(length)
}
}

func concatFiles(filePaths []string, fileName string) error {

var writeBytes [][]byte

for _, path := range filePaths {
f, err := os.Open(path)
if err != nil {
return err
}

// 一気に全部読み取り
readBytes, err := ioutil.ReadAll(f)
writeBytes = append(writeBytes, readBytes)

if err := f.Close(); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writeじゃないのでエラー処理は不要ではある。

return err
}
}

emptyByte := []byte{}

fmt.Println(fileName)

err := ioutil.WriteFile(fileName, bytes.Join(writeBytes, emptyByte), 0644)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こうすると一気にメモリを消費するので、徐々に書き込むべき。


return err
}
82 changes: 82 additions & 0 deletions kadai3/su-san/typing-game/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package main

import (
"bufio"
"context"
"fmt"
"io"
"math/rand"
"os"
"time"
)

func main() {

var counter int
ch := input(os.Stdin)

words, err := RegisterWords()
if err != nil {
fmt.Println("err")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

エラーメッセージは丁寧に書く。
os.Stderrに出す。

}

wordNum := len(words)
rand.Seed(time.Now().UnixNano())

var displayWord string

displayWord = words[rand.Intn(wordNum)]
fmt.Print(displayWord, " : ")

bc := context.Background()
t := 10 * time.Second
ctx, cancel := context.WithTimeout(bc, t)
defer cancel()

for {
select {
case v := <-ch:
fmt.Println(v, "come")
if v == displayWord {
counter += 1
fmt.Println("o")
} else {
fmt.Println("x")
}
displayWord = words[rand.Intn(wordNum)]
fmt.Print(displayWord, " : ")
case <-ctx.Done():
fmt.Println("\ntime up!\nscore :", counter)
goto finish
}
}
finish:
}

func RegisterWords() ([]string, error) {
f, err := os.Open("list.csv")
if err != nil {
return nil, err
}

var words []string

scanner := bufio.NewScanner(f)
for scanner.Scan() {
words = append(words, scanner.Text())
}
err = f.Close()
return words, err
}

func input(r io.Reader) chan string {
c := make(chan string)
go func() {
s := bufio.NewScanner(r)
for s.Scan() {
c <- s.Text()
}
close(c)
}()
return c
}
Binary file added kadai3/su-san/typing_game/.DS_Store
Binary file not shown.
5 changes: 5 additions & 0 deletions kadai3/su-san/typing_game/list.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apple
orange
wood
tree
flower
82 changes: 82 additions & 0 deletions kadai3/su-san/typing_game/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package main

import (
"bufio"
"context"
"fmt"
"io"
"math/rand"
"os"
"time"
)

func main() {

var counter int
ch := input(os.Stdin)

words, err := RegisterWords()
if err != nil {
fmt.Println("err")
}

wordNum := len(words)
rand.Seed(time.Now().UnixNano())

var displayWord string

displayWord = words[rand.Intn(wordNum)]
fmt.Print(displayWord, " : ")

bc := context.Background()
t := 10 * time.Second
ctx, cancel := context.WithTimeout(bc, t)
defer cancel()

for {
select {
case v := <-ch:
fmt.Println(v, "come")
if v == displayWord {
counter += 1
fmt.Println("o")
} else {
fmt.Println("x")
}
displayWord = words[rand.Intn(wordNum)]
fmt.Print(displayWord, " : ")
case <-ctx.Done():
fmt.Println("\ntime up!\nscore :", counter)
goto finish
}
}
finish:
}

func RegisterWords() ([]string, error) {
f, err := os.Open("list.csv")
if err != nil {
return nil, err
}

var words []string

scanner := bufio.NewScanner(f)
for scanner.Scan() {
words = append(words, scanner.Text())
}
err = f.Close()
return words, err
}

func input(r io.Reader) chan string {
c := make(chan string)
go func() {
s := bufio.NewScanner(r)
for s.Scan() {
c <- s.Text()
}
close(c)
}()
return c
}