github.com/cavaliergopher/grab/v3@v3.0.1/response.go (about) 1 package grab 2 3 import ( 4 "bytes" 5 "context" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "sync/atomic" 11 "time" 12 ) 13 14 // Response represents the response to a completed or in-progress download 15 // request. 16 // 17 // A response may be returned as soon a HTTP response is received from a remote 18 // server, but before the body content has started transferring. 19 // 20 // All Response method calls are thread-safe. 21 type Response struct { 22 // The Request that was submitted to obtain this Response. 23 Request *Request 24 25 // HTTPResponse represents the HTTP response received from an HTTP request. 26 // 27 // The response Body should not be used as it will be consumed and closed by 28 // grab. 29 HTTPResponse *http.Response 30 31 // Filename specifies the path where the file transfer is stored in local 32 // storage. 33 Filename string 34 35 // Size specifies the total expected size of the file transfer. 36 sizeUnsafe int64 37 38 // Start specifies the time at which the file transfer started. 39 Start time.Time 40 41 // End specifies the time at which the file transfer completed. 42 // 43 // This will return zero until the transfer has completed. 44 End time.Time 45 46 // CanResume specifies that the remote server advertised that it can resume 47 // previous downloads, as the 'Accept-Ranges: bytes' header is set. 48 CanResume bool 49 50 // DidResume specifies that the file transfer resumed a previously incomplete 51 // transfer. 52 DidResume bool 53 54 // Done is closed once the transfer is finalized, either successfully or with 55 // errors. Errors are available via Response.Err 56 Done chan struct{} 57 58 // ctx is a Context that controls cancelation of an inprogress transfer 59 ctx context.Context 60 61 // cancel is a cancel func that can be used to cancel the context of this 62 // Response. 63 cancel context.CancelFunc 64 65 // fi is the FileInfo for the destination file if it already existed before 66 // transfer started. 67 fi os.FileInfo 68 69 // optionsKnown indicates that a HEAD request has been completed and the 70 // capabilities of the remote server are known. 71 optionsKnown bool 72 73 // writer is the file handle used to write the downloaded file to local 74 // storage 75 writer io.Writer 76 77 // storeBuffer receives the contents of the transfer if Request.NoStore is 78 // enabled. 79 storeBuffer bytes.Buffer 80 81 // bytesCompleted specifies the number of bytes which were already 82 // transferred before this transfer began. 83 bytesResumed int64 84 85 // transfer is responsible for copying data from the remote server to a local 86 // file, tracking progress and allowing for cancelation. 87 transfer *transfer 88 89 // bufferSize specifies the size in bytes of the transfer buffer. 90 bufferSize int 91 92 // Error contains any error that may have occurred during the file transfer. 93 // This should not be read until IsComplete returns true. 94 err error 95 } 96 97 // IsComplete returns true if the download has completed. If an error occurred 98 // during the download, it can be returned via Err. 99 func (c *Response) IsComplete() bool { 100 select { 101 case <-c.Done: 102 return true 103 default: 104 return false 105 } 106 } 107 108 // Cancel cancels the file transfer by canceling the underlying Context for 109 // this Response. Cancel blocks until the transfer is closed and returns any 110 // error - typically context.Canceled. 111 func (c *Response) Cancel() error { 112 c.cancel() 113 return c.Err() 114 } 115 116 // Wait blocks until the download is completed. 117 func (c *Response) Wait() { 118 <-c.Done 119 } 120 121 // Err blocks the calling goroutine until the underlying file transfer is 122 // completed and returns any error that may have occurred. If the download is 123 // already completed, Err returns immediately. 124 func (c *Response) Err() error { 125 <-c.Done 126 return c.err 127 } 128 129 // Size returns the size of the file transfer. If the remote server does not 130 // specify the total size and the transfer is incomplete, the return value is 131 // -1. 132 func (c *Response) Size() int64 { 133 return atomic.LoadInt64(&c.sizeUnsafe) 134 } 135 136 // BytesComplete returns the total number of bytes which have been copied to 137 // the destination, including any bytes that were resumed from a previous 138 // download. 139 func (c *Response) BytesComplete() int64 { 140 return c.bytesResumed + c.transfer.N() 141 } 142 143 // BytesPerSecond returns the number of bytes per second transferred using a 144 // simple moving average of the last five seconds. If the download is already 145 // complete, the average bytes/sec for the life of the download is returned. 146 func (c *Response) BytesPerSecond() float64 { 147 if c.IsComplete() { 148 return float64(c.transfer.N()) / c.Duration().Seconds() 149 } 150 return c.transfer.BPS() 151 } 152 153 // Progress returns the ratio of total bytes that have been downloaded. Multiply 154 // the returned value by 100 to return the percentage completed. 155 func (c *Response) Progress() float64 { 156 size := c.Size() 157 if size <= 0 { 158 return 0 159 } 160 return float64(c.BytesComplete()) / float64(size) 161 } 162 163 // Duration returns the duration of a file transfer. If the transfer is in 164 // process, the duration will be between now and the start of the transfer. If 165 // the transfer is complete, the duration will be between the start and end of 166 // the completed transfer process. 167 func (c *Response) Duration() time.Duration { 168 if c.IsComplete() { 169 return c.End.Sub(c.Start) 170 } 171 172 return time.Now().Sub(c.Start) 173 } 174 175 // ETA returns the estimated time at which the the download will complete, given 176 // the current BytesPerSecond. If the transfer has already completed, the actual 177 // end time will be returned. 178 func (c *Response) ETA() time.Time { 179 if c.IsComplete() { 180 return c.End 181 } 182 bt := c.BytesComplete() 183 bps := c.transfer.BPS() 184 if bps == 0 { 185 return time.Time{} 186 } 187 secs := float64(c.Size()-bt) / bps 188 return time.Now().Add(time.Duration(secs) * time.Second) 189 } 190 191 // Open blocks the calling goroutine until the underlying file transfer is 192 // completed and then opens the transferred file for reading. If Request.NoStore 193 // was enabled, the reader will read from memory. 194 // 195 // If an error occurred during the transfer, it will be returned. 196 // 197 // It is the callers responsibility to close the returned file handle. 198 func (c *Response) Open() (io.ReadCloser, error) { 199 if err := c.Err(); err != nil { 200 return nil, err 201 } 202 return c.openUnsafe() 203 } 204 205 func (c *Response) openUnsafe() (io.ReadCloser, error) { 206 if c.Request.NoStore { 207 return ioutil.NopCloser(bytes.NewReader(c.storeBuffer.Bytes())), nil 208 } 209 return os.Open(c.Filename) 210 } 211 212 // Bytes blocks the calling goroutine until the underlying file transfer is 213 // completed and then reads all bytes from the completed tranafer. If 214 // Request.NoStore was enabled, the bytes will be read from memory. 215 // 216 // If an error occurred during the transfer, it will be returned. 217 func (c *Response) Bytes() ([]byte, error) { 218 if err := c.Err(); err != nil { 219 return nil, err 220 } 221 if c.Request.NoStore { 222 return c.storeBuffer.Bytes(), nil 223 } 224 f, err := c.Open() 225 if err != nil { 226 return nil, err 227 } 228 defer f.Close() 229 return ioutil.ReadAll(f) 230 } 231 232 func (c *Response) requestMethod() string { 233 if c == nil || c.HTTPResponse == nil || c.HTTPResponse.Request == nil { 234 return "" 235 } 236 return c.HTTPResponse.Request.Method 237 } 238 239 func (c *Response) checksumUnsafe() ([]byte, error) { 240 f, err := c.openUnsafe() 241 if err != nil { 242 return nil, err 243 } 244 defer f.Close() 245 t := newTransfer(c.Request.Context(), nil, c.Request.hash, f, nil) 246 if _, err = t.copy(); err != nil { 247 return nil, err 248 } 249 sum := c.Request.hash.Sum(nil) 250 return sum, nil 251 } 252 253 func (c *Response) closeResponseBody() error { 254 if c.HTTPResponse == nil || c.HTTPResponse.Body == nil { 255 return nil 256 } 257 return c.HTTPResponse.Body.Close() 258 }