126 lines
3.1 KiB
Go
126 lines
3.1 KiB
Go
package tracker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/anacrolix/torrent"
|
|
"github.com/anacrolix/torrent/bencode"
|
|
"github.com/anacrolix/torrent/metainfo"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
type MagnetResolver struct {
|
|
client *torrent.Client
|
|
timeout time.Duration
|
|
}
|
|
|
|
func NewMagnetResolver(timeout time.Duration) (*MagnetResolver, error) {
|
|
cfg := torrent.NewDefaultClientConfig()
|
|
cfg.DataDir = ""
|
|
cfg.NoDHT = false
|
|
cfg.NoUpload = true
|
|
cfg.Seed = false
|
|
cfg.ListenPort = 0
|
|
|
|
client, err := torrent.NewClient(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating torrent client: %w", err)
|
|
}
|
|
|
|
log.Info().Dur("timeout", timeout).Msg("magnet resolver initialized")
|
|
|
|
return &MagnetResolver{
|
|
client: client,
|
|
timeout: timeout,
|
|
}, nil
|
|
}
|
|
|
|
func (r *MagnetResolver) Resolve(magnetURI string) ([]byte, error) {
|
|
truncated := magnetURI
|
|
if len(truncated) > 80 {
|
|
truncated = truncated[:80] + "..."
|
|
}
|
|
log.Trace().Str("magnet", truncated).Msg("resolving magnet")
|
|
|
|
t, err := r.client.AddMagnet(magnetURI)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("adding magnet: %w", err)
|
|
}
|
|
defer t.Drop()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
|
|
defer cancel()
|
|
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
noActiveSince := time.Now()
|
|
|
|
for {
|
|
select {
|
|
case <-t.GotInfo():
|
|
ticker.Stop()
|
|
goto resolved
|
|
case <-ctx.Done():
|
|
stats := t.Stats()
|
|
log.Warn().
|
|
Str("hash", t.InfoHash().HexString()).
|
|
Int("total_peers", stats.TotalPeers).
|
|
Int("active_peers", stats.ActivePeers).
|
|
Int("pending_peers", stats.PendingPeers).
|
|
Int("half_open_peers", stats.HalfOpenPeers).
|
|
Int("connected_seeders", stats.ConnectedSeeders).
|
|
Msg("magnet resolve timed out")
|
|
return nil, fmt.Errorf("timeout resolving magnet after %s: peers=%d active=%d seeders=%d",
|
|
r.timeout, stats.TotalPeers, stats.ActivePeers, stats.ConnectedSeeders)
|
|
case <-ticker.C:
|
|
stats := t.Stats()
|
|
log.Trace().
|
|
Str("hash", t.InfoHash().HexString()).
|
|
Int("total_peers", stats.TotalPeers).
|
|
Int("active_peers", stats.ActivePeers).
|
|
Int("connected_seeders", stats.ConnectedSeeders).
|
|
Msg("magnet resolve waiting")
|
|
|
|
if stats.ActivePeers > 0 {
|
|
noActiveSince = time.Now()
|
|
}
|
|
|
|
if stats.TotalPeers > 0 && time.Since(noActiveSince) > 15*time.Second {
|
|
log.Warn().
|
|
Str("hash", t.InfoHash().HexString()).
|
|
Int("total_peers", stats.TotalPeers).
|
|
Int("active_peers", stats.ActivePeers).
|
|
Msg("magnet has peers but none active for 15s, giving up early")
|
|
return nil, fmt.Errorf("no active peers after 15s: total=%d active=%d", stats.TotalPeers, stats.ActivePeers)
|
|
}
|
|
}
|
|
}
|
|
resolved:
|
|
|
|
info := t.Info()
|
|
log.Debug().
|
|
Str("name", info.Name).
|
|
Int("files", len(info.Files)).
|
|
Int64("size", info.TotalLength()).
|
|
Msg("magnet resolved")
|
|
|
|
mi := t.Metainfo()
|
|
data, err := bencode.Marshal(metainfo.MetaInfo{
|
|
InfoBytes: mi.InfoBytes,
|
|
Announce: mi.Announce,
|
|
AnnounceList: mi.AnnounceList,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling torrent data: %w", err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func (r *MagnetResolver) Close() {
|
|
r.client.Close()
|
|
}
|