Adam Dinwoodie 588622a0e3 fetchtorrent: add flatten argument for rqbit
Currently, the output from fetchtorrent will be different depending on
whether the default "transmission" backend or the "rqbit" backend is
used, because "rqbit" changed its behaviour in v6.0.0 to create
subdirectories.

Restore the old behaviour for the rqbit backend of flattening the
directory structure, but add a "flatten" argument to allow users to
explicitly request the behaviour without this change.

Because fetchtorrent produces fixed-output derivations, it's possible
that people won't notice the changes in behaviour here, so add a warning
that the behaviour might be unexpected (in either direction!) if the
flatten argument isn't specified for the rqbit backend, and -- to avoid
needing to support it indefinitely -- a warning that `flatten = false`
will be deprecated in future.

Update the fetchtorrent tests to check all the relevant combinations,
and to mark all tests as now working.

Update the release notes to advertise this breaking change.

Fixes #432001.
2025-08-09 20:22:29 +01:00

174 lines
5.3 KiB
Nix

{
lib,
runCommand,
transmission_3_noSystemd,
rqbit,
writeShellScript,
formats,
cacert,
rsync,
}:
let
urlRegexp = ''.*xt=urn:bt[im]h:([^&]{64}|[^&]{40}).*'';
in
{
url,
name ?
if (builtins.match urlRegexp url) == null then
"bittorrent"
else
"bittorrent-" + builtins.head (builtins.match urlRegexp url),
config ? { },
hash,
backend ? "transmission",
recursiveHash ? true,
flatten ? null,
postFetch ? "",
postUnpack ? "",
meta ? { },
}:
let
# Default to flattening if no flatten argument was specified.
flatten' = if flatten == null then true else flatten;
transmissionFinishScript = writeShellScript "fetch-bittorrent-done.sh" ''
${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
${postFetch}
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 lib.assertMsg (config != { } -> backend == "transmission") ''
json config for configuring fetchtorrent only works with the transmission backend
'';
assert lib.assertMsg (backend == "transmission" -> flatten') ''
`flatten = false` is only supported by the rqbit backend for fetchtorrent
'';
runCommand name
{
inherit meta;
nativeBuildInputs = [
cacert
]
++ (
if (backend == "transmission") then
[ transmission_3_noSystemd ]
else if (backend == "rqbit") then
[ rqbit ]
else
throw "rqbit or transmission are the only available backends for fetchtorrent"
);
outputHashAlgo = if hash != "" then null else "sha256";
outputHash = hash;
outputHashMode = if recursiveHash then "recursive" else "flat";
# url will be written to the derivation, meaning it can be parsed and utilized
# by external tools, such as tools that may want to seed fetchtorrent calls
# in nixpkgs
inherit url;
}
(
if (backend == "transmission") then
''
export HOME=$TMP
export downloadedDirectory=$out/downloadedDirectory
mkdir -p $downloadedDirectory
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
exit 0
}
trap handleChild CHLD
transmission-cli \
--port "$port" \
--portmap \
--finish ${transmissionFinishScript} \
--download-dir "$downloadedDirectory" \
--config-dir "$HOME"/.config/transmission \
"$url"
''
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)"
rqbit \
--disable-dht-persistence \
--http-api-listen-addr "127.0.0.1:$port" \
download \
-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}
''
)