Difference between revisions of "BASH script to run bittorrent as a daemon"

From Theory.org Wiki
Jump to: navigation, search
(1970 TV basic Dans DISC Livrer Acteurs Assortiment)
m (Spam deleted)
 
(One intermediate revision by one other user not shown)
Line 1: Line 1:
Imaginez un groupe familial, à peu près tous assis dans le TV pour PC, probablement d'une télévision commune POUR PC à la maison. Papa est le positionnement de votre appareil à distance, qui sera chaque personne regarde comme «le clicker. "Ce soutien informatique à distance a simplement une paire de fonctions. Un effectue de nombreuses tourne le téléviseur hors tension de la ainsi que sur. Le contraire modifie les programmes particuliers, à peu près tous les huit six ou peut-être des conseillers, qui comprend une il suffit de cliquer sur le bouton. Maintenant faire un effort pour imaginer tout ce que exactement de ce qui est effectivement proches d'observation. <a href="http://www.saclancelsoldey.com/">Sac Lancel</a>
+
By using the btlaunchmany.py executable you can do this:
Avez-vous été deviner ces personnes regardent l'un des nombreux spectacles d'époque sur les années 1970?
 
  
Dans les années 1970 presque tous les <a href="http://www.longchampfrs.com/">Sac Longchamp</a>
+
I created a '''torrent directory''' which only contains two other directories: '''active''' and '''standby''' I then download all .torrent file into the '''standby''' directory and when I want to start fetching the file I simply move the file over to the active directory.
ménages que vous possédiez, éventuellement 2 téléviseurs à la maison, et le second téléviseur avait été normalement disponible de côté avec maman et aussi salon de papa où ils sont en mesure de regarder votre <a href="http://www.saclancell.com/">Sac Lancel Pas Cher</a>
 
millésime illustre la façon dont les enfants ne pouvaient pas voir tout de même comme Maude et aussi de l'eau et du savon. Votre téléviseurs à la maison ont probablement fait la démonstration des exercices particuliers avec niveaux de gris.
 
  
Exactement ce qui a gardé les individus collés pour ce genre de téléviseur qui a acquis seulement une grande partie de chaînes, clicker début, et aussi souvent pas de l'ombre? Les Spectacles particuliers communs avec les années 1970, évidemment. Il <a href="http://www.lesaclongchampfr.com/">Sac Longchamp</a>
+
I start the [[BitTorrent]] executable as a background task using this script:
ya eu de nombreuses expositions favorables à la famille autour d'une grande partie de genres.
+
<pre>
 +
#!/bin/sh
 +
cd
 +
nohup btlaunchmany.py torrent/active/ > torrent.log &
 +
tail -f torrent.log
 +
</pre>
  
Pour les jeunes familles qui ont aimé les comédies, les années 1970 avaient le plus réels de la majorité des comédies de situation du uniques. Vos personnes au sein du tout '70 's réalisé en ce qui concerne les familles alors que dans les années 1950 par le biais de profiter de la très <a href="http://www.saclancelventes.com/">lancel Sac Lancel Vente</a>
+
It will start the [[BitTorrent]] and scans the ''active'' directory for all *.torrent references and will then download the files in the same directory. Fortunately the btlaunchmany.py is scanning the ''active'' directory frequently, so by simply moving a *.torrent file into that directory it will be recognized and a new download thread will be created.
réelle du bien-connu des jours et des nuits satisfaits avec elle est l'angle non moins célèbre loin de Laverne, en plus de Shirley. Jours satisfaits à quelques semaines de plus que la vocation de présenter, avec le comédien Robin Williams, qui semblent pris d'être en mesure de légende dans une des autres retombées et réécrire à partir de votre démontrer - Mork, en plus de Mindy.
 
  
Certains autres amis et la famille chaleureuse et conviviale des spectacles typiques avec la gamme des années 1970 à partir de la Maison Partridge (avec les adolescentes <a href="http://www.saclanceles.com/">Sac Lancel</a>
+
To avoid this process exiting when you log out, you can either
cheminée battre Cassidy Jesse), cet écran que nous a créés pour aider Steve Travolta, a permis à De retour Kotter, finalement à un moment donné, Chico et l'homme, ce nombre impair Petit, instances Grands, WKRP tout au long de Cincinnati, Qu'est-ce qui transpire, l'entreprise Trois et le Jefferson.
+
 
 +
* use ''nohup''
 +
 
 +
<code>nohup btlaunchmany.py torrent/active/ > torrent.log &</code>
 +
 
 +
* double background it
 +
 
 +
<pre>
 +
    (./btlaunchmany.py torrent/active/ > torrent.log 2>&1 &) &
 +
</pre>
 +
 
 +
This will display a message something like this:
 +
