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