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.
This commit is contained in:
62
main.go
62
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),
|
||||
|
||||
Reference in New Issue
Block a user