[Debtorrent-commits] r57 - in /debtorrent/trunk: ./ DebTorrent/ DebTorrent/BT1/
camrdale-guest at users.alioth.debian.org
camrdale-guest at users.alioth.debian.org
Mon May 21 03:38:40 UTC 2007
Author: camrdale-guest
Date: Mon May 21 03:38:40 2007
New Revision: 57
URL: http://svn.debian.org/wsvn/debtorrent/?sc=1&rev=57
Log:
Add some initial documentation (docstrings)
Modified:
debtorrent/trunk/DebTorrent/BT1/Choker.py
debtorrent/trunk/DebTorrent/BT1/Connecter.py
debtorrent/trunk/DebTorrent/BT1/Storage.py
debtorrent/trunk/DebTorrent/BT1/__init__.py
debtorrent/trunk/DebTorrent/BT1/btformats.py
debtorrent/trunk/DebTorrent/__init__.py
debtorrent/trunk/DebTorrent/bencode.py
debtorrent/trunk/DebTorrent/inifile.py
debtorrent/trunk/btcompletedir.py
debtorrent/trunk/btcopyannounce.py
debtorrent/trunk/btdownloadheadless.py
debtorrent/trunk/btlaunchmany.py
debtorrent/trunk/btmakemetafile.py
debtorrent/trunk/btreannounce.py
debtorrent/trunk/btrename.py
debtorrent/trunk/btsethttpseeds.py
debtorrent/trunk/btshowmetainfo.py
debtorrent/trunk/bttrack.py
debtorrent/trunk/setup.py
Modified: debtorrent/trunk/DebTorrent/BT1/Choker.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/BT1/Choker.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/BT1/Choker.py (original)
+++ debtorrent/trunk/DebTorrent/BT1/Choker.py Mon May 21 03:38:40 2007
@@ -1,8 +1,10 @@
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
+
+"""Contains the Choker class."""
from random import randrange, shuffle
from DebTorrent.clock import clock
@@ -13,7 +15,47 @@
False = 0
class Choker:
+ """Manages the choking and unchoking of other downloaders.
+
+ @type config: C{dictonary}
+ @ivar config: the configuration variables
+ @type round_robin_period: C{int}
+ @ivar round_robin_period: the number of seconds between the client's
+ switching of upload targets
+ @type schedule: C{method}
+ @ivar schedule: the method to call to schedule future events
+ @type picker: L{PiecePicker}
+ @ivar picker: the PiecePicker to get connection information from
+ @type connections: C{list} of L{Connecter.Connection}
+ @ivar connections: the connections from peers to the client
+ @type last_preferred: C{int}
+ @ivar last_preferred: the number of preferred connections found in the
+ last examination
+ @type last_round_robin: C{long}
+ @ivar last_round_robin: the last time the connections were examined
+ @type done: C{Event}
+ @ivar done: flag to indicate when the download is complete
+ @type super_seed: C{boolean}
+ @ivar super_seed: whether super-seeding is enabled
+ @type paused: C{Event}
+ @ivar paused: flag to indicate when the download is paused
+
+ """
+
def __init__(self, config, schedule, picker, done = lambda: False):
+ """Initialize the Choker instance.
+
+ @type config: C{dictonary}
+ @param config: the configuration variables
+ @type schedule: C{method}
+ @param schedule: the method to call to schedule future events
+ @type picker: L{PiecePicker}
+ @param picker: the piece picker to use to
+ @type done: C{Event}
+ @param done: flag to indicate when the download is complete
+
+ """
+
self.config = config
self.round_robin_period = config['round_robin_period']
self.schedule = schedule
@@ -27,9 +69,18 @@
schedule(self._round_robin, 5)
def set_round_robin_period(self, x):
+ """Set a new round-robin period.
+
+ @type x: C{int}
+ @param x: the new round-robin period
+ @see: L{Choker.round_robin_period}
+
+ """
+
self.round_robin_period = x
def _round_robin(self):
+ """Periodically determine the ordering for connections and call the choker."""
self.schedule(self._round_robin, 5)
if self.super_seed:
cons = range(len(self.connections))
@@ -59,6 +110,13 @@
self._rechoke()
def _rechoke(self):
+ """Unchoke some connections.
+
+ Reads the current upload and download rates from the connections,
+ as well as the connection state, and unchokes the most preferable
+ ones.
+
+ """
preferred = []
maxuploads = self.config['max_uploads']
if self.paused:
@@ -101,31 +159,70 @@
u.unchoke()
def connection_made(self, connection, p = None):
+ """Adds a new connection to the list.
+
+ @type connection: L{Connecter.Connection}
+ @param connection: the connection to the client from the peer
+ @type p: C{int}
+ @param p: the location to insert the new connection into the list
+ (optional, default is to choose a random location)
+
+ """
+
if p is None:
p = randrange(-2, len(self.connections) + 1)
self.connections.insert(max(p, 0), connection)
self._rechoke()
def connection_lost(self, connection):
+ """Removes a lost connection from the list.
+
+ @type connection: L{Connecter.Connection}
+ @param connection: the connection to the client from the peer
+
+ """
+
self.connections.remove(connection)
self.picker.lost_peer(connection)
if connection.get_upload().is_interested() and not connection.get_upload().is_choked():
self._rechoke()
def interested(self, connection):
+ """Indicate the connection is now interesting.
+
+ @type connection: L{Connecter.Connection}
+ @param connection: the connection to the client from the peer
+
+ """
+
if not connection.get_upload().is_choked():
self._rechoke()
def not_interested(self, connection):
+ """Indicate the connection is no longer interesting.
+
+ @type connection: L{Connecter.Connection}
+ @param connection: the connection to the client from the peer
+
+ """
+
if not connection.get_upload().is_choked():
self._rechoke()
def set_super_seed(self):
+ """Change to super seed state."""
while self.connections: # close all connections
self.connections[0].close()
self.picker.set_superseed()
self.super_seed = True
def pause(self, flag):
+ """Pause the choker.
+
+ @type flag: C{Event}
+ @param flag: flag to indicate when pausing is finished
+
+ """
+
self.paused = flag
self._rechoke()
Modified: debtorrent/trunk/DebTorrent/BT1/Connecter.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/BT1/Connecter.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/BT1/Connecter.py (original)
+++ debtorrent/trunk/DebTorrent/BT1/Connecter.py Mon May 21 03:38:40 2007
@@ -1,8 +1,35 @@
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
+
+"""For maintaining connections to peers.
+
+ at type DEBUG1: C{boolean}
+ at var DEBUG1: whether to print debugging information for the L{Connection}
+ at type DEBUG2: C{boolean}
+ at var DEBUG2: whether to print debugging information for the L{Connecter}
+ at type CHOKE: C{char}
+ at var CHOKE: the code for choke messages
+ at type UNCHOKE: C{char}
+ at var UNCHOKE: the code for unchoke messages
+ at type INTERESTED: C{char}
+ at var INTERESTED: the code for interested messages
+ at type NOT_INTERESTED: C{char}
+ at var NOT_INTERESTED: the code for not interested messages
+ at type HAVE: C{char}
+ at var HAVE: the code for have messages
+ at type BITFIELD: C{char}
+ at var BITFIELD: the code for bitfield messages
+ at type REQUEST: C{char}
+ at var REQUEST: the code for request messages
+ at type PIECE: C{char}
+ at var PIECE: the code for piece messages
+ at type CANCEL: C{char}
+ at var CANCEL: the code for cancel messages
+
+"""
from DebTorrent.bitfield import Bitfield
from DebTorrent.clock import clock
@@ -18,9 +45,23 @@
DEBUG2 = True
def toint(s):
+ """Convert four-byte big endian representation to a long.
+
+ @type s: C{string}
+ @param s: the string to convert
+
+ """
+
return long(b2a_hex(s), 16)
def tobinary(i):
+ """Convert an integer to a four-byte big endian representation.
+
+ @type i: C{int}
+ @param i: the integer to convert
+
+ """
+
return (chr(i >> 24) + chr((i >> 16) & 0xFF) +
chr((i >> 8) & 0xFF) + chr(i & 0xFF))
@@ -40,7 +81,46 @@
CANCEL = chr(8)
class Connection:
+ """A connection to an individual peer.
+
+ @type connection: unknown
+ @ivar connection: the connection
+ @type connecter: L{Connecter}
+ @ivar connecter: the collection of all connections
+ @type ccount: C{int}
+ @ivar ccount: the number of the connection
+ @type got_anything: C{boolean}
+ @ivar got_anything: whether a message has ever been received on the connection
+ @type next_upload: unknown
+ @ivar next_upload: the connection that will next be allowed to upload
+ @type outqueue: C{list}
+ @ivar outqueue: the queue of messages to send on the connection that are
+ waiting for the current piece to finish sending
+ @type partial_message: C{string}
+ @ivar partial_message: the remaining data in the current piece being sent
+ @type upload: C{Uploader.Upload}
+ @ivar upload: the Uploader instance to use for the connection
+ @type download: C{Downloader.Downloader}
+ @ivar download: the Downloader instance to use for the connection
+ @type send_choke_queued: C{boolean}
+ @ivar send_choke_queued: whether to suppress the next L{CHOKE} message
+ @type just_unchoked: C{long}
+ @ivar just_unchoked: the time of a recent L{UNCHOKE}, if it was the first
+
+ """
+
def __init__(self, connection, connecter, ccount):
+ """Initialize the class.
+
+ @type connection: unknown
+ @param connection: the connection
+ @type connecter: L{Connecter}
+ @param connecter: the collection of all connections
+ @type ccount: C{int}
+ @param ccount: the number of the connection
+
+ """
+
self.connection = connection
self.connecter = connecter
self.ccount = ccount
@@ -53,32 +133,71 @@
self.just_unchoked = None
def get_ip(self, real=False):
+ """Get the IP address of the connection.
+
+ @type real: C{boolean}
+ @param real: unknown (optional, defaults to False)
+
+ """
+
return self.connection.get_ip(real)
def get_id(self):
+ """Get the Peer ID of the connection.
+
+ @rtype: C{string}
+ @return: the ID of the connection
+
+ """
+
return self.connection.get_id()
def get_readable_id(self):
+ """Get a human readable version of the ID of the connection.
+
+ @rtype: C{string}
+ @return: the ID of the connection
+
+ """
+
return self.connection.get_readable_id()
def close(self):
+ """Close the connection."""
if DEBUG1:
print (self.ccount,'connection closed')
self.connection.close()
def is_locally_initiated(self):
+ """Check whether the connection was established by the client.
+
+ @rtype: C{boolean}
+ @return: whether the connection was established by the client
+
+ """
+
return self.connection.is_locally_initiated()
def is_encrypted(self):
+ """Check whether the connection is encrypted.
+
+ @rtype: C{boolean}
+ @return: whether the connection is encrypted
+
+ """
+
return self.connection.is_encrypted()
def send_interested(self):
+ """Send the L{INTERESTED} message."""
self._send_message(INTERESTED)
def send_not_interested(self):
+ """Send the L{NOT_INTERESTED} message."""
self._send_message(NOT_INTERESTED)
def send_choke(self):
+ """Send the L{CHOKE} message."""
if self.partial_message:
self.send_choke_queued = True
else:
@@ -87,6 +206,7 @@
self.just_unchoked = 0
def send_unchoke(self):
+ """Send the L{UNCHOKE} message."""
if self.send_choke_queued:
self.send_choke_queued = False
if DEBUG1:
@@ -100,27 +220,73 @@
self.just_unchoked = clock()
def send_request(self, index, begin, length):
+ """Send the L{REQUEST} message.
+
+ @type index: C{int}
+ @param index: the piece to request some of
+ @type begin: C{int}
+ @param begin: the starting offset within the piece
+ @type length: C{int}
+ @param length: the length of the part of the piece to get
+
+ """
+
self._send_message(REQUEST + tobinary(index) +
tobinary(begin) + tobinary(length))
if DEBUG1:
print (self.ccount,'sent request',index,begin,begin+length)
def send_cancel(self, index, begin, length):
+ """Send the L{CANCEL} message.
+
+ Cancels a previously sent L{REQUEST} message.
+
+ @type index: C{int}
+ @param index: the piece that was requested
+ @type begin: C{int}
+ @param begin: the starting offset within the piece
+ @type length: C{int}
+ @param length: the length of the part of the piece to get
+
+ """
+
self._send_message(CANCEL + tobinary(index) +
tobinary(begin) + tobinary(length))
if DEBUG1:
print (self.ccount,'sent cancel',index,begin,begin+length)
def send_bitfield(self, bitfield):
+ """Send the L{BITFIELD} message.
+
+ @type bitfield: C{string}
+ @param bitfield: the bitfield to send
+
+ """
+
self._send_message(BITFIELD + bitfield)
def send_have(self, index):
+ """Send the L{HAVE} message.
+
+ @type index: C{int}
+ @param index: the piece index to indicate having
+
+ """
+
self._send_message(HAVE + tobinary(index))
def send_keepalive(self):
+ """Send a keepalive message."""
self._send_message('')
def _send_message(self, s):
+ """Actually send the message.
+
+ @type s: C{string}
+ @param s: the message to send
+
+ """
+
if DEBUG2:
if s:
print (self.ccount,'SENDING MESSAGE',ord(s[0]),len(s))
@@ -133,6 +299,15 @@
self.connection.send_message_raw(s)
def send_partial(self, bytes):
+ """Send a L{PIECE} message containing part of a piece.
+
+ @type bytes: C{int}
+ @param bytes: the number of bytes of piece data to send
+ @rtype: C{int}
+ @return: the actual number of bytes sent
+
+ """
+
if self.connection.closed:
return 0
if self.partial_message is None:
@@ -165,18 +340,57 @@
return len(q)
def get_upload(self):
+ """Get the L{Uploader.Upload} instance for this connection.
+
+ @rtype: L{Uploader.Upload}
+ @return: the Upload instance
+
+ """
+
return self.upload
def get_download(self):
+ """Get the L{Downloader.Downloader} instance for this connection.
+
+ @rtype: L{Downloader.Downloader}
+ @return: the Downloader instance
+
+ """
+
return self.download
def set_download(self, download):
+ """Set the L{Downloader.Downloader} instance for this connection.
+
+ @type download: L{Downloader.Downloader}
+ @param download: the Downloader instance
+
+ """
+
self.download = download
def backlogged(self):
+ """Check whether the connection is ready to send.
+
+ @rtype: C{boolean}
+ @return: whether the connection is backlogged
+
+ """
+
return not self.connection.is_flushed()
def got_request(self, i, p, l):
+ """Process a request from a peer for a part of a piece.
+
+ @type i: C{int}
+ @param i: the piece index
+ @type p: C{int}
+ @param p: the position to start at
+ @type l: C{int}
+ @param l: the length to send
+
+ """
+
self.upload.got_request(i, p, l)
if self.just_unchoked:
self.connecter.ratelimiter.ping(clock() - self.just_unchoked)
@@ -186,8 +400,59 @@
class Connecter:
+ """A collection of all connections to peers.
+
+ @type downloader: L{Downloader.Downloader}
+ @ivar downloader: the Downloader instance to use
+ @type make_upload: C{method}
+ @ivar make_upload: the method to create a new L{Uploader.Upload}
+ @type choker: L{Choker.Choker}
+ @ivar choker: the Choker instance to use
+ @type numpieces: C{int}
+ @ivar numpieces: the number of pieces in the download
+ @type config: C{dictionary}
+ @ivar config: the configration information
+ @type ratelimiter: L{RateLimiter.RateLimiter}
+ @ivar ratelimiter: the RateLimiter instance to use
+ @type rate_capped: unknown
+ @ivar rate_capped: unknown
+ @type sched: unknown
+ @ivar sched: unknown
+ @type totalup: L{Debtorrent.CurrentRateMeasure.Measure}
+ @ivar totalup: the Measure instance to use
+ @type connections: C{dictionary}
+ @ivar connections: the collection of connections that are open
+ @type external_connection_made: C{int}
+ @ivar external_connection_made: greater than 0 if there have been external connections
+ @type ccount: C{int}
+ @ivar ccount: the largest connection number used
+
+ """
+
def __init__(self, make_upload, downloader, choker, numpieces,
totalup, config, ratelimiter, sched = None):
+ """
+
+ @type make_upload: C{method}
+ @param make_upload: the method to create a new L{Uploader.Upload}
+ @type downloader: L{Downloader.Downloader}
+ @param downloader: the Downloader instance to use
+ @type choker: L{Choker.Choker}
+ @param choker: the Choker instance to use
+ @type numpieces: C{int}
+ @param numpieces: the number of pieces in the download
+ @type totalup: L{Debtorrent.CurrentRateMeasure.Measure}
+ @param totalup: the Measure instance to use
+ @type config: C{dictionary}
+ @param config: the configration information
+ @type ratelimiter: L{RateLimiter.RateLimiter}
+ @param ratelimiter: the RateLimiter instance to use
+ @type sched: C{method}
+ @param sched: the method to call to schedule future actions
+ (optional, default is None)
+
+ """
+
self.downloader = downloader
self.make_upload = make_upload
self.choker = choker
@@ -203,9 +468,25 @@
self.ccount = 0
def how_many_connections(self):
+ """Get the number of currently open connections.
+
+ @rtype: C{int}
+ @return: the number of open connections
+
+ """
+
return len(self.connections)
def connection_made(self, connection):
+ """Make a new connection.
+
+ @type connection: unknown
+ @param connection: the new connection to make
+ @rtype: L{Connection}
+ @return: the new connection
+
+ """
+
self.ccount += 1
c = Connection(connection, self, self.ccount)
if DEBUG2:
@@ -217,6 +498,13 @@
return c
def connection_lost(self, connection):
+ """Process a lost connection.
+
+ @type connection: unknown
+ @param connection: the connection that was lost
+
+ """
+
c = self.connections[connection]
if DEBUG2:
print (c.ccount,'connection closed')
@@ -226,16 +514,39 @@
self.choker.connection_lost(c)
def connection_flushed(self, connection):
+ """Process a flushed connection.
+
+ @type connection: unknown
+ @param connection: the connection that was flushed
+
+ """
+
conn = self.connections[connection]
if conn.next_upload is None and (conn.partial_message is not None
or len(conn.upload.buffer) > 0):
self.ratelimiter.queue(conn)
def got_piece(self, i):
+ """Alert all the open connections that a piece was received.
+
+ @type i: C{int}
+ @param i: the piece index that was received
+
+ """
+
for co in self.connections.values():
co.send_have(i)
def got_message(self, connection, message):
+ """Process a received message on a connection.
+
+ @type connection: unknown
+ @param connection: the connection that the message was received on
+ @type message: C{string}
+ @param message: the message that was received
+
+ """
+
c = self.connections[connection]
t = message[0]
if DEBUG2:
Modified: debtorrent/trunk/DebTorrent/BT1/Storage.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/BT1/Storage.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/BT1/Storage.py (original)
+++ debtorrent/trunk/DebTorrent/BT1/Storage.py Mon May 21 03:38:40 2007
@@ -1,8 +1,27 @@
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
+
+"""Low-level writing of files.
+
+
+
+ at type DEBUG: C{boolean}
+ at var DEBUG: whether to enable printing of debug messages
+ at type MAXREADSIZE: C{long}
+ at var MAXREADSIZE: the maximum number of bytes that can be read at a time
+ at type MAXLOCKSIZE: C{long}
+ at var MAXLOCKSIZE: the maximum size to lock at a time (windows only)
+ at type MAXLOCKRANGE: C{long}
+ at var MAXLOCKRANGE: the maximum range to lock in a file (windows only)
+ at type _pool: L{DebTorrent.piecebuffer.BufferPool}
+ at var _pool: the buffer for temporary storage of pieces
+ at type PieceBuffer: C{method} L{DebTorrent.piecebuffer.BufferPool.new}
+ at var PieceBuffer: returns a new or existing L{DebTorrent.piecebuffer.SingleBuffer}
+
+"""
from DebTorrent.piecebuffer import BufferPool
from threading import Lock
@@ -33,12 +52,111 @@
PieceBuffer = _pool.new
def dummy_status(fractionDone = None, activity = None):
+ """Dummy function to print nothing."""
pass
class Storage:
+ """Low-level writing of files.
+
+ Control the low-level management of files in the download. Contains
+ functions to open and close, read and write, enable and disable,
+ flush and delete, all the files in the download. Also serves as an
+ abstraction layer, as the reading and writing is called with no
+ knowledge of file boundaries.
+
+ @type files: C{list} of C{tuple} of (C{string}, C{long})
+ @ivar files: the files list from the info of the metainfo dictionary
+ @type piece_lengths: C{list} of C{long}
+ @ivar piece_lengths: the list of piece lengths
+ @type doneflag: unknown
+ @ivar doneflag: unknown
+ @type disabled: C{list} of C{boolean}
+ @ivar disabled: list of true for the files that are disabled
+ @type file_ranges: C{list} of (C{long}, C{long}, C{long}, C{string})
+ @ivar file_ranges: for each file, the start offset within the download,
+ end offset, offset within the file, and file name
+ @type file_pieces: C{list} of (C{int}, C{int})
+ @ivar file_pieces: for each file, the starting and ending piece of the file
+ @type disabled_ranges: C{list} of C{tuple}
+ @ivar disabled_ranges: for each file, a tuple containing the working range,
+ shared pieces, and disabled range (see L{_get_disabled_ranges} for their
+ meaning)
+ @type working_ranges: C{list} of C{list} of (C{long}, C{long}, C{long}, C{string})
+ @ivar working_ranges: For each file, the list of files to be written when
+ writing to that file (may not be the actual file, i.e if it is
+ disabled). Ranges are temporarily stored here, before eventually being
+ written to self.ranges by L{_reset_ranges} to be used.
+ @type handles: C{dictionary} of {C{string}, C{file handle}}
+ @ivar handles: the file handles that are open, keys are file names and
+ values are the file handles
+ @type whandles: C{dictionary} of {C{string}, C{int}}
+ @ivar whandles: the files that are open for writing, keys are the file
+ names and values are all 1
+ @type tops: C{dictionary} of {C{string}, C{long}}
+ @ivar tops: the current length of each file (by name)
+ @type sizes: C{dictionary} of {C{string}, C{long}}
+ @ivar sizes: the desired length of each file (by name)
+ @type mtimes: C{dictionary} of {C{string}, C{long}}
+ @ivar mtimes: the last modified time of each file (by name)
+ @type lock_file: C{method}
+ @ivar lock_file: locks a file (if file locking is enabled, otherwise does
+ nothing)
+ @type lock_file: C{method}
+ @ivar lock_file: locks a file (if file locking is enabled, otherwise does
+ nothing)
+ @type unlock_file: C{method}
+ @ivar unlock_file: unlocks a file (if file locking is enabled, otherwise
+ does nothing)
+ @type lock_while_reading: C{boolean}
+ @ivar lock_while_reading: whether to lock files while reading them
+ @type lock: C{lock}
+ @ivar lock: a threading lock object for synchorizing threads (semaphore)
+ @type total_length: C{long}
+ @ivar total_length: the total length in bytes of the download
+ @type max_files_open: C{int}
+ @ivar max_files_open: the maximum number of files to have open at a time
+ (0 means no maximum)
+ @type handlebuffer: C{list}
+ @ivar handlebuffer: the list of open files, in the order of most recently
+ accessed, with the most recently accessed at the end of the list
+ (only if there is a limit on the number of open files, otherwise None)
+ @type ranges: C{list} of C{list} of (C{long}, C{long}, C{long}, C{string})
+ @ivar ranges: for each file, the list of files to be written when
+ writing to that file (may not be the actual file, i.e if it is
+ disabled)
+ @type begins: C{list} of C{long}
+ @ivar begins: the offset within the download that each non-disabled file
+ begins at
+ @type bufferdir: C{string}
+ @ivar bufferdir: the buffer directory
+ @type reset_file_status: C{method}
+ @ivar reset_file_status: a shortcut to the _reset_ranges method
+
+ """
+
def __init__(self, files, piece_lengths, doneflag, config,
disabled_files = None):
- # can raise IOError and ValueError
+ """Initializes the Storage.
+
+ Initializes some variables, and calculates defaults for others,
+ such as which pieces are contained by which file.
+
+ @type files: C{list} of C{tuple} of (C{string}, C{long})
+ @param files: the files list from the info of the metainfo dictionary
+ @type piece_lengths: C{list} of C{long}
+ @param piece_lengths: the list of piece lengths
+ @type doneflag: unknown
+ @param doneflag: unknown
+ @type config: C{dictionary}
+ @param config: the configuration information
+ @type disabled_files: C{list} of C{boolean}
+ @param disabled_files: list of true for the files that are disabled
+ (optional, default is no files disabled)
+ @raise IOError: unknown
+ @raise ValueError: unknown
+
+ """
+
self.files = files
self.piece_lengths = piece_lengths
self.doneflag = doneflag
@@ -129,6 +247,15 @@
if os.name == 'nt':
def _lock_file(self, name, f):
+ """Lock a file on Windows.
+
+ @type name: C{string}
+ @param name: The file name to lock (only used to get the file size)
+ @type f: C{file}
+ @param f: a file handle for the file to lock
+
+ """
+
import msvcrt
for p in range(0, min(self.sizes[name],MAXLOCKRANGE), MAXLOCKSIZE):
f.seek(p)
@@ -136,6 +263,15 @@
min(MAXLOCKSIZE,self.sizes[name]-p))
def _unlock_file(self, name, f):
+ """Unlock a file on Windows.
+
+ @type name: C{string}
+ @param name: The file name to unlock (only used to get the file size)
+ @type f: C{file}
+ @param f: a file handle for the file to unlock
+
+ """
+
import msvcrt
for p in range(0, min(self.sizes[name],MAXLOCKRANGE), MAXLOCKSIZE):
f.seek(p)
@@ -144,21 +280,53 @@
elif os.name == 'posix':
def _lock_file(self, name, f):
+ """Lock a file on Linux.
+
+ @type name: C{string}
+ @param name: The file name to lock (only used to get the file size)
+ @type f: C{file}
+ @param f: a file handle for the file to lock
+
+ """
+
import fcntl
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
def _unlock_file(self, name, f):
+ """Unlock a file on Linux.
+
+ @type name: C{string}
+ @param name: The file name to unlock (only used to get the file size)
+ @type f: C{file}
+ @param f: a file handle for the file to unlock
+
+ """
+
import fcntl
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
else:
def _lock_file(self, name, f):
+ """Dummy function to not Lock a file on other systems."""
pass
+
def _unlock_file(self, name, f):
+ """Dummy function to not unlock a file on other systems."""
pass
def was_preallocated(self, pos, length):
+ """Check if a download location was pre-allocated.
+
+ @type pos: C{long}
+ @param pos: the location to start checking
+ @type length: C{long}
+ @param length: the amount of the download to check
+ @rtype: C{boolean}
+ @return: whether the download location was pre-allocated
+
+ """
+
for file, begin, end in self._intervals(pos, length):
if self.tops.get(file, 0) < end:
return False
@@ -166,17 +334,44 @@
def _sync(self, file):
+ """Synchronize a file to disk.
+
+ Closes the open file so that it is synchronize to disk, the next time
+ it is referenced it will be reopened.
+
+ @type file: C{string}
+ @param file: the name of the file to sync
+
+ """
+
self._close(file)
if self.handlebuffer:
self.handlebuffer.remove(file)
def sync(self):
- # may raise IOError or OSError
+ """Synchronize all read/write files to disk.
+
+ @raise IOError: unknown
+ @raise OSError: unknown
+
+ """
+
for file in self.whandles.keys():
self._sync(file)
def set_readonly(self, f=None):
+ """Set a file (or all files) to be read-only.
+
+ Synchronizes the file (or all read/write files if none is given) to
+ disk. The next time the file is accessed it will be reopened.
+
+ @type f: C{int}
+ @param f: the number of the file to set read-only
+ (optional, default is to set all read/write files read-only)
+
+ """
+
if f is None:
self.sync()
return
@@ -186,10 +381,30 @@
def get_total_length(self):
+ """Get the total length of the download.
+
+ @rtype: C{long}
+ @return: the total length of the download
+
+ """
+
return self.total_length
def _open(self, file, mode):
+ """Open a file with the given mode.
+
+ @type file: C{string}
+ @param file: the file name to open
+ @type mode: C{string}
+ @param mode: the mode to open the file in
+ (r = read, w = write, a = append, add b for binary)
+ @rtype: C{file handle}
+ @return: the file handle to access the file with
+ @raise IOError: if the file has been modified since it was last read
+
+ """
+
if self.mtimes.has_key(file):
try:
if self.handlebuffer is not None:
@@ -214,6 +429,13 @@
def _close(self, file):
+ """Close the file
+
+ @type file: C{string}
+ @param file: the name of the file to close
+
+ """
+
f = self.handles[file]
del self.handles[file]
if self.whandles.has_key(file):
@@ -230,6 +452,13 @@
def _close_file(self, file):
+ """Close the file and release the handle.
+
+ @type file: C{string}
+ @param file: the name of the file to close
+
+ """
+
if not self.handles.has_key(file):
return
self._close(file)
@@ -238,6 +467,18 @@
def _get_file_handle(self, file, for_write):
+ """Get a new or existing flie handle for the file.
+
+ @type file: C{string}
+ @param file: the name of the file to get a handle for
+ @type for_write: C{boolean}
+ @param for_write: whether to open the file for writing
+ @rtype: C{file handle}
+ @return: the file handle that can be used for the file
+ @raise IOError: if the file can not be opened
+
+ """
+
if self.handles.has_key(file):
if for_write and not self.whandles.has_key(file):
self._close(file)
@@ -283,6 +524,7 @@
def _reset_ranges(self):
+ """Re-initialize the ranges from the working copies."""
self.ranges = []
for l in self.working_ranges:
self.ranges.extend(l)
@@ -292,6 +534,22 @@
print str(self.begins)
def _intervals(self, pos, amount):
+ """Get the files that are within the range.
+
+ Finds all the files that occur within a given range in the download,
+ and return a list of them. Includes the range of the file that is
+ inside the range, which will be the start (0) and end (length) of the
+ file unless it goes past the beginning or end of the range.
+
+ @type pos: C{long}
+ @param pos: the start of the range within the download
+ @type amount: C{long}
+ @param amount: the length of the range
+ @rtype: C{list} of C{tuple} of (C{string}, C{long}, C{long})
+ @return: the list of files and their start and end offsets in the range
+
+ """
+
r = []
stop = pos + amount
p = bisect(self.begins, pos) - 1
@@ -308,6 +566,20 @@
def read(self, pos, amount, flush_first = False):
+ """Read data from the download.
+
+ @type pos: C{long}
+ @param pos: the offset in the download to start reading from
+ @type amount: C{long}
+ @param amount: the length of the data to read
+ @type flush_first: C{boolean}
+ @param flush_first: whether to flush the files before reading the data
+ (optional, default is not to flush)
+ @rtype: L{DebTorrent.piecebuffer.SingleBuffer}
+ @return: the data that was read
+
+ """
+
r = PieceBuffer()
for file, pos, end in self._intervals(pos, amount):
if DEBUG:
@@ -329,6 +601,15 @@
return r
def write(self, pos, s):
+ """Write data to the download.
+
+ @type pos: C{long}
+ @param pos: the offset in the download to start writing at
+ @type s: unknown
+ @param s: data to write
+
+ """
+
# might raise an IOError
total = 0
for file, begin, end in self._intervals(pos, len(s)):
@@ -342,11 +623,19 @@
total += end - begin
def make_directories(self, file):
+ """Create missing parent directories for a file.
+
+ @type file: C{string}
+ @param file: the file name to create directories for
+
+ """
+
file = split(file)[0]
if file != '' and not exists(file):
makedirs(file)
def top_off(self):
+ """Extend all files to their appropriate length."""
for begin, end, offset, file in self.ranges:
l = offset + end - begin
if l > self.tops.get(file, 0):
@@ -357,6 +646,7 @@
self.lock.release()
def flush(self):
+ """Flush all files to disk."""
# may raise IOError or OSError
for file in self.whandles.keys():
self.lock.acquire()
@@ -364,6 +654,7 @@
self.lock.release()
def close(self):
+ """Close all open files."""
for file, f in self.handles.items():
try:
self.unlock_file(file, f)
@@ -379,6 +670,28 @@
def _get_disabled_ranges(self, f):
+ """Calculate the file ranges for the disabled file.
+
+ Calculates, based on the file lengths and piece lengths, the ranges
+ to write for the file. There are three lists calculated.
+
+ The working range is the list of files and file offsets to write if
+ the file is enabled.
+
+ The shared pieces is a list of piece numbers that the file shares
+ with other files.
+
+ The disabled range is the list of files and file offsets to write if
+ the file is disabled.
+
+ @type f: C{int}
+ @param f: the index of the file
+ @rtype: C{tuple}
+ @return: a tuple containing the working range, shared pieces, and
+ disabled range
+
+ """
+
if not self.file_ranges[f]:
return ((),(),())
r = self.disabled_ranges[f]
@@ -406,9 +719,23 @@
def set_bufferdir(self, dir):
+ """Sets the buffer directory.
+
+ @type dir: C{string}
+ @param dir: the new buffer directory
+
+ """
+
self.bufferdir = dir
def enable_file(self, f):
+ """Enable a file for writing.
+
+ @type f: C{int}
+ @param f: the index of the file to enable
+
+ """
+
if not self.disabled[f]:
return
self.disabled[f] = False
@@ -428,6 +755,12 @@
self.working_ranges[f] = [r]
def disable_file(self, f):
+ """Disable a file from writing.
+
+ @type f: C{int}
+ @param f: the index of the file to disable
+
+ """
if self.disabled[f]:
return
self.disabled[f] = True
@@ -451,35 +784,56 @@
def get_piece_update_list(self, f):
+ """Get the list of pieces the file shares with other files.
+
+ @type f: C{int}
+ @param f: the index of the file to disable
+ @rtype: C{list} of C{int}
+ @return: the list of piece indexes
+
+ """
+
return self._get_disabled_ranges(f)[1]
def delete_file(self, f):
+ """Delete the file.
+
+ @type f: C{int}
+ @param f: the index of the file to delete
+
+ """
try:
os.remove(self.files[f][0])
except:
pass
- '''
- Pickled data format:
-
- d['files'] = [ file #, size, mtime {, file #, size, mtime...} ]
- file # in torrent, and the size and last modification
- time for those files. Missing files are either empty
- or disabled.
- d['partial files'] = [ name, size, mtime... ]
- Names, sizes and last modification times of files containing
- partial piece data. Filenames go by the following convention:
- {file #, 0-based}{nothing, "b" or "e"}
- eg: "0e" "3" "4b" "4e"
- Where "b" specifies the partial data for the first piece in
- the file, "e" the last piece, and no letter signifying that
- the file is disabled but is smaller than one piece, and that
- all the data is cached inside so adjacent files may be
- verified.
- '''
def pickle(self):
+ """Create a dictionary representing the current state of the download.
+
+ Pickled data format::
+
+ d['files'] = [ file #, size, mtime {, file #, size, mtime...} ]
+ file # in torrent, and the size and last modification
+ time for those files. Missing files are either empty
+ or disabled.
+ d['partial files'] = [ name, size, mtime... ]
+ Names, sizes and last modification times of files containing
+ partial piece data. Filenames go by the following convention:
+ {file #, 0-based}{nothing, "b" or "e"}
+ eg: "0e" "3" "4b" "4e"
+ Where "b" specifies the partial data for the first piece in
+ the file, "e" the last piece, and no letter signifying that
+ the file is disabled but is smaller than one piece, and that
+ all the data is cached inside so adjacent files may be
+ verified.
+
+ @rtype: C{dictionary}
+ @return: the pickled current status of the download
+
+ """
+
files = []
pfiles = []
for i in xrange(len(self.files)):
@@ -495,7 +849,17 @@
def unpickle(self, data):
- # assume all previously-disabled files have already been disabled
+ """Extract the current status of the download from a pickled dictionary.
+
+ Assumes all previously-disabled files have already been disabled.
+
+ @type data: C{dictionary}
+ @param data: the pickled current status of the download
+ @rtype: C{list} of C{int}
+ @return: a list of the currently enabled pieces
+
+ """
+
try:
files = {}
pfiles = {}
@@ -528,6 +892,19 @@
print valid_pieces.keys()
def test(old, size, mtime):
+ """Test that the file has not changed since the status save.
+
+ @type old: C{tuple} of (C{long}, C{long})
+ @param old: the previous size and modification time of the file
+ @type size: C{long}
+ @param size: the current size of the file
+ @type mtime: C{long}
+ @param mtime: the current modification time of the file
+ @rtype: C{boolean}
+ @return: whether the file has been changed
+
+ """
+
oldsize, oldmtime = old
if size != oldsize:
return False
Modified: debtorrent/trunk/DebTorrent/BT1/__init__.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/BT1/__init__.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/BT1/__init__.py (original)
+++ debtorrent/trunk/DebTorrent/BT1/__init__.py Mon May 21 03:38:40 2007
@@ -1,6 +1,11 @@
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
-# placeholder
+"""More specific sub-modules for the debtorrent protocol.
+
+This package contains ome more specific sub-modules used by the
+L{DebTorrent} package.
+
+"""
Modified: debtorrent/trunk/DebTorrent/BT1/btformats.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/BT1/btformats.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/BT1/btformats.py (original)
+++ debtorrent/trunk/DebTorrent/BT1/btformats.py Mon May 21 03:38:40 2007
@@ -1,8 +1,20 @@
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Functions for verifying debtorrent metainfo.
+
+These functions all work on bdecoded debtorrent metainfo, and are used
+to verify their conformance with the protocol.
+
+ at type reg: C{regex}
+ at var reg: a compiled regex for verifying the security of path names
+ at type ints: C{tuple} of C{types}
+ at var ints: the types that are acceptable for integer values
+
+"""
from types import StringType, LongType, IntType, ListType, DictType
from re import compile
@@ -13,6 +25,17 @@
ints = (LongType, IntType)
def check_info(info):
+ """Checks the info dictionary for conformance.
+
+ Verifies that the info dictionary of the metainfo conforms to the
+ debtorrent protocol.
+
+ @type info: C{dictionary}
+ @param info: the info field from the metainfo dictionary
+ @raise ValueError: if the info doesn't conform
+
+ """
+
if type(info) != DictType:
raise ValueError, 'bad metainfo - not a dictionary'
pieces = info.get('pieces')
@@ -58,6 +81,17 @@
# raise ValueError, 'bad metainfo - duplicate path'
def check_message(message):
+ """Checks the metainfo dictionary for conformance.
+
+ Verifies that the metainfo dictionary conforms to the
+ debtorrent protocol.
+
+ @type message: C{dictionary}
+ @param message: the bdecoded metainfo dictionary
+ @raise ValueError: if the metainfo doesn't conform
+
+ """
+
if type(message) != DictType:
raise ValueError
check_info(message.get('info'))
@@ -70,6 +104,17 @@
raise ValueError, 'name %s disallowed for security reasons' % name
def check_peers(message):
+ """Checks the peers dictionary returned by a tracker for conformance.
+
+ Verifies that the peers dictionary returned by a tracker conforms to the
+ debtorrent protocol.
+
+ @type message: C{dictionary}
+ @param message: the bdecoded peers dictionary returned by a tracker
+ @raise ValueError: if the info doesn't conform
+
+ """
+
if type(message) != DictType:
raise ValueError
if message.has_key('failure reason'):
Modified: debtorrent/trunk/DebTorrent/__init__.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/__init__.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/__init__.py (original)
+++ debtorrent/trunk/DebTorrent/__init__.py Mon May 21 03:38:40 2007
@@ -1,7 +1,19 @@
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""The main package to implement the debtorrent protocol.
+
+This package, and it's subpackage L{BT1}, contains all the modules needed
+to implement the DebTorrent protocol.
+
+ at type product_name: C{string}
+ at var product_name: the name given for the package
+ at type version_short: C{string}
+ at var version_short: the short version number
+
+"""
product_name = 'DebTorrent'
version_short = 'T-0.1.0'
Modified: debtorrent/trunk/DebTorrent/bencode.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/bencode.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/bencode.py (original)
+++ debtorrent/trunk/DebTorrent/bencode.py Mon May 21 03:38:40 2007
@@ -1,8 +1,24 @@
# Written by Petru Paler, Uoti Urpala, Ross Cohen and John Hoffman
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
+
+"""Functions for bencoding and bdecoding data.
+
+ at type decode_func: C{dictionary} of C{function}
+ at var decode_func: a dictionary of function calls to be made, based on data,
+ the keys are the first character of the data and the value is the
+ function to use to decode that data
+ at type bencached_marker: C{list}
+ at var bencached_marker: mutable type to ensure class origination
+ at type encode_func: C{dictionary} of C{function}
+ at var encode_func: a dictionary of function calls to be made, based on data,
+ the keys are the type of the data and the value is the
+ function to use to encode that data
+ at type BencachedType: C{type}
+ at var BencachedType: the L{Bencached} type
+"""
from types import IntType, LongType, StringType, ListType, TupleType, DictType
try:
@@ -16,6 +32,18 @@
from cStringIO import StringIO
def decode_int(x, f):
+ """Bdecode an integer.
+
+ @type x: C{string}
+ @param x: the data to decode
+ @type f: C{int}
+ @param f: the offset in the data to start at
+ @rtype: C{int}, C{int}
+ @return: the bdecoded integer, and the offset to read next
+ @raise ValueError: if the data is improperly encoded
+
+ """
+
f += 1
newf = x.index('e', f)
try:
@@ -30,6 +58,18 @@
return (n, newf+1)
def decode_string(x, f):
+ """Bdecode a string.
+
+ @type x: C{string}
+ @param x: the data to decode
+ @type f: C{int}
+ @param f: the offset in the data to start at
+ @rtype: C{string}, C{int}
+ @return: the bdecoded string, and the offset to read next
+ @raise ValueError: if the data is improperly encoded
+
+ """
+
colon = x.index(':', f)
try:
n = int(x[f:colon])
@@ -41,10 +81,32 @@
return (x[colon:colon+n], colon+n)
def decode_unicode(x, f):
+ """Bdecode a unicode string.
+
+ @type x: C{string}
+ @param x: the data to decode
+ @type f: C{int}
+ @param f: the offset in the data to start at
+ @rtype: C{int}, C{int}
+ @return: the bdecoded unicode string, and the offset to read next
+
+ """
+
s, f = decode_string(x, f+1)
return (s.decode('UTF-8'),f)
def decode_list(x, f):
+ """Bdecode a list.
+
+ @type x: C{string}
+ @param x: the data to decode
+ @type f: C{int}
+ @param f: the offset in the data to start at
+ @rtype: C{list}, C{int}
+ @return: the bdecoded list, and the offset to read next
+
+ """
+
r, f = [], f+1
while x[f] != 'e':
v, f = decode_func[x[f]](x, f)
@@ -52,6 +114,18 @@
return (r, f + 1)
def decode_dict(x, f):
+ """Bdecode a dictionary.
+
+ @type x: C{string}
+ @param x: the data to decode
+ @type f: C{int}
+ @param f: the offset in the data to start at
+ @rtype: C{dictionary}, C{int}
+ @return: the bdecoded dictionary, and the offset to read next
+ @raise ValueError: if the data is improperly encoded
+
+ """
+
r, f = {}, f+1
lastkey = None
while x[f] != 'e':
@@ -79,6 +153,18 @@
#decode_func['u'] = decode_unicode
def bdecode(x, sloppy = 0):
+ """Bdecode a string of data.
+
+ @type x: C{string}
+ @param x: the data to decode
+ @type sloppy: C{boolean}
+ @param sloppy: whether to allow errors in the decoding
+ @rtype: unknown
+ @return: the bdecoded data
+ @raise ValueError: if the data is improperly encoded
+
+ """
+
try:
r, l = decode_func[x[0]](x, 0)
# except (IndexError, KeyError):
@@ -89,6 +175,7 @@
return r
def test_bdecode():
+ """A test routine for the bdecoding functions."""
try:
bdecode('0:0:')
assert 0
@@ -233,36 +320,122 @@
bencached_marker = []
class Bencached:
+ """Dummy data structure for storing bencoded data in memory.
+
+ @type marker: C{list}
+ @ivar marker: mutable type to make sure the data was encoded by this class
+ @type bencoded: C{string}
+ @ivar bencoded: the bencoded data stored in a string
+
+ """
+
def __init__(self, s):
+ """
+
+ @type s: C{string}
+ @param s: the new bencoded data to store
+
+ """
+
self.marker = bencached_marker
self.bencoded = s
BencachedType = type(Bencached('')) # insufficient, but good as a filter
def encode_bencached(x,r):
+ """Bencode L{Bencached} data.
+
+ @type x: L{Bencached}
+ @param x: the data to encode
+ @type r: C{list}
+ @param r: the currently bencoded data, to which the bencoding of x
+ will be appended
+
+ """
+
assert x.marker == bencached_marker
r.append(x.bencoded)
def encode_int(x,r):
+ """Bencode an integer.
+
+ @type x: C{int}
+ @param x: the data to encode
+ @type r: C{list}
+ @param r: the currently bencoded data, to which the bencoding of x
+ will be appended
+
+ """
+
r.extend(('i',str(x),'e'))
def encode_bool(x,r):
+ """Bencode a boolean.
+
+ @type x: C{boolean}
+ @param x: the data to encode
+ @type r: C{list}
+ @param r: the currently bencoded data, to which the bencoding of x
+ will be appended
+
+ """
+
encode_int(int(x),r)
def encode_string(x,r):
+ """Bencode a string.
+
+ @type x: C{string}
+ @param x: the data to encode
+ @type r: C{list}
+ @param r: the currently bencoded data, to which the bencoding of x
+ will be appended
+
+ """
+
r.extend((str(len(x)),':',x))
def encode_unicode(x,r):
+ """Bencode a unicode string.
+
+ @type x: C{unicode}
+ @param x: the data to encode
+ @type r: C{list}
+ @param r: the currently bencoded data, to which the bencoding of x
+ will be appended
+
+ """
+
#r.append('u')
encode_string(x.encode('UTF-8'),r)
def encode_list(x,r):
- r.append('l')
- for e in x:
- encode_func[type(e)](e, r)
- r.append('e')
+ """Bencode a list.
+
+ @type x: C{list}
+ @param x: the data to encode
+ @type r: C{list}
+ @param r: the currently bencoded data, to which the bencoding of x
+ will be appended
+
+ """
+
+ r.append('l')
+ for e in x:
+ encode_func[type(e)](e, r)
+ r.append('e')
def encode_dict(x,r):
+ """Bencode a dictionary.
+
+ @type x: C{dictionary}
+ @param x: the data to encode
+ @type r: C{list}
+ @param r: the currently bencoded data, to which the bencoding of x
+ will be appended
+
+ """
+
r.append('d')
ilist = x.items()
ilist.sort()
@@ -285,6 +458,15 @@
encode_func[UnicodeType] = encode_unicode
def bencode(x):
+ """Bencode some data.
+
+ @type x: unknown
+ @param x: the data to encode
+ @rtype: string
+ @return: the bencoded data
+ @raise ValueError: if the data contains a type that cannot be encoded
+
+ """
r = []
try:
encode_func[type(x)](x, r)
@@ -294,6 +476,7 @@
return ''.join(r)
def test_bencode():
+ """A test routine for the bencoding functions."""
assert bencode(4) == 'i4e'
assert bencode(0) == 'i0e'
assert bencode(-10) == 'i-10e'
Modified: debtorrent/trunk/DebTorrent/inifile.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/inifile.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/inifile.py (original)
+++ debtorrent/trunk/DebTorrent/inifile.py Mon May 21 03:38:40 2007
@@ -4,28 +4,33 @@
# $Id$
-'''
-reads/writes a Windows-style INI file
-format:
-
- aa = "bb"
- cc = 11
-
- [eee]
- ff = "gg"
-
-decodes to:
-d = { '': {'aa':'bb','cc':'11'}, 'eee': {'ff':'gg'} }
-
-the encoder can also take this as input:
-
-d = { 'aa': 'bb, 'cc': 11, 'eee': {'ff':'gg'} }
+'''Functions to read/write a Windows-style INI file
+
+format::
+
+ aa = "bb"
+ cc = 11
+
+ [eee]
+ ff = "gg"
+
+decodes to::
+
+ d = { '': {'aa':'bb','cc':'11'}, 'eee': {'ff':'gg'} }
+
+the encoder can also take this as input::
+
+ d = { 'aa': 'bb, 'cc': 11, 'eee': {'ff':'gg'} }
though it will only decode in the above format. Keywords must be strings.
Values that are strings are written surrounded by quotes, and the decoding
-routine automatically strips any.
-Booleans are written as integers. Anything else aside from string/int/float
-may have unpredictable results.
+routine automatically strips any quotes from the values.
+Booleans are written as integers. Anything other than strings, integers,
+and floats may have unpredictable results.
+
+ at type DEBUG: C{boolean}
+ at var DEBUG: whether to print debugging information
+
'''
from cStringIO import StringIO
@@ -45,6 +50,20 @@
DEBUG = True
def ini_write(f, d, comment=''):
+ """Write the ini file.
+
+ @type f: C{string}
+ @param f: the file name to write
+ @type d: C{dictionary}
+ @param d: the data to write to the ini file
+ @type comment: C{string}
+ @param comment: a comment to write at the top of the file, hash marks
+ will be prefixed (optional, default is no comment)
+ @rtype: C{boolean}
+ @return: whether the write succeeded
+
+ """
+
try:
a = {'':{}}
for k,v in d.items():
@@ -107,11 +126,34 @@
if DEBUG:
def errfunc(lineno, line, err):
+ """Display an error message when reading the ini file fails.
+
+ @type lineno: C{int}
+ @param lineno: the line number that caused the error
+ @type line: C{string}
+ @param line: the line that caused the error
+ @type err: C{string}
+ @param err: the error that was generated
+
+ """
+
print '('+str(lineno)+') '+err+': '+line
else:
errfunc = lambda lineno, line, err: None
def ini_read(f, errfunc = errfunc):
+ """Read the ini file.
+
+ @type f: C{string}
+ @param f: the file name to read
+ @type errfunc: C{function}
+ @param errfunc: the function to call when an error occurs
+ (optional, default is to do nothing, unless debugging is enabled in
+ which case the error is printed to standard output)
+ @rtype: C{dictionary}
+ @return: the data read from the ini file
+
+ """
try:
r = open(f,'r')
ll = r.readlines()
Modified: debtorrent/trunk/btcompletedir.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/btcompletedir.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/btcompletedir.py (original)
+++ debtorrent/trunk/btcompletedir.py Mon May 21 03:38:40 2007
@@ -1,10 +1,16 @@
#!/usr/bin/env python
-
+#
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Make a metainfo (.dtorrent) file for every file in a directory.
+
+Creates a new metainfo file for every file in a given directory.
+
+"""
from DebTorrent import PSYCO
if PSYCO.psyco:
@@ -22,9 +28,24 @@
from DebTorrent.parseargs import parseargs, formatDefinitions
def prog(amount):
+ """Display the current status of the file scan.
+
+ @type amount: C{int}
+ @param amount: the number of packages that have been found so far in the
+ current file
+
+ """
+
print '%d packages found\r' % amount,
def next_file(file):
+ """Print the name of the file being scanned.
+
+ @type file: C{string}
+ @param file: the file name
+
+ """
+
print "\nProcessing file: %s" % file
if len(argv) < 3:
Modified: debtorrent/trunk/btcopyannounce.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/btcopyannounce.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/btcopyannounce.py (original)
+++ debtorrent/trunk/btcopyannounce.py Mon May 21 03:38:40 2007
@@ -1,11 +1,18 @@
#!/usr/bin/env python
-
+#
# btreannounce.py written by Henry 'Pi' James and Bram Cohen
# multitracker extensions by John Hoffman
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Copy the announce information from one metainfo file to others.
+
+Copies all the announce information from an input metainfo file, and
+makes all the other metainfo files match it.
+
+"""
from sys import argv,exit
from os.path import split
@@ -13,6 +20,15 @@
def give_announce_list(l):
+ """Converts an announce list into human-readable output.
+
+ @type l: C{list}
+ @param l: the announce list to convert
+ @rtype: C{string}
+ @return: the human-readbale announce list
+
+ """
+
list = []
for tier in l:
for tracker in tier:
Modified: debtorrent/trunk/btdownloadheadless.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/btdownloadheadless.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/btdownloadheadless.py (original)
+++ debtorrent/trunk/btdownloadheadless.py Mon May 21 03:38:40 2007
@@ -1,10 +1,17 @@
#!/usr/bin/env python
-
+#
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
+
+"""A single downloader for debtorrent.
+
+This script implements a command-line client for managing a single
+debtorrent download.
+
+"""
from DebTorrent import PSYCO
if PSYCO.psyco:
@@ -43,6 +50,18 @@
PROFILER = False
def hours(n):
+ """Formats seconds into a human-readable time.
+
+ Formats a given number of seconds into a human-readable time appropriate
+ for display to the user.
+
+ @type n: C{int}
+ @param n: the number of seconds
+ @rtype: C{string}
+ @return: a displayable representation of the number of seconds
+
+ """
+
if n == 0:
return 'complete!'
try:
@@ -58,7 +77,41 @@
return '%d min %02d sec' % (m, s)
class HeadlessDisplayer:
+ """Displays on the command-line the status of the download.
+
+ Implements a displayer for showing in text format the current status
+ of the download.
+
+ @type done: C{boolean}
+ @ivar done: whether the download has completed or not
+ @type file: C{string}
+ @ivar file: the download file name to display
+ @type percentDone: C{string}
+ @ivar percentDone: the current percentage of the download that is complete
+ @type timeEst: C{string}
+ @ivar timeEst: the estimated time remaining
+ @type downloadTo: C{string}
+ @ivar downloadTo: the location of the download show to the user
+ @type downRate: C{string}
+ @ivar downRate: the last download rate shown to the user
+ @type upRate: C{string}
+ @ivar upRate: the last upload rate shown to the user
+ @type shareRating: C{string}
+ @ivar shareRating: the last sharing information shown to the user
+ @type seedStatus: C{string}
+ @ivar seedStatus: information on the current status of seeds seen
+ @type peerStatus: C{string}
+ @ivar peerStatus: information on the current status of peers seen
+ @type errors: C{list} of C{string}
+ @ivar errors: a list of all the errors that have occurred, in the order
+ in which they occurred
+ @type last_update_time: C{float}
+ @ivar last_update_time: the time in seconds of the last update
+
+ """
+
def __init__(self):
+ """Initialize the status of the download."""
self.done = False
self.file = ''
self.percentDone = ''
@@ -73,6 +126,7 @@
self.last_update_time = -1
def finished(self):
+ """Concludes the download successfully."""
self.done = True
self.percentDone = '100'
self.timeEst = 'Download Succeeded!'
@@ -80,6 +134,7 @@
self.display()
def failed(self):
+ """Ends the download when an error has occurred."""
self.done = True
self.percentDone = '0'
self.timeEst = 'Download Failed!'
@@ -87,12 +142,34 @@
self.display()
def error(self, errormsg):
+ """Displays an error to the user."""
self.errors.append(errormsg)
self.display()
def display(self, dpflag = Event(), fractionDone = None, timeEst = None,
downRate = None, upRate = None, activity = None,
statistics = None, **kws):
+ """Displays the current status of the download.
+
+ Writes a status update to standard output.
+
+ @type dpflag: a threading event
+ @param dpflag: unknown
+ @type fractionDone: C{float}
+ @param fractionDone: the amount of the download that is complete [0-1]
+ @type timeEst: C{int}
+ @param timeEst: the number of seconds remaining to complete the download
+ @type downRate: C{int} or C{float}
+ @param downRate: the current download rate in bytes per second
+ @type upRate: C{int} or C{float}
+ @param upRate: the current upload rate in bytes per second
+ @type activity: C{string}
+ @param activity: the currently occurring activity (overwrites the timeEst)
+ @type statistics: L{DebTorrent.BT1.Statistics.Statistics}
+ @param statistics: various statistics of the current download
+
+ """
+
if self.last_update_time + 0.1 > clock() and fractionDone not in (0.0, 1.0) and activity is not None:
return
self.last_update_time = clock()
@@ -132,6 +209,25 @@
dpflag.set()
def chooseFile(self, default, size, saveas, dir):
+ """Sets up the save directory name.
+
+ Takes the default and user-provided save directory, saves them, and
+ returns the directory to be used for the download.
+
+ @type default: C{string}
+ @param default: the default save location
+ @type size: C{int}
+ @param size: the size of the download in bytes
+ @type saveas: C{string}
+ @param saveas: the user-provided directory to save the download to,
+ or an empty string indicating the default should be used
+ @type dir: unknown
+ @param dir: unknown
+ @rtype: C{string}
+ @return: the directory to save the download to
+
+ """
+
self.file = '%s (%.1f MB)' % (default, float(size) / (1 << 20))
if saveas != '':
default = saveas
@@ -139,9 +235,19 @@
return default
def newpath(self, path):
+ """Overwrite the previously saved download location."""
self.downloadTo = path
def run(params):
+ """Runs the downloader.
+
+ The main function used to create the displayer and start the download.
+
+ @type params: C{list} of C{strings}
+ @param params: a list of the command-line arguments given to the script
+
+ """
+
h = HeadlessDisplayer()
while 1:
configdir = ConfigDir('downloadheadless')
@@ -168,6 +274,7 @@
doneflag = Event()
def disp_exception(text):
+ """Prints exceptions to standard output."""
print text
rawserver = RawServer(doneflag, config['timeout_check_interval'],
config['timeout'], ipv6_enable = config['ipv6_enabled'],
Modified: debtorrent/trunk/btlaunchmany.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/btlaunchmany.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/btlaunchmany.py (original)
+++ debtorrent/trunk/btlaunchmany.py Mon May 21 03:38:40 2007
@@ -1,10 +1,17 @@
#!/usr/bin/env python
-
+#
# Written by John Hoffman
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""A multiple downloader for debtorrent.
+
+This script implements a command-line client for managing multiple
+debtorrent downloads.
+
+"""
from DebTorrent import PSYCO
if PSYCO.psyco:
@@ -32,6 +39,18 @@
False = 0
def hours(n):
+ """Formats seconds into a human-readable time.
+
+ Formats a given number of seconds into a human-readable time appropriate
+ for display to the user.
+
+ @type n: C{int}
+ @param n: the number of seconds
+ @rtype: C{string}
+ @return: a displayable representation of the number of seconds
+
+ """
+
if n == 0:
return 'complete!'
try:
@@ -48,9 +67,28 @@
Exceptions = []
+"""The exceptions that have occurred."""
class HeadlessDisplayer:
+ """Displays on the command-line the status of the downloads.
+
+ Implements a displayer for showing in text format the current status
+ of the downloads.
+
+ """
+
def display(self, data):
+ """Displays the current status of the downloads.
+
+ Writes a status update to standard output.
+
+ @type data: C{list} of C{tuple}
+ @param data: a tuple per download, containing the current status of the download
+ @rtype: C{boolean}
+ @return: False
+
+ """
+
print ''
if not data:
self.message('no torrents')
@@ -63,9 +101,23 @@
return False
def message(self, s):
+ """Prints a message to standard output.
+
+ @type s: C{string}
+ @param s: the message to print to standard output
+
+ """
+
print "### "+s
def exception(self, s):
+ """Saves a new exception and alerts the user.
+
+ @type s: C{string}
+ @param s: the exception that occurred
+
+ """
+
Exceptions.append(s)
self.message('SYSTEM ERROR - EXCEPTION GENERATED')
Modified: debtorrent/trunk/btmakemetafile.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/btmakemetafile.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/btmakemetafile.py (original)
+++ debtorrent/trunk/btmakemetafile.py Mon May 21 03:38:40 2007
@@ -1,11 +1,18 @@
#!/usr/bin/env python
-
+#
# Written by Bram Cohen
# multitracker extensions by John Hoffman
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Creates a metainfo file from a Packages file.
+
+Converts a Packages file into a metainfo file containing all the information
+needed to download all the packages in the Packages file.
+
+"""
from DebTorrent import PSYCO
if PSYCO.psyco:
@@ -24,6 +31,14 @@
def prog(amount):
+ """Display the current status of the file scan.
+
+ @type amount: C{int}
+ @param amount: the number of packages that have been found so far in the
+ file
+
+ """
+
print '%d packages found\r' % amount,
if len(argv) < 3:
Modified: debtorrent/trunk/btreannounce.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/btreannounce.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/btreannounce.py (original)
+++ debtorrent/trunk/btreannounce.py Mon May 21 03:38:40 2007
@@ -1,11 +1,13 @@
#!/usr/bin/env python
-
+#
# Written by Henry 'Pi' James and Bram Cohen
# multitracker extensions by John Hoffman
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Change the announce info in a metainfo file."""
from sys import argv,exit
from os.path import split
Modified: debtorrent/trunk/btrename.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/btrename.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/btrename.py (original)
+++ debtorrent/trunk/btrename.py Mon May 21 03:38:40 2007
@@ -1,10 +1,17 @@
#!/usr/bin/env python
-
+#
# Written by Henry 'Pi' James
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Change the name field in a metainfo file.
+
+Rename the directory that will be created (suggested) when the metainfo file's
+download is started.
+
+"""
from sys import *
from os.path import *
Modified: debtorrent/trunk/btsethttpseeds.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/btsethttpseeds.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/btsethttpseeds.py (original)
+++ debtorrent/trunk/btsethttpseeds.py Mon May 21 03:38:40 2007
@@ -1,11 +1,13 @@
#!/usr/bin/env python
-
+#
# Written by Henry 'Pi' James and Bram Cohen
# multitracker extensions by John Hoffman
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Change the http-seeds info in the metainfo file."""
from sys import argv,exit
from os.path import split
Modified: debtorrent/trunk/btshowmetainfo.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/btshowmetainfo.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/btshowmetainfo.py (original)
+++ debtorrent/trunk/btshowmetainfo.py Mon May 21 03:38:40 2007
@@ -1,11 +1,13 @@
#!/usr/bin/env python
-
+#
# Written by Henry 'Pi' James and Loring Holden
# modified for multitracker display by John Hoffman
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Display a summary of the information in a metainfo file."""
from sys import *
from os.path import *
Modified: debtorrent/trunk/bttrack.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/bttrack.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/bttrack.py (original)
+++ debtorrent/trunk/bttrack.py Mon May 21 03:38:40 2007
@@ -1,10 +1,12 @@
#!/usr/bin/env python
-
+#
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Start a debtorrent download tracker."""
from DebTorrent import PSYCO
if PSYCO.psyco:
Modified: debtorrent/trunk/setup.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/setup.py?rev=57&op=diff
==============================================================================
--- debtorrent/trunk/setup.py (original)
+++ debtorrent/trunk/setup.py Mon May 21 03:38:40 2007
@@ -1,10 +1,18 @@
#!/usr/bin/env python
-
+#
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""The DebTorrent program for downloading debian packages.
+
+This program contains several scripts and packages to implement the debtorrent
+protocol for downloading debian packages from an archive is a bittorrent-like
+way.
+
+"""
import sys
assert sys.version >= '2', "Install Python 2.0 or greater"
@@ -21,11 +29,9 @@
packages = ["DebTorrent","DebTorrent.BT1"],
- scripts = ["btdownloadgui.py", "btdownloadheadless.py",
+ scripts = ["btdownloadheadless.py",
"bttrack.py", "btmakemetafile.py", "btlaunchmany.py", "btcompletedir.py",
- "btdownloadcurses.py", "btcompletedirgui.py", "btlaunchmanycurses.py",
- "btmakemetafile.py", "btreannounce.py", "btrename.py", "btshowmetainfo.py",
- 'btmaketorrentgui.py', 'btcopyannounce.py', 'btsethttpseeds.py',
- 'bt-t-make.py',
+ "btreannounce.py", "btrename.py", "btshowmetainfo.py",
+ 'btcopyannounce.py', 'btsethttpseeds.py',
]
)
More information about the Debtorrent-commits
mailing list