<pre>
 +
    [1] + Done                ( ./btlaunchmany.py torrent/active/ > torrent.l
 +
</pre>
 +
 
 +
That's OK. If you do a 'ps -x' you will see your btlauncher running in the background. No nohup required. The '2>&1' redirects stderr to stdout which is redirected to torrent.log. This makes it totally silent. Then it's put into the background with '&' and it's backgrounded twice with another '&'. What this does is totally disconnect the process from your terminal. It may look weird, but this is the UNIX idiom that says "not only do I  want to run this process asynchronously, I also want the parent process to be the INIT process instead of my terminal". Now, if you actually want to kill the process you can send it a HUP signal 'kill -hup {PID}'.
 +
 +
* use the ''screen'' utility
 +
 
 +
<code>screen</code> 
 +
<code>btlaunchmany.py torrent/active/ > torrent.log &</code>
 +
 
 +
you may exit your terminal session, and when you relogin you can type
 +
 
 +
<code>screen -r</code>
 +
 
 +
to reconnect to it.
 +
 
 +
RH9 users can run this script by  may not pick up new torrents as described above.
 +
----
 +
 
 +
I use this one as /etc/init.d/bittorrent on debian:
 +
<pre>
 +
#! /bin/sh
 +
 
 +
PATH=/sbin:/bin:/usr/sbin:/usr/bin
 +
DAEMON=/usr/bin/bttrack
 +
LAUNCH=/usr/bin/btlaunchmany
 +
MAKEMETA=/usr/bin/btmakemetafile
 +
DFILE=connected.txt
 +
PORT=6969
 +
NAME="bttrack"
 +
DESC="bittorrent tracker"
 +
FILESDIR=/var/torrentfiles
 +
TORRENTSDIR=/var/www/torrents
 +
SERVER=http://www.peix.org:6969
 +
 
 +
OPTIONS="--dfile ./$DFILE --port $PORT"
 +
test -f $DAEMON || exit 0
 +
cd $FILESDIR
 +
set -e
 +
 
 +
case "$1" in
 +
  make)
 +
  echo "Making torrents: "
 +
  for file in $FILESDIR/*
 +
  do
 +
    if [[ `basename $file` = "." ]]; then
 +
        continue;
 +
    fi
 +
    if [[ `basename $file` = "$DFILE" ]]; then
 +
        continue;
 +
    fi
 +
    echo $file
 +
    $MAKEMETA $file $SERVER/announce
 +
    cp $FILESDIR/*.torrent $TORRENTSDIR
 +
  done
 +
  echo "."
 +
  ;;
 +
start)
 +
  echo -n "Starting $DESC: $NAME"
 +
  start-stop-daemon --oknodo -S -b -x $DAEMON -- $OPTIONS
 +
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
 +
  echo "."
 +
  ;;
 +
  stop)
 +
  echo -n "Stopping $DESC: $NAME"
 +
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
 +
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
 +
  echo "."
 +
  ;;
 +
  restart|force-reload)
 +
  echo "Restarting $DESC: $NAME"
 +
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
 +
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
 +
  start-stop-daemon --oknodo -S -b -x  $DAEMON -- $OPTIONS
 +
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
 +
  echo "."
 +
  ;;
 +
  *)
 +
  echo "Usage: $0 {start|stop|restart|force-reload|make}" >&2
 +
  exit 1
 +
  ;;
 +
esac
 +
 
 +
exit 0
 +
</pre>
 +
hope it helps
 +
 
 +
----
 +
 
 +
I, mailto:claw+torrent@kanga.nu,  made some slight modifications to the above so as to support multiple source directories for the files that will be torrented:
 +
 
 +
<pre>
 +
#! /bin/sh
 +
 
 +
PATH=/sbin:/bin:/usr/sbin:/usr/bin
 +
DAEMON=/usr/bin/bttrack
 +
LAUNCH=/usr/bin/btlaunchmany
 +
MAKEMETA=/usr/bin/btmakemetafile
 +
DFILE=connected.txt
 +
PORT=6969
 +
NAME="bttrack"
 +
DESC="bittorrent tracker"
 +
TORRENTSDIR=/var/www/downloads/torrents
 +
SERVER=http://research.warnes.net:6969
 +
DEFAULTS_FILE=/etc/default/bittorrent
 +
OPTIONS="--dfile ./$DFILE --port $PORT"
 +
 
 +
if [[ -s $DEFAULTS_FILE ]]; then
 +
    . $DEFAULTS_FILE
 +
fi
 +
test -f $DAEMON || exit 0
 +
cd $FILESDIR
 +
set -e
 +
 
 +
case "$1" in
 +
  make)
 +
  echo "Making torrents: "
 +
  for dir in ${TORRENT_DIRS}
 +
  do
 +
    #rm -f ${dir}/*.torrent
 +
    for file in ${dir}/*
 +
    do
 +
      base=`basename $file`
 +
      if [[ "$base" = "." ]]; then
 +
        continue;
 +
      fi
 +
      if [[ "$base" = "$DFILE" ]]; then
 +
        continue;
 +
      fi
 +
      echo $file
 +
      $MAKEMETA $file $SERVER/announce
 +
    done
 +
    mv ${dir}/*.torrent $TORRENTSDIR
 +
  done
 +
  chown www-data.www-data ${TORRENTSDIR}/*
 +
  echo "."
 +
  ;;
 +
start)
 +
  echo -n "Starting $DESC: $NAME"
 +
  start-stop-daemon --oknodo -S -b -x $DAEMON -- $OPTIONS
 +
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
 +
  echo "."
 +
  ;;
 +
  stop)
 +
  echo -n "Stopping $DESC: $NAME"
 +
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
 +
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
 +
  echo "."
 +
  ;;
 +
  restart|force-reload)
 +
  echo "Restarting $DESC: $NAME"
 +
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
 +
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
 +
  start-stop-daemon --oknodo -S -b -x  $DAEMON -- $OPTIONS
 +
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
 +
  echo "."
 +
  ;;
 +
  *)
 +
  echo "Usage: $0 {start|stop|restart|force-reload|make}" >&2
 +
  exit 1
 +
  ;;
 +
esac
 +
 
 +
exit 0
 +
</pre>
 +
 
 +
The main added feature is an /etc/default/bittorrent file which should read something like:
 +
 
 +
<pre>
 +
#
 +
# List of directories which can contain torrent files to be served
 +
# by the local system.
 +
#
 +
TORRENT_DIRS="/dir1/dir2/dir3 /dir4/dir5/dir6 /dir7/dir8/dir9"
 +
</pre>
 +
 
 +
----
 +
From: Yuriy Krylov (ykrylov -AT - gmail -DOT- com)
 +
 
 +
This is a debian-style btlaunchmany.bittorrent which pics off "active" torrents from a specified directory moves the file and torrent to "completed" folder when done and emails the owner a notification of completion.
 +
 
 +
<pre>
 +
#!/usr/bin/python
 +
 
 +
# Written by Michael Janssen (jamuraa at base0 dot net)
 +
# originally heavily borrowed code from btlaunchmany.py by Bram Cohen
 +
# and btdownloadcurses.py written by Henry 'Pi' James
 +
# now not so much.
 +
# fmttime and fmtsize stolen from btdownloadcurses.
 +
# see LICENSE.txt for license information
 +
 
 +
from BitTorrent.download import download
 +
from threading import Thread, Event, Lock
 +
from os import listdir, rename
 +
from os.path import abspath, join, exists, getsize, basename, isfile
 +
from sys import argv, stdout, exit
 +
from time import sleep
 +
import traceback
 +
import sys
 +
import smtplib
 +
 
 +
print "btlaunchmany.bittorrent is RUNNING"
 +
 
 +
LOG = "/home/bittorrent/public_html/log.txt"
 +
FILE = open(LOG,"w+")
 +
old_out = sys.stdout
 +
old_err = sys.stderr
 +
sys.stdout = sys.stderr = FILE
 +
 
 +
COMPLETED = "/home/bittorrent/completed/"
 +
 
 +
def cleanup():
 +
        FILE.close()
 +
        sys.stdout = old_out
 +
sys.stderr = old_err
 +
 
 +
 
 +
def sendMail(filename,status):
 +
short_filename = basename(filename)
 +
print "SENDING NOTIFICATION ABOUT %s %s" % (status, short_filename)
 +
sys.stdout.flush()
 +
sys.stderr.flus()
 +
server = smtplib.SMTP("localhost")
 +
server.sendmail("bittorrent@yuriy.org","yuriy@localhost", "Subject: bittorent:%s %s has %s download!" % (short_filename,short_filename,status))
 +
server.quit()
 +
 
 +
def cleanTorrent(filename):
 +
short_filename = basename(filename)
 +
print "REMOVING TORRENT %s" % short_filename
 +
sys.stdout.flush()
 +
sys.stderr.flush()
 +
rename(filename,COMPLETED + short_filename)
 +
rename(filename+".torrent",COMPLETED + short_filename + ".torrent")
 +
 
 +
def fmttime(n):
 +
    if n == -1:
 +
        return '(no seeds?)'
 +
    if n == 0:
 +
        return 'complete'
 +
    n = int(n)
 +
    m, s = divmod(n, 60)
 +
    h, m = divmod(m, 60)
 +
    if h > 1000000:
 +
        return 'n/a'
 +
    return '%d:%02d:%02d' % (h, m, s)
 +
 
 +
def fmtsize(n, baseunit = 0, padded = 1):
 +
    unit = [' B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
 +
    i = baseunit
 +
    while i + 1 < len(unit) and n >= 999:
 +
        i += 1
 +
        n = float(n) / (1 << 10)
 +
    size = ''
 +
    if padded:
 +
        if n < 10:
 +
            size = '  '
 +
        elif n < 100:
 +
            size = ' '
 +
    if i != 0:
 +
        size += '%.1f %s' % (n, unit[i])
 +
    elif padded:
 +
        size += '%.0f  %s' % (n, unit[i])
 +
    else:
 +
        size += '%.0f %s' % (n, unit[i])
 +
    return size
 +
 
 +
 
 +
def dummy(*args, **kwargs):
 +
    pass
 +
 
 +
threads = {}
 +
ext = '.torrent'
 +
print 'btlaunchmany starting..'
 +
print '...logging to ' + LOG
 +
filecheck = Lock()
 +
 
 +
def dropdir_mainloop(d, params):
 +
    deadfiles = []
 +
    while True:
 +
        files = listdir(d)
 +
        # new files
 +
        for file in files:
 +
            if file.endswith(ext) and \:
 +
              file not in threads.keys() + deadfiles:
 +
                threads[file] = {'kill': Event(), 'try': 1}
 +
                print 'New torrent: %s' % file
 +
                sys.stdout.flush()
 +
sys.stderr.flush()
 +
                threads[file]['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file)
 +
                threads[file]['thread'].start()
 +
        # files with multiple tries
 +
        for file, threadinfo in threads.items():
 +
            if threadinfo.get('timeout') == 0:
 +
                # Zero seconds left, try and start the thing again.
 +
                threadinfo['try'] += 1
 +
                threadinfo['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file)
 +
                threadinfo['thread'].start()
 +
                threadinfo['timeout'] = -1
 +
            elif threadinfo.get('timeout') > 0:
 +
                # Decrement our counter by 1
 +
                threadinfo['timeout'] -= 1
 +
            elif not threadinfo['thread'].isAlive():
 +
                # died without permission
 +
                # if it was checking the file, it isn't anymore.
 +
                if threadinfo.get('checking'):
 +
                    filecheck.release()
 +
                if threadinfo.get('try') == 6:
 +
                    # Died on the sixth try? You're dead.
 +
                    deadfiles.append(file)
 +
                    print '%s died 6 times, added to dead list' % fil
 +
                    sys.stdout.flush()
 +
                    sys.stderr.flush()
 +
    del threads[file]
 +
                else:
 +
                    del threadinfo['thread']
 +
                    threadinfo['timeout'] = 10
 +
            # dealing with files that disappear
 +
            if file not in files:
 +
                print 'Torrent file disappeared, killing %s' % file
 +
                stdout.flush()
 +
                if threadinfo.get('timeout', -1) == -1:
 +
                    threadinfo['kill'].set()
 +
                    threadinfo['thread'].join()
 +
                # if this thread was filechecking, open it up
 +
                if threadinfo.get('checking'):
 +
                    filecheck.release()
 +
                del threads[file]
 +
        for file in deadfiles:
 +
            # if the file dissapears, remove it from our dead list
 +
            if file not in files:
 +
                deadfiles.remove(file)
 +
        sleep(1)
 +
 
 +
def display_thread(displaykiller):
 +
    interval = 1.0
 +
    global status
 +
    while True:
 +
        # display file info
 +
        if displaykiller.isSet():
 +
            break
 +
        totalup = 0
 +
        totaldown = 0
 +
        totaluptotal = 0.0
 +
        totaldowntotal = 0.0
 +
        tdis = threads.items()
 +
        tdis.sort()
 +
        for file, threadinfo in tdis:
 +
            uprate = threadinfo.get('uprate', 0)
 +
            downrate = threadinfo.get('downrate', 0)
 +
            uptxt = fmtsize(uprate, padded = 0)
 +
            downtxt = fmtsize(downrate, padded = 0)
 +
            uptotal = threadinfo.get('uptotal', 0.0)
 +
            downtotal = threadinfo.get('downtotal', 0.0)
 +
            uptotaltxt = fmtsize(uptotal, baseunit = 2, padded = 0)
 +
            downtotaltxt = fmtsize(downtotal, baseunit = 2, padded = 0)
 +
            filename = threadinfo.get('savefile', file)
 +
            if threadinfo.get('timeout', 0) > 0:
 +
                trys = threadinfo.get('try', 1)
 +
                timeout = threadinfo.get('timeout')
 +
                print '%s: try %d died, retry in %d' % (basename(filename), trys, timeout)
 +
            else:
 +
                status = threadinfo.get('status','')
 +
                print '%s: Spd: %s/s:%s/s Tot: %s:%s [%s]' % (basename(filename), uptxt, downtxt, uptotaltxt, downtotaltxt, status)
 +
if status == 'complete' and isfile(filename):
 +
sendMail(filename,status)
 +
cleanTorrent(filename)
 +
            totalup += uprate
 +
            totaldown += downrate
 +
            totaluptotal += uptotal
 +
            totaldowntotal += downtotal
 +
        # display totals line
 +
        totaluptxt = fmtsize(totalup, padded = 0)
 +
        totaldowntxt = fmtsize(totaldown, padded = 0)
 +
        totaluptotaltxt = fmtsize(totaluptotal, baseunit = 2, padded = 0)
 +
        totaldowntotaltxt = fmtsize(totaldowntotal, baseunit = 2, padded = 0)
 +
        print 'All: Spd: %s/s:%s/s Tot: %s:%s' % (totaluptxt, totaldowntxt, totaluptotaltxt, totaldowntotaltxt)
 +
        print
 +
        sys.stdout.flush()
 +
        sys.stderr.flush()
 +
sleep(interval)
 +
 
 +
class StatusUpdater:
 +
    def __init__(self, file, params, name):
 +
        self.file = file
 +
        self.params = params
 +
        self.name = name
 +
        self.myinfo = threads[name]
 +
        self.done = 0
 +
        self.checking = 0
 +
        self.activity = 'starting'
 +
        self.display()
 +
        self.myinfo['errors'] = []
 +
 
 +
    def download(self):
 +
        download(self.params + ['--responsefile', self.file], self.choose, self.display, self.finished, self.err, self.myinfo['kill'], 80)
 +
        print 'Torrent %s stopped' % self.file
 +
        sys.stdout.flush()
 +
 
 +
    def finished(self):
 +
        self.done = 1
 +
        self.myinfo['done'] = 1
 +
        self.activity = 'complete'
 +
        self.display({'fractionDone' : 1, 'downRate' : 0})
 +
 
 +
    def err(self, msg):
 +
        self.myinfo['errors'].append(msg)
 +
        self.display()
 +
 
 +
    def failed(self):
 +
        self.activity = 'failed'
 +
        self.display()
 +
 
 +
    def choose(self, default, size, saveas, dir):
 +
        self.myinfo['downfile'] = default
 +
        self.myinfo['filesize'] = fmtsize(size)
 +
        if saveas == '':
 +
            saveas = default
 +
        # it asks me where I want to save it before checking the file..
 +
        if exists(self.file[:-len(ext)]) and getsize(self.file[:-len(ext)]) > 0:
 +
            # file will get checked
 +
            while not filecheck.acquire(0) and not self.myinfo['kill'].isSet():
 +
                self.myinfo['status'] = 'disk wait'
 +
                sleep(0.1)
 +
            if not self.myinfo['kill'].isSet():
 +
                self.checking = 1
 +
                self.myinfo['checking'] = 1
 +
        self.myinfo['savefile'] = self.file[:-len(ext)]
 +
        return self.file[:-len(ext)]
 +
 
 +
    def display(self, dict = {}):
 +
        fractionDone = dict.get('fractionDone')
 +
        timeEst = dict.get('timeEst')
 +
        activity = dict.get('activity')
 +
        if activity is not None and not self.done:
 +
            if activity == 'checking existing file':
 +
                self.activity = 'disk check'
 +
            elif activity == 'connecting to peers':
 +
                self.activity = 'connecting'
 +
            else:
 +
                self.activity = activity
 +
        elif timeEst is not None:
 +
            self.activity = fmttime(timeEst)
 +
        if fractionDone is not None:
 +
            self.myinfo['status'] = '%s %.0f%%' % (self.activity, fractionDone * 100)
 +
        else:
 +
            self.myinfo['status'] = self.activity
 +
        if self.activity != 'checking existing file' and self.checking:
 +
            # we finished checking our files.
 +
            filecheck.release()
 +
            self.checking = 0
 +
            self.myinfo['checking'] = 0
 +
        if 'upRate' in dict:
 +
            self.myinfo['uprate'] = dict['upRate']
 +
        if 'downRate' in dict:
 +
            self.myinfo['downrate'] = dict['downRate']
 +
        if 'upTotal' in dict:
 +
            self.myinfo['uptotal'] = dict['upTotal']
 +
        if 'downTotal' in dict:
 +
            self.myinfo['downtotal'] = dict['downTotal']
 +
 
 +
if __name__ == '__main__':
 +
    if len(argv) < 2:
 +
        print """Usage: btlaunchmany.py <directory> <global options>
 +
  <directory> - directory to look for .torrent files (non-recursive)
 +
  <global options> - options to be applied to all torrents (see btdownloadheadless.py)
 +
"""
 +
        exit(-1)
 +
    try:
 +
        displaykiller = Event()
 +
        displaythread = Thread(target = display_thread, name = 'display', args = [displaykiller])
 +
        displaythread.start()
 +
        dropdir_mainloop(argv[1], argv[2:])
 +
    except KeyboardInterrupt:
 +
        print '^C caught! Killing torrents..'
 +
        for file, threadinfo in threads.items():
 +
            status = 'Killing torrent %s' % file
 +
            threadinfo['kill'].set()
 +
            threadinfo['thread'].join()
 +
            del threads[file]
 +
        displaykiller.set()
 +
        displaythread.join()
 +
cleanup()
 +
    except:
 +
        traceback.print_exc()
 +
</pre>
 +
 
 +
This is called by /etc/init.d/bittorrent which looks like
 +
<pre>
 +
#!/bin/bash
 +
 
 +
PATH=/sbin:/bin:/usr/sbin:/usr/bin
 +
DAEMON=/usr/bin/bttrack
 +
LAUNCH=/usr/bin/btlaunchmany
 +
MAKEMETA=/usr/bin/btmakemetafile
 +
DFILE=connected.txt
 +
LOG=${your_log}
 +
PORT=${your_port}
 +
NAME="bttrack"
 +
DESC="bittorrent tracker"
 +
FILESDIR=/home/bittorrent/active
 +
TORRENTSDIR=/var/www/torrents
 +
SERVER=http://${your_sever}:${your_port}
 +
 
 +
OPTIONS="--dfile ./$DFILE --port $PORT"
 +
test -f $DAEMON || exit 0
 +
cd $FILESDIR
 +
set -e
 +
 
 +
case "$1" in
 +
  make)
 +
  echo "Making torrents: "
 +
  for file in $FILESDIR/*
 +
  do
 +
    if [[ `basename $file` = "." ]]; then
 +
        continue;
 +
    fi
 +
    if [[ `basename $file` = "$DFILE" ]]; then
 +
        continue;
 +
    fi
 +
    echo $file
 +
    $MAKEMETA $file $SERVER/announce
 +
    cp $FILESDIR/*.torrent $TORRENTSDIR
 +
  done
 +
  echo "."
 +
  ;;
 +
start)
 +
  echo -n "Starting $DESC: $NAME"
 +
  start-stop-daemon --oknodo -S -b -x $DAEMON -- $OPTIONS
 +
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
 +
  echo "."
 +
  ;;
 +
  stop)
 +
  echo -n "Stopping $DESC: $NAME"
 +
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
 +
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
 +
  echo "."
 +
  ;;
 +
  restart|force-reload)
 +
  echo "Restarting $DESC: $NAME"
 +
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
 +
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
 +
  start-stop-daemon --oknodo -S -b -x  $DAEMON -- $OPTIONS
 +
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
 +
  echo "."
 +
  ;;
 +
  *)
 +
  echo "Usage: $0 {start|stop|restart|force-reload|make}" >&2
 +
  exit 1
 +
  ;;
 +
esac
 +
 
 +
exit 0
 +
</pre>
 +
 
 +
The log file gets rotated by cron.hourly. So in /etc/init.d/logrotate.d, create logrotate which rotates the files every hour as long as the log reached 1M:
 +
<pre>
 +
{$path_to}/log.txt {
 +
        missingok
 +
        rotate 3
 +
        size 1M
 +
create 775 bittorrent bittorrent
 +
        prerotate
 +
                /etc/init.d/bittorrent stop
 +
endscript
 +
        postrotate
 +
                /etc/init.d/bittorrent start
 +
        endscript
 +
nocompress
 +
notifempty
 +
}
 +
</pre>
 +
 
 +
Finally, tell cron to activate your logrotate script by placing within /etc/cron.hourly/logrotate:
 +
<pre>
 +
#!/bin/sh
 +
 
 +
test -x /usr/sbin/logrotate || exit 0
 +
/usr/sbin/logrotate /etc/logrotate.conf
 +
</pre>
 +
 
 +
Hope this helps.
 +
-Yuriy
 +
----
 +
Part of [[[[Category:BitTorrent]]]]
 +
 
 +
----
 +
Last edit: Fri, 17 Mar 2006 19:24:26 -0800
 +
(%C0%F1%ED%E7%E9%E0%D6%E3%E6%F4%DF%F8%FC)
 +
Revisions: 20
 +
 
 +
[[Category:PhpWiki]]

Latest revision as of 06:58, 7 August 2012

By using the btlaunchmany.py executable you can do this:

I created a torrent directory which only contains two other directories: active and standby I then download all .torrent file into the standby directory and when I want to start fetching the file I simply move the file over to the active directory.

I start the BitTorrent executable as a background task using this script:

#!/bin/sh
cd
nohup btlaunchmany.py torrent/active/ > torrent.log &
tail -f torrent.log

It will start the BitTorrent and scans the active directory for all *.torrent references and will then download the files in the same directory. Fortunately the btlaunchmany.py is scanning the active directory frequently, so by simply moving a *.torrent file into that directory it will be recognized and a new download thread will be created.

To avoid this process exiting when you log out, you can either

  • use nohup

nohup btlaunchmany.py torrent/active/ > torrent.log &

  • double background it
    (./btlaunchmany.py torrent/active/ > torrent.log 2>&1 &) &

This will display a message something like this:

    [1] + Done                 ( ./btlaunchmany.py torrent/active/ > torrent.l

That's OK. If you do a 'ps -x' you will see your btlauncher running in the background. No nohup required. The '2>&1' redirects stderr to stdout which is redirected to torrent.log. This makes it totally silent. Then it's put into the background with '&' and it's backgrounded twice with another '&'. What this does is totally disconnect the process from your terminal. It may look weird, but this is the UNIX idiom that says "not only do I want to run this process asynchronously, I also want the parent process to be the INIT process instead of my terminal". Now, if you actually want to kill the process you can send it a HUP signal 'kill -hup {PID}'.

  • use the screen utility

screen btlaunchmany.py torrent/active/ > torrent.log &

you may exit your terminal session, and when you relogin you can type

screen -r

to reconnect to it.

RH9 users can run this script by may not pick up new torrents as described above.


I use this one as /etc/init.d/bittorrent on debian:

#! /bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/bin/bttrack
LAUNCH=/usr/bin/btlaunchmany
MAKEMETA=/usr/bin/btmakemetafile
DFILE=connected.txt
PORT=6969
NAME="bttrack"
DESC="bittorrent tracker"
FILESDIR=/var/torrentfiles
TORRENTSDIR=/var/www/torrents
SERVER=http://www.peix.org:6969

OPTIONS="--dfile ./$DFILE --port $PORT"
test -f $DAEMON || exit 0
cd $FILESDIR
set -e

case "$1" in
  make)
  echo "Making torrents: "
  for file in $FILESDIR/*
  do
    if [[ `basename $file` = "." ]]; then
        continue;
    fi
    if [[ `basename $file` = "$DFILE" ]]; then
        continue;
    fi
    echo $file
    $MAKEMETA $file $SERVER/announce
    cp $FILESDIR/*.torrent $TORRENTSDIR
  done
  echo "."
  ;;
 start)
  echo -n "Starting $DESC: $NAME"
  start-stop-daemon --oknodo -S -b -x $DAEMON -- $OPTIONS
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
  echo "."
  ;;
  stop)
  echo -n "Stopping $DESC: $NAME"
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
  echo "."
  ;;
  restart|force-reload)
  echo "Restarting $DESC: $NAME"
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
  start-stop-daemon --oknodo -S -b -x  $DAEMON -- $OPTIONS
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
  echo "."
  ;;
  *)
  echo "Usage: $0 {start|stop|restart|force-reload|make}" >&2
  exit 1
  ;;
esac

exit 0

hope it helps


I, mailto:claw+torrent@kanga.nu, made some slight modifications to the above so as to support multiple source directories for the files that will be torrented:

#! /bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/bin/bttrack
LAUNCH=/usr/bin/btlaunchmany
MAKEMETA=/usr/bin/btmakemetafile
DFILE=connected.txt
PORT=6969
NAME="bttrack"
DESC="bittorrent tracker"
TORRENTSDIR=/var/www/downloads/torrents
SERVER=http://research.warnes.net:6969
DEFAULTS_FILE=/etc/default/bittorrent
OPTIONS="--dfile ./$DFILE --port $PORT"

if [[ -s $DEFAULTS_FILE ]]; then
    . $DEFAULTS_FILE
fi
test -f $DAEMON || exit 0
cd $FILESDIR
set -e

case "$1" in
  make)
  echo "Making torrents: "
  for dir in ${TORRENT_DIRS}
  do
    #rm -f ${dir}/*.torrent
    for file in ${dir}/*
    do
      base=`basename $file`
      if [[ "$base" = "." ]]; then
        continue;
      fi
      if [[ "$base" = "$DFILE" ]]; then
        continue;
      fi
      echo $file
      $MAKEMETA $file $SERVER/announce
    done
    mv ${dir}/*.torrent $TORRENTSDIR
  done
  chown www-data.www-data ${TORRENTSDIR}/*
  echo "."
  ;;
 start)
  echo -n "Starting $DESC: $NAME"
  start-stop-daemon --oknodo -S -b -x $DAEMON -- $OPTIONS
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
  echo "."
  ;;
  stop)
  echo -n "Stopping $DESC: $NAME"
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
  echo "."
  ;;
  restart|force-reload)
  echo "Restarting $DESC: $NAME"
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
  start-stop-daemon --oknodo -S -b -x  $DAEMON -- $OPTIONS
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
  echo "."
  ;;
  *)
  echo "Usage: $0 {start|stop|restart|force-reload|make}" >&2
  exit 1
  ;;
esac

exit 0

The main added feature is an /etc/default/bittorrent file which should read something like:

#
# List of directories which can contain torrent files to be served
# by the local system.
#
TORRENT_DIRS="/dir1/dir2/dir3 /dir4/dir5/dir6 /dir7/dir8/dir9"

From: Yuriy Krylov (ykrylov -AT - gmail -DOT- com)

This is a debian-style btlaunchmany.bittorrent which pics off "active" torrents from a specified directory moves the file and torrent to "completed" folder when done and emails the owner a notification of completion.

#!/usr/bin/python

# Written by Michael Janssen (jamuraa at base0 dot net)
# originally heavily borrowed code from btlaunchmany.py by Bram Cohen
# and btdownloadcurses.py written by Henry 'Pi' James
# now not so much.
# fmttime and fmtsize stolen from btdownloadcurses.
# see LICENSE.txt for license information

from BitTorrent.download import download
from threading import Thread, Event, Lock
from os import listdir, rename
from os.path import abspath, join, exists, getsize, basename, isfile
from sys import argv, stdout, exit
from time import sleep
import traceback
import sys
import smtplib

print "btlaunchmany.bittorrent is RUNNING"

LOG = "/home/bittorrent/public_html/log.txt"
FILE = open(LOG,"w+")
old_out = sys.stdout
old_err = sys.stderr
sys.stdout = sys.stderr = FILE

COMPLETED = "/home/bittorrent/completed/"

def cleanup():
        FILE.close()
        sys.stdout = old_out
	sys.stderr = old_err


def sendMail(filename,status):
	short_filename = basename(filename)
	print "SENDING NOTIFICATION ABOUT %s %s" % (status, short_filename)
	sys.stdout.flush()
	sys.stderr.flus()
	server = smtplib.SMTP("localhost")
	server.sendmail("bittorrent@yuriy.org","yuriy@localhost", "Subject: bittorent:%s %s has %s download!" % (short_filename,short_filename,status))
	server.quit()

def cleanTorrent(filename):
	short_filename = basename(filename)
	print "REMOVING TORRENT %s" % short_filename
	sys.stdout.flush()
	sys.stderr.flush()
	rename(filename,COMPLETED + short_filename)
	rename(filename+".torrent",COMPLETED + short_filename + ".torrent")

def fmttime(n):
    if n == -1:
        return '(no seeds?)'
    if n == 0:
        return 'complete'
    n = int(n)
    m, s = divmod(n, 60)
    h, m = divmod(m, 60)
    if h > 1000000:
        return 'n/a'
    return '%d:%02d:%02d' % (h, m, s)

def fmtsize(n, baseunit = 0, padded = 1):
    unit = [' B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
    i = baseunit
    while i + 1 < len(unit) and n >= 999:
        i += 1
        n = float(n) / (1 << 10)
    size = ''
    if padded:
        if n < 10:
            size = '  '
        elif n < 100:
            size = ' '
    if i != 0:
        size += '%.1f %s' % (n, unit[i])
    elif padded:
        size += '%.0f   %s' % (n, unit[i])
    else:
        size += '%.0f %s' % (n, unit[i])
    return size


def dummy(*args, **kwargs):
    pass

threads = {}
ext = '.torrent'
print 'btlaunchmany starting..'
print '...logging to ' + LOG
filecheck = Lock()

def dropdir_mainloop(d, params):
    deadfiles = []
    while True:
        files = listdir(d)
        # new files
        for file in files:
            if file.endswith(ext) and \:
               file not in threads.keys() + deadfiles:
                threads[file] = {'kill': Event(), 'try': 1}
                print 'New torrent: %s' % file
                sys.stdout.flush()
		sys.stderr.flush()
                threads[file]['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file)
                threads[file]['thread'].start()
        # files with multiple tries
        for file, threadinfo in threads.items():
            if threadinfo.get('timeout') == 0:
                # Zero seconds left, try and start the thing again.
                threadinfo['try'] += 1
                threadinfo['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file)
                threadinfo['thread'].start()
                threadinfo['timeout'] = -1
            elif threadinfo.get('timeout') > 0:
                # Decrement our counter by 1
                threadinfo['timeout'] -= 1
            elif not threadinfo['thread'].isAlive():
                # died without permission
                # if it was checking the file, it isn't anymore.
                if threadinfo.get('checking'):
                    filecheck.release()
                if threadinfo.get('try') == 6:
                    # Died on the sixth try? You're dead.
                    deadfiles.append(file)
                    print '%s died 6 times, added to dead list' % fil
                    sys.stdout.flush()
                    sys.stderr.flush()
		    del threads[file]
                else:
                    del threadinfo['thread']
                    threadinfo['timeout'] = 10
            # dealing with files that disappear
            if file not in files:
                print 'Torrent file disappeared, killing %s' % file
                stdout.flush()
                if threadinfo.get('timeout', -1) == -1:
                    threadinfo['kill'].set()
                    threadinfo['thread'].join()
                # if this thread was filechecking, open it up
                if threadinfo.get('checking'):
                    filecheck.release()
                del threads[file]
        for file in deadfiles:
            # if the file dissapears, remove it from our dead list
            if file not in files:
                deadfiles.remove(file)
        sleep(1)

def display_thread(displaykiller):
    interval = 1.0
    global status
    while True:
        # display file info
        if displaykiller.isSet():
            break
        totalup = 0
        totaldown = 0
        totaluptotal = 0.0
        totaldowntotal = 0.0
        tdis = threads.items()
        tdis.sort()
        for file, threadinfo in tdis:
            uprate = threadinfo.get('uprate', 0)
            downrate = threadinfo.get('downrate', 0)
            uptxt = fmtsize(uprate, padded = 0)
            downtxt = fmtsize(downrate, padded = 0)
            uptotal = threadinfo.get('uptotal', 0.0)
            downtotal = threadinfo.get('downtotal', 0.0)
            uptotaltxt = fmtsize(uptotal, baseunit = 2, padded = 0)
            downtotaltxt = fmtsize(downtotal, baseunit = 2, padded = 0)
            filename = threadinfo.get('savefile', file)
            if threadinfo.get('timeout', 0) > 0:
                trys = threadinfo.get('try', 1)
                timeout = threadinfo.get('timeout')
                print '%s: try %d died, retry in %d' % (basename(filename), trys, timeout)
            else:
                status = threadinfo.get('status','')
                print '%s: Spd: %s/s:%s/s Tot: %s:%s [%s]' % (basename(filename), uptxt, downtxt, uptotaltxt, downtotaltxt, status)
		if status == 'complete' and isfile(filename):
			sendMail(filename,status)
			cleanTorrent(filename)
            totalup += uprate
            totaldown += downrate
            totaluptotal += uptotal
            totaldowntotal += downtotal
        # display totals line
        totaluptxt = fmtsize(totalup, padded = 0)
        totaldowntxt = fmtsize(totaldown, padded = 0)
        totaluptotaltxt = fmtsize(totaluptotal, baseunit = 2, padded = 0)
        totaldowntotaltxt = fmtsize(totaldowntotal, baseunit = 2, padded = 0)
        print 'All: Spd: %s/s:%s/s Tot: %s:%s' % (totaluptxt, totaldowntxt, totaluptotaltxt, totaldowntotaltxt)
        print
        sys.stdout.flush()
        sys.stderr.flush()
	sleep(interval)

class StatusUpdater:
    def __init__(self, file, params, name):
        self.file = file
        self.params = params
        self.name = name
        self.myinfo = threads[name]
        self.done = 0
        self.checking = 0
        self.activity = 'starting'
        self.display()
        self.myinfo['errors'] = []

    def download(self):
        download(self.params + ['--responsefile', self.file], self.choose, self.display, self.finished, self.err, self.myinfo['kill'], 80)
        print 'Torrent %s stopped' % self.file
        sys.stdout.flush()

    def finished(self):
        self.done = 1
        self.myinfo['done'] = 1
        self.activity = 'complete'
        self.display({'fractionDone' : 1, 'downRate' : 0})

    def err(self, msg):
        self.myinfo['errors'].append(msg)
        self.display()

    def failed(self):
        self.activity = 'failed'
        self.display()

    def choose(self, default, size, saveas, dir):
        self.myinfo['downfile'] = default
        self.myinfo['filesize'] = fmtsize(size)
        if saveas == '':
            saveas = default
        # it asks me where I want to save it before checking the file..
        if exists(self.file[:-len(ext)]) and getsize(self.file[:-len(ext)]) > 0:
            # file will get checked
            while not filecheck.acquire(0) and not self.myinfo['kill'].isSet():
                self.myinfo['status'] = 'disk wait'
                sleep(0.1)
            if not self.myinfo['kill'].isSet():
                self.checking = 1
                self.myinfo['checking'] = 1
        self.myinfo['savefile'] = self.file[:-len(ext)]
        return self.file[:-len(ext)]

    def display(self, dict = {}):
        fractionDone = dict.get('fractionDone')
        timeEst = dict.get('timeEst')
        activity = dict.get('activity')
        if activity is not None and not self.done:
            if activity == 'checking existing file':
                self.activity = 'disk check'
            elif activity == 'connecting to peers':
                self.activity = 'connecting'
            else:
                self.activity = activity
        elif timeEst is not None:
            self.activity = fmttime(timeEst)
        if fractionDone is not None:
            self.myinfo['status'] = '%s %.0f%%' % (self.activity, fractionDone * 100)
        else:
            self.myinfo['status'] = self.activity
        if self.activity != 'checking existing file' and self.checking:
            # we finished checking our files.
            filecheck.release()
            self.checking = 0
            self.myinfo['checking'] = 0
        if 'upRate' in dict:
            self.myinfo['uprate'] = dict['upRate']
        if 'downRate' in dict:
            self.myinfo['downrate'] = dict['downRate']
        if 'upTotal' in dict:
            self.myinfo['uptotal'] = dict['upTotal']
        if 'downTotal' in dict:
            self.myinfo['downtotal'] = dict['downTotal']

if __name__ == '__main__':
    if len(argv) < 2:
        print """Usage: btlaunchmany.py <directory> <global options>
  <directory> - directory to look for .torrent files (non-recursive)
  <global options> - options to be applied to all torrents (see btdownloadheadless.py)
"""
        exit(-1)
    try:
        displaykiller = Event()
        displaythread = Thread(target = display_thread, name = 'display', args = [displaykiller])
        displaythread.start()
        dropdir_mainloop(argv[1], argv[2:])
    except KeyboardInterrupt:
        print '^C caught! Killing torrents..'
        for file, threadinfo in threads.items():
            status = 'Killing torrent %s' % file
            threadinfo['kill'].set()
            threadinfo['thread'].join()
            del threads[file]
        displaykiller.set()
        displaythread.join()
	cleanup()
    except:
        traceback.print_exc()

This is called by /etc/init.d/bittorrent which looks like

#!/bin/bash

PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/bin/bttrack
LAUNCH=/usr/bin/btlaunchmany
MAKEMETA=/usr/bin/btmakemetafile
DFILE=connected.txt
LOG=${your_log}
PORT=${your_port}
NAME="bttrack"
DESC="bittorrent tracker"
FILESDIR=/home/bittorrent/active
TORRENTSDIR=/var/www/torrents
SERVER=http://${your_sever}:${your_port}

OPTIONS="--dfile ./$DFILE --port $PORT"
test -f $DAEMON || exit 0
cd $FILESDIR
set -e

case "$1" in
  make)
  echo "Making torrents: "
  for file in $FILESDIR/*
  do
    if [[ `basename $file` = "." ]]; then
        continue;
    fi
    if [[ `basename $file` = "$DFILE" ]]; then
        continue;
    fi
    echo $file
    $MAKEMETA $file $SERVER/announce
    cp $FILESDIR/*.torrent $TORRENTSDIR
  done
  echo "."
  ;;
 start)
  echo -n "Starting $DESC: $NAME"
  start-stop-daemon --oknodo -S -b -x $DAEMON -- $OPTIONS
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
  echo "."
  ;;
  stop)
  echo -n "Stopping $DESC: $NAME"
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
  echo "."
  ;;
  restart|force-reload)
  echo "Restarting $DESC: $NAME"
  start-stop-daemon --oknodo -K -q -R 30 -n $NAME
  start-stop-daemon --oknodo -K -q -R 30 -n `basename $LAUNCH`
  start-stop-daemon --oknodo -S -b -x  $DAEMON -- $OPTIONS
  start-stop-daemon --oknodo -S -b -x $LAUNCH -- $FILESDIR
  echo "."
  ;;
  *)
  echo "Usage: $0 {start|stop|restart|force-reload|make}" >&2
  exit 1
  ;;
esac

exit 0

The log file gets rotated by cron.hourly. So in /etc/init.d/logrotate.d, create logrotate which rotates the files every hour as long as the log reached 1M:

{$path_to}/log.txt {
        missingok
        rotate 3
        size 1M
	create 775 bittorrent bittorrent
        prerotate
                /etc/init.d/bittorrent stop
	endscript
        postrotate
                /etc/init.d/bittorrent start
        endscript
	nocompress
	notifempty
}

Finally, tell cron to activate your logrotate script by placing within /etc/cron.hourly/logrotate:

#!/bin/sh

test -x /usr/sbin/logrotate || exit 0
/usr/sbin/logrotate /etc/logrotate.conf

Hope this helps. -Yuriy


Part of [[]]


Last edit: Fri, 17 Mar 2006 19:24:26 -0800
(%C0%F1%ED%E7%E9%E0%D6%E3%E6%F4%DF%F8%FC)
Revisions: 20