diff --git a/kadai3/shiimaxx/range-access/LICENSE b/kadai3/shiimaxx/range-access/LICENSE new file mode 100644 index 0000000..e6ddbbf --- /dev/null +++ b/kadai3/shiimaxx/range-access/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 YOSHIMA Takatada + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/kadai3/shiimaxx/range-access/README.md b/kadai3/shiimaxx/range-access/README.md new file mode 100644 index 0000000..8d5e48f --- /dev/null +++ b/kadai3/shiimaxx/range-access/README.md @@ -0,0 +1,27 @@ +# gurl + +gurl is a like curl command by go + +## Usage + +``` +gurl [(-o | --output) ] [(-p | --parallel) ] +``` + +``` +Usage of gurl: + -o string + output file(Short) (default "./") + -output string + output file (default "./") + -p int + number of parallel(Short) (default 10) + -parallel int + number of parallel (default 10) + -version + print version information +``` + +## Author + +[shiimaxx](https://github.com/shiimaxx) diff --git a/kadai3/shiimaxx/range-access/cli.go b/kadai3/shiimaxx/range-access/cli.go new file mode 100644 index 0000000..523d1fd --- /dev/null +++ b/kadai3/shiimaxx/range-access/cli.go @@ -0,0 +1,76 @@ +package main + +import ( + "flag" + "fmt" + "io" + "os" + + "github.com/shiimaxx/gurl/gurl" +) + +// Exit codes are int values that represent an exit code for a particular error. +const ( + ExitCodeOK int = 0 + ExitCodeError int = 1 + iota +) + +// CLI is the command line object +type CLI struct { + // outStream and errStream are the stdout and stderr + // to write message from the CLI. + outStream, errStream io.Writer +} + +// Run invokes the CLI with the given arguments. +func (c *CLI) Run(args []string) int { + var ( + parallel int + output string + version bool + ) + + flags := flag.NewFlagSet(Name, flag.ContinueOnError) + flags.SetOutput(c.outStream) + + flags.StringVar(&output, "output", "", "output file") + flags.StringVar(&output, "o", "", "output file(Short)") + flags.IntVar(¶llel, "parallel", 10, "number of parallel") + flags.IntVar(¶llel, "p", 10, "number of parallel(Short)") + + flags.BoolVar(&version, "version", false, "print version information") + + if err := flags.Parse(args[1:]); err != nil { + return ExitCodeError + } + + if version { + fmt.Fprintf(c.outStream, "%s version %s\n", Name, Version) + return ExitCodeOK + } + + if len(flags.Args()) < 1 { + fmt.Fprintln(c.errStream, "missing arguments") + return ExitCodeError + } + + if output == "" { + fmt.Fprint(c.errStream, "require output option\n") + return ExitCodeError + } + + if _, err := os.Stat(output); os.IsExist(err) { + fmt.Fprintf(c.errStream, "%s: already exits\n", output) + return ExitCodeError + } + + url := flags.Args()[0] + + client := gurl.NewClient(parallel, output) + if err := client.Get(url); err != nil { + fmt.Fprintf(c.errStream, "%s\n", err) + return ExitCodeError + } + + return ExitCodeOK +} diff --git a/kadai3/shiimaxx/range-access/cli_test.go b/kadai3/shiimaxx/range-access/cli_test.go new file mode 100644 index 0000000..4b1314b --- /dev/null +++ b/kadai3/shiimaxx/range-access/cli_test.go @@ -0,0 +1,54 @@ +package main + +import ( + "bytes" + "fmt" + "strings" + "testing" +) + +func TestRun(t *testing.T) { + var cases = []struct { + name string + args string + expected string + isNormalCase bool + }{ + { + name: "version flag", + args: "gurl -version", + expected: fmt.Sprintf("gurl version %s\n", Version), + isNormalCase: true, + }, + { + name: "no arguments", + args: "gurl", + expected: "missing arguments\n", + isNormalCase: false, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + outStream, errStream := new(bytes.Buffer), new(bytes.Buffer) + cli := &CLI{outStream: outStream, errStream: errStream} + status := cli.Run(strings.Split(c.args, " ")) + + if c.isNormalCase { + if status != ExitCodeOK { + t.Errorf("expected %d to eq %d", status, ExitCodeOK) + } + if !strings.EqualFold(outStream.String(), c.expected) { + t.Errorf("expected %q to eq %q", outStream.String(), c.expected) + } + } else { + if status != ExitCodeError { + t.Errorf("expected %d to eq %d", status, ExitCodeOK) + } + if !strings.EqualFold(errStream.String(), c.expected) { + t.Errorf("expected %q to eq %q", errStream.String(), c.expected) + } + } + }) + } +} diff --git a/kadai3/shiimaxx/range-access/gurl/gurl.go b/kadai3/shiimaxx/range-access/gurl/gurl.go new file mode 100644 index 0000000..a7f0aaf --- /dev/null +++ b/kadai3/shiimaxx/range-access/gurl/gurl.go @@ -0,0 +1,121 @@ +package gurl + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "strconv" + + "golang.org/x/sync/errgroup" +) + +// Client gurl client +type Client struct { + Parallel int + Output string +} + +// Content actual content +type Content struct { + Name string + Length int +} + +// NewClient constractor for Client +func NewClient(parallel int, output string) *Client { + return &Client{ + Parallel: parallel, + Output: output, + } +} + +func rangeGet(ctx context.Context, url string, s, e, i int, tempFiles []*os.File) error { + client := &http.Client{} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", s, e)) + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + reader, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + tempFile, err := ioutil.TempFile("./", "temp") + if err != nil { + return err + } + if err := ioutil.WriteFile(tempFile.Name(), reader, 0644); err != nil { + return err + } + tempFiles[i] = tempFile + + return nil +} + +// Get content of url +func (c *Client) Get(url string) error { + resp, err := http.Head(url) + if err != nil { + return err + } + + contentLength, err := strconv.Atoi(resp.Header.Get("Content-Length")) + if err != nil { + return err + } + + chunkSize := contentLength / c.Parallel + surplus := contentLength % c.Parallel + + eg, ctx := errgroup.WithContext(context.TODO()) + tempFiles := make([]*os.File, c.Parallel) + + for p := 0; p < c.Parallel; p++ { + s := p * chunkSize + e := s + (chunkSize - 1) + if p == c.Parallel-1 { + e += surplus + } + + i := p + eg.Go(func() error { + return rangeGet(ctx, url, s, e, i, tempFiles) + }) + } + + if err := eg.Wait(); err != nil { + return err + } + + tempFilesReaders := make([]io.Reader, c.Parallel) + for i, f := range tempFiles { + tempFilesReaders[i], err = os.Open(f.Name()) + if err != nil { + return err + } + } + + reader := io.MultiReader(tempFilesReaders...) + b, err := ioutil.ReadAll(reader) + if err != nil { + return err + } + if err := ioutil.WriteFile(c.Output, b, 0644); err != nil { + return err + } + + for _, f := range tempFiles { + os.Remove(f.Name()) + } + + return nil +} diff --git a/kadai3/shiimaxx/range-access/main.go b/kadai3/shiimaxx/range-access/main.go new file mode 100644 index 0000000..9c033c1 --- /dev/null +++ b/kadai3/shiimaxx/range-access/main.go @@ -0,0 +1,8 @@ +package main + +import "os" + +func main() { + cli := &CLI{outStream: os.Stdout, errStream: os.Stderr} + os.Exit(cli.Run(os.Args)) +} diff --git a/kadai3/shiimaxx/range-access/version.go b/kadai3/shiimaxx/range-access/version.go new file mode 100644 index 0000000..7271254 --- /dev/null +++ b/kadai3/shiimaxx/range-access/version.go @@ -0,0 +1,7 @@ +package main + +// Name is the application name +const Name string = "gurl" + +// Version is the application version +const Version string = "0.1.0"