main: dedupe followed .torrent files by infohash
a .torrent fetched over http is added to the shared client in a second pass (followUps); two that name the same torrent would share one torrent on the refcount-less client, and the first to finish would Drop it out from under the other, stranding it at 0%. dedupe the followed files by infohash, the guard build() already applies to pass-1 sources.
This commit is contained in:
76
main_test.go
76
main_test.go
@@ -11,6 +11,9 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/anacrolix/torrent/bencode"
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
"github.com/hanbok/got/bt"
|
||||
"github.com/hanbok/got/cli"
|
||||
"github.com/hanbok/got/download"
|
||||
"github.com/hanbok/got/httpdl"
|
||||
@@ -112,8 +115,8 @@ func TestDedupeTorrents(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestGatherURIsGrouping checks the command-line grouping: plain http URIs
|
||||
// become one mirrored download by default, -Z splits them, and torrents/magnets
|
||||
// stay separate.
|
||||
// become one mirrored download by default, -Z splits them, and torrents,
|
||||
// magnets, and .torrent URLs stay separate.
|
||||
func TestGatherURIsGrouping(t *testing.T) {
|
||||
parse := func(args ...string) []target {
|
||||
res, err := cli.Parse(append([]string{"--no-conf"}, args...))
|
||||
@@ -131,6 +134,16 @@ func TestGatherURIsGrouping(t *testing.T) {
|
||||
if ts := parse("http://a/x", "magnet:?xt=urn:btih:abc"); len(ts) != 2 {
|
||||
t.Errorf("http + magnet: got %d targets, want 2 (magnet never mirrors)", len(ts))
|
||||
}
|
||||
// Distinct .torrent URLs are separate downloads, not mirrors of one file
|
||||
// (follow-torrent defaults on, so each is fetched then followed).
|
||||
if ts := parse("http://a/x.torrent", "http://b/y.torrent", "http://c/z.torrent"); len(ts) != 3 {
|
||||
t.Errorf(".torrent URLs: got %d targets, want 3 separate downloads", len(ts))
|
||||
}
|
||||
// A plain http URL still mirror-groups even alongside a .torrent URL: one
|
||||
// http group of 1 + one torrent target = 2.
|
||||
if ts := parse("http://a/x", "http://b/y.torrent"); len(ts) != 2 {
|
||||
t.Errorf("http + .torrent: got %d targets, want 2", len(ts))
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadInputFileTAB checks the input-file grouping: TAB-separated URIs on
|
||||
@@ -152,6 +165,65 @@ func TestReadInputFileTAB(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// writeTorrent creates a .torrent file at dir/name whose info dict is built from
|
||||
// a file of the given content and internal name. Two calls with the same
|
||||
// internalName and content produce the same infohash regardless of the output
|
||||
// filename, so the test can make two .torrent files that name one torrent.
|
||||
func writeTorrent(t *testing.T, dir, name, internalName string, content []byte) string {
|
||||
t.Helper()
|
||||
data := filepath.Join(dir, internalName)
|
||||
if err := os.WriteFile(data, content, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
info := metainfo.Info{PieceLength: 32 * 1024}
|
||||
if err := info.BuildFromFilePath(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := bencode.Marshal(info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tp := filepath.Join(dir, name)
|
||||
f, err := os.Create(tp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := (&metainfo.MetaInfo{InfoBytes: b}).Write(f); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return tp
|
||||
}
|
||||
|
||||
// TestFollowUpInfoHashDedup covers the fix for two fetched .torrent files that
|
||||
// name the same torrent: the second must be flagged a duplicate so followUps
|
||||
// fails it rather than letting both share — and Drop — one torrent on the client.
|
||||
func TestFollowUpInfoHashDedup(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
a := writeTorrent(t, dir, "a.torrent", "same", []byte("AAAAAAAA"))
|
||||
b := writeTorrent(t, dir, "b.torrent", "same", []byte("AAAAAAAA")) // same infohash as a
|
||||
c := writeTorrent(t, dir, "c.torrent", "diff", []byte("BBBBBBBB")) // a distinct torrent
|
||||
|
||||
// Sanity-check the fixture: a and b really share an infohash, c differs.
|
||||
ha, _ := bt.SourceInfoHash(a, true)
|
||||
hb, _ := bt.SourceInfoHash(b, true)
|
||||
hc, _ := bt.SourceInfoHash(c, true)
|
||||
if ha != hb || ha == hc {
|
||||
t.Fatalf("fixture: want a==b!=c infohashes, got a=%v b=%v c=%v", ha, hb, hc)
|
||||
}
|
||||
|
||||
seen := map[metainfo.Hash]string{}
|
||||
if first, dup := markInfoHash(seen, a); dup {
|
||||
t.Fatalf("a is the first occurrence, wrongly flagged a duplicate of %s", first)
|
||||
}
|
||||
if first, dup := markInfoHash(seen, b); !dup || first != a {
|
||||
t.Errorf("b should duplicate a: got first=%q dup=%v, want %q true", first, dup, a)
|
||||
}
|
||||
if _, dup := markInfoHash(seen, c); dup {
|
||||
t.Errorf("c is a distinct torrent, wrongly flagged a duplicate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReportExit(t *testing.T) {
|
||||
canceled := download.Result{Name: "a", Err: context.Canceled}
|
||||
done := download.Result{Name: "b", Err: nil}
|
||||
|
||||
Reference in New Issue
Block a user