gopkg.in/cavaliercoder/grab.v2@v2.0.0/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  }