| // Copyright 2015 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package iotools |
| |
| import ( |
| "errors" |
| "io" |
| ) |
| |
| // ChainReader is an io.Reader that consumes data sequentially from independent |
| // arrays of data to appear as if they were one single concatenated data source. |
| // |
| // The underlying io.Reader will be mutated during operation. |
| type ChainReader []io.Reader |
| |
| var _ interface { |
| io.Reader |
| io.ByteReader |
| } = (*ChainReader)(nil) |
| |
| // Read implements io.Reader. |
| func (cr *ChainReader) Read(p []byte) (int, error) { |
| if len(p) == 0 { |
| return 0, nil |
| } |
| |
| consumed := 0 |
| defer func() { |
| *cr = (*cr)[consumed:] |
| }() |
| |
| total := 0 |
| for idx, source := range *cr { |
| if source == nil { |
| consumed++ |
| continue |
| } |
| |
| count, err := source.Read(p) |
| total += count |
| if err == io.EOF { |
| (*cr)[idx] = nil |
| consumed++ |
| } else if err != nil { |
| return total, err |
| } |
| |
| p = p[count:] |
| if len(p) == 0 { |
| return total, nil |
| } |
| } |
| return total, io.EOF |
| } |
| |
| // ReadByte implements io.ByteReader. |
| func (cr ChainReader) ReadByte() (byte, error) { |
| d := []byte{0} |
| _, err := cr.Read(d) |
| return d[0], err |
| } |
| |
| // Remaining calculates the amount of data left in the ChainReader. It will |
| // panic if an error condition in RemainingErr is encountered. |
| func (cr ChainReader) Remaining() int64 { |
| result, err := cr.RemainingErr() |
| if err != nil { |
| panic(err) |
| } |
| return result |
| } |
| |
| // RemainingErr returns the amount of data left in the ChainReader. An error is |
| // returned if any reader in the chain is not either nil or a bytes.Reader. |
| // |
| // Note that this method iterates over all readers in the chain each time that |
| // it's called. |
| func (cr ChainReader) RemainingErr() (int64, error) { |
| result := int64(0) |
| for _, source := range cr { |
| if source == nil { |
| continue |
| } |
| r, ok := source.(interface { |
| Len() int |
| }) |
| if !ok { |
| return 0, errors.New("chainreader: can only calculate Remaining for instances implementing Len()") |
| } |
| result += int64(r.Len()) |
| } |
| return result, nil |
| } |