From b98c36acb708802b3c8737733696e0e7702c0215 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 22 Jun 2026 08:27:34 +0900 Subject: [PATCH] main: fold the two infohash-dedup paths into one dedupeTorrents and markInfoHash were the same map-of-infohash logic, one batch and one incremental, with the same hazard explained in prose four times. Both now go through a single seenInfoHash helper. --- 13DL.me_Sho Hagoromo Isu v01-07.rar | Bin 0 -> 120 bytes main.go | 62 +++++++++++----------------- main_test.go | 6 +-- 3 files changed, 28 insertions(+), 40 deletions(-) create mode 100644 13DL.me_Sho Hagoromo Isu v01-07.rar diff --git a/13DL.me_Sho Hagoromo Isu v01-07.rar b/13DL.me_Sho Hagoromo Isu v01-07.rar new file mode 100644 index 0000000000000000000000000000000000000000..07342dbe7b012d97f30c7ae373d7d8dc39b2fe03 GIT binary patch literal 120 zcmWGaEK-zWXJjy*wDl<$BP$yND literal 0 HcmV?d00001 diff --git a/main.go b/main.go index fb1806c..63e2d7b 100644 --- a/main.go +++ b/main.go @@ -187,13 +187,27 @@ func (f *failedDownload) Stat() download.Stat { return download.Stat{Name: f.name, Status: download.Errored, Total: -1} } -// dedupeTorrents finds torrent/magnet sources that name the same torrent as an -// earlier source (same infohash), returning each duplicate URI mapped to the -// earlier source it repeats. HTTP(S) URLs and any source whose infohash can't be -// read without a network fetch are never duplicates here. The shared client keys -// torrents by infohash and does not refcount, so build() turns each duplicate -// into a failed download (duplicate infohash) rather than letting two jobs -// share — and Drop — one torrent. +// seenInfoHash records source under its torrent infohash in seen and reports +// whether that infohash was already recorded, returning the source that first +// claimed it. The shared client keys torrents by infohash and does not refcount, +// so two sources naming one torrent would have the first to finish Drop it out +// from under the other; callers fail the duplicate instead. A source whose +// infohash can't be known without a network fetch (an HTTP URL), or that is +// v2-only or unreadable, is never a duplicate: it isn't recorded, so it runs. +func seenInfoHash(seen map[metainfo.Hash]string, source string, isFile bool) (first string, dup bool) { + h, ok := bt.SourceInfoHash(source, isFile) + if !ok { + return "", false + } + if first, dup = seen[h]; dup { + return first, true + } + seen[h] = source + return "", false +} + +// dedupeTorrents finds torrent/magnet sources that repeat an earlier source's +// infohash, returning each duplicate URI mapped to the source it repeats. func dedupeTorrents(uris []string, follow bool) map[string]string { seen := map[metainfo.Hash]string{} dup := map[string]string{} @@ -202,37 +216,13 @@ func dedupeTorrents(uris []string, follow bool) map[string]string { if k != kindMagnet && k != kindTorrent { continue } - h, ok := bt.SourceInfoHash(u, k == kindTorrent) - if !ok { - continue - } - if first, isDup := seen[h]; isDup { + if first, isDup := seenInfoHash(seen, u, k == kindTorrent); isDup { dup[u] = first - } else { - seen[h] = u } } return dup } -// markInfoHash records the v1 infohash of the .torrent at path in seen and -// reports whether it repeats one already seen, returning the path first recorded -// under that infohash. Callers fail the duplicate rather than let two jobs share -// one torrent on the client (see bt.SourceInfoHash for why that aliasing is -// unsafe). A v2-only or unreadable infohash is never a duplicate — the dedup -// can't see it, so it runs. -func markInfoHash(seen map[metainfo.Hash]string, path string) (first string, dup bool) { - h, ok := bt.SourceInfoHash(path, true) - if !ok { - return "", false - } - if first, dup = seen[h]; dup { - return first, true - } - seen[h] = path - return "", false -} - func build(opts *cli.Options, targets []target) ([]job, *torrent.Client, error) { flat := allURIs(targets) // Two sources naming the same torrent (a magnet and its own .torrent, say) @@ -383,11 +373,9 @@ func followUps(jobs []job, client *torrent.Client, bo bt.Options) []job { if _, err := bt.Files(path); err != nil { continue // downloaded file is not a valid torrent } - // Two fetched .torrent files that name the same torrent would share one - // torrent on the refcount-less client: the first to finish Drops it out - // from under the other, stranding it at 0%. build() dedupes pass-1 sources - // but cannot reach a not-yet-fetched HTTP .torrent, so dedupe here too. - if first, dup := markInfoHash(seen, path); dup { + // build() dedupes pass-1 sources but cannot reach a not-yet-fetched HTTP + // .torrent, so dedupe the fetched ones here too against the same hazard. + if first, dup := seenInfoHash(seen, path, true); dup { out = append(out, job{source: path, dl: &failedDownload{ name: path, err: fmt.Errorf("%w: same torrent as %s", errDuplicate, first), diff --git a/main_test.go b/main_test.go index 3287444..ce656bd 100644 --- a/main_test.go +++ b/main_test.go @@ -241,13 +241,13 @@ func TestFollowUpInfoHashDedup(t *testing.T) { } seen := map[metainfo.Hash]string{} - if first, dup := markInfoHash(seen, a); dup { + if first, dup := seenInfoHash(seen, a, true); dup { t.Fatalf("a is the first occurrence, wrongly flagged a duplicate of %s", first) } - if first, dup := markInfoHash(seen, b); !dup || first != a { + if first, dup := seenInfoHash(seen, b, true); !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 { + if _, dup := seenInfoHash(seen, c, true); dup { t.Errorf("c is a distinct torrent, wrongly flagged a duplicate") } }