github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/storage/filereader.go (about)

     1  package storage
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  
    11  	"github.com/docker/distribution/context"
    12  	storagedriver "github.com/docker/distribution/registry/storage/driver"
    13  )
    14  
    15  // TODO(stevvooe): Set an optimal buffer size here. We'll have to
    16  // understand the latency characteristics of the underlying network to
    17  // set this correctly, so we may want to leave it to the driver. For
    18  // out of process drivers, we'll have to optimize this buffer size for
    19  // local communication.
    20  const fileReaderBufferSize = 4 << 20
    21  
    22  // remoteFileReader provides a read seeker interface to files stored in
    23  // storagedriver. Used to implement part of layer interface and will be used
    24  // to implement read side of LayerUpload.
    25  type fileReader struct {
    26  	driver storagedriver.StorageDriver
    27  
    28  	ctx context.Context
    29  
    30  	// identifying fields
    31  	path string
    32  	size int64 // size is the total size, must be set.
    33  
    34  	// mutable fields
    35  	rc     io.ReadCloser // remote read closer
    36  	brd    *bufio.Reader // internal buffered io
    37  	offset int64         // offset is the current read offset
    38  	err    error         // terminal error, if set, reader is closed
    39  }
    40  
    41  // newFileReader initializes a file reader for the remote file. The reader
    42  // takes on the size and path that must be determined externally with a stat
    43  // call. The reader operates optimistically, assuming that the file is already
    44  // there.
    45  func newFileReader(ctx context.Context, driver storagedriver.StorageDriver, path string, size int64) (*fileReader, error) {
    46  	return &fileReader{
    47  		ctx:    ctx,
    48  		driver: driver,
    49  		path:   path,
    50  		size:   size,
    51  	}, nil
    52  }
    53  
    54  func (fr *fileReader) Read(p []byte) (n int, err error) {
    55  	if fr.err != nil {
    56  		return 0, fr.err
    57  	}
    58  
    59  	rd, err := fr.reader()
    60  	if err != nil {
    61  		return 0, err
    62  	}
    63  
    64  	n, err = rd.Read(p)
    65  	fr.offset += int64(n)
    66  
    67  	// Simulate io.EOR error if we reach filesize.
    68  	if err == nil && fr.offset >= fr.size {
    69  		err = io.EOF
    70  	}
    71  
    72  	return n, err
    73  }
    74  
    75  func (fr *fileReader) Seek(offset int64, whence int) (int64, error) {
    76  	if fr.err != nil {
    77  		return 0, fr.err
    78  	}
    79  
    80  	var err error
    81  	newOffset := fr.offset
    82  
    83  	switch whence {
    84  	case os.SEEK_CUR:
    85  		newOffset += int64(offset)
    86  	case os.SEEK_END:
    87  		newOffset = fr.size + int64(offset)
    88  	case os.SEEK_SET:
    89  		newOffset = int64(offset)
    90  	}
    91  
    92  	if newOffset < 0 {
    93  		err = fmt.Errorf("cannot seek to negative position")
    94  	} else {
    95  		if fr.offset != newOffset {
    96  			fr.reset()
    97  		}
    98  
    99  		// No problems, set the offset.
   100  		fr.offset = newOffset
   101  	}
   102  
   103  	return fr.offset, err
   104  }
   105  
   106  func (fr *fileReader) Close() error {
   107  	return fr.closeWithErr(fmt.Errorf("fileReader: closed"))
   108  }
   109  
   110  // reader prepares the current reader at the lrs offset, ensuring its buffered
   111  // and ready to go.
   112  func (fr *fileReader) reader() (io.Reader, error) {
   113  	if fr.err != nil {
   114  		return nil, fr.err
   115  	}
   116  
   117  	if fr.rc != nil {
   118  		return fr.brd, nil
   119  	}
   120  
   121  	// If we don't have a reader, open one up.
   122  	rc, err := fr.driver.ReadStream(fr.ctx, fr.path, fr.offset)
   123  	if err != nil {
   124  		switch err := err.(type) {
   125  		case storagedriver.PathNotFoundError:
   126  			// NOTE(stevvooe): If the path is not found, we simply return a
   127  			// reader that returns io.EOF. However, we do not set fr.rc,
   128  			// allowing future attempts at getting a reader to possibly
   129  			// succeed if the file turns up later.
   130  			return ioutil.NopCloser(bytes.NewReader([]byte{})), nil
   131  		default:
   132  			return nil, err
   133  		}
   134  	}
   135  
   136  	fr.rc = rc
   137  
   138  	if fr.brd == nil {
   139  		fr.brd = bufio.NewReaderSize(fr.rc, fileReaderBufferSize)
   140  	} else {
   141  		fr.brd.Reset(fr.rc)
   142  	}
   143  
   144  	return fr.brd, nil
   145  }
   146  
   147  // resetReader resets the reader, forcing the read method to open up a new
   148  // connection and rebuild the buffered reader. This should be called when the
   149  // offset and the reader will become out of sync, such as during a seek
   150  // operation.
   151  func (fr *fileReader) reset() {
   152  	if fr.err != nil {
   153  		return
   154  	}
   155  	if fr.rc != nil {
   156  		fr.rc.Close()
   157  		fr.rc = nil
   158  	}
   159  }
   160  
   161  func (fr *fileReader) closeWithErr(err error) error {
   162  	if fr.err != nil {
   163  		return fr.err
   164  	}
   165  
   166  	fr.err = err
   167  
   168  	// close and release reader chain
   169  	if fr.rc != nil {
   170  		fr.rc.Close()
   171  	}
   172  
   173  	fr.rc = nil
   174  	fr.brd = nil
   175  
   176  	return fr.err
   177  }