github.com/NebulousLabs/Sia@v1.3.7/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.managedNewDownload(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  	// Set the in-memory buffer to nil just to be safe in case of a memory
    94  	// leak.
    95  	defer func() {
    96  		d.destination = nil
    97  	}()
    98  
    99  	// Block until the download has completed.
   100  	select {
   101  	case <-d.completeChan:
   102  		if d.Err() != nil {
   103  			return 0, errors.AddContext(d.Err(), "download failed")
   104  		}
   105  	case <-s.r.tg.StopChan():
   106  		return 0, errors.New("download interrupted by shutdown")
   107  	}
   108  
   109  	// Copy downloaded data into buffer.
   110  	copy(p, buffer.Bytes())
   111  
   112  	// Adjust offset
   113  	s.offset += int64(length)
   114  	return int(length), nil
   115  }
   116  
   117  // Seek sets the offset for the next Read to offset, interpreted
   118  // according to whence: SeekStart means relative to the start of the file,
   119  // SeekCurrent means relative to the current offset, and SeekEnd means relative
   120  // to the end. Seek returns the new offset relative to the start of the file
   121  // and an error, if any.
   122  func (s *streamer) Seek(offset int64, whence int) (int64, error) {
   123  	var newOffset int64
   124  	switch whence {
   125  	case io.SeekStart:
   126  		newOffset = 0
   127  	case io.SeekCurrent:
   128  		newOffset = s.offset
   129  	case io.SeekEnd:
   130  		s.file.mu.RLock()
   131  		newOffset = int64(s.file.size)
   132  		s.file.mu.RUnlock()
   133  	}
   134  	newOffset += offset
   135  
   136  	if newOffset < 0 {
   137  		return s.offset, errors.New("cannot seek to negative offset")
   138  	}
   139  	s.offset = newOffset
   140  	return s.offset, nil
   141  }