#!/usr/bin/bash

set -eo pipefail

OPTS=()
SPEC=
while IFS= read -r line; do
    if [ "$line" = "---" ]; then
        break
    fi
    case "$line" in
        --enablerepo=*|\
        --disablerepo=*|\
        --repoid=*|\
        --releasever=*|\
        --refresh)
            OPTS+=("$line")
            ;;
        *)
            SPEC="$line"
            ;;
    esac
done

repodir=$(mktemp -d)
trap 'rm -r "$repodir"' EXIT
cat > "$repodir/template.repo"

# extract keys from wrapper in repo file
mkdir "$repodir/keys"
sed -i "s~/etc/qubes/repo-templates/keys/~$repodir/keys/~" "$repodir/template.repo"
in_wrapper=false
line_is_filename=true
while read -r line; do
    [[ "$line" == "###!Q!BEGIN-QUBES-WRAPPER!Q!###" ]] && in_wrapper=true && continue
    [[ "$line" == "###!Q!END-QUBES-WRAPPER!Q!###" ]] && in_wrapper=false && continue
    $in_wrapper || continue
    if $line_is_filename; then
        filename="${line:1}"
        line_is_filename=false
    else
        mkdir -p "$(dirname "$filename")"
        echo "${line:1}" | base64 -d > "$filename"
        line_is_filename=true
    fi
done < "$repodir/template.repo"

if ! DNF=$(command -v dnf5 dnf dnf4 | head -1); then
    echo "ERROR: dnf command is missing, please use newer template for your UpdateVM to download templates." >&2
    echo "You can choose any Fedora version, Debian 11 (or newer), or any other based on those (like Whonix 16)." >&2
    exit 1
fi
DNF5=false
if [[ "$DNF" = *"/dnf5" ]]; then
    DNF5=true
fi

OPTS+=(-y "--setopt=reposdir=${repodir}" --quiet)

if ! $DNF5; then
    # use vendored 'downloadurl' dnf-plugin (fork of 'download' plugin), to print
    # all mirrors
    OPTS+=("--setopt=pluginpath=/usr/lib/qubes/dnf-plugins")
fi


# This creates the hashfile if it doesn't exist, and keep the ctime and mtime
# unchanged otherwise.
# We then copy the {c,m}time to the repo config.
# This allows DNF caching to work properly.
hashfile="/tmp/qvm-template-$(b2sum "$repodir/template.repo" | cut -f1 -d' ')"
touch -a "$hashfile"
touch -r "$hashfile" "$repodir/template.repo"

RET=0

if [ "$1" = "query" ]; then
    if $DNF5; then
        $DNF repoquery "${OPTS[@]}" --qf='%{name}|%{epoch}|%{version}|%{release}|%{repoid}|%{downloadsize}|%{buildtime}|%{license}|%{url}|%{summary}|%{description}|\n' "$SPEC"
    else
        $DNF repoquery "${OPTS[@]}" --qf='%{name}|%{epoch}|%{version}|%{release}|%{repoid}|%{downloadsize}|%{buildtime}|%{license}|%{url}|%{summary}|%{description}|' "$SPEC"
    fi
    RET="$?"
elif [ "$1" = "download" ]; then
    # Download/retry algorithm: take mirrors in random order. In this order,
    # try to download from the first one - if download failed but anything was
    # downloaded - retry from the same one. If download failed and nothing was
    # downloaded, go to the next one. The intention is to retry on interrupted
    # connection, but skip mirrors that are not synchronized yet.
    declare -a urls=()
    if $DNF5 && $DNF download --help | grep -q allmirrors; then
        # The smartest case. DNF5 on Fedora 41 with --allmirrors patch
        space_separated_urls="$($DNF download "${OPTS[@]}" --url --allmirrors "$SPEC")"
        readarray -d ' ' -t urls <<<"$space_separated_urls"
        urls=( $(shuf -e "${urls[@]}") )
    elif $DNF5; then
        # The middle case. DNF5 on Fedora 41 before --allmirror patch
        # TODO: Phase out after DNF5 --allmirrors patch is released
        url="$($DNF download "${OPTS[@]}" --url "$SPEC")"
        urls=("$url")
    else
        # The old DNF4 on Fedora 40 and other old templates
        # use vendored 'downloadurl' dnf-plugin (fork of 'download' plugin),
        # to print all mirrors.
        # TODO: Phase out after DNF4 is EOL
        OPTS+=("--setopt=pluginpath=/usr/lib/qubes/dnf-plugins")
        urls="$($DNF downloadurl "${OPTS[@]}" --url --all-mirrors "$SPEC" | shuf)"
        readarray -t urls <<<"$urls"
    fi
    downloaded=0
    status_file="$repodir/download-status.tmp"
    for url in "${urls[@]}"; do
        while true; do
            # pipe data through dd to count bytes for resuming purpose
            if curl --fail --silent --continue-at "$downloaded" -L "$url" -o - |\
                    dd bs=1M 2>"$status_file"; then
                exit 0
            fi
            now_downloaded=$(grep '^[0-9]\+ bytes' "$status_file")
            now_downloaded=${now_downloaded% bytes*}
            if [ -z "$now_downloaded" ] || [ "$now_downloaded" -eq 0 ]; then
                # go to the next mirror
                break
            fi
            downloaded=$(( downloaded + now_downloaded ))
        done
    done
    # ran out of mirrors to try
    RET=1
fi

exit "$RET"
