Skip to content

課題3 tokunaga #26

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 8 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
4 changes: 4 additions & 0 deletions kadai3-1/tokunaga/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# 課題3-1 タイピングゲームを作ろう
- [ ] 標準出力に英単語を出す(出すものは自由)
- [ ] 標準入力から1行受け取る
- [ ] 制限時間内に何問解けたか表示する
90 changes: 90 additions & 0 deletions kadai3-1/tokunaga/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main

import (
"bufio"
crand "crypto/rand"
"fmt"
"io"
"math"
"math/big"
"math/rand"
"os"
"time"
)

var word = [...]string{
"toukyoutokkyokyokakyoku",
"akamakigamiaomakigamikimakigami",
"sushi",
"tenpura",
"kaiken",
"nisshingeppo",
"hyappatuhyakutyu",
"kumiai",
"taiiku",
"kome",
}

const (
limitSec = 15
ExitAbort = 1
)

func init() {
if err := serRandSeed(); err != nil {
os.Exit(ExitAbort)
}
}

func serRandSeed() error {
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
rand.Seed(seed.Int64())
return err
}

func main() {
fmt.Printf("制限時間は%d秒です\n", limitSec)

ch := input(os.Stdin)
var correctCount int
go output(ch, &correctCount)

<-time.After(limitSec * time.Second)
finish(correctCount)
}

func input(r io.Reader) <-chan string {
ch := make(chan string)
go read(r, ch)
return ch
}

func read(reader io.Reader, ch chan string) {
s := bufio.NewScanner(reader)
for s.Scan() {
ch <- s.Text()
}
close(ch)
}

func output(typeWord <-chan string, correctCount *int) {
wordCount := len(word)
for {
answerWord := word[rand.Intn(wordCount)]
fmt.Println(answerWord)
fmt.Print("> ")
typeWord := <-typeWord
if typeWord == answerWord {
fmt.Println("correct answer!")
*correctCount++
} else {
fmt.Println("incorrect answer...")
}
fmt.Println("-----------------------------")
}
}

func finish(correctCount int) {
fmt.Println("\n-----------FINISH!-----------")
fmt.Printf("正解数:%d\n", correctCount)
}
33 changes: 33 additions & 0 deletions kadai3-1/tokunaga/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"bytes"
"testing"
)

func ExampleFinish() {
finish(3)
// Output: -----------FINISH!-----------
// 正解数:3
}

func TestInput(t *testing.T) {
inputWord := "Hello Worlds!"
inputBuf := bytes.NewBufferString(inputWord)
ch := input(inputBuf)
received := <-ch
if inputWord != received {
t.Errorf("want: receive %s from chanel, got: receive %s from chanel", inputWord, received)
}
}

func TestRead(t *testing.T) {
input := "Hello Worlds!"
ch := make(chan string)
inputBuf := bytes.NewBufferString(input)
go read(inputBuf, ch)
received := <-ch
if input != received {
t.Errorf("want: receive %s from chanel, got: receive %s from chanel", input, received)
}
}
60 changes: 60 additions & 0 deletions kadai3-2/tokunaga/cmd/goget/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"flag"
"fmt"
"net/http"
"os"
"time"

"github.com/gopherdojo/dojo2/kadai3-2/tokunaga"
)

const (
ExitOK = iota
ExitAbort
)
const timeLayout = "2006-01-02 15:04:05"

func main() {
flag.Parse()
if len(flag.Args()) != 1 {
fmt.Fprintln(os.Stderr, "You can only download one file at a time")
os.Exit(ExitAbort)
}
downloadPath := flag.Args()[0]

t := time.Now()
fmt.Printf("--%s-- %s\n", t.Format(timeLayout), downloadPath)
fmt.Print("HTTP による接続要求を送信しました、応答を待っています...")

// head request
responseHead, err := http.Head(downloadPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(ExitAbort)
}
// response info
acceptRanges := responseHead.Header.Get("Accept-Ranges")
statusCode := responseHead.StatusCode
fileSize := responseHead.ContentLength
contentType := responseHead.Header.Get("Content-Type")

downloadFile := tokunaga.File{Uri: downloadPath, FileSize: fileSize}
fmt.Printf("%d %s\n", statusCode, http.StatusText(statusCode))
if responseHead.StatusCode != 200 {
fmt.Fprintln(os.Stderr, http.StatusText(responseHead.StatusCode))
os.Exit(ExitAbort)
}
fmt.Printf("長さ: %d [%s]\n", fileSize, contentType)
fmt.Printf("`%s' に保存中\n", downloadFile.Filename())
fmt.Println("")

if err := downloadFile.Download(acceptRanges); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(ExitAbort)
}

