├── doc.go ├── README.md ├── status.go ├── example_test.go ├── LICENSE ├── progress_test.go └── progress.go /doc.go: -------------------------------------------------------------------------------- 1 | // Package progress provides methods for monitoring the progress of io. 2 | package progress 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # progress 2 | [![Circle CI](https://circleci.com/gh/Bowery/progress/tree/master.png?style=badge)](https://circleci.com/gh/Bowery/progress/tree/master) 3 | 4 | [![GoDoc](https://godoc.org/github.com/Bowery/progress?status.png)](https://godoc.org/github.com/Bowery/progress) 5 | 6 | Progress provides methods for monitoring the progress of io operations. 7 | 8 | ## License 9 | 10 | progress is MIT licensed, details can be found [here](https://raw.githubusercontent.com/Bowery/progress/master/LICENSE). 11 | -------------------------------------------------------------------------------- /status.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Bowery, Inc. 2 | 3 | package progress 4 | 5 | // Status represents the copy progress. It contains the current progress 6 | // and total size in bytes. 7 | type Status struct { 8 | Current int64 9 | Total int64 10 | finished bool 11 | } 12 | 13 | // Completion returns the current completion in the range [0, 1]. 14 | func (s *Status) Completion() float64 { 15 | return float64(s.Current) / float64(s.Total) 16 | } 17 | 18 | // IsFinished returns a boolean indicating whether the copy 19 | // has completed. 20 | func (s *Status) IsFinished() bool { 21 | return s.finished 22 | } 23 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Bowery, Inc. 2 | 3 | package progress_test 4 | 5 | import ( 6 | "bytes" 7 | "os" 8 | 9 | "github.com/Bowery/progress" 10 | ) 11 | 12 | func ExampleCopy() { 13 | file, err := os.Open("/path/to/file") 14 | if err != nil { 15 | panic(err) 16 | } 17 | defer file.Close() 18 | 19 | stat, err := file.Stat() 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | var buf bytes.Buffer 25 | progChan, errChan := progress.Copy(&buf, file, stat.Size()) 26 | 27 | isCopied := false 28 | for !isCopied { 29 | select { 30 | case status := <-progChan: 31 | if status.IsFinished() { 32 | isCopied = true 33 | break 34 | } 35 | case err := <-errChan: 36 | panic(err) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Bowery 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /progress_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Bowery, Inc. 2 | 3 | package progress 4 | 5 | import ( 6 | "bytes" 7 | "net/http" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestCopyFileSuccess(t *testing.T) { 13 | file, err := os.Open("progress_test.go") 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | defer file.Close() 18 | 19 | stat, err := file.Stat() 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | var buf bytes.Buffer 25 | progChan, errChan := Copy(&buf, file, stat.Size()) 26 | 27 | for { 28 | select { 29 | case status := <-progChan: 30 | if status.IsFinished() { 31 | // Actual testing occurs here. 32 | if int64(buf.Len()) != stat.Size() { 33 | t.Error("Copied length does not match file length") 34 | } 35 | 36 | return 37 | } 38 | case err := <-errChan: 39 | t.Error(err) 40 | } 41 | } 42 | } 43 | 44 | func TestCopyGetRequestSuccess(t *testing.T) { 45 | res, err := http.Get("http://bowery.io") 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | defer res.Body.Close() 50 | 51 | var buf bytes.Buffer 52 | progChan, errChan := Copy(&buf, res.Body, res.ContentLength) 53 | 54 | for { 55 | select { 56 | case status := <-progChan: 57 | if status.IsFinished() { 58 | // Actual testing occurs here. 59 | if int64(buf.Len()) != res.ContentLength { 60 | t.Error("Copied length does not match file length") 61 | } 62 | 63 | return 64 | } 65 | case err := <-errChan: 66 | t.Error(err) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /progress.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Bowery, Inc. 2 | 3 | package progress 4 | 5 | import ( 6 | "io" 7 | ) 8 | 9 | // transmitter monitors copying progress. It contains the current progress 10 | // and channels to report the progress and any errors that occur. 11 | type transmitter struct { 12 | src io.Reader 13 | current int64 14 | total int64 15 | progressChan chan *Status 16 | errorChan chan error 17 | } 18 | 19 | // newTransmitter creates a transmitter reading from src a total of n bytes. 20 | func newTransmitter(src io.Reader, n int64) *transmitter { 21 | return &transmitter{ 22 | src: src, 23 | total: n, 24 | progressChan: make(chan *Status), 25 | errorChan: make(chan error), 26 | } 27 | } 28 | 29 | // Close closes all channels associated with the transmitter. 30 | func (t *transmitter) Close() error { 31 | close(t.progressChan) 32 | close(t.errorChan) 33 | return nil 34 | } 35 | 36 | // Read implements io.Reader, reading from the source and incrementing 37 | // the progress and reporting it to the progress channel. 38 | func (t *transmitter) Read(p []byte) (int, error) { 39 | n, err := t.src.Read(p) 40 | if n > 0 { 41 | t.current += int64(n) 42 | t.progressChan <- &Status{ 43 | Current: t.current, 44 | Total: t.total, 45 | } 46 | } 47 | 48 | return n, err 49 | } 50 | 51 | // Copy tracks progress of copied data from src to dst, progress events are 52 | // sent to the return status channel, once completed the IsFinished routine 53 | // will report true. 54 | func Copy(dst io.Writer, src io.Reader, n int64) (chan *Status, chan error) { 55 | t := newTransmitter(src, n) 56 | 57 | go func() { 58 | defer t.Close() 59 | 60 | _, err := io.Copy(dst, t) 61 | if err != nil { 62 | t.errorChan <- err 63 | return 64 | } 65 | 66 | t.progressChan <- &Status{ 67 | Current: t.total, 68 | Total: t.total, 69 | finished: true, 70 | } 71 | }() 72 | 73 | return t.progressChan, t.errorChan 74 | } 75 | --------------------------------------------------------------------------------