diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8e3873c1..f7931a83 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,9 +54,10 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: Run Unit tests. run: go test $(go list ./... | grep -v /cmd/ | grep -v /bind/ ) -v -covermode=count -coverprofile=coverage.txt - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 with: files: ./coverage.txt + token: ${{ secrets.CODECOV_UPLOAD_TOKEN }} build: name: Build check runs-on: ubuntu-latest diff --git a/go.mod b/go.mod index 3b281d44..033aea50 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/matoous/go-nanoid/v2 v2.0.0 github.com/mattn/go-ieproxy v0.0.12 github.com/pkg/errors v0.9.1 + github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 github.com/rs/zerolog v1.31.0 github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459 github.com/xiaoqidun/setft v0.0.0-20220310121541-be86327699ad @@ -99,7 +100,6 @@ require ( github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/skeema/knownhosts v1.2.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect diff --git a/go.sum b/go.sum index a264f515..320e02d7 100644 --- a/go.sum +++ b/go.sum @@ -114,9 +114,6 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= -github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= @@ -125,22 +122,11 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= -github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw= -github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja v0.0.0-20240806095544-3491d4a58fbe h1:jwFJkgsdelB87ohlXaAGSd05Cb5ALDFa9iW9IGRHcRM= github.com/dop251/goja v0.0.0-20240806095544-3491d4a58fbe/go.mod h1:DF+w/nLMIkvRpyhd/0K+Okbh3fVZBtXLwRtS/ccAa5w= -github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= -github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= -github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c h1:hLoodLRD4KLWIH8eyAQCLcH8EqIrjac7fCkp/fHnvuQ= -github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c/go.mod h1:bhGPmCgCCTSRfiMYWjpS46IDo9EUZXlsuUaPXSWGbv0= github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc h1:MKYt39yZJi0Z9xEeRmDX2L4ocE0ETKcHKw6MVL3R+co= github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc/go.mod h1:VULptt4Q/fNzQUJlqY/GP3qHyU7ZH46mFkBZe0ZTokU= github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -192,8 +178,6 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= -github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -228,9 +212,6 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -258,7 +239,6 @@ github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -465,6 +445,8 @@ go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6b go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -527,8 +509,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -549,7 +529,6 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -582,15 +561,12 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= diff --git a/internal/protocol/bt/dns_cache_resolver.go b/internal/protocol/bt/dns_cache_resolver.go new file mode 100644 index 00000000..44a95c63 --- /dev/null +++ b/internal/protocol/bt/dns_cache_resolver.go @@ -0,0 +1,49 @@ +package bt + +import ( + "context" + "net" + "time" + + "github.com/rs/dnscache" +) + +// DnsCacheResolver resolves DNS requests for an HTTP client using an in-memory cache. +type DnsCacheResolver struct { + RefreshTimeout time.Duration + + resolver dnscache.Resolver +} + +func (r *DnsCacheResolver) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + ips, err := r.resolver.LookupHost(ctx, host) + if err != nil { + return nil, err + } + var conn net.Conn + for _, ip := range ips { + var dialer net.Dialer + conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip, port)) + if err == nil { + break + } + } + return conn, err +} + +func (r *DnsCacheResolver) Run(ctx context.Context) { + ticker := time.NewTicker(r.RefreshTimeout) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + r.resolver.Refresh(true) + } + } +} diff --git a/internal/protocol/bt/fetcher.go b/internal/protocol/bt/fetcher.go index e50ed2e7..1366ffa3 100644 --- a/internal/protocol/bt/fetcher.go +++ b/internal/protocol/bt/fetcher.go @@ -2,6 +2,7 @@ package bt import ( "bytes" + "context" "fmt" "github.com/GopeedLab/gopeed/internal/controller" "github.com/GopeedLab/gopeed/internal/fetcher" @@ -22,6 +23,8 @@ var ( lock sync.Mutex torrentDirMap = make(map[string]string) ftMap = make(map[string]*fileTorrentImpl) + closeCtx context.Context + closeFunc func() ) type Fetcher struct { @@ -32,10 +35,11 @@ type Fetcher struct { meta *fetcher.FetcherMeta data *fetcherData - torrentReady atomic.Bool - torrentDrop atomic.Bool - torrentUpload atomic.Bool - uploadDoneCh chan any + torrentReady atomic.Bool + torrentUpload atomic.Bool + torrentDropCtx context.Context + torrentDropFunc func() + uploadDoneCh chan any } func (f *Fetcher) Setup(ctl *controller.Controller) { @@ -46,6 +50,8 @@ func (f *Fetcher) Setup(ctl *controller.Controller) { if f.data == nil { f.data = &fetcherData{} } + f.uploadDoneCh = make(chan any, 1) + f.torrentDropCtx, f.torrentDropFunc = context.WithCancel(context.Background()) f.ctl.GetConfig(&f.config) return } @@ -57,6 +63,9 @@ func (f *Fetcher) initClient() (err error) { if client != nil { return } + if closeCtx == nil { + closeCtx, closeFunc = context.WithCancel(context.Background()) + } cfg := torrent.NewDefaultClientConfig() cfg.Seed = true @@ -73,12 +82,22 @@ func (f *Fetcher) initClient() (err error) { ftMap[infoHash.String()] = ft }, }) + dnsResolver := &DnsCacheResolver{RefreshTimeout: 5 * time.Minute} + cfg.TrackerDialContext = dnsResolver.DialContext client, err = torrent.NewClient(cfg) + if err != nil { + return + } + + closeCtx, closeFunc = context.WithCancel(context.Background()) + go func() { + dnsResolver.Run(closeCtx) + }() return } func (f *Fetcher) Resolve(req *base.Request) error { - if err := f.addTorrent(req); err != nil { + if err := f.addTorrent(req, false); err != nil { return err } f.updateRes() @@ -91,13 +110,12 @@ func (f *Fetcher) Create(opts *base.Options) (err error) { if f.meta.Res != nil { torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath() } - f.uploadDoneCh = make(chan any, 1) return nil } func (f *Fetcher) Start() (err error) { if !f.torrentReady.Load() { - if err = f.addTorrent(f.meta.Req); err != nil { + if err = f.addTorrent(f.meta.Req, false); err != nil { return } } @@ -133,9 +151,12 @@ func (f *Fetcher) Pause() (err error) { } func (f *Fetcher) Close() (err error) { - f.torrentDrop.Store(true) f.safeDrop() + f.torrentDropFunc() f.uploadDoneCh <- nil + if len(client.Torrents()) == 0 { + err = closeClient() + } return nil } @@ -153,7 +174,12 @@ func (f *Fetcher) Meta() *fetcher.FetcherMeta { } func (f *Fetcher) Stats() any { - stats := f.torrent.Stats() + var stats torrent.TorrentStats + if f.torrent != nil { + stats = f.torrent.Stats() + } else { + stats = torrent.TorrentStats{} + } return &bt.Stats{ TotalPeers: stats.TotalPeers, ActivePeers: stats.ActivePeers, @@ -178,30 +204,30 @@ func (f *Fetcher) Progress() fetcher.Progress { func (f *Fetcher) Wait() (err error) { for { - if f.torrentDrop.Load() { - break - } - if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 { - if f.isDone() { - // remove unselected files - for i, file := range f.torrent.Files() { - selected := false - for _, selectIndex := range f.meta.Opts.SelectFiles { - if i == selectIndex { - selected = true - break + select { + case <-f.torrentDropCtx.Done(): + return + case <-time.After(time.Second): + if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 { + if f.isDone() { + // remove unselected files + for i, file := range f.torrent.Files() { + selected := false + for _, selectIndex := range f.meta.Opts.SelectFiles { + if i == selectIndex { + selected = true + break + } + } + if !selected { + util.SafeRemove(filepath.Join(f.meta.Opts.Path, f.meta.Res.Name, file.Path())) } } - if !selected { - util.SafeRemove(filepath.Join(f.meta.Opts.Path, f.meta.Res.Name, file.Path())) - } + return } - break } } - time.Sleep(time.Millisecond * 500) } - return nil } func (f *Fetcher) isDone() bool { @@ -240,14 +266,13 @@ func (f *Fetcher) updateRes() { } func (f *Fetcher) Upload() (err error) { - return f.addTorrent(f.meta.Req) + return f.addTorrent(f.meta.Req, true) } -func (f *Fetcher) doUpload() { - if f.torrentUpload.Load() { +func (f *Fetcher) doUpload(fromUpload bool) { + if !f.torrentUpload.CompareAndSwap(false, true) { return } - f.torrentUpload.Store(true) // Check and update seed data lastData := &fetcherData{ @@ -256,47 +281,46 @@ func (f *Fetcher) doUpload() { } var doneTime int64 = 0 for { - time.Sleep(time.Second) - - if f.torrentDrop.Load() { - break - } - - if !f.torrentReady.Load() { - continue - } - - stats := f.torrent.Stats() - f.data.SeedBytes = lastData.SeedBytes + stats.BytesWrittenData.Int64() - - // Check is download complete, if not don't check and stop seeding - if !f.isDone() { - continue - } - if doneTime == 0 { - doneTime = time.Now().Unix() - } - f.data.SeedTime = lastData.SeedTime + time.Now().Unix() - doneTime - - // If the seed forever is true, keep seeding - if f.config.SeedKeep { - continue - } - - // If the seed ratio is reached, stop seeding - if f.config.SeedRatio > 0 { - seedRadio := f.seedRadio() - if seedRadio >= f.config.SeedRatio { - f.Close() - break + select { + case <-f.torrentDropCtx.Done(): + return + case <-time.After(time.Second): + if !f.torrentReady.Load() { + continue } - } - // If the seed time is reached, stop seeding - if f.config.SeedTime > 0 { - if f.data.SeedTime >= f.config.SeedTime { - f.Close() - break + stats := f.torrent.Stats() + f.data.SeedBytes = lastData.SeedBytes + stats.BytesWrittenData.Int64() + + // Check is download complete, if not don't check and stop seeding + if !fromUpload && !f.isDone() { + continue + } + if doneTime == 0 { + doneTime = time.Now().Unix() + } + f.data.SeedTime = lastData.SeedTime + time.Now().Unix() - doneTime + + // If the seed forever is true, keep seeding + if f.config.SeedKeep { + continue + } + + // If the seed ratio is reached, stop seeding + if f.config.SeedRatio > 0 { + seedRadio := f.seedRadio() + if seedRadio >= f.config.SeedRatio { + f.Close() + break + } + } + + // If the seed time is reached, stop seeding + if f.config.SeedTime > 0 { + if f.data.SeedTime >= f.config.SeedTime { + f.Close() + break + } } } } @@ -311,7 +335,7 @@ func (f *Fetcher) WaitUpload() (err error) { return nil } -func (f *Fetcher) addTorrent(req *base.Request) (err error) { +func (f *Fetcher) addTorrent(req *base.Request, fromUpload bool) (err error) { if err = base.ParseReqExtra[bt.ReqExtra](req); err != nil { return } @@ -362,12 +386,17 @@ func (f *Fetcher) addTorrent(req *base.Request) (err error) { <-f.torrent.GotInfo() f.torrentReady.Store(true) - go f.doUpload() + go f.doUpload(fromUpload) return } func (f *Fetcher) seedRadio() float64 { - bytesRead := f.data.Progress.TotalDownloaded() + var bytesRead int64 + if f.Meta().Res != nil { + bytesRead = f.Meta().Res.Size + } else { + bytesRead = 0 + } if bytesRead <= 0 { return 0 } @@ -382,6 +411,25 @@ type fetcherData struct { SeedTime int64 } +func closeClient() error { + lock.Lock() + defer lock.Unlock() + + if closeFunc != nil { + closeFunc() + } + if client != nil { + errs := client.Close() + if len(errs) > 0 { + return errs[0] + } + client = nil + closeCtx = nil + closeFunc = nil + } + return nil +} + type FetcherManager struct { } @@ -435,16 +483,7 @@ func (fm *FetcherManager) Restore() (v any, f func(meta *fetcher.FetcherMeta, v } func (fm *FetcherManager) Close() error { - lock.Lock() - defer lock.Unlock() - - if client != nil { - errs := client.Close() - if len(errs) > 0 { - return errs[0] - } - } - return nil + return closeClient() } // parse version to bep20 format, fixed length 4, if not enough, fill 0 diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index ccbf9d14..f4247844 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -63,6 +63,7 @@ type Downloader struct { storage Storage tasks []*Task waitTasks []*Task + watchedTasks sync.Map listener Listener lock *sync.Mutex @@ -170,7 +171,7 @@ func (d *Downloader) Setup() error { go func() { for _, task := range d.tasks { if task.Status == base.DownloadStatusDone && task.Uploading { - if err := d.restoreFetcher(task); err != nil { + if err := d.restoreTask(task); err != nil { d.Logger.Error().Stack().Err(err).Msgf("task upload restore fetcher failed, task id: %s", task.ID) } if uploader, ok := task.fetcher.(fetcher.Uploader); ok { @@ -530,6 +531,17 @@ func (d *Downloader) Stats(id string) (sr any, err error) { if task == nil { return sr, ErrTaskNotFound } + if task.fetcher == nil { + err = func() error { + task.statusLock.Lock() + defer task.statusLock.Unlock() + + return d.restoreFetcher(task) + }() + if err != nil { + return + } + } sr = task.fetcher.Stats() return } @@ -681,6 +693,14 @@ func (d *Downloader) getProtocolConfig(name string, v any) bool { // wait task done func (d *Downloader) watch(task *Task) { + if _, loaded := d.watchedTasks.LoadOrStore(task.ID, true); loaded { + return + } + + defer func() { + d.watchedTasks.Delete(task.ID) + }() + // wait task upload done if task.Uploading { if uploader, ok := task.fetcher.(fetcher.Uploader); ok { @@ -759,42 +779,47 @@ func (d *Downloader) doOnError(task *Task, err error) { } } -func (d *Downloader) restoreFetcher(task *Task) error { +func (d *Downloader) restoreTask(task *Task) error { if task.fetcher == nil { - fm, err := d.parseFm(task.Meta.Req.URL) + if err := d.restoreFetcher(task); err != nil { + return err + } + } + go d.watch(task) + task.fetcher.Create(task.Meta.Opts) + return nil +} + +func (d *Downloader) restoreFetcher(task *Task) error { + var fm fetcher.FetcherManager + for _, f := range d.cfg.FetchManagers { + if f.Name() == task.Protocol { + fm = f + break + } + } + if fm == nil { + return ErrUnSupportedProtocol + } + task.fetcherManager = fm + v, f := fm.Restore() + if v != nil { + err := d.storage.Pop(bucketSave, task.ID, v) if err != nil { return err } - task.fetcherManager = fm - err = func() error { - v, f := fm.Restore() - if v != nil { - err := d.storage.Pop(bucketSave, task.ID, v) - if err != nil { - return err - } - } - task.fetcher = f(task.Meta, v) - return nil - }() - if err != nil { - d.Logger.Error().Stack().Err(err).Msgf("deserialize fetcher failed, task id: %s", task.ID) - } - if task.fetcher == nil { - task.fetcher = fm.Build() - } - d.setupFetcher(fm, task.fetcher) - if task.fetcher.Meta().Req == nil { - task.fetcher.Meta().Req = task.Meta.Req - } - if task.fetcher.Meta().Res == nil { - task.fetcher.Meta().Res = task.Meta.Res - } - go d.watch(task) - } else if task.Status == base.DownloadStatusError { - go d.watch(task) } - task.fetcher.Create(task.Meta.Opts) + task.fetcher = f(task.Meta, v) + if task.fetcher == nil { + task.fetcher = task.fetcherManager.Build() + } + d.setupFetcher(task.fetcherManager, task.fetcher) + if task.fetcher.Meta().Req == nil { + task.fetcher.Meta().Req = task.Meta.Req + } + if task.fetcher.Meta().Res == nil { + task.fetcher.Meta().Res = task.Meta.Res + } return nil } @@ -872,7 +897,7 @@ func (d *Downloader) doStart(task *Task) (err error) { return } - err = d.restoreFetcher(task) + err = d.restoreTask(task) if err != nil { d.Logger.Error().Stack().Err(err).Msgf("restore fetcher failed, task id: %s", task.ID) return diff --git a/pkg/rest/api.go b/pkg/rest/api.go index 80b9bd6a..fde4c3cf 100644 --- a/pkg/rest/api.go +++ b/pkg/rest/api.go @@ -13,12 +13,12 @@ import ( func Info(w http.ResponseWriter, r *http.Request) { info := map[string]any{ - "version": base.Version, - "runtime": runtime.Version(), - "os": runtime.GOOS, - "arch": runtime.GOARCH, + "version": base.Version, + "runtime": runtime.Version(), + "os": runtime.GOOS, + "arch": runtime.GOARCH, + "inDocker": base.InDocker == "true", } - info["inDocker"] = base.InDocker == "true" WriteJson(w, model.NewOkResult(info)) }