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 }