///|
/// Replace distribution endpoints without changing the exact encoded info dictionary.
/// Unknown top-level extension fields are retained. Private torrents must retain a tracker.
pub fn edit_distribution(
data : Bytes,
trackers : Array[Array[String]],
web_seeds : Array[String],
) -> Bytes raise CrateError {
let summary = inspect_torrent(data)
validate_options(summary.files, {
name: summary.name,
mode: summary.mode,
piece_length: summary.piece_length,
single_file: summary.single_file,
private_torrent: summary.private_torrent,
trackers,
web_seeds,
comment: summary.comment,
})
let top = decode_bencode(data)
let info = require_key(top, "info")
let pairs = dict_pairs(top).filter(fn(p) {
p.0 != b"announce" && p.0 != b"announce-list" && p.0 != b"url-list"
})
if !trackers.is_empty() {
pairs.push((b"announce", bn_text(trackers[0][0])))
pairs.push(
(
b"announce-list",
bn(Sequence(trackers.map(fn(t) { bn(Sequence(t.map(bn_text))) }))),
),
)
}
if !web_seeds.is_empty() {
pairs.push((b"url-list", bn(Sequence(web_seeds.map(bn_text)))))
}
pairs.sort_by(fn(a, b) { byte_compare(a.0, b.0) })
let out : Array[Byte] = [100]
for (key, value) in pairs {
append_bytes(out, encode_bencode(bn(Blob(key))))
if key == b"info" {
append_bytes(out, data[info.start:info.end].to_owned())
} else {
append_bytes(out, encode_bencode(value))
}
}
out.push(101)
if out.length() > 16777216 {
reject("limit", "edited metainfo exceeds 16 MiB")
}
let result = Bytes::from_array(out)
let after = inspect_torrent(result)
if summary.info_hash_v1 != after.info_hash_v1 ||
summary.info_hash_v2 != after.info_hash_v2 {
reject("internal", "distribution edit changed content identity")
}
result
}