mirror of
https://github.com/mudler/luet.git
synced 2025-06-30 01:02:00 +00:00
225 lines
6.1 KiB
Go
225 lines
6.1 KiB
Go
package grab
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Response represents the response to a completed or in-progress download
|
|
// request.
|
|
//
|
|
// A response may be returned as soon a HTTP response is received from a remote
|
|
// server, but before the body content has started transferring.
|
|
//
|
|
// All Response method calls are thread-safe.
|
|
type Response struct {
|
|
// The Request that was submitted to obtain this Response.
|
|
Request *Request
|
|
|
|
// HTTPResponse represents the HTTP response received from an HTTP request.
|
|
//
|
|
// The response Body should not be used as it will be consumed and closed by
|
|
// grab.
|
|
HTTPResponse *http.Response
|
|
|
|
// Filename specifies the path where the file transfer is stored in local
|
|
// storage.
|
|
Filename string
|
|
|
|
// Size specifies the total expected size of the file transfer.
|
|
Size int64
|
|
|
|
// Start specifies the time at which the file transfer started.
|
|
Start time.Time
|
|
|
|
// End specifies the time at which the file transfer completed.
|
|
//
|
|
// This will return zero until the transfer has completed.
|
|
End time.Time
|
|
|
|
// CanResume specifies that the remote server advertised that it can resume
|
|
// previous downloads, as the 'Accept-Ranges: bytes' header is set.
|
|
CanResume bool
|
|
|
|
// DidResume specifies that the file transfer resumed a previously incomplete
|
|
// transfer.
|
|
DidResume bool
|
|
|
|
// Done is closed once the transfer is finalized, either successfully or with
|
|
// errors. Errors are available via Response.Err
|
|
Done chan struct{}
|
|
|
|
// ctx is a Context that controls cancelation of an inprogress transfer
|
|
ctx context.Context
|
|
|
|
// cancel is a cancel func that can be used to cancel the context of this
|
|
// Response.
|
|
cancel context.CancelFunc
|
|
|
|
// fi is the FileInfo for the destination file if it already existed before
|
|
// transfer started.
|
|
fi os.FileInfo
|
|
|
|
// optionsKnown indicates that a HEAD request has been completed and the
|
|
// capabilities of the remote server are known.
|
|
optionsKnown bool
|
|
|
|
// writer is the file handle used to write the downloaded file to local
|
|
// storage
|
|
writer io.WriteCloser
|
|
|
|
// bytesCompleted specifies the number of bytes which were already
|
|
// transferred before this transfer began.
|
|
bytesResumed int64
|
|
|
|
// transfer is responsible for copying data from the remote server to a local
|
|
// file, tracking progress and allowing for cancelation.
|
|
transfer *transfer
|
|
|
|
// bytesPerSecond specifies the number of bytes that have been transferred in
|
|
// the last 1-second window.
|
|
bytesPerSecond float64
|
|
bytesPerSecondMu sync.Mutex
|
|
|
|
// bufferSize specifies the size in bytes of the transfer buffer.
|
|
bufferSize int
|
|
|
|
// Error contains any error that may have occurred during the file transfer.
|
|
// This should not be read until IsComplete returns true.
|
|
err error
|
|
}
|
|
|
|
// IsComplete returns true if the download has completed. If an error occurred
|
|
// during the download, it can be returned via Err.
|
|
func (c *Response) IsComplete() bool {
|
|
select {
|
|
case <-c.Done:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Cancel cancels the file transfer by canceling the underlying Context for
|
|
// this Response. Cancel blocks until the transfer is closed and returns any
|
|
// error - typically context.Canceled.
|
|
func (c *Response) Cancel() error {
|
|
c.cancel()
|
|
return c.Err()
|
|
}
|
|
|
|
// Wait blocks until the download is completed.
|
|
func (c *Response) Wait() {
|
|
<-c.Done
|
|
}
|
|
|
|
// Err blocks the calling goroutine until the underlying file transfer is
|
|
// completed and returns any error that may have occurred. If the download is
|
|
// already completed, Err returns immediately.
|
|
func (c *Response) Err() error {
|
|
<-c.Done
|
|
return c.err
|
|
}
|
|
|
|
// BytesComplete returns the total number of bytes which have been copied to
|
|
// the destination, including any bytes that were resumed from a previous
|
|
// download.
|
|
func (c *Response) BytesComplete() int64 {
|
|
return c.bytesResumed + c.transfer.N()
|
|
}
|
|
|
|
// BytesPerSecond returns the number of bytes transferred in the last second. If
|
|
// the download is already complete, the average bytes/sec for the life of the
|
|
// download is returned.
|
|
func (c *Response) BytesPerSecond() float64 {
|
|
if c.IsComplete() {
|
|
return float64(c.transfer.N()) / c.Duration().Seconds()
|
|
}
|
|
c.bytesPerSecondMu.Lock()
|
|
defer c.bytesPerSecondMu.Unlock()
|
|
return c.bytesPerSecond
|
|
}
|
|
|
|
// Progress returns the ratio of total bytes that have been downloaded. Multiply
|
|
// the returned value by 100 to return the percentage completed.
|
|
func (c *Response) Progress() float64 {
|
|
if c.Size == 0 {
|
|
return 0
|
|
}
|
|
return float64(c.BytesComplete()) / float64(c.Size)
|
|
}
|
|
|
|
// Duration returns the duration of a file transfer. If the transfer is in
|
|
// process, the duration will be between now and the start of the transfer. If
|
|
// the transfer is complete, the duration will be between the start and end of
|
|
// the completed transfer process.
|
|
func (c *Response) Duration() time.Duration {
|
|
if c.IsComplete() {
|
|
return c.End.Sub(c.Start)
|
|
}
|
|
|
|
return time.Now().Sub(c.Start)
|
|
}
|
|
|
|
// ETA returns the estimated time at which the the download will complete, given
|
|
// the current BytesPerSecond. If the transfer has already completed, the actual
|
|
// end time will be returned.
|
|
func (c *Response) ETA() time.Time {
|
|
if c.IsComplete() {
|
|
return c.End
|
|
}
|
|
bt := c.BytesComplete()
|
|
bps := c.BytesPerSecond()
|
|
if bps == 0 {
|
|
return time.Time{}
|
|
}
|
|
secs := float64(c.Size-bt) / bps
|
|
return time.Now().Add(time.Duration(secs) * time.Second)
|
|
}
|
|
|
|
// watchBps watches the progress of a transfer and maintains statistics.
|
|
func (c *Response) watchBps() {
|
|
var prev int64
|
|
then := c.Start
|
|
|
|
t := time.NewTicker(time.Second)
|
|
defer t.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-c.Done:
|
|
return
|
|
|
|
case now := <-t.C:
|
|
d := now.Sub(then)
|
|
then = now
|
|
|
|
cur := c.transfer.N()
|
|
bs := cur - prev
|
|
prev = cur
|
|
|
|
c.bytesPerSecondMu.Lock()
|
|
c.bytesPerSecond = float64(bs) / d.Seconds()
|
|
c.bytesPerSecondMu.Unlock()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Response) requestMethod() string {
|
|
if c == nil || c.HTTPResponse == nil || c.HTTPResponse.Request == nil {
|
|
return ""
|
|
}
|
|
return c.HTTPResponse.Request.Method
|
|
}
|
|
|
|
func (c *Response) closeResponseBody() error {
|
|
if c.HTTPResponse == nil || c.HTTPResponse.Body == nil {
|
|
return nil
|
|
}
|
|
return c.HTTPResponse.Body.Close()
|
|
}
|