#!/bin/bash
# Podracer v1.4
# By Lorenzo Taylor
#
#  Podracer is based on Bashpodder by Linc Fessenden of The Linux Link Tech Show.
# Bashpodder can be found at:
# http://linc.homeunix.org:8080/scripts/bashpodder
# See the CREDITS file for more information about all the great work that helped
# make Podracer the winner of the pod race.

# This is the name, version and configuration files for this program
progname=podracer
version=1.4
releasedate='19 February 2006'
progdir="$HOME/.$progname"
systemconf=/etc/$progname.conf
userconf=$progdir/$progname.conf

# We respond to version queries here, since we don't want to do anything but
# display the program name and version number if -v, -version, --version or
# version is specified on the command line.
case $1 in	-v|-version|--version|version)
	echo $progname version $version
	echo Released $releasedate
	echo by Lorenzo Taylor \<lorenzo@taylor.homelinux.net\>
	exit 0
esac

# All default configuration values are set here to avoid forcing the user to
# create a configuration file.  However, an example showing the defaults is
# provided for those of us who think outside the box.

# poddir is the directory where you want podcasts to be saved
# Directory names may be created dynamically based on time, date and many other
# possibilities.  Directory names based on feeds are listed in the subscriptions
# file.
poddir=$HOME/podcasts/$(date +%Y-%m-%d)

# incoming is the directory where incoming podcasts are saved before completion
incoming=$progdir/part

# Subscription file - this holds a list of RSS feeds for podcasts
subscriptions=$progdir/subscriptions

# The location of the sample subscription file that will be copied to a user's
# directory when running Podracer for the first time
sample=/usr/share/doc/$progname/sample.subscriptions

# A temporary place to store subscriptions.  It allows for comments in the
# original subscription file.
tempsub=$progdir/tempsub

# Set to the amount of time in seconds you would like to seed torrents, or if
# you don't have the upload bandwidth to seed, set to 0.  I personally recommend
# seeding for at least an hour, (3600 seconds), which is the default.
seedtime=3600

# Set maximum upload speed in kB/s when seeding - defaults to 0 (no limit)
uploadrate=0

# Directory where torrents are stored
torrentdir=$progdir/torrents

# The number of times Podracer will retry a download if an error occurs
retries=2

# Set the minimum download speed in bytes per second for non-torrent downloads.
# Podracer will abort a download to prevent hangs if the connection drops below
# this speed for the time given below in the downloadtimeout option.
minspeed=1

# set the time in seconds after which to abort a download if the connection
# speed drops below the above minspeed value.
downloadtimeout=60

# Logfiles
podcastlog=$progdir/podcast.log
temppodcastlog=$progdir/temp.log

# Many podcasters check their logs to determine how many people use a podcast
# aggregator vs. directly downloading the shows.  This setting will allow
# Podracer to be recognized as a true podcast agregator.
longname="$progname v$version; $(uname -o); $(uname -m)"

# set to a filename to create an m3u playlist which will be saved in $poddir
# If no filename is set, an m3u playlist will not be created.
m3u=$(date +%Y-%m-%d)-podcasts.m3u

# Override defaults with config file settings
test -e "$systemconf" && source "$systemconf"
test -e "$userconf" && 	source "$userconf"

# Check for and create progdir if necessary:
test -d "$progdir" || mkdir -p "$progdir"

# A subscription file is required in order to receive podcasts
# A sample will be created on user request
if test ! -e "$subscriptions"
	then
	echo You haven\'t subscribed to any podcasts.
	until [[ $copysample == [YyNn] ]]
		do
		echo -n "Would you like a sample subscription file? [Y/N] "
		read -n 1 copysample
		echo
	done
	case $copysample in
		N|n)
		echo Please create a file called $subscriptions and add the feeds for the
		echo podcasts you want to receive.  You may also add comments preceeded by
		echo hash marks, "(#)" to make a note of the name of each podcast or any other
		echo information you may wish to add to your subscription file.
		exit 0
		;;
		Y|y)
		if test -e $sample
			then
			cp $sample $subscriptions
			echo The sample subscription file has been saved to
			echo $subscriptions.
			echo -n Please wait while\ 
			case $1 in
				-c | -catchup | --catchup | catchup)
				echo the log is updated
				;;
				*)
				echo your podcasts are downloaded.
			esac
		else
			echo The sample subscription file is missing.  This indicates either improper
			echo installation or misconfiguration.  Please contact your system administrator to
			echo fix this problem.
			exit 2
		fi
	esac
fi

# Check for and create poddir if necessary:
test -d "$poddir" || mkdir -p "$poddir"

# Check for and create incoming directory
test -d "$incoming" || mkdir -p "$incoming"
	
# Each user may only run one copy of Podracer at a time.  It will fail if it is
# already running for the current user.
test -e "$tempsub" && exit 1
touch "$tempsub"

# Delete any temp file when interrupted or terminated:
trap 'echo Signal caught: exiting.;rm -f "$temppodcastlog";rm -f $tempsub;exit 3' hup int quit term

