github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/renter/downloadstreamer.go (about)

     1  package renter
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"math"
     8  	"time"
     9  
    10  	"github.com/NebulousLabs/errors"
    11  )
    12  
    13  type (
    14  	// streamer is a io.ReadSeeker that can be used to stream downloads from
    15  	// the sia network.
    16  	streamer struct {
    17  		file   *file
    18  		offset int64
    19  		r      *Renter
    20  	}
    21  )
    22  
    23  // min is a helper function to find the minimum of multiple values.
    24  func min(values ...uint64) uint64 {
    25  	min := uint64(math.MaxUint64)
    26  	for _, v := range values {
    27  		if v < min {
    28  			min = v
    29  		}
    30  	}
    31  	return min
    32  }
    33  
    34  // Streamer creates an io.ReadSeeker that can be used to stream downloads from
    35  // the sia network.
    36  func (r *Renter) Streamer(siaPath string) (string, io.ReadSeeker, error) {
    37  	// Lookup the file associated with the nickname.
    38  	lockID := r.mu.RLock()
    39  	file, exists := r.files[siaPath]
    40  	r.mu.RUnlock(lockID)
    41  	if !exists || file.deleted {
    42  		return "", nil, fmt.Errorf("no file with that path: %s", siaPath)
    43  	}
    44  	// Create the streamer
    45  	s := &streamer{
    46  		file: file,
    47  		r:    r,
    48  	}
    49  	return file.name, s, nil
    50  }
    51  
    52  // Read implements the standard Read interface. It will download the requested
    53  // data from the sia network and block until the download is complete.  To
    54  // prevent http.ServeContent from requesting too much data at once, Read can
    55  // only request a single chunk at once.
    56  func (s *streamer) Read(p []byte) (n int, err error) {
    57  	// Get the file's size
    58  	s.file.mu.RLock()
    59  	fileSize := int64(s.file.size)
    60  	s.file.mu.RUnlock()
    61  
    62  	// Make sure we haven't reached the EOF yet.
    63  	if s.offset >= fileSize {
    64  		return 0, io.EOF
    65  	}
    66  
    67  	// Calculate how much we can download. We never download more than a single chunk.
    68  	chunkSize := s.file.staticChunkSize()
    69  	remainingData := uint64(fileSize - s.offset)
    70  	requestedData := uint64(len(p))
    71  	remainingChunk := chunkSize - uint64(s.offset)%chunkSize
    72  	length := min(remainingData, requestedData, remainingChunk)
    73  
    74  	// Download data
    75  	buffer := bytes.NewBuffer([]byte{})
    76  	d, err := s.r.newDownload(downloadParams{
    77  		destination:       newDownloadDestinationWriteCloserFromWriter(buffer),
    78  		destinationType:   destinationTypeSeekStream,
    79  		destinationString: "httpresponse",
    80  		file:              s.file,
    81  
    82  		latencyTarget: 50 * time.Millisecond, // TODO low default until full latency suport is added.
    83  		length:        length,
    84  		needsMemory:   true,
    85  		offset:        uint64(s.offset),
    86  		overdrive:     5,    // TODO: high default until full overdrive support is added.
    87  		priority:      1000, // TODO: high default until full priority support is added.
    88  	})
    89  	if err != nil {
    90  		return 0, errors.AddContext(err, "failed to create new download")
    91  	}
    92  
    93  	// Block until the download has completed.
    94  	select {
    95  	case <-d.completeChan:
    96  		if d.Err() != nil {
    97  			return 0, errors.AddContext(d.Err(), "download failed")
    98  		}
    99  	case <-s.r.tg.StopChan():
   100  		return 0, errors.New("download interrupted by shutdown")
   101  	}
   102  
   103  	// Copy downloaded data into buffer.
   104  	copy(p, buffer.Bytes())
   105  
   106  	// Adjust offset
   107  	s.offset += int64(length)
   108  	return int(length), nil
   109  }
   110  
   111  // Seek sets the offset for the next Read to offset, interpreted
   112  // according to whence: SeekStart means relative to the start of the file,
   113  // SeekCurrent means relative to the current offset, and SeekEnd means relative
   114  // to the end. Seek returns the new offset relative to the start of the file
   115  // and an error, if any.
   116  func (s *streamer) Seek(offset int64, whence int) (int64, error) {
   117  	var newOffset int64
   118  	switch whence {
   119  	case io.SeekStart:
   120  		newOffset = 0
   121  	case io.SeekCurrent:
   122  		newOffset = s.offset
   123  	case io.SeekEnd:
   124  		s.file.mu.RLock()
   125  		newOffset = int64(s.file.size)
   126  		s.file.mu.RUnlock()
   127  	}
   128  	newOffset += offset
   129  
   130  	if newOffset < 0 {
   131  		return s.offset, errors.New("cannot seek to negative offset")
   132  	}
   133  	s.offset = newOffset
   134  	return s.offset, nil
   135  }