github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/fs/chunkedreader/chunkedreader.go (about)

     1  package chunkedreader
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"sync"
     8  
     9  	"github.com/ncw/rclone/fs"
    10  )
    11  
    12  // io related errors returned by ChunkedReader
    13  var (
    14  	ErrorFileClosed  = errors.New("file already closed")
    15  	ErrorInvalidSeek = errors.New("invalid seek position")
    16  )
    17  
    18  // ChunkedReader is a reader for a Object with the possibility
    19  // of reading the source in chunks of given size
    20  //
    21  // A initialChunkSize of <= 0 will disable chunked reading.
    22  type ChunkedReader struct {
    23  	ctx              context.Context
    24  	mu               sync.Mutex    // protects following fields
    25  	o                fs.Object     // source to read from
    26  	rc               io.ReadCloser // reader for the current open chunk
    27  	offset           int64         // offset the next Read will start. -1 forces a reopen of o
    28  	chunkOffset      int64         // beginning of the current or next chunk
    29  	chunkSize        int64         // length of the current or next chunk. -1 will open o from chunkOffset to the end
    30  	initialChunkSize int64         // default chunkSize after the chunk specified by RangeSeek is complete
    31  	maxChunkSize     int64         // consecutive read chunks will double in size until reached. -1 means no limit
    32  	customChunkSize  bool          // is the current chunkSize set by RangeSeek?
    33  	closed           bool          // has Close been called?
    34  }
    35  
    36  // New returns a ChunkedReader for the Object.
    37  //
    38  // A initialChunkSize of <= 0 will disable chunked reading.
    39  // If maxChunkSize is greater than initialChunkSize, the chunk size will be
    40  // doubled after each chunk read with a maximun of maxChunkSize.
    41  // A Seek or RangeSeek will reset the chunk size to it's initial value
    42  func New(ctx context.Context, o fs.Object, initialChunkSize int64, maxChunkSize int64) *ChunkedReader {
    43  	if initialChunkSize <= 0 {
    44  		initialChunkSize = -1
    45  	}
    46  	if maxChunkSize != -1 && maxChunkSize < initialChunkSize {
    47  		maxChunkSize = initialChunkSize
    48  	}
    49  	return &ChunkedReader{
    50  		ctx:              ctx,
    51  		o:                o,
    52  		offset:           -1,
    53  		chunkSize:        initialChunkSize,
    54  		initialChunkSize: initialChunkSize,
    55  		maxChunkSize:     maxChunkSize,
    56  	}
    57  }
    58  
    59  // Read from the file - for details see io.Reader
    60  func (cr *ChunkedReader) Read(p []byte) (n int, err error) {
    61  	cr.mu.Lock()
    62  	defer cr.mu.Unlock()
    63  
    64  	if cr.closed {
    65  		return 0, ErrorFileClosed
    66  	}
    67  
    68  	for reqSize := int64(len(p)); reqSize > 0; reqSize = int64(len(p)) {
    69  		// the current chunk boundary. valid only when chunkSize > 0
    70  		chunkEnd := cr.chunkOffset + cr.chunkSize
    71  
    72  		fs.Debugf(cr.o, "ChunkedReader.Read at %d length %d chunkOffset %d chunkSize %d", cr.offset, reqSize, cr.chunkOffset, cr.chunkSize)
    73  
    74  		switch {
    75  		case cr.chunkSize > 0 && cr.offset == chunkEnd: // last chunk read completely
    76  			cr.chunkOffset = cr.offset
    77  			if cr.customChunkSize { // last chunkSize was set by RangeSeek
    78  				cr.customChunkSize = false
    79  				cr.chunkSize = cr.initialChunkSize
    80  			} else {
    81  				cr.chunkSize *= 2
    82  				if cr.chunkSize > cr.maxChunkSize && cr.maxChunkSize != -1 {
    83  					cr.chunkSize = cr.maxChunkSize
    84  				}
    85  			}
    86  			// recalculate the chunk boundary. valid only when chunkSize > 0
    87  			chunkEnd = cr.chunkOffset + cr.chunkSize
    88  			fallthrough
    89  		case cr.offset == -1: // first Read or Read after RangeSeek
    90  			err = cr.openRange()
    91  			if err != nil {
    92  				return
    93  			}
    94  		}
    95  
    96  		var buf []byte
    97  		chunkRest := chunkEnd - cr.offset
    98  		// limit read to chunk boundaries if chunkSize > 0
    99  		if reqSize > chunkRest && cr.chunkSize > 0 {
   100  			buf, p = p[0:chunkRest], p[chunkRest:]
   101  		} else {
   102  			buf, p = p, nil
   103  		}
   104  		var rn int
   105  		rn, err = io.ReadFull(cr.rc, buf)
   106  		n += rn
   107  		cr.offset += int64(rn)
   108  		if err != nil {
   109  			if err == io.ErrUnexpectedEOF {
   110  				err = io.EOF
   111  			}
   112  			return
   113  		}
   114  	}
   115  	return n, nil
   116  }
   117  
   118  // Close the file - for details see io.Closer
   119  //
   120  // All methods on ChunkedReader will return ErrorFileClosed afterwards
   121  func (cr *ChunkedReader) Close() error {
   122  	cr.mu.Lock()
   123  	defer cr.mu.Unlock()
   124  
   125  	if cr.closed {
   126  		return ErrorFileClosed
   127  	}
   128  	cr.closed = true
   129  
   130  	return cr.resetReader(nil, 0)
   131  }
   132  
   133  // Seek the file - for details see io.Seeker
   134  func (cr *ChunkedReader) Seek(offset int64, whence int) (int64, error) {
   135  	return cr.RangeSeek(context.TODO(), offset, whence, -1)
   136  }
   137  
   138  // RangeSeek the file - for details see RangeSeeker
   139  //
   140  // The specified length will only apply to the next chunk opened.
   141  // RangeSeek will not reopen the source until Read is called.
   142  func (cr *ChunkedReader) RangeSeek(ctx context.Context, offset int64, whence int, length int64) (int64, error) {
   143  	cr.mu.Lock()
   144  	defer cr.mu.Unlock()
   145  
   146  	fs.Debugf(cr.o, "ChunkedReader.RangeSeek from %d to %d length %d", cr.offset, offset, length)
   147  
   148  	if cr.closed {
   149  		return 0, ErrorFileClosed
   150  	}
   151  
   152  	size := cr.o.Size()
   153  	switch whence {
   154  	case io.SeekStart:
   155  		cr.offset = 0
   156  	case io.SeekEnd:
   157  		cr.offset = size
   158  	}
   159  	// set the new chunk start
   160  	cr.chunkOffset = cr.offset + offset
   161  	// force reopen on next Read
   162  	cr.offset = -1
   163  	if length > 0 {
   164  		cr.customChunkSize = true
   165  		cr.chunkSize = length
   166  	} else {
   167  		cr.chunkSize = cr.initialChunkSize
   168  	}
   169  	if cr.chunkOffset < 0 || cr.chunkOffset >= size {
   170  		cr.chunkOffset = 0
   171  		return 0, ErrorInvalidSeek
   172  	}
   173  	return cr.chunkOffset, nil
   174  }
   175  
   176  // Open forces the connection to be opened
   177  func (cr *ChunkedReader) Open() (*ChunkedReader, error) {
   178  	cr.mu.Lock()
   179  	defer cr.mu.Unlock()
   180  
   181  	if cr.rc != nil && cr.offset != -1 {
   182  		return cr, nil
   183  	}
   184  	return cr, cr.openRange()
   185  }
   186  
   187  // openRange will open the source Object with the current chunk range
   188  //
   189  // If the current open reader implements RangeSeeker, it is tried first.
   190  // When RangeSeek fails, o.Open with a RangeOption is used.
   191  //
   192  // A length <= 0 will request till the end of the file
   193  func (cr *ChunkedReader) openRange() error {
   194  	offset, length := cr.chunkOffset, cr.chunkSize
   195  	fs.Debugf(cr.o, "ChunkedReader.openRange at %d length %d", offset, length)
   196  
   197  	if cr.closed {
   198  		return ErrorFileClosed
   199  	}
   200  
   201  	if rs, ok := cr.rc.(fs.RangeSeeker); ok {
   202  		n, err := rs.RangeSeek(cr.ctx, offset, io.SeekStart, length)
   203  		if err == nil && n == offset {
   204  			cr.offset = offset
   205  			return nil
   206  		}
   207  		if err != nil {
   208  			fs.Debugf(cr.o, "ChunkedReader.openRange seek failed (%s). Trying Open", err)
   209  		} else {
   210  			fs.Debugf(cr.o, "ChunkedReader.openRange seeked to wrong offset. Wanted %d, got %d. Trying Open", offset, n)
   211  		}
   212  	}
   213  
   214  	var rc io.ReadCloser
   215  	var err error
   216  	if length <= 0 {
   217  		if offset == 0 {
   218  			rc, err = cr.o.Open(cr.ctx)
   219  		} else {
   220  			rc, err = cr.o.Open(cr.ctx, &fs.RangeOption{Start: offset, End: -1})
   221  		}
   222  	} else {
   223  		rc, err = cr.o.Open(cr.ctx, &fs.RangeOption{Start: offset, End: offset + length - 1})
   224  	}
   225  	if err != nil {
   226  		return err
   227  	}
   228  	return cr.resetReader(rc, offset)
   229  }
   230  
   231  // resetReader switches the current reader to the given reader.
   232  // The old reader will be Close'd before setting the new reader.
   233  func (cr *ChunkedReader) resetReader(rc io.ReadCloser, offset int64) error {
   234  	if cr.rc != nil {
   235  		if err := cr.rc.Close(); err != nil {
   236  			return err
   237  		}
   238  	}
   239  	cr.rc = rc
   240  	cr.offset = offset
   241  	return nil
   242  }
   243  
   244  var (
   245  	_ io.ReadCloser  = (*ChunkedReader)(nil)
   246  	_ io.Seeker      = (*ChunkedReader)(nil)
   247  	_ fs.RangeSeeker = (*ChunkedReader)(nil)
   248  )