#! /bin/bash # /usr/bin/pacman2pacman-get # # Version 1.5.1 # # Copyright (C) 2014 Joseph Graham # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . shopt -s extglob torrent_folder='/srv/pacman2pacman/torrents' pkg_cache_location='/var/cache/pacman/pkg' # Pacman2pacman should be called like this from /etc/pacman.conf: # XferCommand = /usr/bin/pacman2pacman-get %u %o # Check that the args are not empty. [[ -z ${1} ]] && { echo "The first argument should be the download url." ; exit 1 ; } [[ -z ${2} ]] && { echo "The second argument should be the local filename, plus a “.part” extension." ; exit 1 ; } url="${1}" # The download url. filename="${2}" # This is where the downloaded file needs to be saved # or hardlinked # Check if transmission daemon is running. if ! systemctl show --property='ActiveState' transmission | grep -q 'ActiveState=active' then echo 'Error: The transmission daemon is not running.' exit 1 fi cd "${torrent_folder}" # Find the name of the package that will be displayed pname="${url##*/}" pname="${pname%%-[[:digit:]]*}" # Find out if it's a dbfile if [[ "${url}" == *'.db' ]] || [[ "${url}" == *'.db.sig' ]] then dbfile=1 else dbfile=0 fi # Check for a .torrent. # We will cycle through the mirrors in the mirrorlist, trying to get # the torrent file for this package. If it fails we keep trying more # mirrors until we get a 404 or a success, because only a 404 # indicates that the .torrent does not exist. # If it's a .db file then we don't even check for a .torrent if (( dbfile )) then response='no_torrent' else while read mirror do # Remove the `Server = ' part, whether it has spaces or not mirror="${mirror##Server?( )=?( )}" torrent_url="${mirror%%\$*}torrents/${url##*/}.torrent" err=$( curl -O -f -L -s -A 'Pacman2pacman' "${torrent_url}" ) if [[ "${?}" == 0 ]] then response='found_torrent' break elif [[ "${err}" == *'404'* ]] then response='no_torrent' break fi done < <(grep 'Server \?= \?' "/etc/pacman.d/mirrorlist") fi # If there's a .torrent we download the package with transmission # otherwize we just download it by http. if [[ "${response}" == 'found_torrent' ]] then cd "${pkg_cache_location}" # Re-write the webseeds in the torrent to make it use the user's # own mirror. We can just use the ${url} var. # We need to find out the length of the ${url} var. len="${#url}" sed "s#url-listl[[:digit:]]\+.\+.pkg.tar.xz#url-listl${len}:${url}#" < "${torrent_folder}/${url##*/}.torrent" > "${torrent_folder}/${url##*/}.torrent.modified" mv "${torrent_folder}/${url##*/}.torrent.modified" "${torrent_folder}/${url##*/}.torrent" # Add the torrent to transmission transmission-remote -a "${torrent_folder}/${url##*/}.torrent" -w "${pkg_cache_location}" &>/dev/null # The torrent is now downloading. To get info about the torrent in # order to display a progress bar we need to know it's id. # Find out the id of the torrent id=$(transmission-remote -l | grep "${url##*/}") id="${id## }" id="${id## }" id="${id## }" id="${id## }" id="${id## }" id="${id## }" id="${id## }" id="${id## }" id="${id## }" id="${id## }" id="${id## }" # Once should be enough but somehow it's not so I do # it loads of times. id="${id%% *}" # If there is an error there will be an asterix displayed on the # end of the id. We must remove it. id="${id%\*}" if ! [[ ${id} =~ [[:digit:]] ]] then echo 'Error.' exit 1 fi # Display a progress bar until it's finished and then hardlink it to # the right place. progress='0.0%' echo -n "Pacman2pacman p2p download: ${pname}: ${progress}" count=0 declare -a last_webseed_check=(0 'working') webseed_check_after='100' until [[ "${progress}" == '100%' ]] do progress=$(transmission-remote -t "${id}" -i | grep 'Percent Done:') progress="${progress##* }" # Remove stuff we don't want # If we're still at 0.0% after ${webseed_check_after} attempts # then we check if the webseed is actually valid. if (( count >= webseed_check_after )) && [[ "${progress}" == '0.0%' ]] then # Find out if we already checked recently. if (( (last_webseed_check[0] + (webseed_check_after / 2)) > count )) then if [[ last_webseed_check[1] == 'broken' ]] then # Just set the message without checking. progress='0.0% Warning: Webseed dead...' else # We don't need to do anything true fi else if ! $(curl -IL "${url}" | grep '200 OK' &>/dev/null) then # The webseed seems to be invalid so we print an error # message. progress='0.0% Warning: Webseed dead...' # This # most # likely # means # the # user # needs # to run # pacman # -Sy, or # that # their # internet # is # down. # If there are no peers we remove the torrent from # transmission, print an error message and exit. if (( $(transmission-remote -t "${id}" -pi | wc -l) < 2 )) transmission-remote -t "${id}" -r echo "Webseed dead and no peers, removing torrent from transmission and exiting." exit 1 fi last_webseed_check[0]="${count}" last_webseed_check[1]="broken" else # The webseed is valid so we just record that we checked last_webseed_check[0]="${count}" last_webseed_check[1]="working" fi fi fi printf "\rPacman2pacman p2p download: %s: %s " "${pname}" "${progress}" sleep 0.7 (( count ++ )) done echo err_count=0 until mv -f "${pkg_cache_location}/${url##*/}" "${filename}" 2>/dev/null do if (( err_count > 50 )) then echo "error moving \"${pkg_cache_location}/${url##*/}\"" exit 1 fi sleep 0.1 (( ++ err_count )) done else cd "${pkg_cache_location}" # There's no .torrent so we download it by just HTTP. # We must do some complicated stuff to customise the progress # meter how we want it, to make it consistent with the progress # bar when we download stuff with transmission. mod_time=$(stat -c %Y "/srv/pacman2pacman/dbcache/${filename##*/}" 2>/dev/null) progress='0.0%' echo -n "Pacman2pacman http download: ${pname}: ${progress}" # The first loop gets rid of all the backspace and `#'es and # stuff. The second loop isolates the percent done number. { # For dbs we check if they are modified if (( dbfile )) then if [[ -f "/srv/pacman2pacman/dbcache/${filename##*/}" ]] then curl -# -f -L -z "/srv/pacman2pacman/dbcache/${filename##*/}" -o "/srv/pacman2pacman/dbcache/${filename##*/}" -A 'Pacman2pacman' "${url}" 2>&1 else curl -# -f -L -o "/srv/pacman2pacman/dbcache/${filename##*/}" -A 'Pacman2pacman' "${url}" 2>&1 fi else curl -# -f -L -o "${filename}" -A 'Pacman2pacman' "${url}" 2>&1 fi } | while read -N 1 char do [[ "${char}" =~ [[:digit:].%\ ] ]] && echo -n "${char}" done | { while read -d '%' word do progress="${word}%" # There's a crazy bug that causes it to display 100% at the # start of the download. This should fix it. [[ "${progress}" == '100.0%' ]] && continue printf "\rPacman2pacman http download: %s: %s " "${pname}" "${progress}" done # It's the end of the download so we can now display 100% if [[ "${progress}" == '100.0%' ]] then progress='100%' printf "\rPacman2pacman http download: %s: %s " "${pname}" "${progress}" fi } # What was the result of the download? if (( dbfile )) then # If we fail to stat it that means it doesn't exist so the # download failed. if ! new_mod_time=$(stat -c %Y "/srv/pacman2pacman/dbcache/${filename##*/}" 2>/dev/null) then progress='failed' printf "\rPacman2pacman http download: %s: %s " "${pname}" "${progress}" # If the file was not modified we display such elif [[ "${new_mod_time}" == "${mod_time}" ]] then progress='file not modified' printf "\rPacman2pacman http download: %s: %s " "${pname}" "${progress}" fi else # It's not a dbfile. Just check the output file exists. if ! [[ -f "${filename}" ]] then progress='failed' printf "\rPacman2pacman http download: %s: %s " "${pname}" "${progress}" fi fi echo if (( dbfile )) && [[ -f "/srv/pacman2pacman/dbcache/${filename##*/}" ]] then cp "/srv/pacman2pacman/dbcache/${filename##*/}" "${filename}" 2>/dev/null fi [[ -f "${filename}" ]] || exit 1 fi exit