feat: torrent seed config (#599)

This commit is contained in:
Levi authored and GitHub committed 2024-07-30 12:50:06 +08:00
1 parent 473c58796c
commit 3b50465708
22 files changed
+456 -144

No files matched your search

+6
View File
@@ -3,4 +3,10 @@ package bt
type config struct {
ListenPort int `json:"listenPort"`
Trackers []string `json:"trackers"`
// SeedKeep is always keep seeding after downloading is complete, unless manually stopped.
SeedKeep bool `json:"seedKeep`
// SeedRatio is the ratio of uploaded data to downloaded data to seed.
SeedRatio float64 `json:"seedRatio"`
// SeedTime is the time in seconds to seed after downloading is complete.
SeedTime int64 `json:"seedTime"`
}
+162 -55
View File
@@ -30,15 +30,12 @@ type Fetcher struct {
torrent *torrent.Torrent
meta *fetcher.FetcherMeta
data *fetcherData
torrentReady atomic.Bool
torrentDrop atomic.Bool
create atomic.Bool
progress fetcher.Progress
}
func (f *Fetcher) Name() string {
return "bt"
torrentReady atomic.Bool
torrentDrop atomic.Bool
torrentUpload atomic.Bool
uploadDoneCh chan any
}
func (f *Fetcher) Setup(ctl *controller.Controller) {
@@ -46,13 +43,10 @@ func (f *Fetcher) Setup(ctl *controller.Controller) {
if f.meta == nil {
f.meta = &fetcher.FetcherMeta{}
}
exist := f.ctl.GetConfig(&f.config)
if !exist {
f.config = &config{
ListenPort: 0,
Trackers: []string{},
}
if f.data == nil {
f.data = &fetcherData{}
}
f.ctl.GetConfig(&f.config)
return
}
@@ -65,6 +59,7 @@ func (f *Fetcher) initClient() (err error) {
}
cfg := torrent.NewDefaultClientConfig()
cfg.Seed = true
cfg.Bep20 = fmt.Sprintf("-GP%s-", parseBep20())
cfg.ExtendedHandshakeClientVersion = fmt.Sprintf("Gopeed %s", base.Version)
cfg.ListenPort = f.config.ListenPort
@@ -92,11 +87,11 @@ func (f *Fetcher) Resolve(req *base.Request) error {
}
func (f *Fetcher) Create(opts *base.Options) (err error) {
f.create.Store(true)
f.meta.Opts = opts
if f.meta.Res != nil {
torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath()
}
f.uploadDoneCh = make(chan any, 1)
return nil
}
@@ -111,13 +106,15 @@ func (f *Fetcher) Start() (err error) {
}
files := f.torrent.Files()
// If the user does not specify the file to download, all files will be downloaded by default
if len(f.meta.Opts.SelectFiles) == 0 {
f.meta.Opts.SelectFiles = make([]int, len(files))
for i := range files {
f.meta.Opts.SelectFiles[i] = i
if f.data.Progress == nil {
if len(f.meta.Opts.SelectFiles) == 0 {
f.meta.Opts.SelectFiles = make([]int, len(files))
for i := range files {
f.meta.Opts.SelectFiles[i] = i
}
}
f.data.Progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
}
f.progress = make(fetcher.Progress, len(f.meta.Opts.SelectFiles))
if len(f.meta.Opts.SelectFiles) == len(files) {
f.torrent.DownloadAll()
} else {
@@ -126,7 +123,6 @@ func (f *Fetcher) Start() (err error) {
file.Download()
}
}
return
}
@@ -137,8 +133,9 @@ func (f *Fetcher) Pause() (err error) {
}
func (f *Fetcher) Close() (err error) {
f.torrentDrop.Store(false)
f.torrentDrop.Store(true)
f.safeDrop()
f.uploadDoneCh <- nil
return nil
}
@@ -151,21 +148,41 @@ func (f *Fetcher) safeDrop() {
f.torrent.Drop()
}
func (f *Fetcher) Meta() *fetcher.FetcherMeta {
return f.meta
}
func (f *Fetcher) Stats() any {
stats := f.torrent.Stats()
return &bt.Stats{
TotalPeers: stats.TotalPeers,
ActivePeers: stats.ActivePeers,
ConnectedSeeders: stats.ConnectedSeeders,
SeedBytes: f.data.SeedBytes,
SeedRatio: f.seedRadio(),
SeedTime: f.data.SeedTime,
}
}
func (f *Fetcher) Progress() fetcher.Progress {
if !f.torrentReady.Load() {
return f.data.Progress
}
for i := range f.data.Progress {
selectIndex := f.meta.Opts.SelectFiles[i]
file := f.torrent.Files()[selectIndex]
f.data.Progress[i] = file.BytesCompleted()
}
return f.data.Progress
}
func (f *Fetcher) Wait() (err error) {
for {
if f.torrentDrop.Load() {
break
}
if f.torrentReady.Load() && len(f.meta.Opts.SelectFiles) > 0 {
done := true
for _, selectIndex := range f.meta.Opts.SelectFiles {
file := f.torrent.Files()[selectIndex]
if file.BytesCompleted() < file.Length() {
done = false
break
}
}
if done {
if f.isDone() {
// remove unselected files
for i, file := range f.torrent.Files() {
selected := false
@@ -187,30 +204,14 @@ func (f *Fetcher) Wait() (err error) {
return nil
}
func (f *Fetcher) Meta() *fetcher.FetcherMeta {
return f.meta
}
func (f *Fetcher) Stats() any {
stats := f.torrent.Stats()
baseStats := &bt.Stats{
TotalPeers: stats.TotalPeers,
ActivePeers: stats.ActivePeers,
ConnectedSeeders: stats.ConnectedSeeders,
}
return baseStats
}
func (f *Fetcher) Progress() fetcher.Progress {
if !f.torrentReady.Load() {
return f.progress
}
for i := range f.progress {
selectIndex := f.meta.Opts.SelectFiles[i]
func (f *Fetcher) isDone() bool {
for _, selectIndex := range f.meta.Opts.SelectFiles {
file := f.torrent.Files()[selectIndex]
f.progress[i] = file.BytesCompleted()
if file.BytesCompleted() < file.Length() {
return false
}
}
return f.progress
return true
}
func (f *Fetcher) updateRes() {
@@ -235,6 +236,78 @@ func (f *Fetcher) updateRes() {
}
}
func (f *Fetcher) Upload() (err error) {
return f.addTorrent(f.meta.Req)
}
func (f *Fetcher) doUpload() {
if f.torrentUpload.Load() {
return
}
f.torrentUpload.Store(true)
// Check and update seed data
lastData := &fetcherData{
SeedBytes: f.data.SeedBytes,
SeedTime: f.data.SeedTime,
}
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
}
}
// If the seed time is reached, stop seeding
if f.config.SeedTime > 0 {
if f.data.SeedTime >= f.config.SeedTime {
f.Close()
break
}
}
}
}
func (f *Fetcher) UploadedBytes() int64 {
return f.data.SeedBytes
}
func (f *Fetcher) WaitUpload() (err error) {
<-f.uploadDoneCh
return nil
}
func (f *Fetcher) addTorrent(req *base.Request) (err error) {
if err = base.ParseReqExtra[bt.ReqExtra](req); err != nil {
return
@@ -285,14 +358,36 @@ func (f *Fetcher) addTorrent(req *base.Request) (err error) {
}
<-f.torrent.GotInfo()
f.torrentReady.Store(true)
go f.doUpload()
return
}
func (f *Fetcher) seedRadio() float64 {
bytesRead := f.data.Progress.TotalDownloaded()
if bytesRead <= 0 {
return 0
}
return float64(f.data.SeedBytes) / float64(bytesRead)
}
type fetcherData struct {
Progress fetcher.Progress
SeedBytes int64
// SeedTime is the time in seconds to seed after downloading is complete.
SeedTime int64
}
type FetcherBuilder struct {
}
var schemes = []string{"FILE", "MAGNET", "APPLICATION/X-BITTORRENT"}
func (fb *FetcherBuilder) Name() string {
return "bt"
}
func (fb *FetcherBuilder) Schemes() []string {
return schemes
}
@@ -301,14 +396,26 @@ func (fb *FetcherBuilder) Build() fetcher.Fetcher {
return &Fetcher{}
}
func (fb *FetcherBuilder) DefaultConfig() any {
return &config{
ListenPort: 0,
Trackers: []string{},
SeedKeep: false,
SeedRatio: 1.0,
SeedTime: 120 * 60,
}
}
func (fb *FetcherBuilder) Store(f fetcher.Fetcher) (data any, err error) {
return nil, nil
_f := f.(*Fetcher)
return _f.data, nil
}
func (fb *FetcherBuilder) Restore() (v any, f func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher) {
return nil, func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher {
return &fetcherData{}, func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher {
return &Fetcher{
meta: meta,
data: v.(*fetcherData),
}
}
}
+9 -7
View File
@@ -100,8 +100,13 @@ func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
}
func buildFetcher() fetcher.Fetcher {
fetcher := new(FetcherBuilder).Build()
fetcher.Setup(controller.NewController())
fb := new(FetcherBuilder)
fetcher := fb.Build()
newController := controller.NewController()
newController.GetConfig = func(v any) {
json.Unmarshal([]byte(test.ToJson(fb.DefaultConfig())), v)
}
fetcher.Setup(newController)
return fetcher
}
@@ -113,11 +118,8 @@ func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher.Fetcher
"udp://tracker.birkenwald.de:6969/announce",
"udp://tracker.bitsearch.to:1337/announce",
}}
newController.GetConfig = func(v any) bool {
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil {
return false
}
return true
newController.GetConfig = func(v any) {
json.Unmarshal([]byte(test.ToJson(mockCfg)), v)
}
newController.ProxyConfig = proxyConfig
fetcher.Setup(newController)
+12 -11
View File
@@ -64,23 +64,13 @@ type Fetcher struct {
eg *errgroup.Group
}
func (f *Fetcher) Name() string {
return "http"
}
func (f *Fetcher) Setup(ctl *controller.Controller) {
f.ctl = ctl
f.doneCh = make(chan error, 1)
if f.meta == nil {
f.meta = &fetcher.FetcherMeta{}
}
exist := f.ctl.GetConfig(&f.config)
if !exist {
f.config = &config{
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
Connections: 1,
}
}
f.ctl.GetConfig(&f.config)
return
}
@@ -471,6 +461,10 @@ type FetcherBuilder struct {
var schemes = []string{"HTTP", "HTTPS"}
func (fb *FetcherBuilder) Name() string {
return "http"
}
func (fb *FetcherBuilder) Schemes() []string {
return schemes
}
@@ -479,6 +473,13 @@ func (fb *FetcherBuilder) Build() fetcher.Fetcher {
return &Fetcher{}
}
func (fb *FetcherBuilder) DefaultConfig() any {
return &config{
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
Connections: 16,
}
}
func (fb *FetcherBuilder) Store(f fetcher.Fetcher) (data any, err error) {
_f := f.(*Fetcher)
return &fetcherData{
+9 -7
View File
@@ -359,19 +359,21 @@ func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t
}
func buildFetcher() *Fetcher {
fetcher := new(FetcherBuilder).Build()
fetcher.Setup(controller.NewController())
fb := new(FetcherBuilder)
fetcher := fb.Build()
newController := controller.NewController()
newController.GetConfig = func(v any) {
json.Unmarshal([]byte(test.ToJson(fb.DefaultConfig())), v)
}
fetcher.Setup(newController)
return fetcher.(*Fetcher)
}
func buildConfigFetcher(cfg config) fetcher.Fetcher {
fetcher := new(FetcherBuilder).Build()
newController := controller.NewController()
newController.GetConfig = func(v any) bool {
if err := json.Unmarshal([]byte(test.ToJson(cfg)), v); err != nil {
return false
}
return true
newController.GetConfig = func(v any) {
json.Unmarshal([]byte(test.ToJson(cfg)), v)
}
fetcher.Setup(newController)
return fetcher