mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-14 12:23:43 +00:00
丰富单元测试
This commit is contained in:
1 parent
dd47b52952
commit
df2dbca475
6 files changed
+345
-113
No files matched your search
@@ -36,10 +36,9 @@ type Options struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
Name string
|
Name string
|
||||||
Path string
|
Path string
|
||||||
Size int64
|
Size int64
|
||||||
Downloaded int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Process interface {
|
type Process interface {
|
||||||
@@ -53,25 +52,42 @@ type Controller struct {
|
|||||||
Files map[string]*os.File
|
Files map[string]*os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) Touch(name string, size int64) error {
|
func NewController() *Controller {
|
||||||
|
return &Controller{Files: make(map[string]*os.File)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Touch(name string, size int64) (file *os.File, err error) {
|
||||||
if size > 0 {
|
if size > 0 {
|
||||||
return os.Truncate(name, size)
|
err := os.Truncate(name, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
file, err = os.OpenFile(name, os.O_RDWR, 0666)
|
||||||
|
} else {
|
||||||
|
file, err = os.Create(name)
|
||||||
}
|
}
|
||||||
_, err := os.Create(name)
|
if err == nil {
|
||||||
return err
|
c.Files[name] = file
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Open(name string) (file *os.File, err error) {
|
||||||
|
file, err = os.OpenFile(name, os.O_RDWR, 0666)
|
||||||
|
if err == nil {
|
||||||
|
c.Files[name] = file
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) Write(name string, offset int64, buf []byte) (int, error) {
|
func (c *Controller) Write(name string, offset int64, buf []byte) (int, error) {
|
||||||
file, ok := c.Files[name]
|
return c.Files[name].WriteAt(buf, offset)
|
||||||
if !ok {
|
}
|
||||||
var err error
|
|
||||||
file, err = os.OpenFile(name, os.O_RDWR, 0666)
|
func (c *Controller) Close(name string) error {
|
||||||
if err != nil {
|
err := c.Files[name].Close()
|
||||||
return 0, err
|
delete(c.Files, name)
|
||||||
}
|
return err
|
||||||
c.Files[name] = file
|
|
||||||
}
|
|
||||||
return file.WriteAt(buf, offset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) ContextDialer() (proxy.Dialer, error) {
|
func (c *Controller) ContextDialer() (proxy.Dialer, error) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/monkeyWie/gopeed/download/common"
|
"github.com/monkeyWie/gopeed/download/common"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ type Downloader struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewDownloader(fetchers ...common.Fetcher) *Downloader {
|
func NewDownloader(fetchers ...common.Fetcher) *Downloader {
|
||||||
d := &Downloader{Controller: &common.Controller{Files: make(map[string]*os.File)}}
|
d := &Downloader{Controller: common.NewController()}
|
||||||
d.fetches = make(map[string]common.Fetcher)
|
d.fetches = make(map[string]common.Fetcher)
|
||||||
for _, f := range fetchers {
|
for _, f := range fetchers {
|
||||||
for _, p := range f.Protocols() {
|
for _, p := range f.Protocols() {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/monkeyWie/gopeed/download/common"
|
"github.com/monkeyWie/gopeed/download/common"
|
||||||
"github.com/monkeyWie/gopeed/download/http/model"
|
"github.com/monkeyWie/gopeed/download/http/model"
|
||||||
@@ -16,10 +15,27 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RequestError struct {
|
||||||
|
Code int
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequestError(code int, msg string) *RequestError {
|
||||||
|
return &RequestError{Code: code, Msg: msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (re *RequestError) Error() string {
|
||||||
|
return fmt.Sprintf("http request fail,code:%d", re.Code)
|
||||||
|
}
|
||||||
|
|
||||||
type Fetcher struct {
|
type Fetcher struct {
|
||||||
*common.BaseFetcher
|
*common.BaseFetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewFetcher() *Fetcher {
|
||||||
|
return &Fetcher{BaseFetcher: &common.BaseFetcher{}}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Fetcher) Protocols() []string {
|
func (h *Fetcher) Protocols() []string {
|
||||||
return []string{"HTTP", "HTTPS"}
|
return []string{"HTTP", "HTTPS"}
|
||||||
}
|
}
|
||||||
@@ -36,7 +52,8 @@ func (h *Fetcher) Resolve(req *common.Request) (*common.Resource, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer httpResp.Body.Close()
|
// 拿到响应头就关闭,不用加defer
|
||||||
|
httpResp.Body.Close()
|
||||||
res := &common.Resource{
|
res := &common.Resource{
|
||||||
Req: req,
|
Req: req,
|
||||||
Range: false,
|
Range: false,
|
||||||
@@ -57,13 +74,15 @@ func (h *Fetcher) Resolve(req *common.Request) (*common.Resource, error) {
|
|||||||
} else if common.HttpCodeOK == httpResp.StatusCode {
|
} else if common.HttpCodeOK == httpResp.StatusCode {
|
||||||
// 返回200响应码,不支持断点下载,通过Content-Length头获取文件大小,获取不到的话可能是chunked编码
|
// 返回200响应码,不支持断点下载,通过Content-Length头获取文件大小,获取不到的话可能是chunked编码
|
||||||
contentLength := httpResp.Header.Get(common.HttpHeaderContentLength)
|
contentLength := httpResp.Header.Get(common.HttpHeaderContentLength)
|
||||||
parse, err := strconv.ParseInt(contentLength, 10, 64)
|
if contentLength != "" {
|
||||||
if err != nil {
|
parse, err := strconv.ParseInt(contentLength, 10, 64)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res.Size = parse
|
||||||
}
|
}
|
||||||
res.Size = parse
|
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("http request fail")
|
return nil, NewRequestError(httpResp.StatusCode, httpResp.Status)
|
||||||
}
|
}
|
||||||
file := &common.FileInfo{
|
file := &common.FileInfo{
|
||||||
Size: res.Size,
|
Size: res.Size,
|
||||||
|
|||||||
+195
-73
@@ -1,41 +1,171 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"crypto/md5"
|
||||||
"fmt"
|
"encoding/hex"
|
||||||
"github.com/monkeyWie/gopeed/download/common"
|
"github.com/monkeyWie/gopeed/download/common"
|
||||||
"golang.org/x/net/proxy"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startMockServer() net.Listener {
|
const testDir = "./"
|
||||||
|
const buildSize = 200 * 1024 * 1024
|
||||||
|
const buildName = "build.data"
|
||||||
|
const buildFile = testDir + buildName
|
||||||
|
const downloadName = "download.data"
|
||||||
|
const downloadFile = testDir + downloadName
|
||||||
|
|
||||||
|
func TestFetcher_Resolve(t *testing.T) {
|
||||||
|
testResolve(startTestFileServer, &common.Resource{
|
||||||
|
Size: buildSize,
|
||||||
|
Range: true,
|
||||||
|
Files: []*common.FileInfo{
|
||||||
|
{
|
||||||
|
Name: buildName,
|
||||||
|
Size: buildSize,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, t)
|
||||||
|
testResolve(startTestChunkedServer, &common.Resource{
|
||||||
|
Size: 0,
|
||||||
|
Range: false,
|
||||||
|
Files: []*common.FileInfo{
|
||||||
|
{
|
||||||
|
Name: buildName,
|
||||||
|
Size: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testResolve(startTestServer func() net.Listener, want *common.Resource, t *testing.T) {
|
||||||
|
listener := startTestServer()
|
||||||
|
defer listener.Close()
|
||||||
|
fetcher := NewFetcher()
|
||||||
|
res, err := fetcher.Resolve(&common.Request{
|
||||||
|
URL: "http://" + listener.Addr().String() + "/" + buildName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
res.Req = nil
|
||||||
|
if !reflect.DeepEqual(want, res) {
|
||||||
|
t.Errorf("Resolve error = %v, want %v", res, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadNormal(t *testing.T) {
|
||||||
|
listener := startTestFileServer()
|
||||||
|
defer listener.Close()
|
||||||
|
// 正常下载
|
||||||
|
downloadNormal(listener, 1, t)
|
||||||
|
downloadNormal(listener, 5, t)
|
||||||
|
downloadNormal(listener, 8, t)
|
||||||
|
downloadNormal(listener, 16, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadContinue(t *testing.T) {
|
||||||
|
listener := startTestFileServer()
|
||||||
|
defer listener.Close()
|
||||||
|
// 暂停继续
|
||||||
|
//downloadContinue(listener, 1, t)
|
||||||
|
downloadContinue(listener, 5, t)
|
||||||
|
//downloadContinue(listener, 8, t)
|
||||||
|
//downloadContinue(listener, 16, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadChunked(t *testing.T) {
|
||||||
|
listener := startTestChunkedServer()
|
||||||
|
defer listener.Close()
|
||||||
|
// chunked编码下载
|
||||||
|
downloadNormal(listener, 1, t)
|
||||||
|
downloadContinue(listener, 1, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetcher_DownloadRetry(t *testing.T) {
|
||||||
|
listener := startTestRetryServer()
|
||||||
|
defer listener.Close()
|
||||||
|
// chunked编码下载
|
||||||
|
downloadNormal(listener, 1, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestFileServer() net.Listener {
|
||||||
|
return startTestServer(func() http.Handler {
|
||||||
|
return http.FileServer(http.Dir(testDir))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestChunkedServer() net.Listener {
|
||||||
|
return startTestServer(func() http.Handler {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/"+buildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
file, err := os.Open(buildFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
io.Copy(writer, file)
|
||||||
|
})
|
||||||
|
return mux
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestRetryServer() net.Listener {
|
||||||
|
counter := 0
|
||||||
|
return startTestServer(func() http.Handler {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/"+buildName, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
counter++
|
||||||
|
if counter != 1 && counter < 5 {
|
||||||
|
writer.WriteHeader(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, err := os.Open(buildFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
io.Copy(writer, file)
|
||||||
|
})
|
||||||
|
return mux
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestServer(serverHandle func() http.Handler) net.Listener {
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
file, err := os.Create("./test.data")
|
file, err := os.Create(buildFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
buf := make([]byte, 1024)
|
//随机生成一个文件
|
||||||
for i := 0; i < 20*1024; i++ {
|
l := int64(8192)
|
||||||
|
buf := make([]byte, l)
|
||||||
|
size := int64(0)
|
||||||
|
for {
|
||||||
_, err := rand.Read(buf)
|
_, err := rand.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
file.WriteAt(buf, int64(i*1024))
|
if size+l >= buildSize {
|
||||||
|
file.WriteAt(buf[0:buildSize-size], size)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
file.WriteAt(buf, size)
|
||||||
|
size += l
|
||||||
}
|
}
|
||||||
|
server := &http.Server{}
|
||||||
|
server.Handler = serverHandle()
|
||||||
|
go server.Serve(listener)
|
||||||
|
|
||||||
go func() {
|
|
||||||
http.Serve(listener, http.FileServer(http.Dir("./")))
|
|
||||||
}()
|
|
||||||
return &delFileListener{
|
return &delFileListener{
|
||||||
File: file,
|
File: file,
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
@@ -48,93 +178,85 @@ type delFileListener struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *delFileListener) Close() error {
|
func (c *delFileListener) Close() error {
|
||||||
c.File.Close()
|
defer func() {
|
||||||
err := os.Remove(c.File.Name())
|
c.File.Close()
|
||||||
if err != nil {
|
os.Remove(c.File.Name())
|
||||||
fmt.Println(err)
|
os.Remove(downloadFile)
|
||||||
return err
|
}()
|
||||||
}
|
|
||||||
return c.Listener.Close()
|
return c.Listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetcher_Resolve(t *testing.T) {
|
func downloadReady(listener net.Listener, connections int, t *testing.T) common.Process {
|
||||||
listener := startMockServer()
|
fetcher := NewFetcher()
|
||||||
defer listener.Close()
|
fetcher.InitCtl(common.NewController())
|
||||||
fetcher := &Fetcher{}
|
res, err := fetcher.Resolve(&common.Request{
|
||||||
resolve, err := fetcher.Resolve(&common.Request{
|
URL: "http://" + listener.Addr().String() + "/" + buildName,
|
||||||
URL: "http://" + listener.Addr().String() + "/test.data",
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
buf, err := json.Marshal(resolve)
|
process, err := fetcher.Create(res, &common.Options{
|
||||||
|
Name: downloadName,
|
||||||
|
Path: testDir,
|
||||||
|
Connections: connections,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
fmt.Printf("%s\n", string(buf))
|
return process
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test2(t *testing.T) {
|
func downloadNormal(listener net.Listener, connections int, t *testing.T) {
|
||||||
socks5, err := proxy.SOCKS5("tcp", "127.0.0.1:1080", nil, nil)
|
process := downloadReady(listener, connections, t)
|
||||||
|
err := process.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
conn, err := socks5.Dial("tcp", "www.baidu.com:80")
|
want := fileMd5(buildFile)
|
||||||
if err != nil {
|
got := fileMd5(downloadFile)
|
||||||
t.Fatal(err)
|
if want != got {
|
||||||
|
t.Errorf("Download error = %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
conn.Write([]byte("GET"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test3(t *testing.T) {
|
func downloadContinue(listener net.Listener, connections int, t *testing.T) {
|
||||||
var u User
|
process := downloadReady(listener, connections, t)
|
||||||
fmt.Println((&u).Say1())
|
|
||||||
fmt.Println(u.Say2())
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test4(t *testing.T) {
|
|
||||||
resp, err := http.Get("http://192.168.200.163:8088/go1.12.7.linux-amd64.tar.gz")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Second * 3)
|
err := process.Start()
|
||||||
err2 := resp.Body.Close()
|
if err != nil {
|
||||||
if err2 != nil {
|
t.Fatal(err)
|
||||||
fmt.Println("close err2:" + err2.Error())
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
_, err = io.Copy(ioutil.Discard, resp.Body)
|
time.Sleep(time.Millisecond * 100)
|
||||||
if err != nil {
|
if err := process.Pause(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
if err := process.Continue(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := fileMd5(buildFile)
|
||||||
|
got := fileMd5(downloadFile)
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("Download error = %v, want %v", got, want)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test5(t *testing.T) {
|
func fileMd5(filePath string) string {
|
||||||
name := "E:\\test\\gopeed\\http\\test.txt"
|
file, err := os.Open(filePath)
|
||||||
err := os.Truncate(name, 10)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
file, err := os.OpenFile(name, os.O_RDWR, 0666)
|
// Tell the program to call the following function when the current function returns
|
||||||
if err != nil {
|
defer file.Close()
|
||||||
t.Fatal(err)
|
|
||||||
}
|
// Open a new hash interface to write to
|
||||||
_, err = file.WriteAt([]byte("test"), 5)
|
hash := md5.New()
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
// Copy the file in the hash interface and check for any error
|
||||||
|
if _, err := io.Copy(hash, file); err != nil {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
}
|
return hex.EncodeToString(hash.Sum(nil))
|
||||||
|
|
||||||
type User struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) Say1() string {
|
|
||||||
return "hello1 " + u.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u User) Say2() string {
|
|
||||||
return "hello2 " + u.name
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
common2 "github.com/monkeyWie/gopeed/download/common"
|
"github.com/monkeyWie/gopeed/download/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Chunk struct {
|
type Chunk struct {
|
||||||
Status common2.Status
|
Status common.Status
|
||||||
Begin int64
|
Begin int64
|
||||||
End int64
|
End int64
|
||||||
Downloaded int64
|
Downloaded int64
|
||||||
@@ -13,7 +13,7 @@ type Chunk struct {
|
|||||||
|
|
||||||
func NewChunk(begin int64, end int64) *Chunk {
|
func NewChunk(begin int64, end int64) *Chunk {
|
||||||
return &Chunk{
|
return &Chunk{
|
||||||
Status: common2.DownloadStatusReady,
|
Status: common.DownloadStatusReady,
|
||||||
Begin: begin,
|
Begin: begin,
|
||||||
End: end,
|
End: end,
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-10
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Process struct {
|
type Process struct {
|
||||||
@@ -17,6 +18,8 @@ type Process struct {
|
|||||||
status common.Status
|
status common.Status
|
||||||
clients []*http.Response
|
clients []*http.Response
|
||||||
chunks []*model.Chunk
|
chunks []*model.Chunk
|
||||||
|
|
||||||
|
pauseLock *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProcess(fetcher *Fetcher, res *common.Resource, opts *common.Options) *Process {
|
func NewProcess(fetcher *Fetcher, res *common.Resource, opts *common.Options) *Process {
|
||||||
@@ -25,6 +28,8 @@ func NewProcess(fetcher *Fetcher, res *common.Resource, opts *common.Options) *P
|
|||||||
res: res,
|
res: res,
|
||||||
opts: opts,
|
opts: opts,
|
||||||
status: common.DownloadStatusReady,
|
status: common.DownloadStatusReady,
|
||||||
|
|
||||||
|
pauseLock: &sync.Mutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,11 +37,14 @@ func (p *Process) Start() error {
|
|||||||
ctl := p.fetcher.GetCtl()
|
ctl := p.fetcher.GetCtl()
|
||||||
// 创建文件
|
// 创建文件
|
||||||
name := p.name()
|
name := p.name()
|
||||||
err := ctl.Touch(name, p.res.Size)
|
_, err := ctl.Touch(name, p.res.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if p.res.Range && p.res.Size > 0 {
|
defer p.close()
|
||||||
|
p.status = common.DownloadStatusStart
|
||||||
|
var fetchErr error
|
||||||
|
if p.res.Range {
|
||||||
// 每个连接平均需要下载的分块大小
|
// 每个连接平均需要下载的分块大小
|
||||||
var (
|
var (
|
||||||
chunkSize = p.res.Size / int64(p.opts.Connections)
|
chunkSize = p.res.Size / int64(p.opts.Connections)
|
||||||
@@ -62,12 +70,27 @@ func (p *Process) Start() error {
|
|||||||
errCh <- p.fetchChunk(i, name, chunk)
|
errCh <- p.fetchChunk(i, name, chunk)
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
return p.wait(errCh)
|
fetchErr = p.wait(errCh)
|
||||||
|
} else {
|
||||||
|
// 只支持单连接下载
|
||||||
|
p.chunks = make([]*model.Chunk, 1)
|
||||||
|
p.clients = make([]*http.Response, 1)
|
||||||
|
p.chunks[0] = model.NewChunk(0, 0)
|
||||||
|
fetchErr = p.fetchChunk(0, name, p.chunks[0])
|
||||||
}
|
}
|
||||||
return nil
|
if p.status == common.DownloadStatusPause {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fetchErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Process) Pause() error {
|
func (p *Process) Pause() error {
|
||||||
|
p.pauseLock.Lock()
|
||||||
|
defer p.pauseLock.Unlock()
|
||||||
|
if common.DownloadStatusStart != p.status {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.status = common.DownloadStatusPause
|
||||||
if len(p.clients) > 0 {
|
if len(p.clients) > 0 {
|
||||||
for _, client := range p.clients {
|
for _, client := range p.clients {
|
||||||
client.Body.Close()
|
client.Body.Close()
|
||||||
@@ -77,10 +100,28 @@ func (p *Process) Pause() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Process) Continue() error {
|
func (p *Process) Continue() error {
|
||||||
|
if func() bool {
|
||||||
|
p.pauseLock.Lock()
|
||||||
|
defer p.pauseLock.Unlock()
|
||||||
|
if common.DownloadStatusPause != p.status && common.DownloadStatusError != p.status {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.status = common.DownloadStatusStart
|
||||||
|
return false
|
||||||
|
}() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
ctl = p.fetcher.GetCtl()
|
||||||
name = p.name()
|
name = p.name()
|
||||||
errCh = make(chan error)
|
errCh = make(chan error)
|
||||||
)
|
)
|
||||||
|
_, err := ctl.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ctl.Close(name)
|
||||||
for i := 0; i < p.opts.Connections; i++ {
|
for i := 0; i < p.opts.Connections; i++ {
|
||||||
go func(i int) {
|
go func(i int) {
|
||||||
errCh <- p.fetchChunk(i, name, p.chunks[i])
|
errCh <- p.fetchChunk(i, name, p.chunks[i])
|
||||||
@@ -93,6 +134,10 @@ func (p *Process) Delete() error {
|
|||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Process) close() error {
|
||||||
|
return p.fetcher.GetCtl().Close(p.name())
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Process) name() string {
|
func (p *Process) name() string {
|
||||||
// 创建文件
|
// 创建文件
|
||||||
var filename = p.opts.Name
|
var filename = p.opts.Name
|
||||||
@@ -120,6 +165,14 @@ func (p *Process) wait(errCh <-chan error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type action int
|
||||||
|
|
||||||
|
const (
|
||||||
|
actionDirect action = iota
|
||||||
|
actionContinue
|
||||||
|
actionReturn
|
||||||
|
)
|
||||||
|
|
||||||
func (p *Process) fetchChunk(index int, name string, chunk *model.Chunk) (err error) {
|
func (p *Process) fetchChunk(index int, name string, chunk *model.Chunk) (err error) {
|
||||||
httpReq, err := BuildRequest(p.res.Req)
|
httpReq, err := BuildRequest(p.res.Req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -135,14 +188,37 @@ func (p *Process) fetchChunk(index int, name string, chunk *model.Chunk) (err er
|
|||||||
resp *http.Response
|
resp *http.Response
|
||||||
retry bool
|
retry bool
|
||||||
)
|
)
|
||||||
httpReq.Header.Set(common.HttpHeaderRange,
|
if p.res.Range {
|
||||||
fmt.Sprintf(common.HttpHeaderRangeFormat, chunk.Begin+chunk.Downloaded, chunk.End))
|
httpReq.Header.Set(common.HttpHeaderRange,
|
||||||
resp, err = client.Do(httpReq)
|
fmt.Sprintf(common.HttpHeaderRangeFormat, chunk.Begin+chunk.Downloaded, chunk.End))
|
||||||
if err != nil {
|
} else {
|
||||||
// 连接失败,直接重试
|
chunk.Downloaded = 0
|
||||||
|
}
|
||||||
|
var act action
|
||||||
|
act, err = func() (action, error) {
|
||||||
|
p.pauseLock.Lock()
|
||||||
|
defer p.pauseLock.Unlock()
|
||||||
|
if p.status == common.DownloadStatusPause {
|
||||||
|
return actionReturn, nil
|
||||||
|
}
|
||||||
|
resp, err = client.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
// 连接失败,直接重试
|
||||||
|
return actionContinue, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != common.HttpCodeOK && resp.StatusCode != common.HttpCodePartialContent {
|
||||||
|
err = NewRequestError(resp.StatusCode, resp.Status)
|
||||||
|
return actionContinue, err
|
||||||
|
}
|
||||||
|
p.clients[index] = resp
|
||||||
|
return actionDirect, nil
|
||||||
|
}()
|
||||||
|
fmt.Printf("action:%d\n", act)
|
||||||
|
if act == actionContinue {
|
||||||
continue
|
continue
|
||||||
|
} else if act == actionReturn {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
p.clients[index] = resp
|
|
||||||
retry, err = func() (bool, error) {
|
retry, err = func() (bool, error) {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
for {
|
for {
|
||||||
|
|||||||
Reference in new issue
Block a user