# If catching up, remove podcast log.  This keeps the log's size manageable by
# removing unnecessary old entries.
case $1 in -c|-catchup|--catchup|catchup)
	rm $podcastlog
esac

# Check for and create podcast log to avoid spurious errors
test  -e "$podcastlog" || touch "$podcastlog"

# Read the subscription file and curl or catch up any url not already in the podcast log:
grep -E "^(http|ftp|/)" "$subscriptions" > "$tempsub"
while read podcast feeddir
	do
	test -z "$feeddir" -o  -d "$poddir/$feeddir" || mkdir -p "$poddir/$feeddir"
	file=$(curl -A "$longname" -s -L $podcast | tr '\r' '\n' | tr \' \" | sed 's/>/>\n/g' | sed -n 's/.*url="\([^"]*\)".*/\1/p' | sed 's/&amp;/&/g')
	for url in $file
		do

		if ! grep "$url" $podcastlog > /dev/null
			then

			#Catch up on new subscriptions or after a vacationso every podcast isn't downloaded
			case $1 in -c | -catchup | --catchup | catchup)
				echo "$url" >> "$podcastlog"
			;;
			*)
				#lets get the real url in the case of dynamically generated feeds like lugradio
				realurl=`curl -s -I -L -w %{url_effective} --url "$url" | tail -n 1`

				#we'll need filename to allow us to move the podcast into poddir[/feeddir]
				filename=`echo "$realurl" | awk -F / '{print $NF}' | sed -e "s/%20/ /g" -e "s/%27/'/g" -e "s/%23/#/g" | awk '-F?' '{print $1}'`
				#file is a torrent so use the internal torrent downloader
				if [ "$(echo $filename | grep .torrent)" ]
					then
					gottorrent='Yes'
					#We only need to create torrentdir if we
					#have never downloaded a torrent file or
					#if torrentdir has been removed.
					test -d $torrentdir || mkdir -p $torrentdir
					curl -A "$longname" -s -y $downloadtimeout -Y $minspeed --retry $retries -L "$realurl" -o "$incoming/$filename"
					#try with and without .py extension
					if [ `which btshowmetainfo` ];
						then
						audiofilename=`btshowmetainfo "$incoming/$filename" | grep name | sed 's/file name.....: //'`
						else
						audiofilename=`btshowmetainfo.py "$incoming/$filename" | grep name | sed 's/file name.....: //'`
					fi
					#the embedded python code will download the file referenced in the torrent and then
					#return control to Podracer
					python - --saveas "$incoming/$audiofilename" "$incoming/$filename" << "END" > /dev/null
########## Begin python code ##########
# Written by Bram Cohen
# Modified by Lorenzo Taylor for use in Podracer
# Uses code by Huw Lynes <huw@hlynhes.com>
import sys
from BitTorrent.download import download
from threading import Event
from os.path import abspath
from sys import argv, stdout
from cStringIO import StringIO
from string import find
from time import time

def hours(n):
    if n == -1:
        return '<unknown>'
    if n == 0:
        return 'complete!'
    n = long(n)
    h, r = divmod(n, 60 * 60)
    m, sec = divmod(r, 60)
    if h > 1000000:
        return '<unknown>'
    if h > 0:
        return '%d hour %02d min %02d sec' % (h, m, sec)
    else:
        return '%d min %02d sec' % (m, sec)

class HeadlessDisplayer:
    def __init__(self):
        self.done = False
        self.file = ''
        self.percentDone = ''
        self.timeEst = ''
        self.downloadTo = ''
        self.downRate = ''
        self.upRate = ''
        self.downTotal = ''
        self.upTotal = ''
        self.errors = []
        self.last_update_time = 0

    def finished(self):
        self.done = True
        self.percentDone = '100'
        self.timeEst = 'Download Succeeded!'
        self.downRate = ''
        self.display({})
	self.errors.append("sys.exit(0)")
	sys.exit(0)

    def failed(self):
        self.done = True
        self.percentDone = '0'
        self.timeEst = 'Download Failed!'
        self.downRate = ''
        self.display({})

    def error(self, errormsg):
        noneerror = 0
        self.errors.append(errormsg)
        for errorstr in self.errors:
	    self.display({})
            if find(errorstr, "sys.exit(0)") != -1:
                noneerror = 1

        if noneerror == 1 :
            sys.exit(0)
        else:
            self.display({})
            sys.exit(1)

    def display(self, dict):
        if self.last_update_time + 0.1 > time() and dict.get('fractionDone') not in (0.0, 1.0) and not dict.has_key('activity'):
            return
        self.last_update_time = time()
        if dict.has_key('spew'):
            print_spew(dict['spew'])
        if dict.has_key('fractionDone'):
            self.percentDone = str(float(int(dict['fractionDone'] * 1000)) / 10)
        if dict.has_key('timeEst'):
            self.timeEst = hours(dict['timeEst'])
        if dict.has_key('activity') and not self.done:
            self.timeEst = dict['activity']
        if dict.has_key('downRate'):
            self.downRate = '%.2f K/s' % (float(dict['downRate']) / (1 << 10))
        if dict.has_key('upRate'):
            self.upRate = '%.2f K/s' % (float(dict['upRate']) / (1 << 10))
        if dict.has_key('upTotal'):
            self.upTotal = '%.1f M' % (dict['upTotal'])
        if dict.has_key('downTotal'):
            self.downTotal = '%.1f M' % (dict['downTotal'])
        print '\n\n'
        for err in self.errors:
            print 'ERROR: ' + err + '\n'
        print 'saving:        ', self.file
        print 'percent done:  ', self.percentDone
        print 'time left:     ', self.timeEst
        print 'download to:   ', self.downloadTo
        if self.downRate != '':
            print 'download rate: ', self.downRate
        if self.upRate != '':
            print 'upload rate:   ', self.upRate
        if self.downTotal != '':
            print 'download total:', self.downTotal
        if self.upTotal != '':
            print 'upload total:  ', self.upTotal
        stdout.flush()

    def chooseFile(self, default, size, saveas, dir):
        self.file = '%s (%.1f M)' % (default, float(size) / (1 << 20))
        if saveas != '':
            default = saveas
        self.downloadTo = abspath(default)
        return default

    def newpath(self, path):
        self.downloadTo = path

