github.com/raamsri/grab/v3@v3.0.2/transfer.go (about)

     1  package grab
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"sync/atomic"
     7  	"time"
     8  
     9  	"github.com/raamsri/grab/v3/pkg/bps"
    10  )
    11  
    12  type transfer struct {
    13  	n     int64 // must be 64bit aligned on 386
    14  	ctx   context.Context
    15  	gauge bps.Gauge
    16  	lim   RateLimiter
    17  	w     io.Writer
    18  	r     io.Reader
    19  	b     []byte
    20  }
    21  
    22  func newTransfer(ctx context.Context, lim RateLimiter, dst io.Writer, src io.Reader, buf []byte) *transfer {
    23  	return &transfer{
    24  		ctx:   ctx,
    25  		gauge: bps.NewSMA(6), // five second moving average sampling every second
    26  		lim:   lim,
    27  		w:     dst,
    28  		r:     src,
    29  		b:     buf,
    30  	}
    31  }
    32  
    33  // copy behaves similarly to io.CopyBuffer except that it checks for cancelation
    34  // of the given context.Context, reports progress in a thread-safe manner and
    35  // tracks the transfer rate.
    36  func (c *transfer) copy() (written int64, err error) {
    37  	// maintain a bps gauge in another goroutine
    38  	ctx, cancel := context.WithCancel(c.ctx)
    39  	defer cancel()
    40  	go bps.Watch(ctx, c.gauge, c.N, time.Second)
    41  
    42  	// start the transfer
    43  	if c.b == nil {
    44  		c.b = make([]byte, 32*1024)
    45  	}
    46  	for {
    47  		select {
    48  		case <-c.ctx.Done():
    49  			err = c.ctx.Err()
    50  			return
    51  		default:
    52  			// keep working
    53  		}
    54  		nr, er := c.r.Read(c.b)
    55  		if nr > 0 {
    56  			nw, ew := c.w.Write(c.b[0:nr])
    57  			if nw > 0 {
    58  				written += int64(nw)
    59  				atomic.StoreInt64(&c.n, written)
    60  			}
    61  			if ew != nil {
    62  				err = ew
    63  				break
    64  			}
    65  			if nr != nw {
    66  				err = io.ErrShortWrite
    67  				break
    68  			}
    69  			// wait for rate limiter
    70  			if c.lim != nil {
    71  				err = c.lim.WaitN(c.ctx, nr)
    72  				if err != nil {
    73  					return
    74  				}
    75  			}
    76  		}
    77  		if er != nil {
    78  			if er != io.EOF {
    79  				err = er
    80  			}
    81  			break
    82  		}
    83  	}
    84  	return written, err
    85  }
    86  
    87  // N returns the number of bytes transferred.
    88  func (c *transfer) N() (n int64) {
    89  	if c == nil {
    90  		return 0
    91  	}
    92  	n = atomic.LoadInt64(&c.n)
    93  	return
    94  }
    95  
    96  // BPS returns the current bytes per second transfer rate using a simple moving
    97  // average.
    98  func (c *transfer) BPS() (bps float64) {
    99  	if c == nil || c.gauge == nil {
   100  		return 0
   101  	}
   102  	return c.gauge.BPS()
   103  }