github.com/xxf098/lite-proxy@v0.15.1-0.20230422081941-12c69f323218/download/download_range.go (about)

     1  package download
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net"
     7  	"net/http"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/xxf098/lite-proxy/common/pool"
    12  )
    13  
    14  var (
    15  	contentLength = 242743296
    16  )
    17  
    18  func DownloadRange(ctx context.Context, link string, part int, timeout time.Duration, handshakeTimeout time.Duration, resultChan chan<- int64, startChan chan<- time.Time) (int64, error) {
    19  	client, err := createClient(ctx, link)
    20  	if err != nil {
    21  		return 0, err
    22  	}
    23  
    24  	option := DownloadOption{
    25  		DownloadTimeout:  timeout,
    26  		HandshakeTimeout: handshakeTimeout,
    27  		URL:              downloadLink,
    28  		Ranges:           calcRange(int64(part), int64(contentLength), link),
    29  	}
    30  	return downloadRangeInternal(ctx, option, resultChan, startChan, client.Dial)
    31  }
    32  
    33  func downloadRangeInternal(ctx context.Context, option DownloadOption, resultChan chan<- int64, startOuterChan chan<- time.Time, dial func(network, addr string) (net.Conn, error)) (int64, error) {
    34  	var max int64 = 0
    35  	var wg sync.WaitGroup
    36  	totalCh := make(chan int64)
    37  	// remove
    38  	errorCh := make(chan error)
    39  	startCh := make(chan time.Time, 1)
    40  	for _, rng := range option.Ranges {
    41  		wg.Add(1)
    42  		go func(rng Range, totalChan chan<- int64, errorChan chan<- error, startChan chan<- time.Time) (int64, error) {
    43  			defer wg.Done()
    44  			var max int64 = 0
    45  			httpTransport := &http.Transport{}
    46  			httpClient := &http.Client{Transport: httpTransport, Timeout: option.HandshakeTimeout}
    47  			if dial != nil {
    48  				httpTransport.Dial = dial
    49  			}
    50  			req, err := http.NewRequest("GET", option.URL, nil)
    51  			if err != nil {
    52  				errorChan <- err
    53  				return max, err
    54  			}
    55  			// add range
    56  			ranges := rng.toHeader(int64(contentLength))
    57  			req.Header.Add("Range", ranges)
    58  			response, err := httpClient.Do(req)
    59  			if err != nil {
    60  				errorChan <- err
    61  				return max, err
    62  			}
    63  			defer response.Body.Close()
    64  			prev := time.Now()
    65  			startChan <- prev
    66  			var total int64
    67  			buf := pool.Get(20 * 1024)
    68  			pool.Put(buf)
    69  		Loop:
    70  			for {
    71  				if ctx.Err() != nil {
    72  					break
    73  				}
    74  				select {
    75  				case <-ctx.Done():
    76  					return max, err
    77  				default:
    78  					// buf := pool.Get(20 * 1024)
    79  					nr, er := response.Body.Read(buf)
    80  					total += int64(nr)
    81  					// pool.Put(buf)
    82  					now := time.Now()
    83  					if now.Sub(prev) >= 100*time.Millisecond || er != nil {
    84  						prev = now
    85  						if totalChan != nil {
    86  							totalChan <- total
    87  						}
    88  						if max < total {
    89  							max = total
    90  						}
    91  						total = 0
    92  					}
    93  					if er != nil {
    94  						if er != io.EOF {
    95  							errorChan <- err
    96  							err = er
    97  						}
    98  						break Loop
    99  					}
   100  				}
   101  
   102  			}
   103  			return max, nil
   104  
   105  		}(rng, totalCh, errorCh, startCh)
   106  	}
   107  	var sum int64 = 0
   108  	var errorResult error = nil
   109  
   110  	doneCh := make(chan bool, 1)
   111  	go func(doneChan chan<- bool) {
   112  		wg.Wait()
   113  		doneChan <- true
   114  	}(doneCh)
   115  	var prev time.Time
   116  	for {
   117  		if !prev.IsZero() {
   118  			now := time.Now()
   119  			if now.Sub(prev) >= time.Second {
   120  				prev = now
   121  				if resultChan != nil {
   122  					resultChan <- sum
   123  				}
   124  				if max < sum {
   125  					max = sum
   126  				}
   127  				sum = 0
   128  			}
   129  		}
   130  		select {
   131  		case total := <-totalCh:
   132  			if total < 0 {
   133  				return max, nil
   134  			}
   135  			sum += total
   136  		case err := <-errorCh:
   137  			if err != nil {
   138  				errorResult = err
   139  			}
   140  		case start := <-startCh:
   141  			// init only once
   142  			if prev.IsZero() {
   143  				prev = start
   144  				if startOuterChan != nil {
   145  					startOuterChan <- start
   146  				}
   147  			}
   148  		case <-doneCh:
   149  			return max, errorResult
   150  		case <-ctx.Done():
   151  			err := ctx.Err()
   152  			if max > 0 {
   153  				err = nil
   154  			}
   155  			return max, err
   156  		}
   157  	}
   158  }