def print_spew(spew):
    s = StringIO()
    s.write('\n\n\n')
    for c in spew:
        s.write('%20s ' % c['ip'])
        if c['initiation'] == 'local':
            s.write('l')
        else:
            s.write('r')
        rate, interested, choked = c['upload']
        s.write(' %10s ' % str(int(rate)))
        if c['is_optimistic_unchoke']:
            s.write('*')
        else:
            s.write(' ')
        if interested:
            s.write('i')
        else:
            s.write(' ')
        if choked:
            s.write('c')
        else:
            s.write(' ')

        rate, interested, choked, snubbed = c['download']
        s.write(' %10s ' % str(int(rate)))
        if interested:
            s.write('i')
        else:
            s.write(' ')
        if choked:
            s.write('c')
        else:
            s.write(' ')
        if snubbed:
            s.write('s')
        else:
            s.write(' ')
        s.write('\n')
    print s.getvalue()

def run(params):
    cols = 80

    h = HeadlessDisplayer()
    download(params, h.chooseFile, h.display, h.finished, h.error, Event(), cols, h.newpath)
    if not h.done:
        h.failed()

if __name__ == '__main__':
    run(argv[1:])
########## End python code ##########
END

					#if we've succeeded add file to podcast log and move to poddir[/feeddir]
					if [ $? -eq 0 ]
						then
						mv "$incoming/$audiofilename" "$poddir/$feeddir"
						mv "$incoming/$filename" "$torrentdir"
						echo "$url" >> $podcastlog
					fi

				#not a torrent so directly download the enclosure
				else
					curl -A "$longname" -s -y $downloadtimeout -Y $minspeed --retry $retries -L -C - -o "$incoming/$filename" "$realurl"

					#if we succeeded in the download add the file to the log
					if [ $? -eq 0 ]
						then
						mv "$incoming/$filename" "$poddir/$feeddir"
						echo "$url" >> $podcastlog
					fi
				fi
			esac
		fi
		done
			test $feeddir && rmdir --ignore-fail-on-non-empty "$poddir/$feeddir"
			unset feeddir
	done < "$tempsub"
rm "$tempsub"

# Move dynamically created log file to permanent log file:
cat "$podcastlog" > "$temppodcastlog"
sort "$temppodcastlog" | uniq > "$podcastlog"
rm "$temppodcastlog"

# remove poddir if there are no files
if [[ -z $(ls $poddir) ]]; then
	rmdir $poddir
else
# Create an m3u playlist if configured by the user
#slightly more complicated to deal with
#geeknewscentral who put their stuff in directories
	if [ $m3u ]
		then
		(
		cd "$poddir"
		find . -name \*.mp3 > "$m3u"
		find . -name \*.ogg  >> "$m3u"
		find . -name \*.m4a  >> "$m3u"
		find . -name \*.m4b  >> "$m3u"
		)
	fi
fi

# Relaunch the torrents to seed.  This will timeout automatically, so there is no
# need to find the process and shut it down.
if [ $seedtime -gt 0 ]
	then
	#we only need to do the following if we actually encountered
	#some torrents
	if [ $gottorrent ]
		then
			#Some distributions of BitTorrent remove the .py
			#extention.  We will try both cases.
			if [ `which btlaunchmany` ]; then
				#Screen has the advantage here of letting podracer
				#finish normally while letting torrents seed in
				#the background.
				screen -d -m timeout $seedtime btlaunchmany  --max_upload_rate $uploadrate --saveas "$poddir/$feeddir" "$torrentdir"
			else
								screen -d -m timeout $seedtime btlaunchmany.py --saveas "$poddir/$feeddir" "$torrentdir"
			fi
	fi
fi