fmt.Printf("`%s' に保存完了\n", downloadFile.Filename())
os.Exit(ExitOK)
}
135 changes: 135 additions & 0 deletions kadai3-2/tokunaga/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package tokunaga

import (
"fmt"
"io"
"net/http"
"os"
"path"
"runtime"
"golang.org/x/sync/errgroup"
)

type File struct {
Uri string
FileSize int64
}

// ダウンロードファイルのUriからファイル名を取得
func (f File) Filename() string {
return path.Base(f.Uri)
}

// acceptRangesによってダウンロード方法を切り替える
func (f File) Download(acceptRanges string) error {
if acceptRanges == "" {
return f.singleDownload()
} else {
return f.splitDownload()
}
}

// ファイルを一括ダウンロード
func (f File) singleDownload() error {
responseGet, err := http.Get(f.Uri)
if err != nil {
return err
}
defer responseGet.Body.Close()

file, err := os.Create(f.Filename())
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, responseGet.Body)
if err != nil {
return err
}
return nil
}

// ファイルを分割ダウンロード
func (f File) splitDownload() error {
splitNum := runtime.NumCPU()
splitBytes := f.SplitByteSize(int64(splitNum))
ranges := formatRange(splitBytes)
createFileMap := map[int]string{}

eg := errgroup.Group{}
for no, rangeValue := range ranges {
index := no
value := rangeValue
eg.Go(func() error {
return f.rangeDownload(index, value, createFileMap)
})
}
if err := eg.Wait(); err != nil {
return err
}
if err := f.joinSplitFiles(createFileMap); err != nil {
return err
}
return nil
}

func (f File) rangeDownload(fileNo int, rangeValue string, createFiles map[int]string) error {
req, _ := http.NewRequest("GET", f.Uri, nil)
req.Header.Set("RANGE", rangeValue)

client := new(http.Client)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
createFileName := fmt.Sprintf("%s_%s", f.Filename(), rangeValue)
file, err := os.Create(createFileName)
if err != nil {
return err
}
defer file.Close()
createFiles[fileNo] = createFileName
_, err = io.Copy(file, resp.Body)
if err != nil {
return err
}
return nil
}

// 分割されたファイルを一つに結合させる
func (f File) joinSplitFiles(createFiles map[int]string) error {
originFile, err := os.Create(f.Filename())
if err != nil {
return err
}
defer originFile.Close()
for i := 0; i < len(createFiles); i++ {
splitFile, err := os.Open(createFiles[i])
if err != nil {
return err
}
_, err = io.Copy(originFile, splitFile)
if err != nil {
return err
}
splitFile.Close()
if err := os.Remove(createFiles[i]); err != nil {
return err
}
}
return nil
}

// ファイルのバイト数と分割数から、分割ダウンロードするファイルの各バイト数の配列を返す
// SplitByteSize(1002, 8) -> [125, 125, 125, 125, 125, 125, 125, 127]
func (f File) SplitByteSize(splitNum int64) []int64 {
var response = make([]int64, splitNum)
rest := f.FileSize % splitNum // ファイルのサイズを分割数で割った余り
splitUnit := (f.FileSize - rest) / splitNum // 分割したファイルのサイズ
for i := int64(0); i < splitNum-1; i++ {
response[i] = splitUnit
}
response[splitNum-1] = splitUnit + rest
return response
}
16 changes: 16 additions & 0 deletions kadai3-2/tokunaga/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package tokunaga

import "fmt"

// 分割ダウンロードするファイルの各バイト数の配列から、request headerのRANGEに設定する値の配列を返す
// [25 25 25 28]
// -> [bytes=0-25 bytes=26-51 bytes=52-77 bytes=78-103]
func formatRange(splitBytes []int64) []string {
var response []string
var bytePosition int64
for _, bytes := range splitBytes {
response = append(response, fmt.Sprintf("bytes=%d-%d", bytePosition, bytePosition+bytes-1))
bytePosition = bytePosition + bytes
}
return response
}