github.com/floatingorchard/grab/v3@v3.0.0-20240419111603-44e36a9c1818/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  }