fetchtorrent: fix single-file torrents (#497402)

This commit is contained in:
Sandro
2026-08-21 20:10:51 +00:00
committed by GitHub
5 changed files with 132 additions and 101 deletions

View File

@@ -79,7 +79,7 @@ indent_size = unset
trim_trailing_whitespace = true
# binaries
[*.nib]
[*.{nib,torrent}]
end_of_line = unset
insert_final_newline = unset
trim_trailing_whitespace = unset

3
.gitattributes vendored
View File

@@ -62,3 +62,6 @@ ci/OWNERS linguist-language=CODEOWNERS
# patching CRLF line endings from an upstream source package.
*.diff !text !eol
*.patch !text !eol
# Torrent files are binary files and should not be re-encoded.
*.torrent !text !eol

View File

@@ -31,57 +31,48 @@ let
# Default to flattening if no flatten argument was specified.
flatten' = if flatten == null then true else flatten;
transmissionFinishScript = writeShellScript "fetch-bittorrent-done.sh" ''
setupScript = ''
export HOME=$TMP
mkdir -p $out
downloadedDirectory=${
if flatten' then "$(mktemp -d $out/downloadedDirectory.XXXXXXXXXX)" else "$out"
}
export downloadedDirectory # See https://www.shellcheck.net/wiki/SC2155
port="$(shuf -n 1 -i 49152-65535)"
'';
flattenScript = ''
(
shopt -s dotglob nullglob
downloadedFiles=($downloadedDirectory/*)
if [[ ''${#downloadedFiles[@]} -eq 0 ]]; then
echo "Failed to download any files."
exit 1
elif [[ ''${#downloadedFiles[@]} -eq 1 ]] && [[ -d "$downloadedFiles" ]]; then
# Flatten the directory, so that only the torrent contents are in $out,
# not the folder name
mv -v "$downloadedFiles"/* $out
else
# Either we downloaded a single (regular) file, or we downloaded
# multiple files/directories. We can't flatten, so we move them to
# $out.
mv -v "''${downloadedFiles[@]}" $out
fi
rm -v -rf $downloadedDirectory
)
'';
finishScript = ''
${postUnpack}
# Flatten the directory, so that only the torrent contents are in $out, not
# the folder name
shopt -s dotglob
mv -v $downloadedDirectory/*/* $out
rm -v -rf $downloadedDirectory
unset downloadedDirectory
${lib.optionalString flatten' flattenScript}
${postFetch}
'';
transmissionFinishScript = writeShellScript "fetch-bittorrent-done.sh" ''
${finishScript}
kill $PPID
'';
jsonConfig = (formats.json { }).generate "jsonConfig" config;
# https://github.com/NixOS/nixpkgs/issues/432001
#
# For a while, the transmission backend would put the downloaded torrent in
# the output directory, but whether the rqbit backend would put the output in
# the output directory or a subdirectory depended on the version of rqbit.
# We want to standardise on a single behaviour, but give users of
# fetchtorrent with the rqbit backend some warning that the behaviour might
# be unexpected, particularly since we can't know what behaviour users might
# be expecting at this point, and they probably wouldn't notice a change
# straight away because the results are fixed-output derivations.
#
# This warning was introduced for 25.11, so we can remove handling of the
# `flatten` argument once that release is no longer supported.
warnings =
if backend == "rqbit" && flatten == null then
[
''
`fetchtorrent` with the rqbit backend may or may not have the
downloaded files stored in a subdirectory of the output directory.
Verify which behaviour you need, and set the `flatten` argument to
`fetchtorrent` accordingly.
The `flatten = false` behaviour will still produce a warning, as this
behaviour is deprecated. It is only available with the "rqbit" backend
to provide temporary support for users who are relying on the
previous incorrect behaviour. For a warning-free evaluation, use
`flatten = true`.
''
]
else if flatten == false then
[
''
`fetchtorrent` with `flatten = false` is deprecated and will be
removed in a future release.
''
]
else
[ ];
in
assert
(config != { } -> backend == "transmission")
@@ -103,7 +94,41 @@ runCommand name
if (backend == "transmission") then
[ transmission_4 ]
else if (backend == "rqbit") then
[ rqbit ]
lib.warnIf (flatten != true) (
# https://github.com/NixOS/nixpkgs/issues/432001
#
# For a while, the transmission backend would put the downloaded torrent in
# the output directory, but whether the rqbit backend would put the output in
# the output directory or a subdirectory depended on the version of rqbit.
# We want to standardise on a single behaviour, but give users of
# fetchtorrent with the rqbit backend some warning that the behaviour might
# be unexpected, particularly since we can't know what behaviour users might
# be expecting at this point, and they probably wouldn't notice a change
# straight away because the results are fixed-output derivations.
#
# This warning was introduced for 25.11, so we can remove handling of the
# `flatten` argument once that release is no longer supported.
if flatten == null then
''
`fetchtorrent` with the rqbit backend may or may not have the
downloaded files stored in a subdirectory of the output directory.
Verify which behaviour you need, and set the `flatten` argument to
`fetchtorrent` accordingly.
The `flatten = false` behaviour will still produce a warning, as this
behaviour is deprecated. It is only available with the "rqbit" backend
to provide temporary support for users who are relying on the
previous incorrect behaviour. For a warning-free evaluation, use
`flatten = true`.
''
else if flatten == false then
''
`fetchtorrent` with `flatten = false` is deprecated and will be
removed in a future release.
''
else
throw "invalid value for flatten: must be true, false, or null"
) [ rqbit ]
else
throw "rqbit or transmission are the only available backends for fetchtorrent"
);
@@ -119,12 +144,9 @@ runCommand name
(
if (backend == "transmission") then
''
export HOME=$TMP
export downloadedDirectory=$out/downloadedDirectory
mkdir -p $downloadedDirectory
${setupScript}
mkdir -p $HOME/.config/transmission
cp ${jsonConfig} $HOME/.config/transmission/settings.json
port="$(shuf -n 1 -i 49152-65535)"
function handleChild {
# This detects failures and logs the contents of the transmission fetch
find $out
@@ -140,18 +162,7 @@ runCommand name
''
else
''
export HOME=$TMP
''
+ lib.optionalString flatten' ''
downloadedDirectory=$out/downloadedDirectory
mkdir -p $downloadedDirectory
''
+ lib.optionalString (!flatten') ''
downloadedDirectory=$out
''
+ ''
port="$(shuf -n 1 -i 49152-65535)"
${setupScript}
rqbit \
--disable-dht-persistence \
--http-api-listen-addr "127.0.0.1:$port" \
@@ -159,18 +170,6 @@ runCommand name
-o "$downloadedDirectory" \
--exit-on-finish \
"$url"
${postUnpack}
''
+ lib.optionalString flatten' ''
# Flatten the directory, so that only the torrent contents are in $out,
# not the folder name
shopt -s dotglob
mv -v $downloadedDirectory/*/* $out
rm -v -rf $downloadedDirectory
unset downloadedDirectory
''
+ ''
${postFetch}
${finishScript}
''
)

View File

@@ -0,0 +1 @@
d10:created by13:mktorrent 1.113:creation datei1772926249e4:infod6:lengthi1097e4:name7:COPYING12:piece lengthi262144e6:pieces20:€åÏÊ\c™èçæÉ\Q3Ní-e8:url-list96:https://raw.githubusercontent.com/NixOS/nixpkgs/3d82431ec4b51262989559b821b83abe4a9f6dbd/COPYINGe

View File

@@ -47,7 +47,7 @@ let
pushd "$out" &&
sha512sum --check --strict ${./test-hashes.sha512sum} &&
sed 's/.* //' ${./test-hashes.sha512sum} | xargs rm --verbose &&
popd
popd || exit 1
'';
unflattened.postFetch = ''
pushd "$out" &&
@@ -56,7 +56,7 @@ let
sed 's/.* //' ${./test-hashes.sha512sum} | xargs rm --verbose &&
popd &&
rm --dir --verbose Sintel &&
popd
popd || exit 1
'';
fetchtorrentWithHash =
@@ -75,7 +75,7 @@ let
);
in
# Seems almost but not quite worth using lib.mapCartesianProduct...
builtins.mapAttrs (n: v: testers.invalidateFetcherByDrvHash fetchtorrentWithHash v) {
(builtins.mapAttrs (n: v: testers.invalidateFetcherByDrvHash fetchtorrentWithHash v) {
http-link = {
inherit (http) url;
inherit (flattened) postFetch;
@@ -94,16 +94,22 @@ builtins.mapAttrs (n: v: testers.invalidateFetcherByDrvHash fetchtorrentWithHash
backend = "transmission";
inherit (flattened) postFetch;
};
http-link-rqbit = {
inherit (http) url;
backend = "rqbit";
inherit (flattened) postFetch;
};
magnet-link-rqbit = {
inherit (magnet) url;
backend = "rqbit";
inherit (flattened) postFetch;
};
#
# Disabled because these warn that flatten hasn't been explicitly
# set to true, and warnings are treated as failures in tests.
#
# Re-enable these tests when flatten defaults to true.
#
#http-link-rqbit = {
# inherit (http) url;
# backend = "rqbit";
# inherit (flattened) postFetch;
#};
#magnet-link-rqbit = {
# inherit (magnet) url;
# backend = "rqbit";
# inherit (flattened) postFetch;
#};
http-link-rqbit-flattened = {
inherit (http) url;
backend = "rqbit";
@@ -116,16 +122,38 @@ builtins.mapAttrs (n: v: testers.invalidateFetcherByDrvHash fetchtorrentWithHash
flatten = true;
inherit (flattened) postFetch;
};
http-link-rqbit-unflattened = {
inherit (http) url;
backend = "rqbit";
flatten = false;
inherit (unflattened) postFetch;
};
magnet-link-rqbit-unflattened = {
inherit (magnet) url;
backend = "rqbit";
flatten = false;
inherit (unflattened) postFetch;
#
# Disabled because these warn that `flatten = false` is deprecated
# and will be removed.
#
# Remove these when support for `flatten = false` is completely removed.
#
#http-link-rqbit-unflattened = {
# inherit (http) url;
# backend = "rqbit";
# flatten = false;
# inherit (unflattened) postFetch;
#};
#magnet-link-rqbit-unflattened = {
# inherit (magnet) url;
# backend = "rqbit";
# flatten = false;
# inherit (unflattened) postFetch;
#};
})
// {
# Make sure we can download and "flatten" single-file torrents.
# We only test with transmission because we use a web-seed and rqbit
# doesn't support web-seeds.
single-file = fetchtorrent {
hash = "sha256-u5c/ZN5V79Jg1aN6spbui4OdhExsXjofk1JpvUdW7Ro=";
backend = "transmission";
flatten = true;
url = "${./test-single-file.torrent}";
meta = {
hydraPlatforms = [ ];
license = lib.licenses.mit;
description = "The license file for the Nixpkgs repository as of 7 March 2026";
};
};
}