github.com/upyun/upx@v0.4.7-0.20240419023638-b184a7cb7c10/partial/downloader.go (about)

     1  package partial
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"os"
     8  	"sync"
     9  )
    10  
    11  const DefaultChunkSize = 1024 * 1024 * 10
    12  
    13  type ChunkDownFunc func(start, end int64) ([]byte, error)
    14  
    15  type MultiPartialDownloader struct {
    16  
    17  	// 文件路径
    18  	filePath string
    19  
    20  	// 最终文件大小
    21  	finalSize int64
    22  
    23  	// 本地文件大小
    24  	localSize int64
    25  
    26  	//分片大小
    27  	chunkSize int64
    28  
    29  	writer   io.Writer
    30  	works    int
    31  	downFunc ChunkDownFunc
    32  }
    33  
    34  func NewMultiPartialDownloader(filePath string, finalSize, chunkSize int64, writer io.Writer, works int, fn ChunkDownFunc) *MultiPartialDownloader {
    35  	return &MultiPartialDownloader{
    36  		filePath:  filePath,
    37  		finalSize: finalSize,
    38  		works:     works,
    39  		writer:    writer,
    40  		chunkSize: chunkSize,
    41  		downFunc:  fn,
    42  	}
    43  }
    44  
    45  func (p *MultiPartialDownloader) Download() error {
    46  	fileinfo, err := os.Stat(p.filePath)
    47  
    48  	// 如果异常
    49  	// - 文件不存在异常: localSize 默认值 0
    50  	// - 不是文件不存在异常: 报错
    51  	if err != nil && !os.IsNotExist(err) {
    52  		return err
    53  	}
    54  	if err == nil {
    55  		p.localSize = fileinfo.Size()
    56  	}
    57  
    58  	// 计算需要下载的块数
    59  	needDownSize := p.finalSize - p.localSize
    60  	chunkCount := needDownSize / p.chunkSize
    61  	if needDownSize%p.chunkSize != 0 {
    62  		chunkCount++
    63  	}
    64  
    65  	chunksSorter := NewChunksSorter(
    66  		chunkCount,
    67  		p.works,
    68  	)
    69  
    70  	// 下载切片任务
    71  	var wg sync.WaitGroup
    72  	ctx, cancel := context.WithCancel(context.Background())
    73  	defer func() {
    74  		// 取消切片下载任务,并等待
    75  		cancel()
    76  		wg.Wait()
    77  	}()
    78  
    79  	for i := 0; i < p.works; i++ {
    80  		wg.Add(1)
    81  		go func(ctx context.Context, workId int) {
    82  			defer func() {
    83  				// 关闭 workId 下的接收通道
    84  				chunksSorter.Close(workId)
    85  				wg.Done()
    86  			}()
    87  
    88  			// 每个 work 取自己倍数的 chunk
    89  			for j := workId; j < int(chunkCount); j += p.works {
    90  				select {
    91  				case <-ctx.Done():
    92  					return
    93  				default:
    94  					var (
    95  						err    error
    96  						buffer []byte
    97  					)
    98  					start := p.localSize + int64(j)*p.chunkSize
    99  					end := p.localSize + int64(j+1)*p.chunkSize
   100  					if end > p.finalSize {
   101  						end = p.finalSize
   102  					}
   103  					chunk := NewChunk(int64(j), start, end)
   104  
   105  					// 重试三次
   106  					for t := 0; t < 3; t++ {
   107  						// ? 由于长度是从1开始,而数据是从0地址开始
   108  						// ? 计算字节时容量会多出开头的一位,所以末尾需要减少一位
   109  						buffer, err = p.downFunc(chunk.start, chunk.end-1)
   110  						if err == nil {
   111  							break
   112  						}
   113  					}
   114  					chunk.SetData(buffer)
   115  					chunk.SetError(err)
   116  					chunksSorter.Write(chunk)
   117  
   118  					if err != nil {
   119  						return
   120  					}
   121  				}
   122  			}
   123  		}(ctx, i)
   124  	}
   125  
   126  	// 将分片顺序写入到文件
   127  	for {
   128  		chunk := chunksSorter.Read()
   129  		if chunk == nil {
   130  			break
   131  		}
   132  		if chunk.Error() != nil {
   133  			return chunk.Error()
   134  		}
   135  		if len(chunk.Data()) == 0 {
   136  			return errors.New("chunk buffer download but size is 0")
   137  		}
   138  		p.writer.Write(chunk.Data())
   139  	}
   140  	return nil
   141  }