diff --git a/interfaces/17-write-an-io-reader/.gitignore b/interfaces/17-write-an-io-reader/.gitignore new file mode 100644 index 0000000..975ab52 --- /dev/null +++ b/interfaces/17-write-an-io-reader/.gitignore @@ -0,0 +1,4 @@ +rosie.unknown +rosie.png +_other.go +toc \ No newline at end of file diff --git a/interfaces/17-write-an-io-reader/main.go b/interfaces/17-write-an-io-reader/main.go new file mode 100644 index 0000000..2be89e6 --- /dev/null +++ b/interfaces/17-write-an-io-reader/main.go @@ -0,0 +1,41 @@ +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// +// For more tutorials : https://learngoprogramming.com +// In-person training : https://www.linkedin.com/in/inancgumus/ +// Follow me on twitter: https://twitter.com/inancgumus + +package main + +import ( + "fmt" + "io" + "net/http" + "os" +) + +func main() { + resp, err := http.Get("https://inancgumus.github.com/x/rosie.unknown") + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + defer resp.Body.Close() + + file, err := os.Create("rosie.png") + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + defer file.Close() + + // io.Copy reads from pngReader first + // pngReader reads from the resp.Body + n, err := io.Copy(file, pngReader(resp.Body)) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + fmt.Printf("%d bytes transferred.\n", n) +} diff --git a/interfaces/17-write-an-io-reader/reader.go b/interfaces/17-write-an-io-reader/reader.go new file mode 100644 index 0000000..4b265b8 --- /dev/null +++ b/interfaces/17-write-an-io-reader/reader.go @@ -0,0 +1,49 @@ +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// +// For more tutorials : https://learngoprogramming.com +// In-person training : https://www.linkedin.com/in/inancgumus/ +// Follow me on twitter: https://twitter.com/inancgumus + +package main + +import ( + "errors" + "io" +) + +// create a reader for detecting the png signatures. +func pngReader(r io.Reader) io.Reader { + return &signatureReader{r: r, sign: []byte("\x89PNG\r\n\x1a\n")} +} + +// reader reads from `r` if the stream starts with PNG signature. +// otherwise it stops and returns with an error. +type signatureReader struct { + r io.Reader // reads from the response.Body (or from any reader) + sign []byte // stream should start with this initial signature +} + +// Read implements the io.Reader interface. +func (sr *signatureReader) Read(b []byte) (n int, err error) { + n, err = sr.r.Read(b) + + l := len(sr.sign) + + // simply return if the signature has already been detected. + if l == 0 { + return + } + // limit the compared bytes at most to the buffer length. + if lb := len(b); l > lb { + l = lb + } + if string(b[:l]) != string(sr.sign[:l]) { + err = errors.New("not png") + } + // the next read will compare the rest of the signature after `l` + // `sr.sign` will be zero after one or several reads later. + sr.sign = sr.sign[l:] + return +} diff --git a/interfaces/17-write-an-io-reader/reader_test.go b/interfaces/17-write-an-io-reader/reader_test.go new file mode 100644 index 0000000..ca5b14f --- /dev/null +++ b/interfaces/17-write-an-io-reader/reader_test.go @@ -0,0 +1,33 @@ +package main + +import ( + "io" + "strings" + "testing" +) + +func Test_PNG_Reader_Correct_Signature(t *testing.T) { + const input = "\x89PNG\r\n\x1a\nHELLO" + + out, err := readSignature(input) + if err != nil { + t.Fatalf("got: %q; want: err", err) + } + if out != input { + t.Fatalf("invalid output, got: '%x'; want: '%x'", out, input) + } +} + +func Test_PNG_Reader_Incorrect_Signature(t *testing.T) { + _, err := readSignature("\x89INCORRECT") + if err == nil { + t.Fatal("got: nil; want: !nil err") + } +} + +func readSignature(in string) (string, error) { + r := strings.NewReader(in) + w := strings.Builder{} + _, err := io.Copy(&w, pngReader(r)) + return w.String(), err +}