Btc
Views:
btc - the Bit Torrent Console
Back in the day you didn't have fancy bit torrent clients. just some very clever python code written by Bram Cohen. I wanted to run bittorrent as a service that I could control remotely. This monster started out as a simple bit of bash script and grew up to be a full sized beast. It was also the first bit of bash I did
#!/bin/bash
########################
#**********************#
#* BITTORRENT CONSOLE *#
#**********************#
########################
#
# A script to help you use BitTorrent headless.
#
# Never run this script unless you want: serious
# damage to occur to your system, your house to
# burn down, to loose your job, to be attacked by
# hoards of moomins with chainsaws plus many many
# other really bad things
#
#############
# COPYRIGHT #
#############
#
# Copyright 2005 Kieran Whitbread
#
###########
# LICENSE #
###########
#
# 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 2 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 General Public License for more details.
#
# Read all about the GNU General Public License version 2 on the Free
# Software Foundation website, http://www.gnu.org/licenses/gpl.html
#
###########
# VERSION #
###########
#
# v0.72 - 8 December 2005
#
################
# REQUIREMENTS #
################
#
# This script depends on the following commands
#
# awk
# basename
# bash
# bittorrent (http://www.bittorrent.com tested with version 4.21)
# cat
# chmod
# cut
# dirname
# echo
# grep
# head
# kill
# mkdir
# mv
# nice
# nohup
# ps
# read
# rm
# sed
# setterm
# tail
#
#############
# FILENAMES #
#############
#
# Each download needs it's own directory to store the log file.
# Directories are created automatically based on the file name of the
# .torrent file (aka the response file).
# The characters in between the first 2 dots in your filename are used.
#
# file.name.blahblah.torrent saves to file/name/<downloadfilename.ext>
# or if you have filename.torrent it will save to
# filename/<downloadfilename.ext>
#
# <downloadfilename.ext> being the name of the file you are downloading
# as specified by whoever created the torrent file
#
############
# Settings #
############
#
DOWNLOAD_TREE="/store/da_pit/torrents" # Where your .torrent files live
MAX_UPLOADS=7 # default 7
MIN_LISTENING_PORT=6871 # default 6881, you need one port for each active job
MAX_LISTENING_PORT=6879 # default 6889
DEFAULT_MAX_UPLOAD_RATE=4 # in kB/s, 0 means unlimited, default 0
LOG_UPDATE_RATE=5 # log (nohup.out) update rate in seconds, default 30
BIT_TORRENT="/usr/local/share/bittorrent" # Path to BitTorrent Executable Files
NICENESS=10 # -19 to 19, higher means lower priority, default 10
ARCHIVE="TRUE" # save (TRUE) or delete (FALSE) old response files, default TRUE
#
###############################################################################
ALREADY_TIDY=" WARNING: Response Files Already Tidy"
#ARCHIVE_DIR after DOWNLOAD_TREE
BIT_TORRENT=`echo $BIT_TORRENT|sed -e 's/\/$//'`
BIT_TORRENT_DOWNLOADER="$BIT_TORRENT/bittorrent-console.py"
BIT_TORRENT_METAINFO="$BIT_TORRENT/torrentinfo-console.py"
DOWNLOAD_TREE=`echo $DOWNLOAD_TREE|sed -e 's/\/$//'`
ARCHIVE_DIR="$DOWNLOAD_TREE/old-response-files"
MKDIR_ERROR=" FAILURE: Unable to create directory"
NO_COMPLETED_JOBS=" WARNING: No Completed Jobs"
NO_JOBS=" WARNING: No Active Jobs"
NO_NEW_RESPONSE_FILES=" WARNING: No New Response Files"
NO_RESPONSE_FILES=" WARNING: No Response Files Found"
NUMBER_OF_PORTS=$(($MAX_LISTENING_PORT - $MIN_LISTENING_PORT))
OUT_OF_PORTS=" FAILURE: Not Enough Ports Available"
PID_ONLY="pidOnly"
prog=`basename $0`
RESPONSE_ONLY="responseOnly"
START_JOB_OK=" Success: Starting Job"
START_JOB_BAD=" Cannot Start Job"
STOP_JOB_OK=" Success: Job Stopped"
uploadRate=$DEFAULT_MAX_UPLOAD_RATE
function adviseUser()
{
echo
echo " New Job(s) Sent to BitTorrent"
echo " Please check the Jobs (J) and Info (I or AI) sections"
echo
}
function allJobInfo()
{
activeJobs=`pidAndResponseFile`
echo
if [ ! "$activeJobs" = "" ]
then
for lines in `pidAndResponseFile $RESPONSE_ONLY|sed -e 's/\ /§/g'`
do
cleanLine=`echo "$lines"|sed -e 's/§/\ /g'`
jobInfo "$cleanLine"
echo
done
else
echo "$NO_JOBS"
echo
fi
}
function allNew()
{
usedAllNew=0
currentResponseFile=1
curJob=""
findResponseFiles
if [ ! "${responseFiles[1]}" = "" ]
then
echo
while [ ! $responseFileCount -eq $currentResponseFile ]
do
curJob=`basename "${responseFiles[$currentResponseFile]}"`
if ! jobCompleted "$curJob"
then
if ! jobExists `basename "${responseFiles[$currentResponseFile]}"|sed -e 's/.torrent$//'`
then
if portAvailable
then
choice=`basename "${responseFiles[$currentResponseFile]}"`
echo -n "$START_JOB_OK - "
choiceFeedback=`echo $choice|sed -e 's/.torrent$//'`
echo "$choiceFeedback"
getTargetFileName
preExecution
executeDownload
permissions `dirname "$targetFile"`
usedAllNew=1
else
setterm -bold on
echo "$OUT_OF_PORTS"
setterm -default
echo -n "$START_JOB_BAD - "
echo `basename "${responseFiles[$currentResponseFile]}" .torrent`
fi
fi
fi
(( currentResponseFile++ ))
done
if [ $usedAllNew -eq 1 ]
then
adviseUser
else
echo "$NO_NEW_RESPONSE_FILES"
echo
fi
else
echo
echo "$NO_RESPONSE_FILES"
echo
fi
}
function allStop()
{
process=""
echo
for process in `pidAndResponseFile $PID_ONLY`
do
stopJob "$process" "no invalid"
done
echo
}
function archiveDir()
{
result=""
if [ ! "$ARCHIVE" = "FALSE" ]
then
result=`mkdir -p "$ARCHIVE_DIR" 2>&1`
if [ ! "$result" = "" ]
then
mkdirError "$result" "$ARCHIVE_DIR"
return 1
else
return 0
fi
else
return 0
fi
}
function cmdLineHelp()
{
echo
echo "BTC, BitTorrent Console for managing headless BitTorrent Downloads"
echo
echo " > $prog -use console mode"
echo " > $prog AI -list information on all active jobs"
echo " > $prog AN -start work on all new response files"
echo " > $prog AS -stop all jobs"
echo " > $prog J -list active jobs"
echo " > $prog SC -stop completed jobs"
echo " > $prog T -tidy-up response files"
echo
}
function confirmAllStop()
{
activeJobs=""
confirm=""
echo
activeJobs=`pidAndResponseFile`
if [ ! "$activeJobs" = "" ]
then
echo "Really Stop All Jobs? (y|n)"
read -a confirm -p "$prog/all stop> "
case $confirm
in
y|Y) allStop ;;
*) continue ;;
esac
else
echo "$NO_JOBS"
echo
fi
}
function executeDownload()
{
cd "$workingDirectory"
echo -n " "
nohup nice -n $NICENESS $BIT_TORRENT_DOWNLOADER\
--max_uploads $MAX_UPLOADS\
--minport $MIN_LISTENING_PORT\
--maxport $MAX_LISTENING_PORT\
--max_upload_rate $uploadRate\
--display_interval $LOG_UPDATE_RATE\
"$DOWNLOAD_TREE"/"$choice"&
sleep 1
}
function findResponseFiles()
{
responseFileCount=1
unset responsefiles
for line in `ls -1 ${DOWNLOAD_TREE}/*.torrent 2>/dev/null|sed -e 's/\ /§/g'`
do
responseFiles[$responseFileCount]=`echo "$line"|sed -e 's/§/\ /g'`
(( responseFileCount ++ ))
done
}
function getResponseFileName()
{
echo "Choose a Response File"
local selection=""
read -a selection -p "$prog/new> "
if [ ! "${responseFiles[$selection]}" ]
then
invalidInput
return 1
elif jobExists `basename "${responseFiles[$selection]}" .torrent`
then
echo
echo " This Job Already Active"
echo
return 1
else
choice=`basename "${responseFiles[$selection]}"`
echo
setterm -bold on
echo -n " Response File: "
setterm -default
echo "$choice"
getTargetFileName
setterm -bold on
echo -n " Target File: "
setterm -default
echo "$targetFile"
return 0
fi
}
function getTargetFileName()
{
targetFile=`$BIT_TORRENT_METAINFO "$DOWNLOAD_TREE/$choice"|\
head -n 5|tail -n 1|cut -d: -f 2|cut -d\ -f 2-`
}
function heading()
{
echo
echo " No Response File"
echo
}
function invalid()
{
echo
echo " Not a Valid Menu Item"
echo " Enter H for Help"
echo
}
function invalidInput()
{
echo
setterm -bold on
echo " FAILURE: Not a Valid Entry"
setterm -default
echo
}
function jobCompleted()
{
completed=`jobInfo "$1"|sed -n -e '/^Time Left: seeding/p'`
if [ ! "$completed" = "" ]
then
return 0
fi
return 1
}
function jobExists()
{
for lines in `pidAndResponseFile $RESPONSE_ONLY`
do
if [ "$1" = "$lines" ]
then
return 0
fi
done
return 1
}
function jobInfo()
{
upperDirectory=`echo "$1"|cut -d. -f 1`
lowerDirectory=`echo "$1"|cut -d. -s -f 2`
if [ "$lowerDirectory" = "torrent" ]
then
responseFile="$DOWNLOAD_TREE/$upperDirectory/nohup.out"
else
responseFile="$DOWNLOAD_TREE/$upperDirectory/$lowerDirectory/nohup.out"
fi
if [ -f "$responseFile" ]
then
info_saving=""
info_file_size=""
info_percent_done=""
info_time_left=""
info_download_to=""
info_download_rate=""
info_upload_rate=""
info_share_rating=""
info_seed_status=""
info_peer_status=""
for lines in `cat -s "$responseFile"|\
tail -n 20|sed -e '/^[E|\[].*/d' -e '/^$/d' -e 's/\ /§/g'`
do
info_property=`echo "$lines"|cut -d: -f 1|sed -e 's/§/\ /g'`
info_value=`echo "$lines"|cut -d: -f 2-|sed -e 's/§/\ /g'`
case "$info_property"
in
saving) info_saving="$info_value";;
file\ size) info_file_size="$info_value";;
percent\ done) info_percent_done="$info_value";;
time\ left) info_time_left="$info_value";;
download\ to) info_download_to="$info_value";;
download\ rate) info_download_rate="$info_value";;
upload\ rate) info_upload_rate="$info_value";;
share\ rating) info_share_rating="$info_value";;
seed\ status) info_seed_status="$info_value";;
peer\ status) info_peer_status="$info_value";;
esac
done
echo "Saving:$info_saving"
echo "File Size:$info_file_size"
echo "Percent Done:$info_percent_done"
echo "Time Left:$info_time_left"
echo "Download To:$info_download_to"
echo "Download Rate:$info_download_rate"
echo "Upload Rate:$info_upload_rate"
echo "Share Rating:$info_share_rating"
echo "Seed Status:$info_seed_status"
echo "Peer Status:$info_peer_status"
fi
}
function jobInfoMenu()
{
activeJobs=""
count=1
selection=""
activeJobs=`pidAndResponseFile`
if [ ! "$activeJobs" = "" ]
then
heading
for lines in `pidAndResponseFile $RESPONSE_ONLY|sed -e 's/\ /§/g'`
do
activeJobs[$count]=`echo "$lines"|sed -e 's/§/\ /g'`
echo "$count ${activeJobs[$count]}"
(( count++ ))
done
echo
echo "Enter Job Number"
read -a selection -p "$prog/info> "
if [ "${activeJobs[$selection]}" = "" ]
then
invalid
else
echo
jobInfo "${activeJobs[$selection]}"
fi
else
echo
echo "$NO_JOBS"
fi
echo
}
function listCommands()
{
echo
echo " AI - All Information"
echo " AN - All New"
echo " AS - All Stop"
echo " H - Display This Message"
echo " I - Display Information"
echo " J - Display Current Jobs"
echo " N - New Job"
echo " S - Stop Job"
echo " SC - Stop Completed Jobs"
echo " SR - Set Rate of New Uploads"
echo " T - Tidy-up Response Files"
echo " X or Q - Exit"
echo
}
function listJobs()
{
jobsRunning=`pidAndResponseFile`
if [ ! "$jobsRunning" = "" ]
then
echo
echo " Current Jobs:"
echo
echo " PID Response File "
echo
pidAndResponseFile
echo
else
echo
echo "$NO_JOBS"
echo
fi
}
function listResponseFiles()
{
count=1
listItem=""
heading
while [ ! "${responseFiles[$count]}" = "" ]
do
listItem=`basename "${responseFiles[$count]}" .torrent$`
echo "$count $listItem"
(( count++ ))
done
echo
}
function mainMenu()
{
while read -a result -p "$prog> "
do
case "$result"
in
x|X|q|Q) setterm -default
exit 0 ;;
j|J) listJobs ;;
s|S) stopJobMenu ;;
h|H|[Hh][Ee][Ll][Pp]) listCommands ;;
N|n) newJob ;;
i|I) jobInfoMenu ;;
[aA][sS]) confirmAllStop ;;
[aA][nN]) allNew ;;
[aA][iI]) allJobInfo ;;
t|T) tidy ;;
[sS][cC]) stopCompleted ;;
[sS][rR]) setUploadRate ;;
*) invalid ;;
esac
done
}
function mkdirError()
{
message=`echo $1|cut -d: -f 3`
setterm -bold on
echo "$MKDIR_ERROR, $message"
setterm -default
echo " Couldn't create $2"
}
function newJob()
{
if portAvailable
then
findResponseFiles
if [ "${responseFiles[1]}" = "" ]
then
echo
echo $NO_RESPONSE_FILES
echo
else
listResponseFiles
if getResponseFileName
then
preExecution
executeDownload
permissions `dirname "$targetFile"`
adviseUser
fi
fi
else
echo
setterm -bold on
echo "$OUT_OF_PORTS"
setterm -default
echo
fi
}
function permissions()
{
chmod o+rwx "$1" -R
}
function pidAndResponseFile()
{
if [ "$1" = "$RESPONSE_ONLY" ]
then
finalCut="2"
elif [ "$1" = "$PID_ONLY" ]
then
finalCut="1"
else
finalCut="1,2"
fi
field=`echo "$DOWNLOAD_TREE"|awk -F"/" '{ print NF }'`
(( field++ ))
ps ax -o pid= -o args=|grep bittorrent-console.py|\
sed -e 's/^ //' -e 's/^ //'|\
cut -d\ -f 1,14-|\
cut -d/ -f 1,5|\
sed -e '/^[0-9]* *$/d' -e '/:?*:/d' -e '/MATHPATH/d' -e 's/.torrent$//' -e 's/\ /\t/' -e 's/\///'|\
cut -f $finalCut
}
function portAvailable()
{
numberOfPortsInUse=0
for items in `pidAndResponseFile $PID_ONLY`
do
(( numberOfPortsInUse++ ))
done
if [ $numberOfPortsInUse -lt $NUMBER_OF_PORTS ]
then
return 0
else
return 1
fi
}
function preExecution()
{
upperDirectory=`echo $choice|cut -d. -f 1`
lowerDirectory=`echo $choice|cut -d. -f 2`
result=`mkdir -p "$DOWNLOAD_TREE/$upperDirectory" 2>&1`
if [ $? -eq 0 ]
then
if [ "$lowerDirectory" = "torrent" ]
then
workingDirectory="$DOWNLOAD_TREE/$upperDirectory"
targetFile="$DOWNLOAD_TREE/$upperDirectory/$targetFile"
else
mkdir -p "$DOWNLOAD_TREE/$upperDirectory/$lowerDirectory"
workingDirectory="$DOWNLOAD_TREE/$upperDirectory/$lowerDirectory/"
targetFile="$DOWNLOAD_TREE/$upperDirectory/$lowerDirectory/$targetFile"
fi
else
mkdirError "$result" "$DOWNLOAD_TREE/$upperDirectory"
fi
}
function rmTest()
{
group=""
groups=""
groupW=""
otherW=""
user=""
userW=`ls -l $1|sed -n -e '/^..w/p'`
if [ ! "$userW" = "" ]
then
user=`ls -l $1|cut -d\ -f 4 -s`
if [ "$user" = "$USER" ]
then
return 0
fi
fi
groupW=`ls -l $1|sed -n -e '/^.....w/p'`
if [ ! "$groupW" = "" ]
then
group=`ls -l -g $1|cut -d\ -f 4 -s`
groups=`cat /etc/group|sed -n -e '/'$USER'/p'|cut -d: -f 1`
for lines in $groups
do
if [ "$lines" = "$group" ]
then
return 0
fi
done
fi
otherW=`ls -l $1|sed -n -e '/^........w/p'`
if [ ! "$otherW" = "" ]
then
return 0
fi
return 1
}
function setUploadRate()
{
echo
echo "Enter new upload rate in KB/s"
read -a selection -p "$prog/set rate> "
if [ $selection -lt 2048 -a $selection -gt -1 ] 2>/dev/null
then
uploadRate=$selection
else
invalidInput
fi
}
function stopCompleted()
{
activeJobs=""
unset activeJobsArray
count="1"
done=""
processes=""
unset processesArray
usedStopCompleted=0
activeJobs=`pidAndResponseFile|sed -e 's/^\([0-9]*\)\t\(.*\)/\1 \2/'`
processes=`echo "$activeJobs"|cut -d\ -f 1`
for lines in `echo "$processes"`
do
processesArray[$count]="$lines"
(( count++ ))
done
count=1
for lines in `echo "$activeJobs"|sed -e 's/^[0-9]* \(.*$\)/\1/' -e 's/ /§/g'`
do
activeJobsArray[$count]=`echo "$lines"|sed -e 's/§/ /g'`
(( count++ ))
done
count=1
echo
while [ ! "${activeJobsArray[$count]}" = "" ]
do
jobCompleted "${activeJobsArray[$count]}"
if [ $? -eq 0 ]
then
stopJob "${processesArray[$count]}" "no invalid"
usedStopCompleted=1
fi
(( count++ ))
done
if [ "$usedStopCompleted" -eq 0 ]
then
echo "$NO_COMPLETED_JOBS"
fi
echo
}
function stopJob()
{
if [ -d /proc/"$1" ]
then
cat /proc/"$1"/cmdline|grep bittorrent-console.py -a -q
if [ "$?" = 0 ]
then
kill -s SIGTERM "$1"
if [ $? = "0" ]
then
echo "$STOP_JOB_OK"
else
stopJobBad
fi
else
if [ ! "$2" = "no invalid" ]
then
invalid
fi
fi
else
if [ ! "$2" = "no invalid" ]
then
invalid
fi
fi
}
function stopJobBad()
{
setterm -bold on
echo " *****************************************"
echo " * FAILURE: Unable to Terminate Process *"
echo " *****************************************"
setterm -default
}
function stopJobMenu()
{
listJobs
echo "Enter PID"
read -a process -p "$prog/stop> "
if [ $process -gt 0 ] 2>/dev/null
then
echo
stopJob "$process"
echo
else
invalidInput
fi
}
function tartIntro()
{
setterm -bold on
clear
echo
echo " KRAFTY TTTTTTTT CCCC"
echo " BB BB TT CC CC"
echo " BB BB TT CC"
echo " BBBBBB TT CC"
echo " BB BB TT CC CC"
echo " BB BB TT CC CC"
echo " BBBBBBB TT CUTS"
setterm -bold off
echo
echo " BitTorrent Console"
echo " (H for help)"
if [ $UID == "0" ]
then
setterm -bold on
echo
echo
echo " *****************************************"
echo " * You are running this program as root! *"
echo " * It is bad security to run bittorent *"
echo " * as root. *"
echo " *****************************************"
setterm -bold off
fi
}
function testSettingsOK()
{
if [ ! -f "$BIT_TORRENT_DOWNLOADER" -o ! -f "$BIT_TORRENT_METAINFO" -o \
"$NICENESS" -gt 19 -o "$NICENESS" -lt -19 -o \
"$MAX_UPLOADS" -lt 1 -o "$MIN_LISTENING_PORT" -gt "$MAX_LISTENING_PORT" -o \
"$DEFAULT_MAX_UPLOAD_RATE" -lt 0 -o "$LOG_UPDATE_RATE" -lt 0 ] 2>/dev/null
then
return 1
else
return 0
fi
}
function tidy()
{
count=1
filesNeededTidying=0
job=""
result=""
findResponseFiles
echo
if [ ! "${responseFiles[$count]}" = "" ]
then
if archiveDir
then
while [ ! "${responseFiles[$count]}" = "" ]
do
job=`basename "${responseFiles[$count]}"`
if jobCompleted "$job"
then
filesNeededTidying=1
if [ "$ARCHIVE" = "FALSE" ]
then
rmTest "${responseFiles[$count]}"
if [ $? -eq 0 ]
then
result=`rm -f "${responseFiles[$count]}" 2>&1`
else
result=" Permission denied"
fi
else
result=`mv -f "${responseFiles[$count]}" "$ARCHIVE_DIR/$job" 2>&1`
fi
if [ ! "$result" = "" ]
then
tidyErrorMsg "$result" "$job"
else
echo " Success: $job Tidied"
fi
fi
(( count++ ))
result=""
done
if [ $filesNeededTidying -eq 0 ]
then
echo "$ALREADY_TIDY"
fi
fi
else
echo "$ALREADY_TIDY"
fi
echo
}
function tidyErrorMsg()
{
message=`echo "$1"|cut -d: -f 3`
setterm -bold on
echo " FAILURE: Unable to Tidy Response File,$message"
setterm -default
echo " $2"
}
if ! testSettingsOK
then
{
echo
echo "BTC, BitTorrent Console"
echo
echo "There seems to be a problem with your settings"
echo "you might need to edit the settings at the top"
echo "of this script."
echo
echo "Check that paths have been entered corectly &"
echo "that BitTorrent & python are installed OK."
echo "All settings must be filled in."
echo
echo "$0"
echo
exit 1
}
fi
case $1
in
[aA][sS]) allStop ;;
[aA][nN]) allNew ;;
[aA][iI]) allJobInfo ;;
j|J) listJobs ;;
[sS][cC]) stopCompleted ;;
t|T) tidy ;;
*) if [ "$1" = "" ]
then
tartIntro
listJobs
mainMenu
else
cmdLineHelp
fi ;;
esac
