[Debtorrent-commits] r58 - in /debtorrent/trunk/DebTorrent: BT1/Downloader.py BT1/Storage.py BTcrypto.py ConfigDir.py ConnChoice.py CurrentRateMeasure.py bitfield.py clock.py download_bt1.py
camrdale-guest at users.alioth.debian.org
camrdale-guest at users.alioth.debian.org
Tue May 22 07:56:33 UTC 2007
Author: camrdale-guest
Date: Tue May 22 07:56:32 2007
New Revision: 58
URL: http://svn.debian.org/wsvn/debtorrent/?sc=1&rev=58
Log:
More documentation
Modified:
debtorrent/trunk/DebTorrent/BT1/Downloader.py
debtorrent/trunk/DebTorrent/BT1/Storage.py
debtorrent/trunk/DebTorrent/BTcrypto.py
debtorrent/trunk/DebTorrent/ConfigDir.py
debtorrent/trunk/DebTorrent/ConnChoice.py
debtorrent/trunk/DebTorrent/CurrentRateMeasure.py
debtorrent/trunk/DebTorrent/bitfield.py
debtorrent/trunk/DebTorrent/clock.py
debtorrent/trunk/DebTorrent/download_bt1.py
Modified: debtorrent/trunk/DebTorrent/BT1/Downloader.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/BT1/Downloader.py?rev=58&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/BT1/Downloader.py (original)
+++ debtorrent/trunk/DebTorrent/BT1/Downloader.py Tue May 22 07:56:32 2007
@@ -1,8 +1,15 @@
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
+
+"""Download pieces from remote peers.
+
+ at type EXPIRE_TIME: C{int}
+ at var EXPIRE_TIME: number of seconds after which disconnected seeds are expired
+
+"""
from DebTorrent.CurrentRateMeasure import Measure
from DebTorrent.bitfield import Bitfield
@@ -16,8 +23,31 @@
EXPIRE_TIME = 60 * 60
-class PerIPStats:
+class PerIPStats:
+ """Statistics relating to downloads from a single peer.
+
+ @type numgood: C{int}
+ @ivar numgood: the number of good pieces received
+ @type bad: C{dictionary}
+ @ivar bad: keys are piece numbers, values are the number of bad copies
+ of the piece received from the peer
+ @type numconnections: C{int}
+ @ivar numconnections: the number of connections made to the peer
+ @type lastdownload: L{SingleDownload}
+ @ivar lastdownload: the most recent SingleDownload instance
+ @type peerid: C{string}
+ @ivar peerid: the peer's ID
+
+ """
+
def __init__(self, ip):
+ """Initialize the statistics.
+
+ @type ip: unknown
+ @param ip: the IP address of the peer (not used)
+
+ """
+
self.numgood = 0
self.bad = {}
self.numconnections = 0
@@ -25,7 +55,29 @@
self.peerid = None
class BadDataGuard:
+ """Process good and bad received pieces from a single peer.
+
+ @type download: L{SingleDownload}
+ @ivar download: the SingleDownload instance
+ @type ip: C{string}
+ @ivar ip: IP address of the peer
+ @type downloader: L{Downloader}
+ @ivar downloader: the Downloader instance
+ @type stats: L{PerIPStats}
+ @ivar stats: the PerIPStats instance
+ @type lastindex: C{int}
+ @ivar lastindex: the last good piece that was received
+
+ """
+
def __init__(self, download):
+ """Initialize the class.
+
+ @type download: L{SingleDownload}
+ @param download: the SingleDownload instance for the download
+
+ """
+
self.download = download
self.ip = download.ip
self.downloader = download.downloader
@@ -33,6 +85,16 @@
self.lastindex = None
def failed(self, index, bump = False):
+ """Process the failed piece.
+
+ @type index: C{int}
+ @param index: the piece that failed
+ @type bump: C{boolean}
+ @param bump: whether to increase the interest level in the
+ L{PiecePicker.PiecePicker} (optional, defaults to False)
+
+ """
+
self.stats.bad.setdefault(index, 0)
self.downloader.gotbaddata[self.ip] = 1
self.stats.bad[index] += 1
@@ -47,6 +109,13 @@
self.downloader.picker.bump(index)
def good(self, index):
+ """Process the successful piece.
+
+ @type index: C{int}
+ @param index: the piece that succeeded
+
+ """
+
# lastindex is a hack to only increase numgood by one for each good
# piece, however many chunks come from the connection(s) from this IP
if index != self.lastindex:
@@ -54,7 +123,49 @@
self.lastindex = index
class SingleDownload:
+ """Manage downloads from a single peer.
+
+ @type downloader: L{Downloader}
+ @ivar downloader: the Downloader instance
+ @type connection: unknown
+ @ivar connection: the connection to the peer
+ @type choked: C{boolean}
+ @ivar choked: whether the peer is choking the download
+ @type interested: C{boolean}
+ @ivar interested: whether the peer is interesting
+ @type active_requests: C{list}
+ @ivar active_requests: unknown
+ @type measure: L{DebTorrent.CurrentRateMeasure.Measure}
+ @ivar measure: for measuring the download rate from the peer
+ @type peermeasure: L{DebTorrent.CurrentRateMeasure.Measure}
+ @ivar peermeasure: for measuring the download rate of the peer
+ @type have: L{DebTorrent.bitfield.Bitfield}
+ @ivar have: the bitfield the peer has
+ @type last: C{float}
+ @ivar last: the last time a chunk was received from the peer
+ @type last2: C{float}
+ @ivar last2: the last time a chunk or an unchoke was received
+ @type example_interest: C{int}
+ @ivar example_interest: an example piece to request
+ @type backlog: C{int}
+ @ivar backlog: the current backlog of chunk requests
+ @type ip: C{string}
+ @ivar ip: the IP address of the peer
+ @type guard: L{BadDataGuard}
+ @ivar guard: the guard to use to process pieces
+
+ """
+
def __init__(self, downloader, connection):
+ """Initialize the instance.
+
+ @type downloader: L{Downloader}
+ @param downloader: the parent Downloader instance
+ @type connection: unknown
+ @param connection: the connection to the peer
+
+ """
+
self.downloader = downloader
self.connection = connection
self.choked = True
@@ -71,6 +182,15 @@
self.guard = BadDataGuard(self)
def _backlog(self, just_unchoked):
+ """Calculate the backlog of chunk requests to the peer.
+
+ @type just_unchoked: C{boolean}
+ @param just_unchoked: whether the connection was just unchoked
+ @rtype: C{int}
+ @return: the new backlog
+
+ """
+
self.backlog = min(
2+int(4*self.measure.get_rate()/self.downloader.chunksize),
(2*just_unchoked)+self.downloader.queue_limit() )
@@ -79,6 +199,7 @@
return self.backlog
def disconnected(self):
+ """Remove the newly disconnected peer."""
self.downloader.lost_peer(self)
if self.have.complete():
self.downloader.picker.lost_seed()
@@ -92,6 +213,14 @@
self.guard.download = None
def _letgo(self):
+ """Remove the oustanding requests to the peer.
+
+ For each active request that was unfulfilled by the peer, inform the
+ Storage that the request was lost, and send interested messages to any
+ remaining peers that have the piece.
+
+ """
+
if self.downloader.queued_out.has_key(self):
del self.downloader.queued_out[self]
if not self.active_requests:
@@ -119,11 +248,13 @@
break
def got_choke(self):
+ """Update the choked status and remove any active requests."""
if not self.choked:
self.choked = True
self._letgo()
def got_unchoke(self):
+ """Update the status and request any needed pieces."""
if self.choked:
self.choked = False
if self.interested:
@@ -131,12 +262,27 @@
self.last2 = clock()
def is_choked(self):
+ """Get the choked status of the connection.
+
+ @rtype: C{boolean}
+ @return: whether the peer is choking the connection
+
+ """
+
return self.choked
def is_interested(self):
+ """Get the interest in the peer.
+
+ @rtype: C{boolean}
+ @return: whether the peer is interesting
+
+ """
+
return self.interested
def send_interested(self):
+ """Send the interested message to the peer."""
if not self.interested:
self.interested = True
self.connection.send_interested()
@@ -144,11 +290,28 @@
self.last2 = clock()
def send_not_interested(self):
+ """Send the not interested message to the peer."""
if self.interested:
self.interested = False
self.connection.send_not_interested()
def got_piece(self, index, begin, piece):
+ """Process a received chunk.
+
+ Add the newly received chunk to the Storage, remove any oustanding
+ requests for it, and request more chunks from the peer.
+
+ @type index: C{int}
+ @param index: the piece index
+ @type begin: C{int}
+ @param begin: the offset within the piece
+ @type piece: C{string}
+ @param piece: the chunk
+ @rtype: C{boolean}
+ @return: whether the piece was accepted by the Storage (valid)
+
+ """
+
length = len(piece)
try:
self.active_requests.remove((index, begin, length))
@@ -187,6 +350,14 @@
return self.downloader.storage.do_I_have(index)
def _request_more(self, new_unchoke = False):
+ """Request more chunks from the peer.
+
+ @type new_unchoke: C{boolean}
+ @param new_unchoke: whether this request was the result of a recent
+ unchoke (optional, defaults to False)
+
+ """
+
assert not self.choked
if self.downloader.endgamemode:
self.fix_download_endgame(new_unchoke)
@@ -241,6 +412,14 @@
def fix_download_endgame(self, new_unchoke = False):
+ """Request more chunks from the peer in endgame mode.
+
+ @type new_unchoke: C{boolean}
+ @param new_unchoke: whether this request was the result of a recent
+ unchoke (optional, defaults to False)
+
+ """
+
if self.downloader.paused:
return
if len(self.active_requests) >= self._backlog(new_unchoke):
@@ -263,6 +442,15 @@
self.downloader.chunk_requested(length)
def got_have(self, index):
+ """Receive a Have message from the peer.
+
+ @type index: C{int}
+ @param index: the piece the peer now has
+ @rtype: C{boolean}
+ @return: whether the peer is now a seed
+
+ """
+
self.downloader.totalmeasure.update_rate(self.downloader.storage.piece_lengths[index])
self.peermeasure.update_rate(self.downloader.storage.piece_lengths[index])
if not self.have[index]:
@@ -285,6 +473,7 @@
return self.have.complete()
def _check_interests(self):
+ """Check if the peer is now interesting."""
if self.interested or self.downloader.paused:
return
for i in xrange(len(self.have)):
@@ -295,6 +484,15 @@
return
def got_have_bitfield(self, have):
+ """Receive a Bitfield message from the peer.
+
+ @type have: L{DebTorrent.bitfield.Bitfield}
+ @param have: the bitfield received from the peer
+ @rtype: C{boolean}
+ @return: whether the peer is a seed
+
+ """
+
if self.downloader.storage.am_I_complete() and have.complete():
if self.downloader.super_seeding:
self.connection.send_bitfield(have.tostring()) # be nice, show you're a seed too
@@ -318,9 +516,23 @@
return have.complete()
def get_rate(self):
+ """Get the current download rate from the peer.
+
+ @rtype: C{float}
+ @return: the peer's download rate
+
+ """
+
return self.measure.get_rate()
def is_snubbed(self):
+ """Check if the peer is snubbing the download.
+
+ @rtype: C{boolean}
+ @return: whether the peer is snubbing the connection
+
+ """
+
if ( self.interested and not self.choked
and clock() - self.last2 > self.downloader.snub_time ):
for index, begin, length in self.active_requests:
@@ -330,9 +542,109 @@
class Downloader:
+ """A collection of all single downloads.
+
+ @type storage: L{StorageWrapper.StorageWrapper}
+ @ivar storage: the StorageWrapper instance
+ @type picker: L{PiecePicker.PiecePicker}
+ @ivar picker: the PiecePicker instance
+ @type backlog: C{int}
+ @ivar backlog: the maximum number of requests for a single connection
+ @type max_rate_period: C{float}
+ @ivar max_rate_period: maximum amount of time to guess the current
+ rate estimate represents
+ @type measurefunc: C{method}
+ @ivar measurefunc: the method to call to add downloaded data to the
+ measurement of the download rate
+ @type totalmeasure: L{DebTorrent.CurrentRateMeasure.Measure}
+ @ivar totalmeasure: for measuring the total download rate from all peers
+ @type numpieces: C{int}
+ @ivar numpieces: total number of pieces in the download
+ @type chunksize: C{int}
+ @ivar chunksize: the number of bytes to query for per request
+ @type snub_time: C{float}
+ @ivar snub_time: seconds to wait for data to come in over a connection
+ before assuming it's semi-permanently choked
+ @type kickfunc: C{method}
+ @ivar kickfunc: method to call to kick a peer
+ @type banfunc: C{method}
+ @ivar banfunc: method to call to ban a peer
+ @type disconnectedseeds: C{dictionary}
+ @ivar disconnectedseeds: unknown
+ @type downloads: C{list} of C{SingleDownload}
+ @ivar downloads: unknown
+ @type perip: C{dictionary}
+ @ivar perip: unknown
+ @type gotbaddata: C{dictionary}
+ @ivar gotbaddata: unknown
+ @type kicked: C{dictionary}
+ @ivar kicked: unknown
+ @type banned: C{dictionary}
+ @ivar banned: unknown
+ @type kickbans_ok: C{boolean}
+ @ivar kickbans_ok: whether to automatically kick/ban peers that send
+ bad data
+ @type kickbans_halted: C{boolean}
+ @ivar kickbans_halted: unknown
+ @type super_seeding: C{boolean}
+ @ivar super_seeding: unknown
+ @type endgamemode: C{boolean}
+ @ivar endgamemode: unknown
+ @type endgame_queued_pieces: C{list}
+ @ivar endgame_queued_pieces: unknown
+ @type all_requests: C{list}
+ @ivar all_requests: unknown
+ @type discarded: C{long}
+ @ivar discarded: unknown
+ @type download_rate: C{float}
+ @ivar download_rate: the maximum rate to download at
+ @type bytes_requested: C{int}
+ @ivar bytes_requested: the number of bytes in oustanding requests
+ @type last_time: C{float}
+ @ivar last_time: the last time the queue limit was calculated
+ @type queued_out: C{dictionary}
+ @ivar queued_out: unknown
+ @type requeueing: C{boolean}
+ @ivar requeueing: unknown
+ @type paused: C{boolean}
+ @ivar paused: unknown
+
+ """
+
def __init__(self, storage, picker, backlog, max_rate_period,
numpieces, chunksize, measurefunc, snub_time,
kickbans_ok, kickfunc, banfunc):
+ """Initialize the instance.
+
+ @type storage: L{StorageWrapper.StorageWrapper}
+ @param storage: the StorageWrapper instance
+ @type picker: L{PiecePicker.PiecePicker}
+ @param picker: the PiecePicker instance
+ @type backlog: C{int}
+ @param backlog: the maximum number of requests for a single connection
+ @type max_rate_period: C{float}
+ @param max_rate_period: maximum amount of time to guess the current
+ rate estimate represents
+ @type numpieces: C{int}
+ @param numpieces: total number of pieces in the download
+ @type chunksize: C{int}
+ @param chunksize: the number of bytes to query for per request
+ @type measurefunc: C{method}
+ @param measurefunc: the method to call to add downloaded data to the
+ measurement of the download rate
+ @type snub_time: C{float}
+ @param snub_time: seconds to wait for data to come in over a connection
+ before assuming it's semi-permanently choked
+ @type kickbans_ok: C{boolean}
+ @param kickbans_ok: whether to automatically kick/ban peers that send
+ bad data
+ @type kickfunc: C{method}
+ @param kickfunc: method to call to kick a peer
+ @type banfunc: C{method}
+ @param banfunc: method to call to ban a peer
+
+ """
+
self.storage = storage
self.picker = picker
self.backlog = backlog
@@ -366,10 +678,24 @@
self.paused = False
def set_download_rate(self, rate):
+ """Set the maximum download rate for all downloads.
+
+ @type rate: C{float}
+ @param rate: maximum kB/s to download at (0 = no limit)
+
+ """
+
self.download_rate = rate * 1000
self.bytes_requested = 0
def queue_limit(self):
+ """Get the maximum number of bytes to request.
+
+ @rtype: C{int}
+ @return: the limit on the number of bytes to request
+
+ """
+
if not self.download_rate:
return 10e10 # that's a big queue!
t = clock()
@@ -388,11 +714,27 @@
return max(int(-self.bytes_requested/self.chunksize),0)
def chunk_requested(self, size):
+ """Add the new request size to the tally.
+
+ @type size: C{int}
+ @param size: the number of bytes that were requested
+
+ """
+
self.bytes_requested += size
external_data_received = chunk_requested
def make_download(self, connection):
+ """Create a new L{SingleDownload} instance for a new connection.
+
+ @type connection: unknown
+ @param connection: the connection that was received
+ @rtype: L{SingleDownload}
+ @return: the newly created SingleDownload instance
+
+ """
+
ip = connection.get_ip()
if self.perip.has_key(ip):
perip = self.perip[ip]
@@ -406,6 +748,13 @@
return d
def piece_flunked(self, index):
+ """Request a failed piece from other peers.
+
+ @type index: C{int}
+ @param index: the piece index that failed
+
+ """
+
if self.paused:
return
if self.endgamemode:
@@ -428,9 +777,23 @@
d.send_interested()
def has_downloaders(self):
+ """Get the number of active downloads.
+
+ @rtype: C{int}
+ @return: the number of active download connections
+
+ """
+
return len(self.downloads)
def lost_peer(self, download):
+ """Remove a lost peer from the collection of downloads.
+
+ @type download: L{SingleDownload}
+ @param download: the download peer that was lost
+
+ """
+
ip = download.ip
self.perip[ip].numconnections -= 1
if self.perip[ip].lastdownload == download:
@@ -439,7 +802,8 @@
if self.endgamemode and not self.downloads: # all peers gone
self._reset_endgame()
- def _reset_endgame(self):
+ def _reset_endgame(self):
+ """Stop the endgame mode."""
self.storage.reset_endgame(self.all_requests)
self.endgamemode = False
self.all_requests = []
@@ -447,6 +811,13 @@
def add_disconnected_seed(self, id):
+ """Save the time of a disconnected seed.
+
+ @type id: C{string}
+ @param id: the peer ID of the disconnected seed
+
+ """
+
# if not self.disconnectedseeds.has_key(id):
# self.picker.seed_seen_recently()
self.disconnectedseeds[id]=clock()
@@ -454,6 +825,13 @@
# def expire_disconnected_seeds(self):
def num_disconnected_seeds(self):
+ """Expire old disconnected seeds and calculate the recent number.
+
+ @rtype: C{int}
+ @return: the number of recently seen disconnected seeds
+
+ """
+
# first expire old ones
expired = []
for id,t in self.disconnectedseeds.items():
@@ -467,12 +845,26 @@
# it should be scheduled to run every minute or two.
def _check_kicks_ok(self):
+ """Check whether peers can be kicked for bad data.
+
+ @rtype: C{boolean}
+ @return: whether it is OK to kick peers
+
+ """
+
if len(self.gotbaddata) > 10:
self.kickbans_ok = False
self.kickbans_halted = True
return self.kickbans_ok and len(self.downloads) > 2
def try_kick(self, download):
+ """If allowed, kick a peer.
+
+ @type download: L{SingleDownload}
+ @param download: the peer's download connection
+
+ """
+
if self._check_kicks_ok():
download.guard.download = None
ip = download.ip
@@ -482,6 +874,13 @@
self.kickfunc(download.connection)
def try_ban(self, ip):
+ """If allowed, ban a peer.
+
+ @type ip: C{string}
+ @param ip: the IP address of the peer
+
+ """
+
if self._check_kicks_ok():
self.banfunc(ip)
self.banned[ip] = self.perip[ip].peerid
@@ -489,9 +888,22 @@
del self.kicked[ip]
def set_super_seed(self):
+ """Enable super-seed mode."""
self.super_seeding = True
def check_complete(self, index):
+ """Check whether the download is complete.
+
+ If it is complete, send the piece to the connected seeds and then
+ disconnect them.
+
+ @type index: C{int}
+ @param index: the last received piece
+ @rtype: C{boolean}
+ @return: whether the download is complete
+
+ """
+
if self.endgamemode and not self.all_requests:
self.endgamemode = False
if self.endgame_queued_pieces and not self.endgamemode:
@@ -507,10 +919,25 @@
return False
def too_many_partials(self):
+ """Check whether there are too many outstanding incomplete pieces.
+
+ @rtype: C{boolean}
+ @return: if the number of incomplete pieces is greater than half the
+ number of connected downloads
+
+ """
+
return len(self.storage.dirty) > (len(self.downloads)/2)
def cancel_piece_download(self, pieces):
+ """Cancel any outstanding requests for the pieces.
+
+ @type pieces: C{list} of C{int}
+ @param pieces: the list of pieces to cancel
+
+ """
+
if self.endgamemode:
if self.endgame_queued_pieces:
for piece in pieces:
@@ -542,6 +969,13 @@
d._check_interests()
def requeue_piece_download(self, pieces = []):
+ """Request more pieces.
+
+ @type pieces: C{list} of C{int}
+ @param pieces: the list of pieces to requeue
+
+ """
+
if self.endgame_queued_pieces:
for piece in pieces:
if not piece in self.endgame_queued_pieces:
@@ -563,6 +997,7 @@
d._request_more()
def start_endgame(self):
+ """Switch to endgame mode."""
assert not self.endgamemode
self.endgamemode = True
assert not self.all_requests
@@ -576,6 +1011,13 @@
d.fix_download_endgame()
def pause(self, flag):
+ """Pause or unpause the download.
+
+ @type flag: C{boolean}
+ @param flag: whether to pause of unpause.
+
+ """
+
self.paused = flag
if flag:
for d in self.downloads:
Modified: debtorrent/trunk/DebTorrent/BT1/Storage.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/BT1/Storage.py?rev=58&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/BT1/Storage.py (original)
+++ debtorrent/trunk/DebTorrent/BT1/Storage.py Tue May 22 07:56:32 2007
@@ -98,9 +98,6 @@
@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)
Modified: debtorrent/trunk/DebTorrent/BTcrypto.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/BTcrypto.py?rev=58&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/BTcrypto.py (original)
+++ debtorrent/trunk/DebTorrent/BTcrypto.py Tue May 22 07:56:32 2007
@@ -2,8 +2,21 @@
# based on code by Uoti Urpala
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
+
+"""Encrypted communication support.
+
+ at type KEY_LENGTH: C{int}
+ at var KEY_LENGTH: the length of the keys to generate
+ at type DH_PRIME: C{long}
+ at var DH_PRIME: a very large prime number
+ at type PAD_MAX: unknown
+ at var PAD_MAX: unknown
+ at type DH_BYTES: C{int}
+ at var DH_BYTES: the number of bytes to use for key lengths
+
+"""
from __future__ import generators # for python 2.2
from random import randrange,randint,seed
@@ -32,15 +45,77 @@
DH_BYTES = 96
def bytetonum(x):
+ """Convert a long number in a string to a number.
+
+ @type x: C{string}
+ @param x: the data to convert
+ @rtype: C{long}
+ @return: the converted data
+
+ """
+
return long(x.encode('hex'), 16)
def numtobyte(x):
+ """Convert a very large number to a string.
+
+ @type x: C{long}
+ @param x: the number to convert
+ @rtype: C{string}
+ @return: the string representation of the number
+
+ """
+
x = hex(x).lstrip('0x').rstrip('Ll')
x = '0'*(192 - len(x)) + x
return x.decode('hex')
class Crypto:
+ """
+
+ @type initiator: C{boolean}
+ @ivar initiator: whether the connection was initiated locally
+ @type disable_crypto: C{boolean}
+ @ivar disable_crypto: whether crypto has been disabled
+ @type privkey: C{long}
+ @ivar privkey: randomly generated private key
+ @type pubkey: C{string}
+ @ivar pubkey: public key corresponding to the private key
+ @type keylength: C{int}
+ @ivar keylength: the key length to use
+ @type _VC_pattern: unknown
+ @ivar _VC_pattern: unknown
+ @type S: unknown
+ @ivar S: unknown
+ @type block3a: unknown
+ @ivar block3a: unknown
+ @type block3bkey: unknown
+ @ivar block3bkey: unknown
+ @type block3b: unknown
+ @ivar block3b: unknown
+ @type encrypt: C{method}
+ @ivar encrypt: the method to call to encrypt data
+ @type decrypt: C{method}
+ @ivar decrypt: the method to call to decrypt data
+ @type _read: C{method}
+ @ivar _read: the method to call to read decrypted data
+ @type _write: C{method}
+ @ivar _write: the method to call to write encrypted data
+
+ """
+
def __init__(self, initiator, disable_crypto = False):
+ """Initialize the instance.
+
+ @type initiator: C{boolean}
+ @param initiator: whether the connection was initiated locally
+ @type disable_crypto: C{boolean}
+ @param disable_crypto: whether crypto has been disabled
+ (optional, default is False)
+ @raise NotImplementedError: if encryption is not installed
+
+ """
+
self.initiator = initiator
self.disable_crypto = disable_crypto
if not disable_crypto and not CRYPTO_OK:
@@ -51,17 +126,42 @@
self._VC_pattern = None
def received_key(self, k):
+ """Process a received key.
+
+ @type k: C{string}
+ @param k: the key that was received
+
+ """
+
self.S = numtobyte(pow(bytetonum(k), self.privkey, DH_PRIME))
self.block3a = sha('req1'+self.S).digest()
self.block3bkey = sha('req3'+self.S).digest()
self.block3b = None
def _gen_block3b(self, SKEY):
+ """
+
+ @type SKEY: C{string}
+ @param SKEY: unknown
+
+ """
+
a = sha('req2'+SKEY).digest()
return ''.join([ chr(ord(a[i])^ord(self.block3bkey[i]))
for i in xrange(20) ])
def test_skey(self, s, SKEY):
+ """Check that the encoding matches the encoded value.
+
+ @type s: C{string}
+ @param s: unknown
+ @type SKEY: C{string}
+ @param SKEY: unknown
+ @rtype: C{boolean}
+ @return: whether the encoding of SKEY matches s
+
+ """
+
block3b = self._gen_block3b(SKEY)
if block3b != s:
return False
@@ -71,6 +171,13 @@
return True
def set_skey(self, SKEY):
+ """
+
+ @type SKEY: C{string}
+ @param SKEY: unknown
+
+ """
+
if not self.block3b:
self.block3b = self._gen_block3b(SKEY)
crypta = ARC4.new(sha('keyA'+self.S+SKEY).digest())
@@ -85,22 +192,60 @@
self.decrypt('x'*1024)
def VC_pattern(self):
+ """Unknown.
+
+ @rtype: unknown
+ @return: unknown
+
+ """
if not self._VC_pattern:
self._VC_pattern = self.decrypt('\x00'*8)
return self._VC_pattern
def read(self, s):
+ """Decrypt and pass on the decrypted value.
+
+ @type s: C{string}
+ @param s: the string to decrypt
+
+ """
+
self._read(self.decrypt(s))
def write(self, s):
+ """Encrypt and pass on the encrypted value.
+
+ @type s: C{string}
+ @param s: the string to encrypt
+
+ """
+
self._write(self.encrypt(s))
def setrawaccess(self, _read, _write):
+ """Setup the methods to call for reading and writing.
+
+ @type _read: C{method}
+ @param _read: the method to call for reading decrypted data
+ @type _write: C{method}
+ @param _write: the method to call for writing encrypted data
+
+ """
+
self._read = _read
self._write = _write
def padding(self):
+ """Generate a random amount of random padding.
+
+ Generates random bytes, with a random length from 16 to L{PAD_MAX}.
+
+ @rtype: C{string}
+ @return: the randomly generated padding
+
+ """
+
return urandom(randrange(PAD_MAX-16)+16)
Modified: debtorrent/trunk/DebTorrent/ConfigDir.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/ConfigDir.py?rev=58&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/ConfigDir.py (original)
+++ debtorrent/trunk/DebTorrent/ConfigDir.py Tue May 22 07:56:32 2007
@@ -1,8 +1,21 @@
#written by John Hoffman
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
+
+"""Manage configuration and cache files.
+
+ at type DIRNAME: C{string}
+ at var DIRNAME: the directory name to use for storing config files
+ at type hexchars: C{string}
+ at var hexchars: the 16 hex characters, in order
+ at type hexmap: C{list}
+ at var hexmap: a mapping from characters to the hex repesentation of the character
+ at type revmap: C{dictionary}
+ at var revmap: the reverse of L{hexmap}
+
+"""
from inifile import ini_write, ini_read
from bencode import bencode, bdecode
@@ -29,16 +42,45 @@
revmap[x] = chr(i)
def tohex(s):
+ """Convert a string to hex representation.
+
+ @type s: C{string}
+ @param s: the string to convert
+ @rtype: C{string}
+ @return: the converted string
+
+ """
+
r = []
for c in s:
r.append(hexmap[ord(c)])
return ''.join(r)
def unhex(s):
+ """Convert a hex representation back to a string.
+
+ @type s: C{string}
+ @param s: the hex representation of a string
+ @rtype: C{string}
+ @return: the original string
+
+ """
+
r = [ revmap[s[x:x+2]] for x in xrange(0, len(s), 2) ]
return ''.join(r)
-def copyfile(oldpath, newpath): # simple file copy, all in RAM
+def copyfile(oldpath, newpath):
+ """Simple file copy, all in RAM.
+
+ @type oldpath: C{string}
+ @param oldpath: the file name to copy from
+ @type newpath: C{string}
+ @param newpath: the file name to copy to
+ @rtype: C{boolean}
+ @return: whether the copy was successful
+
+ """
+
try:
f = open(oldpath,'rb')
r = f.read()
@@ -64,10 +106,46 @@
class ConfigDir:
+ """Manage configuration and cache files.
+
+ @type config_type: C{string}
+ @ivar config_type: the extension to include in the saved files' names
+ @type dir_root: C{string}
+ @ivar dir_root: the root directory to save config files in
+ @type dir_torrentcache: C{string}
+ @ivar dir_torrentcache: the directory to save torrent files in
+ @type dir_datacache: C{string}
+ @ivar dir_datacache: the directory to save stopped torrent's state in
+ @type dir_piececache: C{string}
+ @ivar dir_piececache: the directory to store temporary piece files in
+ @type configfile: C{string}
+ @ivar configfile: the file name for the saved configuration data
+ @type statefile: C{string}
+ @ivar statefile: the file name for the saved state
+ @type TorrentDataBuffer: C{dictionary}
+ @ivar TorrentDataBuffer: unknown
+ @type config: C{dictionary}
+ @ivar config: the current configuration variables
+
+ @group Config Handling: setDefaults, checkConfig, loadConfig, saveConfig, getConfig
+ @group State: getState, saveState
+ @group Torrent Files: getTorrents, getTorrentVariations, getTorrent, writeTorrent
+ @group Torrent Data: getTorrentData, writeTorrentData, deleteTorrentData, getPieceDir
+ @group Expire Cache: deleteOldCacheData, deleteOldTorrents
+
+ """
###### INITIALIZATION TASKS ######
def __init__(self, config_type = None):
+ """Initialize the instance, create directories and file names.
+
+ @type config_type: C{string}
+ @param config_type: the extension to include in the saved files' names
+ (optional, default is to use no extension)
+
+ """
+
self.config_type = config_type
if config_type:
config_ext = '.'+config_type
@@ -75,6 +153,15 @@
config_ext = ''
def check_sysvars(x):
+ """Check a system variable to see if it expands to something.
+
+ @type x: C{string}
+ @param x: the system variable to check
+ @rtype: C{string}
+ @return: the expanded variable, or None if it doesn't expand
+
+ """
+
y = os.path.expandvars(x)
if y != x and os.path.isdir(y):
return y
@@ -116,15 +203,39 @@
###### CONFIG HANDLING ######
def setDefaults(self, defaults, ignore=[]):
+ """Set the default values to use for the configuration.
+
+ @type defaults: C{dictionary}
+ @param defaults: the default config values
+ @type ignore: C{list}
+ @param ignore: the keys in the defaults to ignore
+ (optional, default is to ignore none of them)
+
+ """
+
self.config = defaultargs(defaults)
for k in ignore:
if self.config.has_key(k):
del self.config[k]
def checkConfig(self):
+ """Check if a config file already exists.
+
+ @rtype: C{boolean}
+ @return: whether the config file exists
+
+ """
+
return os.path.exists(self.configfile)
def loadConfig(self):
+ """Load a configuration from a config file.
+
+ @rtype: C{dictionary}
+ @return: the loaded configuration variables
+
+ """
+
try:
r = ini_read(self.configfile)['']
except:
@@ -148,6 +259,16 @@
return self.config
def saveConfig(self, new_config = None):
+ """Sets and writes to the file the new configuration.
+
+ @type new_config: C{dictionary}
+ @param new_config: the configuration to set and write
+ (optional, default is to use the previously set one)
+ @rtype: boolean
+ @return: whether writing to the file was successful
+
+ """
+
if new_config:
for k,v in new_config.items():
if self.config.has_key(k):
@@ -161,12 +282,27 @@
return False
def getConfig(self):
+ """Get the current configuration variables.
+
+ @rtype: C{dictionary}
+ @return: the current configuration variables
+
+ """
+
return self.config
###### STATE HANDLING ######
def getState(self):
+ """Get the state from the state file.
+
+ @rtype: unknown
+ @return: the previosuly saved state, or None if there was no previously
+ saved state
+
+ """
+
try:
f = open(self.statefile,'rb')
r = f.read()
@@ -183,6 +319,15 @@
return r
def saveState(self, state):
+ """Saves the state to the state file.
+
+ @type state: unknown
+ @param state: the state to save
+ @rtype: boolean
+ @return: whether the saving was successful
+
+ """
+
try:
f = open(self.statefile,'wb')
f.write(bencode(state))
@@ -199,6 +344,13 @@
###### TORRENT HANDLING ######
def getTorrents(self):
+ """Get a list of the torrents that have cache data.
+
+ @rtype: C{list} of C{string}
+ @return: the torrent hashes found
+
+ """
+
d = {}
for f in os.listdir(self.dir_torrentcache):
f = os.path.basename(f)
@@ -210,6 +362,15 @@
return d.keys()
def getTorrentVariations(self, t):
+ """Get the torrent variations in the cache data for a given hash.
+
+ @type t: C{string}
+ @param t: the torrent hash to check for
+ @rtype: C{list} of C{int}
+ @return: the variations of the hash found
+
+ """
+
t = tohex(t)
d = []
for f in os.listdir(self.dir_torrentcache):
@@ -224,6 +385,17 @@
return d
def getTorrent(self, t, v = -1):
+ """Get the torrent data for the hash.
+
+ @type t: C{string}
+ @param t: the torrent hash to lookup
+ @type v: C{int}
+ @param v: the variation to get (optional, default is the largest)
+ @rtype: C{dictionary}
+ @return: the torrent metainfo found
+
+ """
+
t = tohex(t)
if v == -1:
v = max(self.getTorrentVariations(t)) # potential exception
@@ -241,6 +413,20 @@
return r
def writeTorrent(self, data, t, v = -1):
+ """Save the torrent data.
+
+ @type data: C{dictionary}
+ @param data: the torrent metainfo
+ @type t: C{string}
+ @param t: the hash of the torrent metainfo
+ @type v: C{int}
+ @param v: the variation to save as, or None for no variation
+ (optional, default is the next after the largest)
+ @rtype: C{int}
+ @return: the variation used, or None if the write failed
+
+ """
+
t = tohex(t)
if v == -1:
try:
@@ -264,6 +450,15 @@
###### TORRENT DATA HANDLING ######
def getTorrentData(self, t):
+ """Retrieve cached data for a torrent.
+
+ @type t: C{string}
+ @param t: the info hash to retrieve cached data for
+ @rtype: C{dictionary}
+ @return: the bdecoded cached data
+
+ """
+
if self.TorrentDataBuffer.has_key(t):
return self.TorrentDataBuffer[t]
t = os.path.join(self.dir_datacache,tohex(t))
@@ -282,6 +477,17 @@
return r
def writeTorrentData(self, t, data):
+ """Write cached data for a torrent.
+
+ @type t: C{string}
+ @param t: the info hash to write cached data for
+ @type data: C{dictionary}
+ @param data: the data to cache
+ @rtype: C{boolean}
+ @return: whether the write was successful
+
+ """
+
self.TorrentDataBuffer[t] = data
try:
f = open(os.path.join(self.dir_datacache,tohex(t)),'wb')
@@ -298,18 +504,46 @@
return success
def deleteTorrentData(self, t):
+ """Delete the cached data for a torrent.
+
+ @type t: C{string}
+ @param t: the info hash to delete the cached data of
+
+ """
+
try:
os.remove(os.path.join(self.dir_datacache,tohex(t)))
except:
pass
def getPieceDir(self, t):
+ """Get the directory to save temporary pieces for a torrent.
+
+ @type t: C{string}
+ @param t: the info hash to get the piece cache data of
+ @rtype: C{string}
+ @return: the directory to save temporary pieces in
+
+ """
+
return os.path.join(self.dir_piececache,tohex(t))
###### EXPIRATION HANDLING ######
def deleteOldCacheData(self, days, still_active = [], delete_torrents = False):
+ """Delete old cache data after a period of time.
+
+ @type days: C{int}
+ @param days: the number of days to delete cached data after
+ @type still_active: C{list} of C{string}
+ @param still_active: the hashes of torrents that are still running
+ (optional, default is to delete all torrent's cached data)
+ @type delete_torrents: C{boolean}
+ @param delete_torrents: whether to delete the torrent files as well
+
+ """
+
if not days:
return
exptime = time() - (days*24*3600)
@@ -380,4 +614,14 @@
def deleteOldTorrents(self, days, still_active = []):
+ """Delete old cached data and torrents after a period of time.
+
+ @type days: C{int}
+ @param days: the number of days to delete cached data after
+ @type still_active: C{list} of C{string}
+ @param still_active: the hashes of torrents that are still running
+ (optional, default is to delete all torrent's cached data)
+
+ """
+
self.deleteOldCacheData(days, still_active, True)
Modified: debtorrent/trunk/DebTorrent/ConnChoice.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/ConnChoice.py?rev=58&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/ConnChoice.py (original)
+++ debtorrent/trunk/DebTorrent/ConnChoice.py Tue May 22 07:56:32 2007
@@ -1,7 +1,17 @@
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Sets the connection choices that are available.
+
+ at type connChoices: C{list} of C{dictionary}
+ at var connChoiceList: Details for each type of connection. Includes limits
+ for each type on the upload rate and number of connections.
+ at type connChoiceList: C{list} of C{string}
+ at var connChoiceList: the names of the connections that are available
+
+"""
connChoices=(
{'name':'automatic',
Modified: debtorrent/trunk/DebTorrent/CurrentRateMeasure.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/CurrentRateMeasure.py?rev=58&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/CurrentRateMeasure.py (original)
+++ debtorrent/trunk/DebTorrent/CurrentRateMeasure.py Tue May 22 07:56:32 2007
@@ -1,13 +1,42 @@
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Measuring rates of download and upload."""
from clock import clock
class Measure:
+ """The measurement of one rate.
+
+ @type max_rate_period: C{float}
+ @ivar max_rate_period: maximum amount of time to guess the current rate
+ estimate represents
+ @type ratesince: C{float}
+ @ivar ratesince: the oldest time the rate estimate is for
+ @type last: C{float}
+ @ivar last: the last time the rate was updated
+ @type rate: C{float}
+ @ivar rate: the latest calculated rate
+ @type total: C{long}
+ @ivar total: the total amount that went in to calculating the rate
+
+ """
+
def __init__(self, max_rate_period, fudge = 1):
+ """Initialize the measurement.
+
+ @type max_rate_period: C{float}
+ @param max_rate_period: maximum amount of time to guess the current
+ rate estimate represents
+ @type fudge: C{int}
+ @param fudge: time equivalent of writing to kernel-level TCP buffer,
+ for rate adjustment (optional, defaults to 1)
+
+ """
+
self.max_rate_period = max_rate_period
self.ratesince = clock() - fudge
self.last = self.ratesince
@@ -15,6 +44,13 @@
self.total = 0l
def update_rate(self, amount):
+ """Update the rate with new data.
+
+ @type amount: C{long}
+ @param amount: the new data to add into the rate calculation
+
+ """
+
self.total += amount
t = clock()
self.rate = (self.rate * (self.last - self.ratesince) +
@@ -24,17 +60,48 @@
self.ratesince = t - self.max_rate_period
def get_rate(self):
+ """Get the current rate measurement.
+
+ @rtype: C{float}
+ @return: the current rate
+
+ """
+
self.update_rate(0)
return self.rate
def get_rate_noupdate(self):
+ """Get the current rate measurement without updating it.
+
+ @rtype: C{float}
+ @return: the current rate
+
+ """
+
return self.rate
def time_until_rate(self, newrate):
+ """Calculate how long until the rate drops to the target.
+
+ @type newrate: C{float}
+ @param newrate: the target rate
+ @rtype: C{float}
+ @return: the number of seconds until the rate decreases to the target
+ rate, or 0 if it's already there (or below it)
+
+ """
+
if self.rate <= newrate:
return 0
t = clock() - self.ratesince
return ((self.rate * t) / newrate) - t
def get_total(self):
+ """Get the total amount used to calculate the rate..
+
+ @rtype: C{float}
+ @return: the total amount
+
+ """
+
return self.total
Modified: debtorrent/trunk/DebTorrent/bitfield.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/bitfield.py?rev=58&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/bitfield.py (original)
+++ debtorrent/trunk/DebTorrent/bitfield.py Tue May 22 07:56:32 2007
@@ -1,8 +1,21 @@
# Written by Bram Cohen, Uoti Urpala, and John Hoffman
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
+
+"""Manage creation and modification of bitfields.
+
+ at type lookup_table: C{list} of C{tuple} of C{boolean}
+ at var lookup_table: A lookup table for converting characters to a tuple of
+ booleans. Each tuple has 8 booleans, which are true for all the bits
+ in the character that are set.
+ at type reverse_lookup_table: C{dictionary}
+ at var reverse_lookup_table: A reverse mapping of lookup_table. The keys are
+ the tuples of 8 booleans, which map to values that are the characters
+ the tuples were created from.
+
+"""
try:
True
@@ -18,6 +31,15 @@
negsum = lambda a: reduce(lambda x,y: x+(not y), a, 0)
def _int_to_booleans(x):
+ """Convert an integer to a list of booleans.
+
+ @type x: C{int}
+ @param x: the integer to convert
+ @rtype: C{tuple} of C{list} of C{boolean}
+ @return: the list of booleans
+
+ """
+
r = []
for i in range(8):
r.append(bool(x & 0x80))
@@ -33,7 +55,32 @@
class Bitfield:
+ """ A bitfield, a indicating the pieces a peer has.
+
+ @type length: C{int}
+ @ivar length: the length of the bitfield
+ @type array: C{list} of C{boolean}
+ @ivar array: the bitfield
+ @type numfalse: C{int}
+ @ivar numfalse: the number of false entries in the bitfield
+
+ """
+
def __init__(self, length = None, bitstring = None, copyfrom = None):
+ """
+
+ @type length: C{int}
+ @param length: the length of the bitfield to create
+ (optional, if missing use length of copyfrom)
+ @type bitstring: C{string}
+ @param bitstring: the bitfield string to initialize the bitfield from
+ (optional, default is to initialize all to false)
+ @type copyfrom: L{Bitfield}
+ @param copyfrom: another bitfield to make a copy of
+ (optional, default is to create a new empty one)
+
+ """
+
if copyfrom is not None:
self.length = copyfrom.length
self.array = copyfrom.array[:]
@@ -61,17 +108,49 @@
self.numfalse = length
def __setitem__(self, index, val):
+ """Set one of the bitfield entries.
+
+ @type index: C{int}
+ @param index: the index to set
+ @type val: C{boolean}
+ @param val: the value to set to
+
+ """
+
val = bool(val)
self.numfalse += self.array[index]-val
self.array[index] = val
def __getitem__(self, index):
+ """Get one of the bitfield entries.
+
+ @type index: C{int}
+ @param index: the index to get
+ @rtype: C{boolean}
+ @return: the value of the bitfield entry
+
+ """
+
return self.array[index]
def __len__(self):
+ """Get the length of the bitfield.
+
+ @rtype:C{int}
+ @return: the length of the bitfield
+
+ """
+
return self.length
def tostring(self):
+ """Convert the bitfield to a string
+
+ @rtype: C{string}
+ @return: the bitfield represented as a string
+
+ """
+
booleans = self.array
t = reverse_lookup_table
s = len(booleans) % 8
@@ -81,10 +160,18 @@
return ''.join(r)
def complete(self):
+ """Check if the bitfield is all true.
+
+ @rtype: C{boolean}
+ @return: whether the bitfield is complete (all true)
+
+ """
+
return not self.numfalse
def test_bitfield():
+ """Test the bitfield implementation."""
try:
x = Bitfield(7, 'ab')
assert False
Modified: debtorrent/trunk/DebTorrent/clock.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/clock.py?rev=58&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/clock.py (original)
+++ debtorrent/trunk/DebTorrent/clock.py Tue May 22 07:56:32 2007
@@ -1,8 +1,25 @@
# Written by John Hoffman
# Modified by Cameron Dale
# see LICENSE.txt for license information
+#
+# $Id$
-# $Id$
+"""Implement wall clock time for Unix systems.
+
+This module implements a clock() function that will return a non-decreasing
+time, regardless of the system it is called on. This is necessary for Unix
+systems, whose clock() function instead returns the current processor time.
+
+ at type _MAXFORWARD: C{int}
+ at var _MAXFORWARD: the maximum number of seconds to allow the clock
+ to move forward
+ at type _FUDGE: C{int}
+ at var _FUDGE: the fudged time change to use if the clock moved forward more
+ than L{_MAXFORWARD}, or if the clock moved back
+ at type _RTIME: L{RelativeTime}
+ at var _RTIME: the RelativeTime instance to use
+
+"""
from time import *
import sys
@@ -11,11 +28,35 @@
_FUDGE = 1
class RelativeTime:
+ """Calculate relative time on Unix systems.
+
+ @type time: C{float}
+ @ivar time: the last time value measured
+ @type offset: C{float}
+ @ivar offset: the offset to use from the current time values due to
+ any changes made in the clock while the program was running
+
+ """
+
def __init__(self):
+ """Initialize the time values."""
self.time = time()
self.offset = 0
def get_time(self):
+ """Calculate a non-decreasing time.
+
+ Uses the time() function to calculate non-decreasing time values.
+ Checks to make sure the time values are non-decreasing, and also
+ don't change by more than L{_MAXFORWARD} seconds within a reading.
+ These could occur if the system clock was changed during the running
+ of the program.
+
+ @rtype: C{float}
+ @return: the current time
+
+ """
+
t = time() + self.offset
if t < self.time or t > self.time + _MAXFORWARD:
self.time += _FUDGE
@@ -27,4 +68,14 @@
if sys.platform != 'win32':
_RTIME = RelativeTime()
def clock():
+ """Override the clock() function for Unix systems.
+
+ This function will return a non-decreasing measure of the current
+ time. This is only used on Unix systems. On Windows systems, the
+ clock() function from the C{time} module will be used.
+
+ @rtype: C{float}
+ @return: the relative time from the L{RelativeTime} instance
+
+ """
return _RTIME.get_time()
Modified: debtorrent/trunk/DebTorrent/download_bt1.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/download_bt1.py?rev=58&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/download_bt1.py (original)
+++ debtorrent/trunk/DebTorrent/download_bt1.py Tue May 22 07:56:32 2007
@@ -1,8 +1,19 @@
# Written by Bram Cohen
# Modified by Cameron Dale
# see LICENSE.txt for license information
-
+#
# $Id$
+
+"""Manage a single download.
+
+ at type DEBUG: C{boolean}
+ at var DEBUG: whether to print debugging information
+ at type defaults: C{list} of C{tuple}
+ at var defaults: the default configuration variables, including descriptions
+ at type argslistheader: C{string}
+ at var argslistheader: the header to print before the default config
+
+"""
from zurllib import urlopen
from urlparse import urlparse
@@ -192,12 +203,46 @@
def _failfunc(x):
+ """Default function to use for printing error messages.
+
+ @type x: C{string}
+ @param x: the error message to print
+
+ """
+
print x
-# old-style downloader
def download(params, filefunc, statusfunc, finfunc, errorfunc, doneflag, cols,
pathFunc = None, presets = {}, exchandler = None,
failed = _failfunc, paramfunc = None):
+ """The old-style downloader (no longer used).
+
+ @type params: unknown
+ @param params: unknown
+ @type filefunc: unknown
+ @param filefunc: unknown
+ @type statusfunc: unknown
+ @param statusfunc: unknown
+ @type finfunc: unknown
+ @param finfunc: unknown
+ @type errorfunc: unknown
+ @param errorfunc: unknown
+ @type doneflag: unknown
+ @param doneflag: unknown
+ @type cols: unknown
+ @param cols: unknown
+ @type pathFunc: unknown
+ @param pathFunc: unknown
+ @type presets: unknown
+ @param presets: unknown
+ @type exchandler: unknown
+ @param exchandler: unknown
+ @type failed: unknown
+ @param failed: unknown
+ @type paramfunc: unknown
+ @param paramfunc: unknown
+
+ """
try:
config = parse_params(params, presets)
@@ -266,6 +311,18 @@
def parse_params(params, presets = {}):
+ """Parse the command-line parameters.
+
+ @type params: C{list} of C{string}
+ @param params: the command-line parameters
+ @type presets: C{dictionary}
+ @param presets: the preset values to use (optional)
+ @rtype: C{dictionary}
+ @return: the configuration variables
+ @raise ValueError: if the parameters are not properly specified
+
+ """
+
if len(params) == 0:
return None
config, args = parseargs(params, defaults, 0, 1, presets = presets)
@@ -286,10 +343,45 @@
def get_usage(defaults = defaults, cols = 100, presets = {}):
+ """Print the usage information for the program.
+
+ @type defaults: C{list} of C{tuple}
+ @param defaults: the default configuration variables
+ (optional, default is to use L{defaults})
+ @type cols: C{int}
+ @param cols: the width of the print out (optional, default is 100)
+ @type presets: C{dictionary}
+ @param presets: the preset values to use (optional)
+
+ """
+
return (argslistheader + formatDefinitions(defaults, cols, presets))
def get_response(file, url, status_to_download, errorfunc):
+ """Get the response data from a metainfo or Packages file.
+
+ First checks to see if the data is in the Packages file format, and
+ returns the extracted response data if it is. Otherwise, assumes it is
+ a metainfo file and tries to get the response data from it.
+
+ @type file: C{string}
+ @param file: the file name to use, or None to indicate that the url is
+ to be used
+ @type url: C{string}
+ @param url: the URL to download the metainfo file from
+ @type status_to_download: C{int}
+ @param status_to_download: determines which packages to download based on
+ /var/lib/dpkg/status (0 = disabled [download all or use --priority],
+ 1 = download updated versions of installed packages,
+ 2 = download all installed packages)
+ @type errorfunc: C{function}
+ @param errorfunc: the function to use to print any error messages
+ @rtype: C{dictionary}
+ @return: the metainfo data
+
+ """
+
(response, priority) = get_packages(file, url, status_to_download, errorfunc)
if response:
try:
@@ -346,6 +438,25 @@
return (response, None)
def get_packages(file, url, status_to_download, errorfunc):
+ """Extract the response data from a Packages file.
+
+ @type file: C{string}
+ @param file: the file name to use, or None to indicate that the url is
+ to be used
+ @type url: C{string}
+ @param url: the URL to download the metainfo file from
+ @type status_to_download: C{int}
+ @param status_to_download: determines which packages to download based on
+ /var/lib/dpkg/status (0 = disabled [download all or use --priority],
+ 1 = download updated versions of installed packages,
+ 2 = download all installed packages)
+ @type errorfunc: C{function}
+ @param errorfunc: the function to use to print any error messages
+ @rtype: C{dictionary}
+ @return: the metainfo data
+
+ """
+
encoding = None
# if params.has_key('filesystem_encoding'):
# encoding = params['filesystem_encoding']
@@ -480,9 +591,151 @@
class BT1Download:
+ """Manage a single download.
+
+ @type statusfunc: C{method}
+ @ivar statusfunc: the method to call to print status updates
+ @type finfunc: C{method}
+ @ivar finfunc: the method to call when the download is completed
+ @type errorfunc: C{method}
+ @ivar errorfunc: the method to call when an error occurs
+ @type excfunc: C{method}
+ @ivar excfunc: the method to call when an exception occurs
+ @type doneflag: C{threading.Event}
+ @ivar doneflag: the flag that indicates when the program is to be shutdown
+ @type config: C{dictionary}
+ @ivar config: the configuration variables
+ @type response: C{dictionary}
+ @ivar response: the response data from the metainfo file
+ @type infohash: C{string}
+ @ivar infohash: the hash of the info from the response data
+ @type myid: C{string}
+ @ivar myid: the peer ID to use
+ @type rawserver: L{Rawserver.Rawserver}
+ @ivar rawserver: the server controlling the program
+ @type port: C{int}
+ @ivar port: the port being listened to
+ @type info: C{dictionary}
+ @ivar info: the info data from the response data
+ @type pieces: C{list} of C{string}
+ @ivar pieces: the hashes of the pieces
+ @type piece_lengths: C{list} of C{int}
+ @ivar piece_lengths: the lengths of the pieces
+ @type len_pieces: C{int}
+ @ivar len_pieces: the number of pieces
+ @type status_priority: C{dictionary}
+ @ivar status_priority: the file priorities from the dpkg/status file
+ @type argslistheader: C{string}
+ @ivar argslistheader: the header to print before the default config
+ @type unpauseflag: C{threading.Event}
+ @ivar unpauseflag: the flag to unset to pause the download
+ @type downloader: L{BT1.Downloader.Downloader}
+ @ivar downloader: the Downloader instance
+ @type storagewrapper: L{BT1.StorageWrapper.StorageWrapper}
+ @ivar storagewrapper: the StorageWrapper instance
+ @type fileselector: L{BT1.FileSelector.FileSelector}
+ @ivar fileselector: the FileSelector instance
+ @type super_seeding_active: C{boolean}
+ @ivar super_seeding_active: whether the download is in super-seed mode
+ @type filedatflag: C{threading.Event}
+ @ivar filedatflag: unknown
+ @type spewflag: C{threading.Event}
+ @ivar spewflag: unknown
+ @type superseedflag: C{threading.Event}
+ @ivar superseedflag: unknown
+ @type whenpaused: unknown
+ @ivar whenpaused: unknown
+ @type finflag: C{threading.Event}
+ @ivar finflag: unknown
+ @type rerequest: unknown
+ @ivar rerequest: unknown
+ @type tcp_ack_fudge: C{float}
+ @ivar tcp_ack_fudge: the fraction of TCP ACK download overhead to add to
+ upload rate calculations
+ @type selector_enabled: C{boolean}
+ @ivar selector_enabled: whether to enable the file selector and fast resume function
+ @type appdataobj: L{ConfigDir.ConfigDir}
+ @ivar appdataobj: the configuration and cache directory manager
+ @type excflag: C{threading.Event}
+ @ivar excflag: unknown
+ @type failed: unknown
+ @ivar failed: unknown
+ @type checking: unknown
+ @ivar checking: unknown
+ @type started: unknown
+ @ivar started: unknown
+ @type picker: L{BT1.PiecePicker.PiecePicker}
+ @ivar picker: the PiecePicker instance
+ @type choker: L{BT1.Choker.Choker}
+ @ivar choker: the Choker instance
+ @type filename: C{string}
+ @ivar filename: the save location
+ @type files: C{list} of (C{string}, C{long})
+ @ivar files: the full file names and lengths of all the files in the download
+ @type datalength: C{long}
+ @ivar datalength: the total length of the download
+ @type priority: unknown
+ @ivar priority: unknown
+ @type storage: unknown
+ @ivar storage: unknown
+ @type upmeasure: unknown
+ @ivar upmeasure: unknown
+ @type downmeasure: unknown
+ @ivar downmeasure: unknown
+ @type ratelimiter: unknown
+ @ivar ratelimiter: unknown
+ @type ratemeasure: unknown
+ @ivar ratemeasure: unknown
+ @type ratemeasure_datarejected: unknown
+ @ivar ratemeasure_datarejected: unknown
+ @type connecter: unknown
+ @ivar connecter: unknown
+ @type encoder: unknown
+ @ivar encoder: unknown
+ @type encoder_ban: unknown
+ @ivar encoder_ban: unknown
+ @type httpdownloader: unknown
+ @ivar httpdownloader: unknown
+ @type statistics: unknown
+ @ivar statistics: unknown
+
+ """
+
def __init__(self, statusfunc, finfunc, errorfunc, excfunc, doneflag,
config, response, infohash, id, rawserver, port,
status_priority = None, appdataobj = None):
+ """Initialize the instance.
+
+ @type statusfunc: C{method}
+ @param statusfunc: the method to call to print status updates
+ @type finfunc: C{method}
+ @param finfunc: the method to call when the download is completed
+ @type errorfunc: C{method}
+ @param errorfunc: the method to call when an error occurs
+ @type excfunc: C{method}
+ @param excfunc: the method to call when an exception occurs
+ @type doneflag: C{threading.Event}
+ @param doneflag: the flag that indicates when the program is to be shutdown
+ @type config: C{dictionary}
+ @param config: the configuration variables
+ @type response: C{dictionary}
+ @param response: the response data from the metainfo file
+ @type infohash: C{string}
+ @param infohash: the hash of the info from the response data
+ @type id: C{string}
+ @param id: the peer ID to use
+ @type rawserver: L{Rawserver.Rawserver}
+ @param rawserver: the server controlling the program
+ @type port: C{int}
+ @param port: the port being listened to
+ @type status_priority: C{dictionary}
+ @param status_priority: the file priorities, keys are file names,
+ values are the priority to use (optional, defaults to download all)
+ @type appdataobj: L{ConfigDir.ConfigDir}
+ @param appdataobj: the configuration and cache directory manager
+
+ """
+
self.statusfunc = statusfunc
self.finfunc = finfunc
self.errorfunc = errorfunc
@@ -536,6 +789,17 @@
def checkSaveLocation(self, loc):
+ """Check whether the download location exists.
+
+ For multiple files, returns true if a single one exists.
+
+ @type loc: C{string}
+ @param loc: the save location to check
+ @rtype: C{boolean}
+ @return: whether the location exists.
+
+ """
+
if self.info.has_key('length'):
return path.exists(loc)
for x in self.info['files']:
@@ -545,8 +809,31 @@
def saveAs(self, filefunc, pathfunc = None):
+ """Initialize the location to save the download to.
+
+ @type filefunc: C{method}
+ @param filefunc: the method to call to get the location to save the
+ download
+ @type pathfunc: C{method}
+ @param pathfunc: the method to call to alert the UI to any possible
+ change in the download location
+ @rtype: C{string}
+ @return: the location to save to
+
+ """
+
try:
def make(f, forcedir = False):
+ """Create the parent directories of a file.
+
+ @type f: C{string}
+ @param f: the file name
+ @type forcedir: C{boolean}
+ @param forcedir: set to True if f is a directory name
+ (optional, defaults to False)
+
+ """
+
if not forcedir:
f = path.split(f)[0]
if f != '' and not path.exists(f):
@@ -614,10 +901,24 @@
def getFilename(self):
+ """Get the download location.
+
+ @rtype: C{string}
+ @return: the download location
+
+ """
+
return self.filename
def _finished(self):
+ """Finish the download.
+
+ Set the files to read-only, update the Choker for seeding, stop the
+ piece requester.
+
+ """
+
self.finflag.set()
try:
self.storage.set_readonly()
@@ -633,11 +934,27 @@
self.finfunc()
def _data_flunked(self, amount, index):
+ """Process a failed hash check on a piece.
+
+ @type amount: C{int}
+ @param amount: the amount of failed data
+ @type index: C{int}
+ @param index: the piece that failed
+
+ """
+
self.ratemeasure_datarejected(amount)
if not self.doneflag.isSet():
self.errorfunc('piece %d failed hash check, re-downloading it' % index)
def _failed(self, reason):
+ """Stop the failed download.
+
+ @type reason: C{string}
+ @param reason: the reason for the failure
+
+ """
+
self.failed = True
self.doneflag.set()
if reason is not None:
@@ -645,6 +962,22 @@
def initFiles(self, old_style = False, statusfunc = None):
+ """Initialize the files for the download.
+
+ Initialize the priorities, then create the Storage, StorageWrapper,
+ and FileSelector. Then initialize the StorageWrapper.
+
+ @type old_style: C{boolean}
+ @param old_style: whether to use the old-style StorageWrapper
+ initialization (optional, defaults to False)
+ @type statusfunc: C{method}
+ @param statusfunc: the method to use to diplay status updates
+ (optional, defaults to using L{statusfunc}
+ @rtype: C{boolean}
+ @return: whether the initialization was successful
+
+ """
+
if self.doneflag.isSet():
return None
if not statusfunc:
@@ -739,20 +1072,63 @@
def getCachedTorrentData(self):
+ """Get the cached torrent data from the cache directory.
+
+ @rtype: C{dictionary}
+ @return: the bdecoded cached data
+
+ """
+
return self.appdataobj.getTorrentData(self.infohash)
def _make_upload(self, connection, ratelimiter, totalup):
+ """Create a new Upload instance
+
+ @type connection: unknown
+ @param connection: the connection to upload on
+ @type ratelimiter: L{BT1.RateLimiter.RateLimiter}
+ @param ratelimiter: the RateLimiter instance to use
+ @type totalup: L{CurrentRateMeasure.Measure}
+ @param totalup: the Measure instance to use to calculate the total
+ upload rate
+ @rtype: L{BT1.Uploader.Upload}
+ @return: the new Upload instance
+
+ """
+
return Upload(connection, ratelimiter, totalup,
self.choker, self.storagewrapper, self.picker,
self.config)
def _kick_peer(self, connection):
+ """Disconnect a peer.
+
+ @type connection: unknown
+ @param connection: the connection of the peer to disconnect
+
+ """
+
def k(connection = connection):
+ """Close a connection.
+
+ @type connection: unknown
+ @param connection: the connection of the peer to disconnect
+ (optional, defaults to the _kick_peer connection)
+
+ """
+
connection.close()
self.rawserver.add_task(k,0)
def _ban_peer(self, ip):
+ """Ban a peer from the download.
+
+ @type ip: C{string}
+ @param ip: the IP address of the peer to ban
+
+ """
+
self.encoder_ban(ip)
def _received_raw_data(self, x):
More information about the Debtorrent-commits
mailing list