diff options
author | Jeffrey C. Ollie <jeff@ocjtech.us> | 2008-08-19 19:34:21 -0500 |
---|---|---|
committer | Jeffrey C. Ollie <jeff@ocjtech.us> | 2008-08-19 19:34:21 -0500 |
commit | cfc058e6ecd9d7e132d5ccf4600ead1f0641ddc9 (patch) | |
tree | 5d9d4756703d8e640a60188abd818b74d5e0cca9 /BitTorrent | |
parent | ad0fe53806ab5da2ead4a790af22f47e4ea2e713 (diff) | |
download | nohgooee-cfc058e6ecd9d7e132d5ccf4600ead1f0641ddc9.tar.gz nohgooee-cfc058e6ecd9d7e132d5ccf4600ead1f0641ddc9.tar.xz nohgooee-cfc058e6ecd9d7e132d5ccf4600ead1f0641ddc9.zip |
Some renames.
Diffstat (limited to 'BitTorrent')
46 files changed, 0 insertions, 11242 deletions
diff --git a/BitTorrent/BeautifulSupe.py b/BitTorrent/BeautifulSupe.py deleted file mode 100644 index 79072d4..0000000 --- a/BitTorrent/BeautifulSupe.py +++ /dev/null @@ -1,132 +0,0 @@ -# A very very minimal BeautifulSoup immitation. -# -# BS uses SGMLlib to parse, which converts everything to lower case. -# This uses real xml parsing to mimic the parts of BS we use. - -import xml.dom.minidom - -def _getText(node): - nodelist = node.childNodes - rc = [] - for node in nodelist: - if node.nodeType == node.TEXT_NODE: - rc.append(str(node.data)) - return rc - -def _getNodesAsTags(root): - nodelist = root.childNodes - tags = [] - for node in nodelist: - if node.nodeType == node.ELEMENT_NODE: - tags.append(Tag(node)) - return tags - -class Tag(object): - def __init__(self, node): - self.node = node - self.name = node.nodeName - self.contents = _getNodesAsTags(self.node) - text = _getText(self.node) - self.contents += text - self.text = ''.join(text) - - def child_elements(self): - children = [] - for tag in self.contents: - if isinstance(tag, Tag): - children.append(tag) - return children - - def get(self, tagname): - got = self.first(tagname) - if got: - return got.text - - def first(self, tagname): - found = None - - for tag in self.contents: - if isinstance(tag, Tag): - if tag.name == tagname: - found = tag - break - - return found - -class BeautifulSupe(object): - - def __init__(self, data): - #please don't give us your null terminators - data = data.strip(chr(0)) - self.dom = xml.dom.minidom.parseString(data) - - def first(self, tagname, root = None): - found = None - if root == None: - e = self.dom.getElementsByTagName(tagname) - if len(e) > 0: - found = e[0] - else: - for node in root.childNodes: - if node.nodeName == tagname: - found = node - break - - if not found: - return None - - tag = Tag(found) - return tag - - def fetch(self, tagname, restraints = {}): - e = self.dom.getElementsByTagName(tagname) - - matches = [] - - for node in e: - match = 1 - - for restraint in restraints: - f = self.first(restraint, node) - if not f: - match = 0 - break - text = restraints[restraint] - if not f.contents[0].startswith(text): - match = 0 - break - - if match: - tag = Tag(node) - matches.append(tag) - - return matches - - - def scour(self, prefix, suffix = None, node = None): - if node is None: - root = self.dom.getElementsByTagName(self.dom.documentElement.tagName)[0] - node = root - - matches = [] - - for node in node.childNodes: - match = 0 - - name = node.nodeName - - if name.startswith(prefix): - if suffix: - if name.endswith(suffix): - match = 1 - else: - match = 1 - - if match: - tag = Tag(node) - matches.append(tag) - - matches += self.scour(prefix, suffix, node) - - return matches - diff --git a/BitTorrent/Choker.py b/BitTorrent/Choker.py deleted file mode 100644 index ed3d500..0000000 --- a/BitTorrent/Choker.py +++ /dev/null @@ -1,157 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -from random import randrange -from math import sqrt - -class Choker(object): - - def __init__(self, config, schedule, done = lambda: False): - self.config = config - self.schedule = schedule - self.connections = [] - self.count = 0 - self.done = done - self.unchokes_since_last = 0 - schedule(self._round_robin, 10) - - def _round_robin(self): - self.schedule(self._round_robin, 10) - self.count += 1 - if self.done(): - self._rechoke_seed(True) - return - if self.count % 3 == 0: - for i in xrange(len(self.connections)): - u = self.connections[i].upload - if u.choked and u.interested: - self.connections = self.connections[i:] + self.connections[:i] - break - self._rechoke() - - def _rechoke(self): - if self.done(): - self._rechoke_seed() - return - preferred = [] - for i in xrange(len(self.connections)): - c = self.connections[i] - if c.upload.interested and not c.download.is_snubbed() and c.download.have.numfalse: - preferred.append((-c.download.get_rate(), i)) - preferred.sort() - prefcount = min(len(preferred), self._max_uploads() -1) - mask = [0] * len(self.connections) - for _, i in preferred[:prefcount]: - mask[i] = 1 - count = max(1, self.config['min_uploads'] - prefcount) - for i in xrange(len(self.connections)): - c = self.connections[i] - u = c.upload - if mask[i]: - u.unchoke(self.count) - elif count > 0 and c.download.have.numfalse: - u.unchoke(self.count) - if u.interested: - count -= 1 - else: - u.choke() - - def _rechoke_seed(self, force_new_unchokes = False): - if force_new_unchokes: - # number of unchokes per 30 second period - i = (self._max_uploads() + 2) // 3 - # this is called 3 times in 30 seconds, if i==4 then unchoke 1+1+2 - # and so on; substract unchokes recently triggered by disconnects - num_force_unchokes = max(0, (i + self.count % 3) // 3 - \ - self.unchokes_since_last) - else: - num_force_unchokes = 0 - preferred = [] - new_limit = self.count - 3 - for i in xrange(len(self.connections)): - c = self.connections[i] - u = c.upload - if not u.choked and u.interested and c.download.have.numfalse: - if u.unchoke_time > new_limit or ( - u.buffer and c.connection.is_flushed()): - preferred.append((-u.unchoke_time, -u.get_rate(), i)) - else: - preferred.append((1, -u.get_rate(), i)) - num_kept = self._max_uploads() - num_force_unchokes - assert num_kept >= 0 - preferred.sort() - preferred = preferred[:num_kept] - mask = [0] * len(self.connections) - for _, _, i in preferred: - mask[i] = 1 - num_nonpref = self._max_uploads() - len(preferred) - if force_new_unchokes: - self.unchokes_since_last = 0 - else: - self.unchokes_since_last += num_nonpref - last_unchoked = None - for i in xrange(len(self.connections)): - c = self.connections[i] - u = c.upload - if not mask[i]: - if not u.interested: - u.choke() - elif u.choked: - if num_nonpref > 0 and c.connection.is_flushed() and c.download.have.numfalse: - u.unchoke(self.count) - num_nonpref -= 1 - if num_nonpref == 0: - last_unchoked = i - else: - if num_nonpref == 0 or not c.download.have.numfalse: - u.choke() - else: - num_nonpref -= 1 - if num_nonpref == 0: - last_unchoked = i - if last_unchoked is not None: - self.connections = self.connections[last_unchoked + 1:] + \ - self.connections[:last_unchoked + 1] - - def connection_made(self, connection): - p = randrange(len(self.connections) + 1) - self.connections.insert(p, connection) - - def connection_lost(self, connection): - self.connections.remove(connection) - if connection.upload.interested and not connection.upload.choked: - self._rechoke() - - def interested(self, connection): - if not connection.upload.choked: - self._rechoke() - - def not_interested(self, connection): - if not connection.upload.choked: - self._rechoke() - - def _max_uploads(self): - uploads = self.config['max_uploads'] - rate = self.config['max_upload_rate'] - if uploads > 0: - pass - elif rate <= 0: - uploads = 7 # unlimited, just guess something here... - elif rate < 9: - uploads = 2 - elif rate < 15: - uploads = 3 - elif rate < 42: - uploads = 4 - else: - uploads = int(sqrt(rate * .6)) - return uploads diff --git a/BitTorrent/ClientIdentifier.py b/BitTorrent/ClientIdentifier.py deleted file mode 100644 index cec2fa5..0000000 --- a/BitTorrent/ClientIdentifier.py +++ /dev/null @@ -1,157 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# Written by Matt Chisholm -# Client list updated by Ed Savage-Jones - May 28th 2005 - -import re - -v64p = '[\da-zA-Z.-]{3}' - -matches = ( - ('-AZ(?P<version>\d+)-+.+$' , "Azureus" ), - ('M(?P<version>\d-\d-\d)--.+$' , "BitTorrent" ), - ('T(?P<version>%s)0?-+.+$'%v64p , "BitTornado" ), - ('-UT(?P<version>[\dA-F]+)-+.+$' , u"\xb5Torrent" ), - ('-TS(?P<version>\d+)-+.+$' , "TorrentStorm" ), - ('exbc(?P<bcver>.+)LORD.+$' , "BitLord" ), - ('exbc(?P<bcver>[^-][^-]+)(?!---).+$', "BitComet" ), - ('-BC0(?P<version>\d+)-.+$' , "BitComet" ), - ('FUTB(?P<bcver>.+).+$' , "BitComet Mod1" ), - ('xUTB(?P<bcver>.+).+$' , "BitComet Mod2" ), - ('A(?P<version>%s)-+.+$'%v64p , "ABC" ), - ('S(?P<version>%s)-+.+$'%v64p , "Shadow's" ), - (chr(0)*12 + 'aa.+$' , "Experimental 3.2.1b2" ), - (chr(0)*12 + '.+$' , "BitTorrent (obsolete)"), - ('-G3.+$' , "G3Torrent" ), - ('-[Ll][Tt](?P<version>\d+)-+.+$' , "libtorrent" ), - ('Mbrst(?P<version>\d-\d-\d).+$' , "burst!" ), - ('eX.+$' , "eXeem" ), - ('\x00\x02BS.+(?P<strver>UDP0|HTTPBT)$', "BitSpirit v2" ), - ('\x00[\x02|\x00]BS.+$' , "BitSpirit v2" ), - ('.*(?P<strver>UDP0|HTTPBT)$' , "BitSpirit" ), - ('-BOWP?(?P<version>[\dA-F]+)-.+$', "Bits on Wheels" ), - ('(?P<rsver>.+)RSAnonymous.+$' , "Rufus Anonymous" ), - ('(?P<rsver>.+)RS.+$' , "Rufus" ), - ('-ML(?P<version>(\d\.)+\d)(?:\.(?P<strver>CVS))?-+.+$',"MLDonkey"), - ('346------.+$' , "TorrentTopia 1.70" ), - ('OP(?P<strver>\d{4}).+$' , "Opera" ), - ('-KT(?P<version>\d+)(?P<rc>R\d+)-+.+$', "KTorrent" ), -# Unknown but seen in peer lists: - ('-S(?P<version>10059)-+.+$' , "S (unknown)" ), - ('-TR(?P<version>\d+)-+.+$' , "TR (unknown)" ), - ('S\x05\x07\x06\x00{7}.+' , "S 576 (unknown)" ), -# Clients I've never actually seen in a peer list: - ('exbc..---.+$' , "BitVampire 1.3.1" ), - ('-BB(?P<version>\d+)-+.+$' , "BitBuddy" ), - ('-CT(?P<version>\d+)-+.+$' , "CTorrent" ), - ('-MT(?P<version>\d+)-+.+$' , "MoonlightTorrent" ), - ('-BX(?P<version>\d+)-+.+$' , "BitTorrent X" ), - ('-TN(?P<version>\d+)-+.+$' , "TorrentDotNET" ), - ('-SS(?P<version>\d+)-+.+$' , "SwarmScope" ), - ('-XT(?P<version>\d+)-+.+$' , "XanTorrent" ), - ('U(?P<version>\d+)-+.+$' , "UPnP NAT Bit Torrent" ), - ('-AR(?P<version>\d+)-+.+$' , "Arctic" ), - ('(?P<rsver>.+)BM.+$' , "BitMagnet" ), - ('BG(?P<version>\d+).+$' , "BTGetit" ), - ('-eX(?P<version>[\dA-Fa-f]+)-.+$',"eXeem beta" ), - ('Plus12(?P<rc>[\dR]+)-.+$' , "Plus! II" ), - ('XBT(?P<version>\d+)[d-]-.+$' , "XBT" ), - ('-ZT(?P<version>\d+)-+.+$' , "ZipTorrent" ), - ('-BitE\?(?P<version>\d+)-.+$' , "BitEruct" ), - ('O(?P<version>%s)-+.+$'%v64p , "Osprey Permaseed" ), -# Guesses based on Rufus source code, never seen in the wild: - ('-BS(?P<version>\d+)-+.+$' , "BTSlave" ), - ('-SB(?P<version>\d+)-+.+$' , "SwiftBit" ), - ('-SN(?P<version>\d+)-+.+$' , "ShareNET" ), - ('-bk(?P<version>\d+)-+.+$' , "BitKitten" ), - ('-SZ(?P<version>\d+)-+.+$' , "Shareaza" ), - ('-MP(?P<version>\d+)-+.+$' , "MooPolice" ), - ('Deadman Walking-.+$' , "Deadman" ), - ('270------.+$' , "GreedBT 2.7.0" ), - ('XTORR302.+$' , "TorrenTres 0.0.2" ), - ('turbobt(?P<version>\d\.\d).+$' , "TurboBT" ), - ('DansClient.+$' , "XanTorrent" ), - ('-PO(?P<version>\d+)-+.+$' , "PO (unknown)" ), - ('-UR(?P<version>\d+)-+.+$' , "UR (unknown)" ), -# Patterns that should be executed last - ('.*Azureus.*' , "Azureus 2.0.3.2" ), - ) - -matches = [(re.compile(pattern, re.DOTALL), name) for pattern, name in matches] - -unknown_clients = {} - -def identify_client(peerid, log=None): - client = 'unknown' - version = '' - for pat, name in matches: - m = pat.match(peerid) - if m: - client = name - d = m.groupdict() - if d.has_key('version'): - version = d['version'] - version = version.replace('-','.') - if version.find('.') >= 0: - version = ''.join(version.split('.')) - - version = list(version) - for i,c in enumerate(version): - if '0' <= c <= '9': - version[i] = c - elif 'A' <= c <= 'Z': - version[i] = str(ord(c) - 55) - elif 'a' <= c <= 'z': - version[i] = str(ord(c) - 61) - elif c == '.': - version[i] = '62' - elif c == '-': - version[i] = '63' - else: - break - version = '.'.join(version) - elif d.has_key('bcver'): - bcver = d['bcver'] - version += str(ord(bcver[0])) + '.' - if len(bcver) > 1: - version += str(ord(bcver[1])/10) - version += str(ord(bcver[1])%10) - elif d.has_key('rsver'): - rsver = d['rsver'] - version += str(ord(rsver[0])) + '.' - if len(rsver) > 1: - version += str(ord(rsver[1])/10) + '.' - version += str(ord(rsver[1])%10) - if d.has_key('strver'): - if d['strver'] is not None: - version += d['strver'] - if d.has_key('rc'): - rc = 'RC ' + d['rc'][1:] - if version: - version += ' ' - version += rc - break - if client == 'unknown': - # identify Shareaza 2.0 - 2.1 - if len(peerid) == 20 and chr(0) not in peerid[:15]: - for i in range(16,20): - if ord(peerid[i]) != (ord(peerid[i - 16]) ^ ord(peerid[31 - i])): - break - else: - client = "Shareaza" - - - if log is not None and 'unknown' in client: - if not unknown_clients.has_key(peerid): - unknown_clients[peerid] = True - log.write('%s\n'%peerid) - log.write('------------------------------\n') - return client, version diff --git a/BitTorrent/Connecter.py b/BitTorrent/Connecter.py deleted file mode 100644 index 5fc3add..0000000 --- a/BitTorrent/Connecter.py +++ /dev/null @@ -1,334 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Originally written by Bram Cohen, heavily modified by Uoti Urpala - -from binascii import b2a_hex -from struct import pack, unpack - -from BitTorrent.RawServer_magic import Handler -from BitTorrent.bitfield import Bitfield - -def toint(s): - return unpack("!i", s)[0] - -def tobinary(i): - return pack("!i", i) - -CHOKE = chr(0) -UNCHOKE = chr(1) -INTERESTED = chr(2) -NOT_INTERESTED = chr(3) -# index -HAVE = chr(4) -# index, bitfield -BITFIELD = chr(5) -# index, begin, length -REQUEST = chr(6) -# index, begin, piece -PIECE = chr(7) -# index, begin, piece -CANCEL = chr(8) - -# 2-byte port message -PORT = chr(9) - -# reserved flags -DHT = 1 -FLAGS = '\0' * 7 + '\1' -protocol_name = 'BitTorrent protocol' - - -class Connection(Handler): - - def __init__(self, encoder, connection, id, is_local): - self.encoder = encoder - self.connection = connection - self.connection.handler = self - self.id = id - self.ip = connection.ip - self.locally_initiated = is_local - self.complete = False - self.closed = False - self.got_anything = False - self.next_upload = None - self.upload = None - self.download = None - self._buffer = [] - self._buffer_len = 0 - self._reader = self._read_messages() - self._next_len = self._reader.next() - self._partial_message = None - self._outqueue = [] - self.choke_sent = True - self.uses_dht = False - self.dht_port = None - if self.locally_initiated: - connection.write(chr(len(protocol_name)) + protocol_name + - FLAGS + self.encoder.download_id) - if self.id is not None: - connection.write(self.encoder.my_id) - - def close(self): - if not self.closed: - self.connection.close() - self._sever() - - def send_interested(self): - self._send_message(INTERESTED) - - def send_not_interested(self): - self._send_message(NOT_INTERESTED) - - def send_choke(self): - if self._partial_message is None: - self._send_message(CHOKE) - self.choke_sent = True - self.upload.sent_choke() - - def send_unchoke(self): - if self._partial_message is None: - self._send_message(UNCHOKE) - self.choke_sent = False - - def send_port(self, port): - self._send_message(PORT+pack('!H', port)) - - def send_request(self, index, begin, length): - self._send_message(pack("!ciii", REQUEST, index, begin, length)) - - def send_cancel(self, index, begin, length): - self._send_message(pack("!ciii", CANCEL,index, begin, length)) - - def send_bitfield(self, bitfield): - self._send_message(BITFIELD + bitfield) - - def send_have(self, index): - self._send_message(pack("!ci", HAVE, index)) - - def send_keepalive(self): - self._send_message('') - - def send_partial(self, bytes): - if self.closed: - return 0 - if self._partial_message is None: - s = self.upload.get_upload_chunk() - if s is None: - return 0 - index, begin, piece = s - self._partial_message = pack("!icii%ss" % len(piece), len(piece) + 9, PIECE, - index, begin, piece) - if bytes < len(self._partial_message): - self.upload.update_rate(bytes) - self.connection.write(buffer(self._partial_message, 0, bytes)) - self._partial_message = buffer(self._partial_message, bytes) - return bytes - - queue = [str(self._partial_message)] - self._partial_message = None - if self.choke_sent != self.upload.choked: - if self.upload.choked: - self._outqueue.append(pack("!ic", 1, CHOKE)) - self.upload.sent_choke() - else: - self._outqueue.append(pack("!ic", 1, UNCHOKE)) - self.choke_sent = self.upload.choked - queue.extend(self._outqueue) - self._outqueue = [] - queue = ''.join(queue) - self.upload.update_rate(len(queue)) - self.connection.write(queue) - return len(queue) - - # yields the number of bytes it wants next, gets those in self._message - def _read_messages(self): - yield 1 # header length - if ord(self._message) != len(protocol_name): - return - - yield len(protocol_name) - if self._message != protocol_name: - return - - yield 8 # reserved - # dht is on last reserved byte - if ord(self._message[7]) & DHT: - self.uses_dht = True - - yield 20 # download id - if self.encoder.download_id is None: # incoming connection - # modifies self.encoder if successful - self.encoder.select_torrent(self, self._message) - if self.encoder.download_id is None: - return - elif self._message != self.encoder.download_id: - return - if not self.locally_initiated: - self.connection.write(chr(len(protocol_name)) + protocol_name + - FLAGS + self.encoder.download_id + self.encoder.my_id) - - yield 20 # peer id - if not self.id: - self.id = self._message - if self.id == self.encoder.my_id: - return - for v in self.encoder.connections.itervalues(): - if v is not self: - if v.id == self.id: - return - if self.encoder.config['one_connection_per_ip'] and \ - v.ip == self.ip: - return - if self.locally_initiated: - self.connection.write(self.encoder.my_id) - else: - self.encoder.everinc = True - else: - if self._message != self.id: - return - self.complete = True - self.encoder.connection_completed(self) - - while True: - yield 4 # message length - l = toint(self._message) - if l > self.encoder.config['max_message_length']: - return - if l > 0: - yield l - self._got_message(self._message) - - def _got_message(self, message): - t = message[0] - if t == BITFIELD and self.got_anything: - self.close() - return - self.got_anything = True - if (t in [CHOKE, UNCHOKE, INTERESTED, NOT_INTERESTED] and - len(message) != 1): - self.close() - return - if t == CHOKE: - self.download.got_choke() - elif t == UNCHOKE: - self.download.got_unchoke() - elif t == INTERESTED: - self.upload.got_interested() - elif t == NOT_INTERESTED: - self.upload.got_not_interested() - elif t == HAVE: - if len(message) != 5: - self.close() - return - i = unpack("!xi", message)[0] - if i >= self.encoder.numpieces: - self.close() - return - self.download.got_have(i) - elif t == BITFIELD: - try: - b = Bitfield(self.encoder.numpieces, message[1:]) - except ValueError: - self.close() - return - self.download.got_have_bitfield(b) - elif t == REQUEST: - if len(message) != 13: - self.close() - return - i, a, b = unpack("!xiii", message) - if i >= self.encoder.numpieces: - self.close() - return - self.upload.got_request(i, a, b) - elif t == CANCEL: - if len(message) != 13: - self.close() - return - i, a, b = unpack("!xiii", message) - if i >= self.encoder.numpieces: - self.close() - return - self.upload.got_cancel(i, a, b) - elif t == PIECE: - if len(message) <= 9: - self.close() - return - n = len(message) - 9 - i, a, b = unpack("!xii%ss" % n, message) - if i >= self.encoder.numpieces: - self.close() - return - if self.download.got_piece(i, a, b): - for co in self.encoder.complete_connections: - co.send_have(i) - elif t == PORT: - if len(message) != 3: - self.close() - return - self.dht_port = unpack('!H', message[1:3])[0] - self.encoder.got_port(self) - else: - self.close() - - def _sever(self): - self.closed = True - self._reader = None - del self.encoder.connections[self.connection] - self.connection = None - self.encoder.replace_connection() - if self.complete: - del self.encoder.complete_connections[self] - self.download.disconnected() - self.encoder.choker.connection_lost(self) - self.upload = self.download = None - - def _send_message(self, message): - s = tobinary(len(message)) + message - if self._partial_message is not None: - self._outqueue.append(s) - else: - self.connection.write(s) - - def data_came_in(self, conn, s): - while True: - if self.closed: - return - i = self._next_len - self._buffer_len - if i > len(s): - self._buffer.append(s) - self._buffer_len += len(s) - return - m = s[:i] - if self._buffer_len > 0: - self._buffer.append(m) - m = ''.join(self._buffer) - self._buffer = [] - self._buffer_len = 0 - s = s[i:] - self._message = m - try: - self._next_len = self._reader.next() - except StopIteration: - self.close() - return - - def connection_lost(self, conn): - if self.connection is None: - assert self.closed - else: - assert conn is self.connection - self._sever() - - def connection_flushed(self, connection): - if self.complete and self.next_upload is None and (self._partial_message is not None - or (self.upload and self.upload.buffer)): - self.encoder.ratelimiter.queue(self, self.encoder.context.rlgroup) diff --git a/BitTorrent/ConvertedMetainfo.py b/BitTorrent/ConvertedMetainfo.py deleted file mode 100644 index 0e22205..0000000 --- a/BitTorrent/ConvertedMetainfo.py +++ /dev/null @@ -1,283 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Uoti Urpala - -import os -import sys -from sha import sha - -from BitTorrent.bencode import bencode -from BitTorrent import btformats -from BitTorrent import BTFailure, WARNING, ERROR - - -WINDOWS_UNSUPPORTED_CHARS ='"*/:<>?\|' -windows_translate = [chr(i) for i in range(256)] -for x in WINDOWS_UNSUPPORTED_CHARS: - windows_translate[ord(x)] = '-' -windows_translate = ''.join(windows_translate) - -noncharacter_translate = {} -for i in range(0xD800, 0xE000): - noncharacter_translate[i] = ord('-') -for i in range(0xFDD0, 0xFDF0): - noncharacter_translate[i] = ord('-') -for i in (0xFFFE, 0xFFFF): - noncharacter_translate[i] = ord('-') - -del x, i - -def set_filesystem_encoding(encoding, errorfunc): - global filesystem_encoding - filesystem_encoding = 'ascii' - if encoding == '': - try: - sys.getfilesystemencoding - except AttributeError: - errorfunc(WARNING, - _("This seems to be an old Python version which " - "does not support detecting the filesystem " - "encoding. Assuming 'ascii'.")) - return - encoding = sys.getfilesystemencoding() - if encoding is None: - errorfunc(WARNING, - _("Python failed to autodetect filesystem encoding. " - "Using 'ascii' instead.")) - return - try: - 'a1'.decode(encoding) - except: - errorfunc(ERROR, - _("Filesystem encoding '%s' is not supported. " - "Using 'ascii' instead.") % encoding) - return - filesystem_encoding = encoding - - -def generate_names(name, is_dir): - if is_dir: - prefix = name + '.' - suffix = '' - else: - pos = name.rfind('.') - if pos == -1: - pos = len(name) - prefix = name[:pos] + '.' - suffix = name[pos:] - i = 0 - while True: - yield prefix + str(i) + suffix - i += 1 - - -class ConvertedMetainfo(object): - - def __init__(self, metainfo): - self.bad_torrent_wrongfield = False - self.bad_torrent_unsolvable = False - self.bad_torrent_noncharacter = False - self.bad_conversion = False - self.bad_windows = False - self.bad_path = False - self.reported_errors = False - self.is_batch = False - self.orig_files = None - self.files_fs = None - self.total_bytes = 0 - self.sizes = [] - self.comment = None - - btformats.check_message(metainfo, check_paths=False) - info = metainfo['info'] - if info.has_key('length'): - self.total_bytes = info['length'] - self.sizes.append(self.total_bytes) - else: - self.is_batch = True - r = [] - self.orig_files = [] - self.sizes = [] - i = 0 - for f in info['files']: - l = f['length'] - self.total_bytes += l - self.sizes.append(l) - path = self._get_attr_utf8(f, 'path') - for x in path: - if not btformats.allowed_path_re.match(x): - if l > 0: - raise BTFailure(_("Bad file path component: ")+x) - # BitComet makes bad .torrent files with empty - # filename part - self.bad_path = True - break - else: - p = [] - for x in path: - p.append((self._enforce_utf8(x), x)) - path = p - self.orig_files.append('/'.join([x[0] for x in path])) - k = [] - for u,o in path: - tf2 = self._to_fs_2(u) - k.append((tf2, u, o)) - r.append((k,i)) - i += 1 - # If two or more file/subdirectory names in the same directory - # would map to the same name after encoding conversions + Windows - # workarounds, change them. Files are changed as - # 'a.b.c'->'a.b.0.c', 'a.b.1.c' etc, directories or files without - # '.' as 'a'->'a.0', 'a.1' etc. If one of the multiple original - # names was a "clean" conversion, that one is always unchanged - # and the rest are adjusted. - r.sort() - self.files_fs = [None] * len(r) - prev = [None] - res = [] - stack = [{}] - for x in r: - j = 0 - x, i = x - while x[j] == prev[j]: - j += 1 - del res[j:] - del stack[j+1:] - name = x[j][0][1] - if name in stack[-1]: - for name in generate_names(x[j][1], j != len(x) - 1): - name = self._to_fs(name) - if name not in stack[-1]: - break - stack[-1][name] = None - res.append(name) - for j in range(j + 1, len(x)): - name = x[j][0][1] - stack.append({name: None}) - res.append(name) - self.files_fs[i] = os.path.join(*res) - prev = x - - self.name = self._get_field_utf8(info, 'name') - self.name_fs = self._to_fs(self.name) - self.piece_length = info['piece length'] - self.is_trackerless = False - if metainfo.has_key('announce'): - self.announce = metainfo['announce'] - elif metainfo.has_key('nodes'): - self.is_trackerless = True - self.nodes = metainfo['nodes'] - - if metainfo.has_key('comment'): - self.comment = metainfo['comment'] - - self.hashes = [info['pieces'][x:x+20] for x in xrange(0, - len(info['pieces']), 20)] - self.infohash = sha(bencode(info)).digest() - - def show_encoding_errors(self, errorfunc): - self.reported_errors = True - if self.bad_torrent_unsolvable: - errorfunc(ERROR, - _("This .torrent file has been created with a broken " - "tool and has incorrectly encoded filenames. Some or " - "all of the filenames may appear different from what " - "the creator of the .torrent file intended.")) - elif self.bad_torrent_noncharacter: - errorfunc(ERROR, - _("This .torrent file has been created with a broken " - "tool and has bad character values that do not " - "correspond to any real character. Some or all of the " - "filenames may appear different from what the creator " - "of the .torrent file intended.")) - elif self.bad_torrent_wrongfield: - errorfunc(ERROR, - _("This .torrent file has been created with a broken " - "tool and has incorrectly encoded filenames. The " - "names used may still be correct.")) - elif self.bad_conversion: - errorfunc(WARNING, - _('The character set used on the local filesystem ("%s") ' - 'cannot represent all characters used in the ' - 'filename(s) of this torrent. Filenames have been ' - 'changed from the original.') % filesystem_encoding) - elif self.bad_windows: - errorfunc(WARNING, - _("The Windows filesystem cannot handle some " - "characters used in the filename(s) of this torrent. " - "Filenames have been changed from the original.")) - elif self.bad_path: - errorfunc(WARNING, - _("This .torrent file has been created with a broken " - "tool and has at least 1 file with an invalid file " - "or directory name. However since all such files " - "were marked as having length 0 those files are " - "just ignored.")) - - # At least BitComet seems to make bad .torrent files that have - # fields in an arbitrary encoding but separate 'field.utf-8' attributes - def _get_attr_utf8(self, d, attrib): - v = d.get(attrib + '.utf-8') - if v is not None: - if v != d[attrib]: - self.bad_torrent_wrongfield = True - else: - v = d[attrib] - return v - - def _enforce_utf8(self, s): - try: - s = s.decode('utf-8') - except: - self.bad_torrent_unsolvable = True - s = s.decode('utf-8', 'replace') - t = s.translate(noncharacter_translate) - if t != s: - self.bad_torrent_noncharacter = True - return t.encode('utf-8') - - def _get_field_utf8(self, d, attrib): - r = self._get_attr_utf8(d, attrib) - return self._enforce_utf8(r) - - def _fix_windows(self, name, t=windows_translate): - bad = False - r = name.translate(t) - # for some reason name cannot end with '.' or space - if r[-1] in '. ': - r = r + '-' - if r != name: - self.bad_windows = True - bad = True - return (r, bad) - - def _to_fs(self, name): - return self._to_fs_2(name)[1] - - def _to_fs_2(self, name): - bad = False - if sys.platform.startswith('win'): - name, bad = self._fix_windows(name) - name = name.decode('utf-8') - try: - r = name.encode(filesystem_encoding) - except: - self.bad_conversion = True - bad = True - r = name.encode(filesystem_encoding, 'replace') - - if sys.platform.startswith('win'): - # encoding to mbcs with or without 'replace' will make the - # name unsupported by windows again because it adds random - # '?' characters which are invalid windows filesystem - # character - r, bad = self._fix_windows(r) - return (bad, r) diff --git a/BitTorrent/CurrentRateMeasure.py b/BitTorrent/CurrentRateMeasure.py deleted file mode 100644 index 3dd940f..0000000 --- a/BitTorrent/CurrentRateMeasure.py +++ /dev/null @@ -1,48 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -from BitTorrent.platform import bttime - - -class Measure(object): - - def __init__(self, max_rate_period, fudge=5): - self.max_rate_period = max_rate_period - self.ratesince = bttime() - fudge - self.last = self.ratesince - self.rate = 0.0 - self.total = 0 - - def update_rate(self, amount): - self.total += amount - t = bttime() - self.rate = (self.rate * (self.last - self.ratesince) + - amount) / (t - self.ratesince) - self.last = t - if self.ratesince < t - self.max_rate_period: - self.ratesince = t - self.max_rate_period - - def get_rate(self): - self.update_rate(0) - return self.rate - - def get_rate_noupdate(self): - return self.rate - - def time_until_rate(self, newrate): - if self.rate <= newrate: - return 0 - t = bttime() - self.ratesince - return ((self.rate * t) / newrate) - t - - def get_total(self): - return self.total diff --git a/BitTorrent/Desktop.py b/BitTorrent/Desktop.py deleted file mode 100644 index b724811..0000000 --- a/BitTorrent/Desktop.py +++ /dev/null @@ -1,33 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# written by Matt Chisholm - -import os -import sys - -from BitTorrent.platform import get_home_dir, get_shell_dir -if os.name == 'nt': - from win32com.shell import shellcon - -desktop = None - -if os.name == 'nt': - desktop = get_shell_dir(shellcon.CSIDL_DESKTOPDIRECTORY) -else: - homedir = get_home_dir() - if homedir == None : - desktop = '/tmp/' - else: - desktop = homedir - if os.name in ('mac', 'posix'): - tmp_desktop = os.path.join(homedir, 'Desktop') - if os.access(tmp_desktop, os.R_OK|os.W_OK): - desktop = tmp_desktop + os.sep diff --git a/BitTorrent/Downloader.py b/BitTorrent/Downloader.py deleted file mode 100644 index accb332..0000000 --- a/BitTorrent/Downloader.py +++ /dev/null @@ -1,363 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen, Uoti Urpala - -from random import shuffle - -from BitTorrent.platform import bttime -from BitTorrent.CurrentRateMeasure import Measure -from BitTorrent.bitfield import Bitfield - - -class PerIPStats(object): - - def __init__(self): - self.numgood = 0 - self.bad = {} - self.numconnections = 0 - self.lastdownload = None - self.peerid = None - - -class BadDataGuard(object): - - def __init__(self, download): - self.download = download - self.ip = download.connection.ip - self.downloader = download.downloader - self.stats = self.downloader.perip[self.ip] - self.lastindex = None - - def bad(self, index, bump = False): - self.stats.bad.setdefault(index, 0) - self.stats.bad[index] += 1 - if self.ip not in self.downloader.bad_peers: - self.downloader.bad_peers[self.ip] = (False, self.stats) - if self.download is not None: - self.downloader.kick(self.download) - self.download = None - elif len(self.stats.bad) > 1 and self.stats.numconnections == 1 and \ - self.stats.lastdownload is not None: - # kick new connection from same IP if previous one sent bad data, - # mainly to give the algorithm time to find other bad pieces - # in case the peer is sending a lot of bad data - self.downloader.kick(self.stats.lastdownload) - if len(self.stats.bad) >= 3 and len(self.stats.bad) > \ - self.stats.numgood // 30: - self.downloader.ban(self.ip) - elif bump: - self.downloader.picker.bump(index) - - def good(self, index): - # lastindex is a hack to only increase numgood for by one for each good - # piece, however many chunks came from the connection(s) from this IP - if index != self.lastindex: - self.stats.numgood += 1 - self.lastindex = index - - -class SingleDownload(object): - - def __init__(self, downloader, connection): - self.downloader = downloader - self.connection = connection - self.choked = True - self.interested = False - self.active_requests = [] - self.measure = Measure(downloader.config['max_rate_period']) - self.peermeasure = Measure(max(downloader.storage.piece_size / 10000, - 20)) - self.have = Bitfield(downloader.numpieces) - self.last = 0 - self.example_interest = None - self.backlog = 2 - self.guard = BadDataGuard(self) - - def _backlog(self): - backlog = 2 + int(4 * self.measure.get_rate() / - self.downloader.chunksize) - if backlog > 50: - backlog = max(50, int(.075 * backlog)) - self.backlog = backlog - return backlog - - def disconnected(self): - self.downloader.lost_peer(self) - for i in xrange(len(self.have)): - if self.have[i]: - self.downloader.picker.lost_have(i) - self._letgo() - self.guard.download = None - - def _letgo(self): - if not self.active_requests: - return - if self.downloader.storage.endgame: - self.active_requests = [] - return - lost = [] - for index, begin, length in self.active_requests: - self.downloader.storage.request_lost(index, begin, length) - if index not in lost: - lost.append(index) - self.active_requests = [] - ds = [d for d in self.downloader.downloads if not d.choked] - shuffle(ds) - for d in ds: - d._request_more(lost) - for d in self.downloader.downloads: - if d.choked and not d.interested: - for l in lost: - if d.have[l] and self.downloader.storage.do_I_have_requests(l): - d.interested = True - d.connection.send_interested() - break - - def got_choke(self): - if not self.choked: - self.choked = True - self._letgo() - - def got_unchoke(self): - if self.choked: - self.choked = False - if self.interested: - self._request_more() - - def got_piece(self, index, begin, piece): - try: - self.active_requests.remove((index, begin, len(piece))) - except ValueError: - self.downloader.discarded_bytes += len(piece) - return False - if self.downloader.storage.endgame: - self.downloader.all_requests.remove((index, begin, len(piece))) - self.last = bttime() - self.measure.update_rate(len(piece)) - self.downloader.measurefunc(len(piece)) - self.downloader.downmeasure.update_rate(len(piece)) - if not self.downloader.storage.piece_came_in(index, begin, piece, - self.guard): - if self.downloader.storage.endgame: - while self.downloader.storage.do_I_have_requests(index): - nb, nl = self.downloader.storage.new_request(index) - self.downloader.all_requests.append((index, nb, nl)) - for d in self.downloader.downloads: - d.fix_download_endgame() - return False - ds = [d for d in self.downloader.downloads if not d.choked] - shuffle(ds) - for d in ds: - d._request_more([index]) - return False - if self.downloader.storage.do_I_have(index): - self.downloader.picker.complete(index) - if self.downloader.storage.endgame: - for d in self.downloader.downloads: - if d is not self and d.interested: - if d.choked: - d.fix_download_endgame() - else: - try: - d.active_requests.remove((index, begin, len(piece))) - except ValueError: - continue - d.connection.send_cancel(index, begin, len(piece)) - d.fix_download_endgame() - self._request_more() - if self.downloader.picker.am_I_complete(): - for d in [i for i in self.downloader.downloads if i.have.numfalse == 0]: - d.connection.close() - return self.downloader.storage.do_I_have(index) - - def _want(self, index): - return self.have[index] and self.downloader.storage.do_I_have_requests(index) - - def _request_more(self, indices = None): - assert not self.choked - if len(self.active_requests) >= self._backlog(): - return - if self.downloader.storage.endgame: - self.fix_download_endgame() - return - lost_interests = [] - while len(self.active_requests) < self.backlog: - if indices is None: - interest = self.downloader.picker.next(self._want, self.have.numfalse == 0) - else: - interest = None - for i in indices: - if self.have[i] and self.downloader.storage.do_I_have_requests(i): - interest = i - break - if interest is None: - break - if not self.interested: - self.interested = True - self.connection.send_interested() - self.example_interest = interest - self.downloader.picker.requested(interest, self.have.numfalse == 0) - while len(self.active_requests) < (self.backlog-2) * 5 + 2: - begin, length = self.downloader.storage.new_request(interest) - self.active_requests.append((interest, begin, length)) - self.connection.send_request(interest, begin, length) - if not self.downloader.storage.do_I_have_requests(interest): - lost_interests.append(interest) - break - if not self.active_requests and self.interested: - self.interested = False - self.connection.send_not_interested() - if lost_interests: - for d in self.downloader.downloads: - if d.active_requests or not d.interested: - continue - if d.example_interest is not None and self.downloader.storage.do_I_have_requests(d.example_interest): - continue - for lost in lost_interests: - if d.have[lost]: - break - else: - continue - interest = self.downloader.picker.next(d._want, d.have.numfalse == 0) - if interest is None: - d.interested = False - d.connection.send_not_interested() - else: - d.example_interest = interest - if self.downloader.storage.endgame: - self.downloader.all_requests = [] - for d in self.downloader.downloads: - self.downloader.all_requests.extend(d.active_requests) - for d in self.downloader.downloads: - d.fix_download_endgame() - - def fix_download_endgame(self): - want = [a for a in self.downloader.all_requests if self.have[a[0]] and a not in self.active_requests] - if self.interested and not self.active_requests and not want: - self.interested = False - self.connection.send_not_interested() - return - if not self.interested and want: - self.interested = True - self.connection.send_interested() - if self.choked or len(self.active_requests) >= self._backlog(): - return - shuffle(want) - del want[self.backlog - len(self.active_requests):] - self.active_requests.extend(want) - for piece, begin, length in want: - self.connection.send_request(piece, begin, length) - - def got_have(self, index): - if self.have[index]: - return - if index == self.downloader.numpieces-1: - self.peermeasure.update_rate(self.downloader.storage.total_length- - (self.downloader.numpieces-1)*self.downloader.storage.piece_size) - else: - self.peermeasure.update_rate(self.downloader.storage.piece_size) - self.have[index] = True - self.downloader.picker.got_have(index) - if self.downloader.picker.am_I_complete() and self.have.numfalse == 0: - self.connection.close() - return - if self.downloader.storage.endgame: - self.fix_download_endgame() - elif self.downloader.storage.do_I_have_requests(index): - if not self.choked: - self._request_more([index]) - else: - if not self.interested: - self.interested = True - self.connection.send_interested() - - def got_have_bitfield(self, have): - if self.downloader.picker.am_I_complete() and have.numfalse == 0: - self.connection.close() - return - self.have = have - for i in xrange(len(self.have)): - if self.have[i]: - self.downloader.picker.got_have(i) - if self.downloader.storage.endgame: - for piece, begin, length in self.downloader.all_requests: - if self.have[piece]: - self.interested = True - self.connection.send_interested() - return - for i in xrange(len(self.have)): - if self.have[i] and self.downloader.storage.do_I_have_requests(i): - self.interested = True - self.connection.send_interested() - return - - def get_rate(self): - return self.measure.get_rate() - - def is_snubbed(self): - return bttime() - self.last > self.downloader.snub_time - - -class Downloader(object): - - def __init__(self, config, storage, picker, numpieces, downmeasure, - measurefunc, kickfunc, banfunc): - self.config = config - self.storage = storage - self.picker = picker - self.chunksize = config['download_slice_size'] - self.downmeasure = downmeasure - self.numpieces = numpieces - self.snub_time = config['snub_time'] - self.measurefunc = measurefunc - self.kickfunc = kickfunc - self.banfunc = banfunc - self.downloads = [] - self.perip = {} - self.bad_peers = {} - self.discarded_bytes = 0 - - def make_download(self, connection): - ip = connection.ip - perip = self.perip.get(ip) - if perip is None: - perip = PerIPStats() - self.perip[ip] = perip - perip.numconnections += 1 - d = SingleDownload(self, connection) - perip.lastdownload = d - perip.peerid = connection.id - self.downloads.append(d) - return d - - def lost_peer(self, download): - self.downloads.remove(download) - ip = download.connection.ip - self.perip[ip].numconnections -= 1 - if self.perip[ip].lastdownload == download: - self.perip[ip].lastdownload = None - - def kick(self, download): - if not self.config['retaliate_to_garbled_data']: - return - ip = download.connection.ip - peerid = download.connection.id - # kickfunc will schedule connection.close() to be executed later; we - # might now be inside RawServer event loop with events from that - # connection already queued, and trying to handle them after doing - # close() now could cause problems. - self.kickfunc(download.connection) - - def ban(self, ip): - if not self.config['retaliate_to_garbled_data']: - return - self.banfunc(ip) - self.bad_peers[ip] = (True, self.perip[ip]) diff --git a/BitTorrent/DownloaderFeedback.py b/BitTorrent/DownloaderFeedback.py deleted file mode 100644 index 6c16c90..0000000 --- a/BitTorrent/DownloaderFeedback.py +++ /dev/null @@ -1,139 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen, Uoti Urpala - -from __future__ import division - - -class DownloaderFeedback(object): - - def __init__(self, choker, upfunc, upfunc2, downfunc, uptotal, downtotal, - remainingfunc, leftfunc, file_length, finflag, downloader, - files, ever_got_incoming, rerequester): - self.downloader = downloader - self.picker = downloader.picker - self.storage = downloader.storage - self.choker = choker - self.upfunc = upfunc - self.upfunc2 = upfunc2 - self.downfunc = downfunc - self.uptotal = uptotal - self.downtotal = downtotal - self.remainingfunc = remainingfunc - self.leftfunc = leftfunc - self.file_length = file_length - self.finflag = finflag - self.files = files - self.ever_got_incoming = ever_got_incoming - self.rerequester = rerequester - self.lastids = [] - - def _rotate(self): - cs = self.choker.connections - for peerid in self.lastids: - for i in xrange(len(cs)): - if cs[i].id == peerid: - return cs[i:] + cs[:i] - return cs - - def collect_spew(self): - l = [ ] - cs = self._rotate() - self.lastids = [c.id for c in cs] - for c in cs: - rec = {} - rec['id'] = c.id - rec["ip"] = c.ip - rec["is_optimistic_unchoke"] = (c is self.choker.connections[0]) - if c.locally_initiated: - rec["initiation"] = "L" - else: - rec["initiation"] = "R" - u = c.upload - rec["upload"] = (u.measure.get_total(), int(u.measure.get_rate()), - u.interested, u.choked) - - d = c.download - rec["download"] = (d.measure.get_total(),int(d.measure.get_rate()), - d.interested, d.choked, d.is_snubbed()) - rec['completed'] = 1 - d.have.numfalse / len(d.have) - rec['speed'] = d.connection.download.peermeasure.get_rate() - l.append(rec) - return l - - def get_statistics(self, spewflag=False, fileflag=False): - status = {} - numSeeds = 0 - numPeers = 0 - for d in self.downloader.downloads: - if d.have.numfalse == 0: - numSeeds += 1 - else: - numPeers += 1 - status['numSeeds'] = numSeeds - status['numPeers'] = numPeers - status['trackerSeeds'] = self.rerequester.tracker_num_seeds - status['trackerPeers'] = self.rerequester.tracker_num_peers - status['upRate'] = self.upfunc() - status['upRate2'] = self.upfunc2() - status['upTotal'] = self.uptotal() - status['ever_got_incoming'] = self.ever_got_incoming() - missingPieces = 0 - numCopyList = [] - numCopies = 0 - for i in self.picker.crosscount: - missingPieces += i - if missingPieces == 0: - numCopies += 1 - else: - fraction = 1 - missingPieces / self.picker.numpieces - numCopyList.append(fraction) - if fraction == 0 or len(numCopyList) >= 3: - break - numCopies -= numSeeds - if self.picker.numgot == self.picker.numpieces: - numCopies -= 1 - status['numCopies'] = numCopies - status['numCopyList'] = numCopyList - status['discarded'] = self.downloader.discarded_bytes - status['storage_numcomplete'] = self.storage.stat_numfound + \ - self.storage.stat_numdownloaded - status['storage_dirty'] = len(self.storage.stat_dirty) - status['storage_active'] = len(self.storage.stat_active) - status['storage_new'] = len(self.storage.stat_new) - status['storage_numflunked'] = self.storage.stat_numflunked - - if spewflag: - status['spew'] = self.collect_spew() - status['bad_peers'] = self.downloader.bad_peers - if fileflag: - undl = self.storage.storage.undownloaded - unal = self.storage.storage.unallocated - status['files_left'] = [undl[fname] for fname in self.files] - status['files_allocated'] = [not unal[fn] for fn in self.files] - if self.finflag.isSet(): - status['downRate'] = 0 - status['downTotal'] = self.downtotal() - status['fractionDone'] = 1 - return status - timeEst = self.remainingfunc() - status['timeEst'] = timeEst - - if self.file_length > 0: - fractionDone = 1 - self.leftfunc() / self.file_length - else: - fractionDone = 1 - status.update({ - "fractionDone" : fractionDone, - "downRate" : self.downfunc(), - "downTotal" : self.downtotal() - }) - return status diff --git a/BitTorrent/Encoder.py b/BitTorrent/Encoder.py deleted file mode 100644 index a5bd6dc..0000000 --- a/BitTorrent/Encoder.py +++ /dev/null @@ -1,286 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -from socket import error as socketerror - -from BitTorrent import BTFailure -from BitTorrent.RawServer_magic import Handler -from BitTorrent.NatTraversal import UPNPError -from BitTorrent.Connecter import Connection -from BitTorrent.platform import is_frozen_exe -from BitTorrent.ClientIdentifier import identify_client - -# header, reserved, download id, my id, [length, message] - -class InitialConnectionHandler(Handler): - def __init__(self, parent, id): - self.parent = parent - self.id = id - self.accept = True - - def connection_started(self, s): - - del self.parent.pending_connections[(s.ip, s.port)] - - # prevents conenctions we no longer care about from being accepted - if not self.accept: - return - - con = Connection(self.parent, s, self.id, True) - self.parent.connections[s] = con - - # it might not be obvious why this is here. - # if the pending queue filled and put the remaining connections - # into the spare list, this will push more connections in to pending - self.parent.replace_connection() - - def connection_failed(self, addr, exception): - del self.parent.pending_connections[addr] -
- if not self.accept: - # we don't need to rotate the spares with replace_connection()
- # if the Encoder object has stopped all connections - return -
- self.parent.replace_connection() - - -class Encoder(object): - - def __init__(self, make_upload, downloader, choker, numpieces, ratelimiter, - rawserver, config, my_id, schedulefunc, download_id, context, addcontactfunc, reported_port): - self.make_upload = make_upload - self.downloader = downloader - self.choker = choker - self.numpieces = numpieces - self.ratelimiter = ratelimiter - self.rawserver = rawserver - self.my_id = my_id - self.config = config - self.schedulefunc = schedulefunc - self.download_id = download_id - self.context = context - self.addcontact = addcontactfunc - self.reported_port = reported_port - self.everinc = False - - # submitted - self.pending_connections = {} - # transport connected - self.connections = {} - # protocol active - self.complete_connections = {} - - self.spares = {} - - self.banned = {} - schedulefunc(self.send_keepalives, config['keepalive_interval']) - - def send_keepalives(self): - self.schedulefunc(self.send_keepalives, - self.config['keepalive_interval']) - for c in self.complete_connections: - c.send_keepalive() - - # returns False if the connection has been pushed on to self.spares - # other filters and a successful connection return True - def start_connection(self, dns, id): - if dns[0] in self.banned: - return True - if id == self.my_id: - return True - for v in self.connections.values(): - if id and v.id == id: - return True - if self.config['one_connection_per_ip'] and v.ip == dns[0]: - return True - - #print "start", len(self.pending_connections), len(self.spares), len(self.connections) - - total_outstanding = len(self.connections) - # it's possible the pending connections could eventually complete, - # so we have to account for those when enforcing max_initiate - total_outstanding += len(self.pending_connections) - - if total_outstanding >= self.config['max_initiate']: - self.spares[dns] = 1 - return False - - # if these fail, I'm getting a very weird dns object - assert isinstance(dns, tuple) - assert isinstance(dns[0], str) - assert isinstance(dns[1], int) - - # looks like we connect to the same peer several times in a row. - # we should probably stop doing that, but this prevents it from crashing - if dns in self.pending_connections: - # uncomment this if you want to debug the multi-connect problem - #print "Double Add on", dns - #traceback.print_stack() - return True - - handler = InitialConnectionHandler(self, id) - self.pending_connections[dns] = handler - started = self.rawserver.async_start_connection(dns, handler, self.context) - - if not started: - del self.pending_connections[dns] - self.spares[dns] = 1 - return False - - return True - - def connection_completed(self, c): - self.complete_connections[c] = 1 - c.upload = self.make_upload(c) - c.download = self.downloader.make_download(c) - self.choker.connection_made(c) - if c.uses_dht: - c.send_port(self.reported_port) - - def got_port(self, c): - if self.addcontact and c.uses_dht and c.dht_port != None: - self.addcontact(c.connection.ip, c.dht_port) - - def ever_got_incoming(self): - return self.everinc - - def how_many_connections(self): - return len(self.complete_connections) - - def replace_connection(self): - while self.spares: - started = self.start_connection(self.spares.popitem()[0], None) - if not started: - # start_connection decided to push this connection back on to - # self.spares because a limit was hit. break now or loop forever - break - - def close_connections(self): - # drop connections which could be made after we're not interested - for c in self.pending_connections.itervalues(): - c.accept = False - - for c in self.connections.itervalues(): - if not c.closed: - c.connection.close() - c.closed = True - - def singleport_connection(self, listener, con): - if con.ip in self.banned: - return - m = self.config['max_allow_in'] - if m and len(self.connections) >= m: - return - self.connections[con.connection] = con - del listener.connections[con.connection] - con.encoder = self - con.connection.context = self.context - - def ban(self, ip): - self.banned[ip] = None - - -class SingleportListener(Handler): - - def __init__(self, rawserver, nattraverser): - self.rawserver = rawserver - self.nattraverser = nattraverser - self.port = 0 - self.ports = {} - self.port_change_notification = None - self.torrents = {} - self.connections = {} - self.download_id = None - - def _close(self, port): - serversocket = self.ports[port][0] - self.nattraverser.unregister_port(port, "TCP") - self.rawserver.stop_listening(serversocket) - serversocket.close() - - def _check_close(self, port): - if not port or self.port == port or len(self.ports[port][1]) > 0: - return - self._close(port) - del self.ports[port] - - def open_port(self, port, config): - if port in self.ports: - self.port = port - return - serversocket = self.rawserver.create_serversocket( - port, config['bind'], reuse=True, tos=config['peer_socket_tos']) - try: - d = self.nattraverser.register_port(port, port, "TCP", config['bind']) - d.addCallback(self._change_port) - except Exception, e: - # blanket, just incase - we don't want to interrupt things - # maybe we should log it, maybe not - #print "UPnP registration error", e - pass - self.rawserver.start_listening(serversocket, self) - oldport = self.port - self.port = port - self.ports[port] = [serversocket, {}] - self._check_close(oldport) - - def _change_port(self, port): - if self.port == port: - return - [serversocket, callbacks] = self.ports[self.port] - self.ports[port] = [serversocket, callbacks] - del self.ports[self.port] - self.port = port - for callback in callbacks: - if callback: - callback(port) - - def get_port(self, callback = None): - if self.port: - callbacks = self.ports[self.port][1] - if not callbacks.has_key(callback): - callbacks[callback] = 1 - else: - callbacks[callback] += 1 - return self.port - - def release_port(self, port, callback = None): - callbacks = self.ports[port][1] - callbacks[callback] -= 1 - if callbacks[callback] == 0: - del callbacks[callback] - self._check_close(port) - - def close_sockets(self): - for port in self.ports.iterkeys(): - self._close(port) - - def add_torrent(self, infohash, encoder): - if infohash in self.torrents: - raise BTFailure(_("Can't start two separate instances of the same " - "torrent")) - self.torrents[infohash] = encoder - - def remove_torrent(self, infohash): - del self.torrents[infohash] - - def select_torrent(self, conn, infohash): - if infohash in self.torrents: - self.torrents[infohash].singleport_connection(self, conn) - - def connection_made(self, connection): - con = Connection(self, connection, None, False) - self.connections[connection] = con - - def replace_connection(self): - pass diff --git a/BitTorrent/GUI.py b/BitTorrent/GUI.py deleted file mode 100644 index 0497367..0000000 --- a/BitTorrent/GUI.py +++ /dev/null @@ -1,679 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# written by Matt Chisholm - -from __future__ import division - -import gtk -import pango -import gobject -import os -import threading - -assert gtk.gtk_version >= (2, 6), _( "GTK %s or newer required") % '2.6' -assert gtk.pygtk_version >= (2, 6), _("PyGTK %s or newer required") % '2.6' - -from BitTorrent import app_name, FAQ_URL, languages, language_names -from BitTorrent.platform import image_root, read_language_file, write_language_file - -def lock_wrap(function, *args): - gtk.gdk.threads_enter() - function(*args) - gtk.gdk.threads_leave() - -def gtk_wrap(function, *args): - gobject.idle_add(lock_wrap, function, *args) - -SPACING = 8 -WINDOW_TITLE_LENGTH = 128 # do we need this? -WINDOW_WIDTH = 600 - -# get screen size from GTK -d = gtk.gdk.display_get_default() -s = d.get_default_screen() -MAX_WINDOW_HEIGHT = s.get_height() -MAX_WINDOW_WIDTH = s.get_width() -if os.name == 'nt': - MAX_WINDOW_HEIGHT -= 32 # leave room for start bar (exact) - MAX_WINDOW_HEIGHT -= 32 # and window decorations (depends on windows theme) -else: - MAX_WINDOW_HEIGHT -= 32 # leave room for window decorations (could be any size) - - -MIN_MULTI_PANE_HEIGHT = 107 - -BT_TARGET_TYPE = 0 -EXTERNAL_FILE_TYPE = 1 -EXTERNAL_STRING_TYPE = 2 - -BT_TARGET = ("application/x-bittorrent" , gtk.TARGET_SAME_APP, BT_TARGET_TYPE ) -EXTERNAL_FILE = ("text/uri-list" , 0 , EXTERNAL_FILE_TYPE ) - -#gtk(gdk actually) is totally unable to receive text drags -#of any sort in windows because they're too lazy to use OLE. -#this list is all the atoms I could possibly find so that -#url dnd works on linux from any browser. -EXTERNAL_TEXTPLAIN = ("text/plain" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_TEXT = ("TEXT" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_COMPOUND_TEXT = ("COMPOUND_TEXT" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_MOZILLA = ("text/x-moz-url" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_NETSCAPE = ("_NETSCAPE_URL" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_HTML = ("text/html" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_UNICODE = ("text/unicode" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_UTF8 = ("text/plain;charset=utf-8" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_UTF8_STRING = ("UTF8_STRING" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_STRING = ("STRING" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_OLE2_DND = ("OLE2_DND" , 0 , EXTERNAL_STRING_TYPE) -EXTERNAL_RTF = ("Rich Text Format" , 0 , EXTERNAL_STRING_TYPE) -#there should alse be text/plain;charset={current charset} - -TARGET_EXTERNAL = [EXTERNAL_FILE, - EXTERNAL_TEXTPLAIN, - EXTERNAL_TEXT, - EXTERNAL_COMPOUND_TEXT, - EXTERNAL_MOZILLA, - EXTERNAL_NETSCAPE, - EXTERNAL_HTML, - EXTERNAL_UNICODE, - EXTERNAL_UTF8, - EXTERNAL_UTF8_STRING, - EXTERNAL_STRING, - EXTERNAL_OLE2_DND, - EXTERNAL_RTF] - -TARGET_ALL = [BT_TARGET, - EXTERNAL_FILE, - EXTERNAL_TEXTPLAIN, - EXTERNAL_TEXT, - EXTERNAL_COMPOUND_TEXT, - EXTERNAL_MOZILLA, - EXTERNAL_NETSCAPE, - EXTERNAL_HTML, - EXTERNAL_UNICODE, - EXTERNAL_UTF8, - EXTERNAL_UTF8_STRING, - EXTERNAL_STRING, - EXTERNAL_OLE2_DND, - EXTERNAL_RTF] - -# a slightly hackish but very reliable way to get OS scrollbar width -sw = gtk.ScrolledWindow() -SCROLLBAR_WIDTH = sw.size_request()[0] - 48 -del sw - -def align(obj,x,y): - if type(obj) is gtk.Label: - obj.set_alignment(x,y) - return obj - else: - a = gtk.Alignment(x,y,0,0) - a.add(obj) - return a - -def halign(obj, amt): - return align(obj,amt,0.5) - -def lalign(obj): - return halign(obj,0) - -def ralign(obj): - return halign(obj,1) - -def valign(obj, amt): - return align(obj,0.5,amt) - -def malign(obj): - return valign(obj, 0.5) - -factory = gtk.IconFactory() - -# these don't seem to be documented anywhere: -# ICON_SIZE_BUTTON = 20x20 -# ICON_SIZE_LARGE_TOOLBAR = 24x24 - -for n in 'abort broken finished info pause paused play queued running remove status-running status-starting status-pre-natted status-natted status-stopped status-broken'.split(): - fn = os.path.join(image_root, 'icons', 'default', ('%s.png'%n)) - if os.access(fn, os.F_OK): - pixbuf = gtk.gdk.pixbuf_new_from_file(fn) - set = gtk.IconSet(pixbuf) - factory.add('bt-%s'%n, set) - # maybe we should load a default icon if none exists - -factory.add_default() - -def get_logo(size=32): - fn = os.path.join(image_root, 'logo', 'bittorrent_%d.png'%size) - logo = gtk.Image() - logo.set_from_file(fn) - return logo - -class Size(long): - """displays size in human-readable format""" - __slots__ = [] - size_labels = ['','K','M','G','T','P','E','Z','Y'] - radix = 2**10 - def __new__(cls, value): - self = long.__new__(cls, value) - return self - - def __init__(self, value): - long.__init__(self, value) - - def __str__(self, precision=None): - if precision is None: - precision = 0 - value = self - for unitname in self.size_labels: - if value < self.radix and precision < self.radix: - break - value /= self.radix - precision /= self.radix - if unitname and value < 10 and precision < 1: - return '%.1f %sB' % (value, unitname) - else: - return '%.0f %sB' % (value, unitname) - - -class Rate(Size): - """displays rate in human-readable format""" - __slots__ = [] - def __init__(self, value): - Size.__init__(self, value) - - def __str__(self, precision=2**10): - return '%s/s'% Size.__str__(self, precision=precision) - - -class Duration(float): - """displays duration in human-readable format""" - __slots__ = [] - def __str__(self): - if self > 365 * 24 * 60 * 60: - return '?' - elif self >= 172800: - return _("%d days") % (self//86400) # 2 days or longer - elif self >= 86400: - return _("1 day %d hours") % ((self-86400)//3600) # 1-2 days - elif self >= 3600: - return _("%d:%02d hours") % (self//3600, (self%3600)//60) # 1 h - 1 day - elif self >= 60: - return _("%d:%02d minutes") % (self//60, self%60) # 1 minute to 1 hour - elif self >= 0: - return _("%d seconds") % int(self) - else: - return _("0 seconds") - - -class FancyLabel(gtk.Label): - def __init__(self, label_string, *values): - self.label_string = label_string - gtk.Label.__init__(self, label_string%values) - - def set_value(self, *values): - self.set_text(self.label_string%values) - - -class IconButton(gtk.Button): - def __init__(self, label, iconpath=None, stock=None): - gtk.Button.__init__(self, label) - - self.icon = gtk.Image() - if stock is not None: - self.icon.set_from_stock(stock, gtk.ICON_SIZE_BUTTON) - elif iconpath is not None: - self.icon.set_from_file(iconpath) - else: - raise TypeError, 'IconButton needs iconpath or stock' - self.set_image(self.icon) - - -class LanguageChooser(gtk.Frame): - def __init__(self): - gtk.Frame.__init__(self, "Translate %s into:" % app_name) - self.set_border_width(SPACING) - - model = gtk.ListStore(*[gobject.TYPE_STRING] * 2) - default = model.append(("System default", '')) - - lang = read_language_file() - for l in languages: - it = model.append((language_names[l].encode('utf8'), l)) - if l == lang: - default = it - - self.combo = gtk.ComboBox(model) - cell = gtk.CellRendererText() - self.combo.pack_start(cell, True) - self.combo.add_attribute(cell, 'text', 0) - - if default is not None: - self.combo.set_active_iter(default) - - self.combo.connect('changed', self.changed) - box = gtk.VBox(spacing=SPACING) - box.set_border_width(SPACING) - box.pack_start(self.combo, expand=False, fill=False) - l = gtk.Label("You must restart %s for the\nlanguage " - "setting to take effect." % app_name) - l.set_alignment(0,1) - l.set_line_wrap(True) - box.pack_start(l, expand=False, fill=False) - self.add(box) - - def changed(self, *a): - it = self.combo.get_active_iter() - model = self.combo.get_model() - code = model.get(it, 1)[0] - write_language_file(code) - -class IconMixin(object): - def __init__(self): - iconname = os.path.join(image_root,'bittorrent.ico') - icon16 = gtk.gdk.pixbuf_new_from_file_at_size(iconname, 16, 16) - icon32 = gtk.gdk.pixbuf_new_from_file_at_size(iconname, 32, 32) - self.set_icon_list(icon16, icon32) - -class Window(IconMixin, gtk.Window): - def __init__(self, *args): - gtk.Window.__init__(self, *args) - IconMixin.__init__(self) - -class HelpWindow(Window): - def __init__(self, main, helptext): - Window.__init__(self) - self.set_title(_("%s Help")%app_name) - self.main = main - self.set_border_width(SPACING) - - self.vbox = gtk.VBox(spacing=SPACING) - - self.faq_box = gtk.HBox(spacing=SPACING) - self.faq_box.pack_start(gtk.Label(_("Frequently Asked Questions:")), expand=False, fill=False) - self.faq_url = gtk.Entry() - self.faq_url.set_text(FAQ_URL) - self.faq_url.set_editable(False) - self.faq_box.pack_start(self.faq_url, expand=True, fill=True) - self.faq_button = gtk.Button(_("Go")) - self.faq_button.connect('clicked', lambda w: self.main.visit_url(FAQ_URL) ) - self.faq_box.pack_start(self.faq_button, expand=False, fill=False) - self.vbox.pack_start(self.faq_box, expand=False, fill=False) - - self.cmdline_args = gtk.Label(helptext) - - self.cmdline_sw = ScrolledWindow() - self.cmdline_sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) - self.cmdline_sw.add_with_viewport(self.cmdline_args) - - self.cmdline_sw.set_size_request(self.cmdline_args.size_request()[0]+SCROLLBAR_WIDTH, 200) - - self.vbox.pack_start(self.cmdline_sw) - - self.add(self.vbox) - - self.show_all() - - if self.main is not None: - self.connect('destroy', lambda w: self.main.window_closed('help')) - else: - self.connect('destroy', lambda w: gtk.main_quit()) - gtk.main() - - - - def close(self, widget=None): - self.destroy() - - -class ScrolledWindow(gtk.ScrolledWindow): - def scroll_to_bottom(self): - child_height = self.child.child.size_request()[1] - self.scroll_to(0, child_height) - - def scroll_by(self, dx=0, dy=0): - v = self.get_vadjustment() - new_y = min(v.upper, v.value + dy) - self.scroll_to(0, new_y) - - def scroll_to(self, x=0, y=0): - v = self.get_vadjustment() - child_height = self.child.child.size_request()[1] - new_adj = gtk.Adjustment(y, 0, child_height) - self.set_vadjustment(new_adj) - - -class AutoScrollingWindow(ScrolledWindow): - def __init__(self): - ScrolledWindow.__init__(self) - self.drag_dest_set(gtk.DEST_DEFAULT_MOTION | - gtk.DEST_DEFAULT_DROP, - TARGET_ALL, - gtk.gdk.ACTION_MOVE|gtk.gdk.ACTION_COPY) - self.connect('drag_motion' , self.drag_motion ) -# self.connect('drag_data_received', self.drag_data_received) - self.vscrolltimeout = None - -# def drag_data_received(self, widget, context, x, y, selection, targetType, time): -# print _("AutoScrollingWindow.drag_data_received("), widget - - def drag_motion(self, widget, context, x, y, time): - v = self.get_vadjustment() - if v.page_size - y <= 10: - amount = (10 - int(v.page_size - y)) * 2 - self.start_scrolling(amount) - elif y <= 10: - amount = (y - 10) * 2 - self.start_scrolling(amount) - else: - self.stop_scrolling() - return True - - def scroll_and_wait(self, amount, lock_held): - if not lock_held: - gtk.gdk.threads_enter() - self.scroll_by(0, amount) - if not lock_held: - gtk.gdk.threads_leave() - if self.vscrolltimeout is not None: - gobject.source_remove(self.vscrolltimeout) - self.vscrolltimeout = gobject.timeout_add(100, self.scroll_and_wait, amount, False) - #print "adding timeout", self.vscrolltimeout, amount - - def start_scrolling(self, amount): - if self.vscrolltimeout is not None: - gobject.source_remove(self.vscrolltimeout) - self.scroll_and_wait(amount, True) - - def stop_scrolling(self): - if self.vscrolltimeout is not None: - #print "removing timeout", self.vscrolltimeout - gobject.source_remove(self.vscrolltimeout) - self.vscrolltimeout = None - -class MessageDialog(IconMixin, gtk.MessageDialog): - flags = gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT - - def __init__(self, parent, title, message, - type=gtk.MESSAGE_ERROR, - buttons=gtk.BUTTONS_OK, - yesfunc=None, nofunc=None, - default=gtk.RESPONSE_OK - ): - gtk.MessageDialog.__init__(self, parent, - self.flags, - type, buttons, message) - IconMixin.__init__(self) - - self.set_size_request(-1, -1) - self.set_resizable(False) - self.set_title(title) - if default is not None: - self.set_default_response(default) - - self.label.set_line_wrap(True) - - self.connect('response', self.callback) - - self.yesfunc = yesfunc - self.nofunc = nofunc - if os.name == 'nt': - parent.present() - self.show_all() - - def callback(self, widget, response_id, *args): - if ((response_id == gtk.RESPONSE_OK or - response_id == gtk.RESPONSE_YES) and - self.yesfunc is not None): - self.yesfunc() - if ((response_id == gtk.RESPONSE_CANCEL or - response_id == gtk.RESPONSE_NO ) - and self.nofunc is not None): - self.nofunc() - self.destroy() - -class ErrorMessageDialog(MessageDialog): - flags = gtk.DIALOG_DESTROY_WITH_PARENT - - -class FileSelection(IconMixin, gtk.FileChooserDialog): - - def __init__(self, action, main, title='', fullname='', - got_location_func=None, no_location_func=None, - got_multiple_location_func=None, show=True): - gtk.FileChooserDialog.__init__(self, action=action, title=title, - buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK)) - IconMixin.__init__(self) - from BitTorrent.ConvertedMetainfo import filesystem_encoding - self.fsenc = filesystem_encoding - try: - fullname.decode('utf8') - except: - fullname = fullname.decode(self.fsenc) - self.set_default_response(gtk.RESPONSE_OK) - if action == gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER: - self.convert_button_box = gtk.HBox() - self.convert_button = gtk.Button(_("Choose an existing folder...")) - self.convert_button.connect('clicked', self.change_action) - self.convert_button_box.pack_end(self.convert_button, - expand=False, - fill=False) - self.convert_button_box.show_all() - self.set_extra_widget(self.convert_button_box) - elif action == gtk.FILE_CHOOSER_ACTION_OPEN: - self.all_filter = gtk.FileFilter() - self.all_filter.add_pattern('*') - self.all_filter.set_name(_("All Files")) - self.add_filter(self.all_filter) - self.torrent_filter = gtk.FileFilter() - self.torrent_filter.add_pattern('*.torrent') - self.torrent_filter.add_mime_type('application/x-bittorrent') - self.torrent_filter.set_name(_("Torrents")) - self.add_filter(self.torrent_filter) - self.set_filter(self.torrent_filter) - - self.main = main - self.set_modal(True) - self.set_destroy_with_parent(True) - if fullname: - if action == gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER: - self.set_filename(fullname) - elif action == gtk.FILE_CHOOSER_ACTION_OPEN: - if fullname[-1] != os.sep: - fullname = fullname + os.sep - path, filename = os.path.split(fullname) - self.set_current_folder(path) - else: - if fullname[-1] == os.sep: - fullname = fullname[:-1] - path, filename = os.path.split(fullname) - if gtk.gtk_version < (2,8): - path = path.encode(self.fsenc) - self.set_current_folder(path) - self.set_current_name(filename) - if got_multiple_location_func is not None: - self.got_multiple_location_func = got_multiple_location_func - self.set_select_multiple(True) - self.got_location_func = got_location_func - self.no_location_func = no_location_func - self.connect('response', self.got_response) - self.d_handle = self.connect('destroy', self.got_response, - gtk.RESPONSE_CANCEL) - if show: - self.show() - - def change_action(self, widget): - if self.get_action() == gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER: - self.convert_button.set_label(_("Create a new folder...")) - self.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - elif self.get_action() == gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER: - self.convert_button.set_label(_("Choose an existing folder...")) - self.set_action(gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER) - - def got_response(self, widget, response): - if response == gtk.RESPONSE_OK: - if self.get_select_multiple(): - if self.got_multiple_location_func is not None: - self.got_multiple_location_func(self.get_filenames()) - elif self.got_location_func is not None: - fn = self.get_filename() - if fn: - self.got_location_func(fn) - else: - self.no_location_func() - else: - if self.no_location_func is not None: - self.no_location_func() - self.disconnect(self.d_handle) - self.destroy() - - def done(self, widget=None): - if self.get_select_multiple(): - self.got_multiple_location() - else: - self.got_location() - self.disconnect(self.d_handle) - self.destroy() - - def close_child_windows(self): - self.destroy() - - def close(self, widget=None): - self.destroy() - - -class OpenFileSelection(FileSelection): - - def __init__(self, *args, **kwargs): - FileSelection.__init__(self, gtk.FILE_CHOOSER_ACTION_OPEN, *args, - **kwargs) - - -class SaveFileSelection(FileSelection): - - def __init__(self, *args, **kwargs): - FileSelection.__init__(self, gtk.FILE_CHOOSER_ACTION_SAVE, *args, - **kwargs) - - -class ChooseFolderSelection(FileSelection): - - def __init__(self, *args, **kwargs): - FileSelection.__init__(self, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, - *args, **kwargs) - -class CreateFolderSelection(FileSelection): - - def __init__(self, *args, **kwargs): - FileSelection.__init__(self, gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER, - *args, **kwargs) - - -class FileOrFolderSelection(FileSelection): - def __init__(self, *args, **kwargs): - FileSelection.__init__(self, gtk.FILE_CHOOSER_ACTION_OPEN, *args, - **kwargs) - self.select_file = _("Select a file" ) - self.select_folder = _("Select a folder") - self.convert_button_box = gtk.HBox() - self.convert_button = gtk.Button(self.select_folder) - self.convert_button.connect('clicked', self.change_action) - self.convert_button_box.pack_end(self.convert_button, - expand=False, - fill=False) - self.convert_button_box.show_all() - self.set_extra_widget(self.convert_button_box) - self.reset_by_action() - self.set_filter(self.all_filter) - - - def change_action(self, widget): - if self.get_action() == gtk.FILE_CHOOSER_ACTION_OPEN: - self.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - elif self.get_action() == gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER: - self.set_action(gtk.FILE_CHOOSER_ACTION_OPEN) - self.reset_by_action() - - def reset_by_action(self): - if self.get_action() == gtk.FILE_CHOOSER_ACTION_OPEN: - self.convert_button.set_label(self.select_folder) - self.set_title(self.select_file) - elif self.get_action() == gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER: - self.convert_button.set_label(self.select_file) - self.set_title(self.select_folder) - - def set_title(self, title): - mytitle = title + ':' - FileSelection.set_title(self, mytitle) - - -class PaddedHSeparator(gtk.VBox): - def __init__(self, spacing=SPACING): - gtk.VBox.__init__(self) - self.sep = gtk.HSeparator() - self.pack_start(self.sep, expand=False, fill=False, padding=spacing) - self.show_all() - - -class HSeparatedBox(gtk.VBox): - - def new_separator(self): - return PaddedHSeparator() - - def _get_children(self): - return gtk.VBox.get_children(self) - - def get_children(self): - return self._get_children()[0::2] - - def _reorder_child(self, child, index): - gtk.VBox.reorder_child(self, child, index) - - def reorder_child(self, child, index): - children = self._get_children() - oldindex = children.index(child) - sep = None - if oldindex == len(children) - 1: - sep = children[oldindex-1] - else: - sep = children[oldindex+1] - - newindex = index*2 - if newindex == len(children) -1: - self._reorder_child(sep, newindex-1) - self._reorder_child(child, newindex) - else: - self._reorder_child(child, newindex) - self._reorder_child(sep, newindex+1) - - def pack_start(self, widget, *args, **kwargs): - if len(self._get_children()): - s = self.new_separator() - gtk.VBox.pack_start(self, s, *args, **kwargs) - s.show() - gtk.VBox.pack_start(self, widget, *args, **kwargs) - - def pack_end(self, widget, *args, **kwargs): - if len(self._get_children()): - s = self.new_separator() - gtk.VBox.pack_start(self, s, *args, **kwargs) - s.show() - gtk.VBox.pack_end(self, widget, *args, **kwargs) - - def remove(self, widget): - children = self._get_children() - if len(children) > 1: - index = children.index(widget) - if index == 0: - sep = children[index+1] - else: - sep = children[index-1] - sep.destroy() - gtk.VBox.remove(self, widget) diff --git a/BitTorrent/GetTorrent.py b/BitTorrent/GetTorrent.py deleted file mode 100644 index a69651c..0000000 --- a/BitTorrent/GetTorrent.py +++ /dev/null @@ -1,94 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# GetTorrent -- abstraction which can get a .torrent file from multiple -# sources: local file, url, etc. - -# written by Matt Chisholm - -import os -import re -import zurllib -from bencode import bdecode -from BitTorrent.platform import get_cache_dir - -urlpat = re.compile('^\w+://') -urlpat_torrent = re.compile('^torrent://') -urlpat_bittorrent = re.compile('^bittorrent://') - -def get_quietly(arg): - (data, errors) = get(arg) - # If there's an error opening a file from the IE cache, - # act like we simply didn't get a file (because we didn't) - if errors: - cache = get_cache_dir() - if (cache is not None) and (cache in arg): - errors = [] - return data, errors - -def get(arg): - data = None - errors = [] - if os.access(arg, os.F_OK): - data, errors = get_file(arg) - elif urlpat.match(arg): - data, errors = get_url(arg) - else: - errors.append(_("Could not read %s") % arg) - return data, errors - - -def get_url(url): - data = None - errors = [] - err_str = _("Could not download or open \n%s\n" - "Try using a web browser to download the torrent file.") % url - u = None - - # pending protocol changes, convert: - # torrent://http://path.to/file - # and: - # bittorrent://http://path.to/file - # to: - # http://path.to/file - url = urlpat_torrent.sub('', url) - url = urlpat_bittorrent.sub('', url) - - try: - u = zurllib.urlopen(url) - data = u.read() - u.close() - b = bdecode(data) - except Exception, e: - if u is not None: - u.close() - errors.append(err_str + "\n(%s)" % e) - data = None - else: - if u is not None: - u.close() - - return data, errors - - -def get_file(filename): - data = None - errors = [] - f = None - try: - f = file(filename, 'rb') - data = f.read() - f.close() - except Exception, e: - if f is not None: - f.close() - errors.append((_("Could not read %s") % filename) + (': %s' % str(e))) - - return data, errors diff --git a/BitTorrent/IPC.py b/BitTorrent/IPC.py deleted file mode 100644 index de50434..0000000 --- a/BitTorrent/IPC.py +++ /dev/null @@ -1,441 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Greg Hazel -# based on code by Uoti Urpala - -import os -import socket -import sys -import traceback -if os.name == 'nt': - import win32api - import win32event - import winerror - import win32ui - import dde - import pywin.mfc.object - -from binascii import b2a_hex - -from BitTorrent.RawServer_magic import RawServer, Handler -from BitTorrent.platform import get_home_dir, get_config_dir -from BitTorrent import INFO, WARNING, ERROR, CRITICAL, BTFailure, app_name - -def toint(s): - return int(b2a_hex(s), 16) - -def tobinary(i): - return (chr(i >> 24) + chr((i >> 16) & 0xFF) + - chr((i >> 8) & 0xFF) + chr(i & 0xFF)) - -CONTROL_SOCKET_PORT = 46881 - -class ControlsocketListener(Handler): - - def __init__(self, callback): - self.callback = callback - - def connection_made(self, connection): - connection.handler = MessageReceiver(self.callback) - - -class MessageReceiver(Handler): - - def __init__(self, callback): - self.callback = callback - self._buffer = [] - self._buffer_len = 0 - self._reader = self._read_messages() - self._next_len = self._reader.next() - - def _read_messages(self): - while True: - yield 4 - l = toint(self._message) - yield l - action = self._message - - if action in ('no-op',): - self.callback(action, None) - else: - yield 4 - l = toint(self._message) - yield l - data = self._message - if action in ('show_error',): - self.callback(action, data) - else: - yield 4 - l = toint(self._message) - yield l - path = self._message - if action in ('start_torrent'): - self.callback(action, data, path) - - # copied from Connecter.py - def data_came_in(self, conn, s): - while True: - i = self._next_len - self._buffer_len - if i > len(s): - self._buffer.append(s) - self._buffer_len += len(s) - return - m = s[:i] - if self._buffer_len > 0: - self._buffer.append(m) - m = ''.join(self._buffer) - self._buffer = [] - self._buffer_len = 0 - s = s[i:] - self._message = m - try: - self._next_len = self._reader.next() - except StopIteration: - self._reader = None - conn.close() - return - - def connection_lost(self, conn): - self._reader = None - pass - - def connection_flushed(self, conn): - pass - -class IPC(object): - def __init__(self, config, log): - self.config = config - self.log = log - self.rawserver = None - self.callback = None - - def create(self): - pass - - def start(self, callback): - self.callback = callback - - def send_command(self, command, *args): - pass - - def handle_command(self, command, *args): - if callable(self.callback): - return self.callback(command, *args) - self.log(WARNING, _("Unhandled command: %s %s" % (str(command), str(args)))) - - def set_rawserver(self, rawserver): - self.rawserver = rawserver - - def stop(self): - pass - -class IPCSocketBase(IPC): - - def __init__(self, *args): - IPC.__init__(self, *args) - self.port = CONTROL_SOCKET_PORT - - self.controlsocket = None - - def start(self, callback): - IPC.start(self, callback) - self.rawserver.start_listening(self.controlsocket, - ControlsocketListener(self.handle_command)) - - def stop(self): - # safe double-stop, since TorrentQueue seems to be prone to do so - if self.controlsocket: - # it's possible we're told to stop after controlsocket creation but - # before rawserver registration - if self.rawserver: - self.rawserver.stop_listening(self.controlsocket) - self.controlsocket.close() - self.controlsocket = None - -class IPCUnixSocket(IPCSocketBase): - - def __init__(self, *args): - IPCSocketBase.__init__(self, *args) - self.socket_filename = os.path.join(self.config['data_dir'], 'ui_socket') - - def create(self): - filename = self.socket_filename - if os.path.exists(filename): - try: - self.send_command('no-op') - except BTFailure: - pass - else: - raise BTFailure(_("Could not create control socket: already in use")) - - try: - os.unlink(filename) - except OSError, e: - raise BTFailure(_("Could not remove old control socket filename:") - + str(e)) - try: - controlsocket = RawServer.create_unixserversocket(filename) - except socket.error, e: - raise BTFailure(_("Could not create control socket: ")+str(e)) - - self.controlsocket = controlsocket - - # blocking version without rawserver - def send_command(self, command, *args): - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - filename = self.socket_filename - try: - s.connect(filename) - s.send(tobinary(len(command))) - s.send(command) - for arg in args: - s.send(tobinary(len(arg))) - s.send(arg) - s.close() - except socket.error, e: - s.close() - raise BTFailure(_("Could not send command: ") + str(e)) - - -class IPCWin32Socket(IPCSocketBase): - def __init__(self, *args): - IPCSocketBase.__init__(self, *args) - self.socket_filename = os.path.join(self.config['data_dir'], 'ui_socket') - self.mutex = None - self.master = 0 - - def _get_sic_path(self): - directory = get_config_dir() - configdir = os.path.join(directory, '.bittorrent') - filename = os.path.join(configdir, ".btcontrol") - return filename - - def create(self): - obtain_mutex = 1 - mutex = win32event.CreateMutex(None, obtain_mutex, app_name) - - # prevent the PyHANDLE from going out of scope, ints are fine - self.mutex = int(mutex) - mutex.Detach() - - lasterror = win32api.GetLastError() - - if lasterror == winerror.ERROR_ALREADY_EXISTS: - takeover = 0 - - try: - # if the mutex already exists, discover which port to connect to. - # if something goes wrong with that, tell us to take over the - # role of master - takeover = self.discover_sic_socket() - except: - pass - - if not takeover: - raise BTFailure(_("Global mutex already created.")) - - self.master = 1 - - # lazy free port code - port_limit = 50000 - while self.port < port_limit: - try: - controlsocket = RawServer.create_serversocket(self.port, - '127.0.0.1', reuse=True) - self.controlsocket = controlsocket - break - except socket.error, e: - self.port += 1 - - if self.port >= port_limit: - raise BTFailure(_("Could not find an open port!")) - - filename = self._get_sic_path() - (path, name) = os.path.split(filename) - try: - os.makedirs(path) - except OSError, e: - # 17 is dir exists - if e.errno != 17: - BTFailure(_("Could not create application data directory!")) - f = open(filename, "w") - f.write(str(self.port)) - f.close() - - # we're done writing the control file, release the mutex so other instances can lock it and read the file - # but don't destroy the handle until the application closes, so that the named mutex is still around - win32event.ReleaseMutex(self.mutex) - - def discover_sic_socket(self): - takeover = 0 - - # mutex exists and has been opened (not created, not locked). - # wait for it so we can read the file - r = win32event.WaitForSingleObject(self.mutex, win32event.INFINITE) - - # WAIT_OBJECT_0 means the mutex was obtained - # WAIT_ABANDONED means the mutex was obtained, and it had previously been abandoned - if (r != win32event.WAIT_OBJECT_0) and (r != win32event.WAIT_ABANDONED): - raise BTFailure(_("Could not acquire global mutex lock for controlsocket file!")) - - filename = self._get_sic_path() - try: - f = open(filename, "r") - self.port = int(f.read()) - f.close() - except: - if (r == win32event.WAIT_ABANDONED): - self.log(WARNING, _("A previous instance of BT was not cleaned up properly. Continuing.")) - # take over the role of master - takeover = 1 - else: - self.log(WARNING, (_("Another instance of BT is running, but \"%s\" does not exist.\n") % filename)+ - _("I'll guess at the port.")) - try: - self.port = CONTROL_SOCKET_PORT - self.send_command('no-op') - self.log(WARNING, _("Port found: %d") % self.port) - try: - f = open(filename, "w") - f.write(str(self.port)) - f.close() - except: - traceback.print_exc() - except: - # this is where this system falls down. - # There's another copy of BitTorrent running, or something locking the mutex, - # but I can't communicate with it. - self.log(WARNING, _("Could not find port.")) - - - # we're done reading the control file, release the mutex so other instances can lock it and read the file - win32event.ReleaseMutex(self.mutex) - - return takeover - - #blocking version without rawserver - def send_command(self, command, *datas): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - s.connect(('127.0.0.1', self.port)) - s.send(tobinary(len(command))) - s.send(command) - for data in datas: - s.send(tobinary(len(data))) - s.send(data) - s.close() - except socket.error, e: - try: - s.close() - except: - pass - raise BTFailure(_("Could not send command: ") + str(e)) - - - def stop(self): - if self.master: - r = win32event.WaitForSingleObject(self.mutex, win32event.INFINITE) - filename = self._get_sic_path() - try: - os.remove(filename) - except OSError, e: - # print, but continue - traceback.print_exc() - self.master = 0 - win32event.ReleaseMutex(self.mutex) - # close it so the named mutex goes away - win32api.CloseHandle(self.mutex) - self.mutex = None - -if os.name == 'nt': - class HandlerObject(pywin.mfc.object.Object): - def __init__(self, handler, target): - self.handler = handler - pywin.mfc.object.Object.__init__(self, target) - - class Topic(HandlerObject): - def __init__(self, handler, target): - target.AddItem(dde.CreateStringItem("")) - HandlerObject.__init__(self, handler, target) - - def Request(self, x): - # null byte hack - x = x.replace("\\**0", "\0") - items = x.split("|") - self.handler(items[0], *items[1:]) - return ("OK") - - # remote procedure call - #def Exec(self, x): - # exec x - - class Server(HandlerObject): - def __init__(self, log, *args): - self.log = log - HandlerObject.__init__(self, *args) - - def CreateSystemTopic(self): - return Topic(self.handler, dde.CreateServerSystemTopic()) - - def Status(self, s): - #if self.log: - # self.log(INFO, _("IPC Status: %s") % s) - pass - - def stop(self): - self.Shutdown() - self.Destroy() - -class IPCWin32DDE(IPC): - def create(self): - self.server = None - - # try to connect first - self.client = Server(None, None, dde.CreateServer()) - self.client.Create(app_name, dde.CBF_FAIL_SELFCONNECTIONS|dde.APPCMD_CLIENTONLY) - self.conversation = dde.CreateConversation(self.client) - try: - self.conversation.ConnectTo(app_name, "controlsocket") - raise BTFailure(_("DDE Conversation connected.")) - except dde.error, e: - # no one is listening - pass - - # clean up - self.client.stop() - del self.client - del self.conversation - - # start server - self.server = Server(self.log, self.handle_command, dde.CreateServer()) - self.server.Create(app_name, dde.CBF_FAIL_SELFCONNECTIONS|dde.APPCLASS_STANDARD) - self.server.AddTopic(Topic(self.handle_command, dde.CreateTopic("controlsocket"))) - - def send_command(self, command, *args): - s = '|'.join([command, ] + list(args)) - # null byte hack - if s.count("\0") > 0: - self.log(WARNING, "IPC: String with null byte(s):" + s) - s = s.replace("\0", "\\**0") - result = self.conversation.Request(s) - - def stop(self): - if self.server: - server = self.server - self.server = None - server.stop() - -if os.name == 'nt': - #ipc_interface = IPCWin32Socket - ipc_interface = IPCWin32DDE -else: - ipc_interface = IPCUnixSocket - diff --git a/BitTorrent/LaunchPath.py b/BitTorrent/LaunchPath.py deleted file mode 100644 index 3c78845..0000000 --- a/BitTorrent/LaunchPath.py +++ /dev/null @@ -1,54 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# LaunchPath -- a cross platform way to "open," "launch," or "start" -# files and directories - -# written by Matt Chisholm - -import os - -can_launch_files = False -posix_browsers = ('gnome-open','konqueror',) #gmc, gentoo only work on dirs -default_posix_browser = '' - -def launchpath_nt(path): - os.startfile(path) - -def launchpath_mac(path): - # BUG: this is untested - os.spawnlp(os.P_NOWAIT, 'open', 'open', path) - -def launchpath_posix(path): - if default_posix_browser: - os.spawnlp(os.P_NOWAIT, default_posix_browser, - default_posix_browser, path) - -def launchpath(path): - pass - -def launchdir(path): - if os.path.isdir(path): - launchpath(path) - -if os.name == 'nt': - can_launch_files = True - launchpath = launchpath_nt -elif os.name == 'mac': - can_launch_files = True - launchpath = launchpath_mac -elif os.name == 'posix': - for b in posix_browsers: - if os.system("which '%s' >/dev/null 2>&1" % b.replace("'","\\'")) == 0: - can_launch_files = True - default_posix_browser = b - launchpath = launchpath_posix - break - diff --git a/BitTorrent/NatCheck.py b/BitTorrent/NatCheck.py deleted file mode 100644 index fe59a15..0000000 --- a/BitTorrent/NatCheck.py +++ /dev/null @@ -1,146 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -from cStringIO import StringIO -from socket import error as socketerror - -protocol_name = 'BitTorrent protocol' - -# header, reserved, download id, my id, [length, message] - -from twisted.internet.protocol import Protocol, ClientFactory -from twisted.internet import reactor -from twisted.python import log - -class NatCheck(object): - - def __init__(self, resultfunc, downloadid, peerid, ip, port): - self.resultfunc = resultfunc - self.downloadid = downloadid - self.peerid = peerid - self.ip = ip - self.port = port - self.answered = False - - factory = NatCheckProtocolFactory(self, downloadid, peerid) - - reactor.connectTCP(ip, port, factory) - - def answer(self, result): - if not self.answered: - self.answered = True - log.msg('NAT check for %s:%i is %s' % (self.ip, self.port, result)) - self.resultfunc(result, self.downloadid, self.peerid, self.ip, self.port) - -class NatCheckProtocolFactory(ClientFactory): - def __init__(self, natcheck, downloadid, peerid): - self.natcheck = natcheck - self.downloadid = downloadid - self.peerid = peerid - - def startedConnecting(self, connector): - log.msg('Started to connect.') - - def buildProtocol(self, addr): - return NatCheckProtocol(self, self.downloadid, self.peerid) - - def clientConnectionLost(self, connector, reason): - self.natcheck.answer(False) - log.msg('Lost connection. Reason: %s' % reason) - - def clientConnectionFailed(self, connector, reason): - self.natcheck.answer(False) - log.msg('Connection failed. Reason: %s' % reason) - -class NatCheckProtocol(Protocol): - def __init__(self, factory, downloadid, peerid): - self.factory = factory - self.downloadid = downloadid - self.peerid = peerid - self.data = '' - self.received_protocol_name_len = None - self.received_protocol_name = None - self.received_reserved = None - self.received_downloadid = None - self.received_peerid = None - - def connectionMade(self): - self.transport.write(chr(len(protocol_name))) - self.transport.write(protocol_name) - self.transport.write(chr(0) * 8) - self.transport.write(self.downloadid) - - def dataReceived(self, data): - self.data += data - - if self.received_protocol_name_len is None: - if len(self.data) >= 1: - self.received_protocol_name_len = ord(self.data[0]) - self.data = self.data[1:] - if self.received_protocol_name_len != len(protocol_name): - self.factory.natcheck.answer(False) - self.transport.loseConnection() - return - else: - return - - if self.received_protocol_name is None: - if len(self.data) >= self.received_protocol_name_len: - self.received_protocol_name = self.data[:self.received_protocol_name_len] - self.data = self.data[self.received_protocol_name_len:] - if self.received_protocol_name != protocol_name: - log.err('Received protocol name did not match!') - self.factory.natcheck.answer(False) - self.transport.loseConnection() - return - else: - return - - if self.received_reserved is None: - if len(self.data) >= 8: - self.received_reserved = self.data[:8] - self.data = self.data[8:] - else: - return - - if self.received_downloadid is None: - if len(self.data) >= 20: - self.received_downloadid = self.data[:20] - self.data = self.data[20:] - if self.received_downloadid != self.downloadid: - log.err('Received download id did not match!') - self.factory.natcheck.answer(False) - self.transport.loseConnection() - return - else: - return - - if self.received_peerid is None: - if len(self.data) >= 20: - log.msg('Peerid length: %i' % len(self.peerid)) - self.received_peerid = self.data[:20] - self.data = self.data[20:] - log.msg('Received: %s' % self.received_peerid.encode('hex')) - log.msg('Received: %s' % self.received_peerid.encode('quoted-printable')) - log.msg('Expected: %s' % self.peerid.encode('hex')) - log.msg('Expected: %s' % self.peerid.encode('quoted-printable')) - if self.received_peerid != self.peerid: - log.err('Received peer id did not match!') - self.factory.natcheck.answer(False) - self.transport.loseConnection() - return - else: - return - - if self.received_protocol_name == protocol_name and self.received_downloadid == self.downloadid and self.received_peerid == self.peerid: - self.factory.natcheck.answer(True) - self.transport.loseConnection() diff --git a/BitTorrent/NatTraversal.py b/BitTorrent/NatTraversal.py deleted file mode 100644 index 30eb377..0000000 --- a/BitTorrent/NatTraversal.py +++ /dev/null @@ -1,757 +0,0 @@ -# someday: http://files.dns-sd.org/draft-nat-port-mapping.txt -# today: http://www.upnp.org/ - -debug = False - -import sys -import socket -import os -if os.name == 'nt': - import pywintypes - import win32com.client - -has_set = False -try: - # python 2.4 - s = set() - del s - has_set = True -except NameError: - try: - # python 2.3 - from sets import Set - set = Set - has_set = True - except ImportError: - # python 2.2 - pass - -from BitTorrent import app_name, defer -from BitTorrent import INFO, WARNING, ERROR -from BitTorrent.platform import os_version -from BitTorrent.RawServer_magic import RawServer, Handler -from BitTorrent.BeautifulSupe import BeautifulSupe, Tag -from urllib2 import URLError, HTTPError, Request - -#bleh -from urllib import urlopen, FancyURLopener, addinfourl -from httplib import HTTPResponse - -import threading -import Queue -import urlparse -import random - -from traceback import print_stack, print_tb, print_exc - -def UnsupportedWarning(logfunc, s): - logfunc(WARNING, "NAT Traversal warning " + ("(%s: %s)." % (os_version, s))) - -def UPNPError(logfunc, s): - logfunc(ERROR, "UPnP ERROR: " + ("(%s: %s)." % (os_version, s))) - -class UPnPException(Exception): - pass - -__host_ip = None - -import thread - -def get_host_ip(): - global __host_ip - - if __host_ip is not None: - return __host_ip - - #try: - # ip = socket.gethostbyname(socket.gethostname()) - #except socket.error, e: - # mac sometimes throws an error, so they can just wait. - # plus, complicated /etc/hosts will return invalid IPs - - try: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(("bittorrent.com", 80)) - endpoint = s.getsockname() - __host_ip = endpoint[0] - except socket.error, e: - __host_ip = socket.gethostbyname(socket.gethostname()) - - return __host_ip - - -class InfoFileHandle(object): - def __init__(self, logfunc): - self.logfunc = logfunc - def write(self, s): - self.logfunc(INFO, s) - -class NATEventLoop(threading.Thread): - def __init__(self, logfunc): - threading.Thread.__init__(self) - self.log = InfoFileHandle(logfunc) - self.queue = Queue.Queue() - self.killswitch = 0 - self.setDaemon(True) - - def run(self): - - while (self.killswitch == 0): - - event = self.queue.get() - - try: - (f, a, kw) = event - f(*a, **kw) - except: - # sys can be none during interpritter shutdown - if sys is None: - break - # this prints the whole thing. - print_exc(file = self.log) - # this prints just the traceback to the application log - #print_tb(sys.exc_info()[2], file = self.log) - # this just prints the exception - #self.logfunc(INFO, str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1].__str__())) - - -class NatTraverser(object): - def __init__(self, rawserver, logfunc): - self.rawserver = rawserver - - def log_severity_filter(level, s, optional=True): - global debug - if level >= ERROR or debug or not optional: - logfunc(level, s) - self.logfunc = log_severity_filter - - self.register_requests = [] - self.unregister_requests = [] - self.list_requests = [] - - self.service = None - self.services = [] - self.current_service = 0 - - if self.rawserver.config['upnp']: - if os.name == 'nt': - self.services.append(WindowsUPnP) - self.services.append(ManualUPnP) - - self.event_loop = NATEventLoop(self.logfunc) - self.event_loop.start() - - self.resume_init_services() - - def add_task(self, f, *a, **kw): - self.event_loop.queue.put((f, a, kw)) - - def init_services(self): - # this loop is a little funny so a service can resume the init if it fails later - if not self.rawserver.config['upnp']: - return - while self.current_service < len(self.services): - service = self.services[self.current_service] - self.current_service += 1 - try: - self.logfunc(INFO, ("Trying: %s" % service.__name__)) - service(self) - break - except Exception, e: - self.logfunc(WARNING, str(e)) - else: - UnsupportedWarning(self.logfunc, "Unable to map a port using any service.") - - def resume_init_services(self): - self.add_task(self.init_services) - - def attach_service(self, service): - self.logfunc(INFO, ("Using: %s" % type(service).__name__)) - self.service = service - self.add_task(self._flush_queue) - - def detach_service(self, service): - if service != self.service: - self.logfunc(ERROR, ("Service: %s is not in use!" % type(service).__name__)) - return - self.logfunc(INFO, ("Detached: %s" % type(service).__name__)) - self.service = None - - def _flush_queue(self): - if self.service: - for mapping in self.register_requests: - self.add_task(self.service.safe_register_port, mapping) - self.register_requests = [] - - for request in self.unregister_requests: - # unregisters can block, because they occur at shutdown - self.service.unregister_port(*request) - self.unregister_requests = [] - - for request in self.list_requests: - self.add_task(self._list_ports, request) - self.list_requests = [] - - def register_port(self, external_port, internal_port, protocol, - host = None, service_name = None): - mapping = UPnPPortMapping(external_port, internal_port, protocol, - host, service_name) - self.register_requests.append(mapping) - - self.add_task(self._flush_queue) - - return mapping.d - - def unregister_port(self, external_port, protocol): - self.unregister_requests.append((external_port, protocol)) - - # unregisters can block, because they occur at shutdown - self._flush_queue() - - def _list_ports(self, d): - matches = self.service._list_ports() - d.callback(matches) - - def list_ports(self): - d = defer.Deferred() - self.list_requests.append(d) - self.add_task(self._flush_queue) - return d - - - - -class NATBase(object): - def __init__(self, logfunc): - self.logfunc = logfunc - self.log = InfoFileHandle(logfunc) - - def safe_register_port(self, new_mapping): - - # check for the host now, while we're in the thread and before - # we need to read it. - new_mapping.populate_host() - - self.logfunc(INFO, "You asked for: " + str(new_mapping)) - new_mapping.original_external_port = new_mapping.external_port - mappings = self._list_ports() - - used_ports = [] - for mapping in mappings: - #only consider ports which match the same protocol - if mapping.protocol == new_mapping.protocol: - # look for exact matches - if (mapping.host == new_mapping.host and - mapping.internal_port == new_mapping.internal_port): - # the service name could not match, that's ok. - new_mapping.d.callback(mapping.external_port) - self.logfunc(INFO, "Already effectively mapped: " + str(new_mapping), optional=False) - return - # otherwise, add it to the list of used external ports - used_ports.append(mapping.external_port) - - if has_set: - used_ports = set(used_ports) - - if (not has_set) or (len(used_ports) < 1000): - # for small sets we can just guess around a little - while new_mapping.external_port in used_ports: - new_mapping.external_port += random.randint(1, 10) - # maybe this happens, I really doubt it - if new_mapping.external_port > 65535: - new_mapping.external_port = 1025 - else: - # for larger sets we don't want to guess forever - all_ports = set(range(1024, 65535)) - free_ports = all_ports - used_ports - new_mapping.external_port = random.choice(free_ports) - - self.logfunc(INFO, "I'll give you: " + str(new_mapping)) - self.register_port(new_mapping) - - def register_port(self, port): - pass - def unregister_port(self, external_port, protocol): - pass - def _list_ports(self): - pass - -class UPnPPortMapping(object): - def __init__(self, external_port, internal_port, protocol, - host = None, service_name = None): - self.external_port = int(external_port) - self.internal_port = int(internal_port) - self.protocol = protocol - - self.host = host - - if service_name: - self.service_name = service_name - else: - self.service_name = app_name - - self.d = defer.Deferred() - - def populate_host(self): - # throw out '' or None or ints, also look for semi-valid IPs - if (not isinstance(self.host, str)) or (self.host.count('.') < 3): - self.host = get_host_ip() - - def __str__(self): - return "%s %s external:%d %s:%d" % (self.service_name, self.protocol, - self.external_port, - self.host, self.internal_port) - -def VerifySOAPResponse(request, response): - if response.code != 200: - raise HTTPError(request.get_full_url(), - response.code, str(response.msg) + " (unexpected SOAP response code)", - response.info(), response) - - data = response.read() - bs = BeautifulSupe(data) - soap_response = bs.scour("m:", "Response") - if not soap_response: - # maybe I should read the SOAP spec. - soap_response = bs.scour("u:", "Response") - if not soap_response: - raise HTTPError(request.get_full_url(), - response.code, str(response.msg) + - " (incorrect SOAP response method)", - response.info(), response) - return soap_response[0] - -def SOAPResponseToDict(soap_response): - result = {} - for tag in soap_response.child_elements(): - value = None - if tag.contents: - value = str(tag.contents[0]) - result[tag.name] = value - return result - -def SOAPErrorToString(response): - if not isinstance(response, Exception): - data = response.read() - bs = BeautifulSupe(data) - error = bs.first('errorDescription') - if error: - return str(error.contents[0]) - return str(response) - -_urlopener = None -def urlopen_custom(req, rawserver): - global _urlopener - - if not _urlopener: - opener = FancyURLopener() - _urlopener = opener - #remove User-Agent - del _urlopener.addheaders[:] - - if not isinstance(req, str): - #for header in r.headers: - # _urlopener.addheaders.append((header, r.headers[header])) - #return _urlopener.open(r.get_full_url(), r.data) - - # All this has to be done manually, since httplib and urllib 1 and 2 - # add headers to the request that some routers do not accept. - # A minimal, functional request includes the headers: - # Content-Length - # Soapaction - # I have found the following to be specifically disallowed: - # User-agent - # Connection - # Accept-encoding - - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(req.get_full_url()) - - if not scheme.startswith("http"): - raise ValueError("UPnP URL scheme is not http: " + req.get_full_url()) - - if len(path) == 0: - path = '/' - - if netloc.count(":") > 0: - host, port = netloc.split(':', 1) - try: - port = int(port) - except: - raise ValueError("UPnP URL port is not int: " + req.get_full_url()) - else: - host = netloc - port = 80 - - header_str = '' - data = '' - method = '' - header_str = " " + path + " HTTP/1.0\r\n" - if req.has_data(): - method = 'POST' - header_str = method + header_str - header_str += "Content-Length: " + str(len(req.data)) + "\r\n" - data = req.data + "\r\n" - else: - method = 'GET' - header_str = method + header_str - - header_str += "Host: " + host + ":" + str(port) + "\r\n" - - for header in req.headers: - header_str += header + ": " + str(req.headers[header]) + "\r\n" - - header_str += "\r\n" - data = header_str + data - - try: - rawserver._add_pending_connection(host) - s.connect((host, port)) - finally: - rawserver._remove_pending_connection(host) - - s.send(data) - r = HTTPResponse(s, method=method) - r.begin() - - r.recv = r.read - fp = socket._fileobject(r) - - resp = addinfourl(fp, r.msg, req.get_full_url()) - resp.code = r.status - resp.msg = r.reason - - return resp - return _urlopener.open(req) - - -class ManualUPnP(NATBase, Handler): - - upnp_addr = ('239.255.255.250', 1900) - - search_string = ('M-SEARCH * HTTP/1.1\r\n' + - 'Host:239.255.255.250:1900\r\n' + - 'ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n' + - 'Man:"ssdp:discover"\r\n' + - 'MX:3\r\n' + - '\r\n') - - # if you think for one second that I'm going to implement SOAP in any fashion, you're crazy - - get_mapping_template = ('<?xml version="1.0"?>' + - '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"' + - 's:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' + - '<s:Body>' + - '<u:GetGenericPortMappingEntry xmlns:u=' + - '"urn:schemas-upnp-org:service:WANIPConnection:1">' + - '<NewPortMappingIndex>%d</NewPortMappingIndex>' + - '</u:GetGenericPortMappingEntry>' + - '</s:Body>' + - '</s:Envelope>') - - add_mapping_template = ('<?xml version="1.0"?>' + - '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle=' + - '"http://schemas.xmlsoap.org/soap/encoding/">' + - '<s:Body>' + - '<u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">' + - '<NewEnabled>1</NewEnabled>' + - '<NewRemoteHost></NewRemoteHost>' + - '<NewLeaseDuration>0</NewLeaseDuration>' + - '<NewInternalPort>%d</NewInternalPort>' + - '<NewExternalPort>%d</NewExternalPort>' + - '<NewProtocol>%s</NewProtocol>' + - '<NewInternalClient>%s</NewInternalClient>' + - '<NewPortMappingDescription>%s</NewPortMappingDescription>' + - '</u:AddPortMapping>' + - '</s:Body>' + - '</s:Envelope>') - - delete_mapping_template = ('<?xml version="1.0"?>' + - '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle=' + - '"http://schemas.xmlsoap.org/soap/encoding/">' + - '<s:Body>' + - '<u:DeletePortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">' + - '<NewRemoteHost></NewRemoteHost>' + - '<NewExternalPort>%d</NewExternalPort>' + - '<NewProtocol>%s</NewProtocol>' + - '</u:DeletePortMapping>' + - '</s:Body>' + - '</s:Envelope>') - - def _pretify(self, body): - # I actually found a router that needed one tag per line - body = body.replace('><', '>\r\n<') - body = body.encode('utf-8') - return body - - def _build_get_mapping_request(self, pmi): - body = (self.get_mapping_template % (pmi)) - body = self._pretify(body) - headers = {'SOAPAction': '"urn:schemas-upnp-org:service:WANIPConnection:1#' + - 'GetGenericPortMappingEntry"'} - return Request(self.controlURL, body, headers) - - def _build_add_mapping_request(self, mapping): - body = (self.add_mapping_template % (mapping.internal_port, mapping.external_port, mapping.protocol, - mapping.host, mapping.service_name)) - body = self._pretify(body) - headers = {'SOAPAction': '"urn:schemas-upnp-org:service:WANIPConnection:1#' + - 'AddPortMapping"'} - return Request(self.controlURL, body, headers) - - def _build_delete_mapping_request(self, external_port, protocol): - body = (self.delete_mapping_template % (external_port, protocol)) - body = self._pretify(body) - headers = {'SOAPAction': '"urn:schemas-upnp-org:service:WANIPConnection:1#' + - 'DeletePortMapping"'} - return Request(self.controlURL, body, headers) - - def __init__(self, traverser): - NATBase.__init__(self, traverser.logfunc) - - self.controlURL = None - self.transport = None - self.traverser = traverser - self.rawserver = traverser.rawserver - - # this service can only be provided if rawserver supports multicast - if not hasattr(self.rawserver, "create_multicastsocket"): - raise AttributeError, "rawserver.create_multicastsocket" - - self.rawserver.external_add_task(self.begin_discovery, 0, ()) - - def begin_discovery(self): - - # bind to an available port, and join the multicast group - for p in xrange(self.upnp_addr[1], self.upnp_addr[1]+5000): - try: - # Original RawServer cannot do this! - s = self.rawserver.create_multicastsocket(p, get_host_ip()) - self.transport = s - self.rawserver.start_listening_multicast(s, self) - s.listening_port.joinGroup(self.upnp_addr[0], socket.INADDR_ANY) - break - except socket.error, e: - pass - - if not self.transport: - # resume init services, because we couldn't bind to a port - self.traverser.resume_init_services() - else: - self.transport.sendto(self.search_string, 0, self.upnp_addr) - self.transport.sendto(self.search_string, 0, self.upnp_addr) - self.rawserver.add_task(self._discovery_timedout, 6, ()) - - def _discovery_timedout(self): - if self.transport: - self.logfunc(WARNING, "Discovery timed out") - self.rawserver.stop_listening_multicast(self.transport) - self.transport = None - # resume init services, because we know we've failed - self.traverser.resume_init_services() - - def register_port(self, mapping): - request = self._build_add_mapping_request(mapping) - - try: - response = urlopen_custom(request, self.rawserver) - response = VerifySOAPResponse(request, response) - mapping.d.callback(mapping.external_port) - self.logfunc(INFO, "registered: " + str(mapping), optional=False) - except Exception, e: #HTTPError, URLError, BadStatusLine, you name it. - error = SOAPErrorToString(e) - mapping.d.errback(error) - - - def unregister_port(self, external_port, protocol): - request = self._build_delete_mapping_request(external_port, protocol) - - try: - response = urlopen_custom(request, self.rawserver) - response = VerifySOAPResponse(request, response) - self.logfunc(INFO, ("unregisterd: %s, %s" % (external_port, protocol)), optional=False) - except Exception, e: #HTTPError, URLError, BadStatusLine, you name it. - error = SOAPErrorToString(e) - self.logfunc(ERROR, error) - - def data_came_in(self, addr, datagram): - if self.transport is None: - return - statusline, response = datagram.split('\r\n', 1) - httpversion, statuscode, reasonline = statusline.split(None, 2) - if (not httpversion.startswith('HTTP')) or (statuscode != '200'): - return - headers = response.split('\r\n') - location = None - for header in headers: - prefix = 'location:' - if header.lower().startswith(prefix): - location = header[len(prefix):] - location = location.strip() - if location: - self.rawserver.stop_listening_multicast(self.transport) - self.transport = None - - self.traverser.add_task(self._got_location, location) - - def _got_location(self, location): - if self.controlURL is not None: - return - - URLBase = location - - data = urlopen_custom(location, self.rawserver).read() - bs = BeautifulSupe(data) - - URLBase_tag = bs.first('URLBase') - if URLBase_tag and URLBase_tag.contents: - URLBase = str(URLBase_tag.contents[0]) - - wanservices = bs.fetch('service', dict(serviceType= - 'urn:schemas-upnp-org:service:WANIPConnection:')) - wanservices += bs.fetch('service', dict(serviceType= - 'urn:schemas-upnp-org:service:WANPPPConnection:')) - for service in wanservices: - controlURL = service.get('controlURL') - if controlURL: - self.controlURL = urlparse.urljoin(URLBase, controlURL) - break - - if self.controlURL is None: - # resume init services, because we know we've failed - self.traverser.resume_init_services() - return - - # attach service, so the queue gets flushed - self.traverser.attach_service(self) - - def _list_ports(self): - mappings = [] - index = 0 - - if self.controlURL is None: - raise UPnPException("ManualUPnP is not prepared") - - while True: - request = self._build_get_mapping_request(index) - - try: - response = urlopen_custom(request, self.rawserver) - soap_response = VerifySOAPResponse(request, response) - results = SOAPResponseToDict(soap_response) - mapping = UPnPPortMapping(results['NewExternalPort'], results['NewInternalPort'], - results['NewProtocol'], results['NewInternalClient'], - results['NewPortMappingDescription']) - mappings.append(mapping) - index += 1 - except URLError, e: - # SpecifiedArrayIndexInvalid, for example - break - except (HTTPError, BadStatusLine), e: - self.logfunc(ERROR, ("list_ports failed with: %s" % (e))) - - - return mappings - -class WindowsUPnPException(UPnPException): - def __init__(self, msg, *args): - msg += " (%s)" % os_version - a = [msg] + list(args) - UPnPException.__init__(self, *a) - -class WindowsUPnP(NATBase): - def __init__(self, traverser): - NATBase.__init__(self, traverser.logfunc) - - self.upnpnat = None - self.port_collection = None - self.traverser = traverser - - win32com.client.pythoncom.CoInitialize() - - try: - self.upnpnat = win32com.client.Dispatch("HNetCfg.NATUPnP") - except pywintypes.com_error, e: - if (e[2][5] == -2147221005): - raise WindowsUPnPException("invalid class string") - else: - raise - - try: - self.port_collection = self.upnpnat.StaticPortMappingCollection - if self.port_collection is None: - raise WindowsUPnPException("none port_collection") - except pywintypes.com_error, e: - #if e[1].lower() == "exception occurred.": - if (e[2][5] == -2147221164): - #I think this is Class Not Registered - #it happens on Windows 98 after the XP ICS wizard has been run - raise WindowsUPnPException("exception occurred, class not registered") - else: - raise - - # attach service, so the queue gets flushed - self.traverser.attach_service(self) - - - def register_port(self, mapping): - try: - self.port_collection.Add(mapping.external_port, mapping.protocol, - mapping.internal_port, mapping.host, - True, mapping.service_name) - self.logfunc(INFO, "registered: " + str(mapping), optional=False) - mapping.d.callback(mapping.external_port) - except pywintypes.com_error, e: - # host == 'fake' or address already bound - #if (e[2][5] == -2147024726): - # host == '', or I haven't a clue - #e.args[0] == -2147024894 - - #mapping.d.errback(e) - - # detach self so the queue isn't flushed - self.traverser.detach_service(self) - - if hasattr(mapping, 'original_external_port'): - mapping.external_port = mapping.original_external_port - del mapping.original_external_port - - # push this mapping back on the queue - self.traverser.register_requests.append(mapping) - - # resume init services, because we know we've failed - self.traverser.resume_init_services() - - def unregister_port(self, external_port, protocol): - try: - self.port_collection.Remove(external_port, protocol) - self.logfunc(INFO, ("unregisterd: %s, %s" % (external_port, protocol)), optional=False) - except pywintypes.com_error, e: - if (e[2][5] == -2147352567): - UPNPError(self.logfunc, ("Port %d:%s not bound" % (external_port, protocol))) - elif (e[2][5] == -2147221008): - UPNPError(self.logfunc, ("Port %d:%s is bound and is not ours to remove" % (external_port, protocol))) - elif (e[2][5] == -2147024894): - UPNPError(self.logfunc, ("Port %d:%s not bound (2)" % (external_port, protocol))) - else: - raise - - def _list_ports(self): - mappings = [] - - try: - for mp in self.port_collection: - mapping = UPnPPortMapping(mp.ExternalPort, mp.InternalPort, mp.Protocol, - mp.InternalClient, mp.Description) - mappings.append(mapping) - except pywintypes.com_error, e: - # it's the "for mp in self.port_collection" iter that can throw - # an exception. - # com_error: (-2147220976, 'The owner of the PerUser subscription is - # not logged on to the system specified', - # None, None) - pass - - return mappings - diff --git a/BitTorrent/NewVersion.py b/BitTorrent/NewVersion.py deleted file mode 100644 index 25445ae..0000000 --- a/BitTorrent/NewVersion.py +++ /dev/null @@ -1,281 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# written by Matt Chisholm - -import os -import sys -import zurllib -import pickle -import threading -from sha import sha - -DEBUG = False - -from BitTorrent import ERROR, WARNING, BTFailure, version, app_name -from BitTorrent import GetTorrent -from BitTorrent.bencode import bdecode, bencode -from BitTorrent.platform import os_version, spawn, get_temp_dir, doc_root, is_frozen_exe, osx -from BitTorrent.ConvertedMetainfo import ConvertedMetainfo - -if osx: - from Foundation import NSAutoreleasePool - -if is_frozen_exe or DEBUG: - # needed for py2exe to include the public key lib - from Crypto.PublicKey import DSA - -version_host = 'http://version.bittorrent.com/' -download_url = 'http://bittorrent.com/download.html' - -# based on Version() class from ShellTools package by Matt Chisholm, -# used with permission -class Version(list): - def __str__(self): - return '.'.join(map(str, self)) - - def is_beta(self): - return self[1] % 2 == 1 - - def from_str(self, text): - return Version( [int(t) for t in text.split('.')] ) - - def name(self): - if self.is_beta(): - return 'beta' - else: - return 'stable' - - from_str = classmethod(from_str) - -currentversion = Version.from_str(version) - -availableversion = None - -class Updater(object): - def __init__(self, threadwrap, newversionfunc, startfunc, installfunc, - errorfunc, test_new_version='', test_current_version=''): - self.threadwrap = threadwrap # for calling back to UI from thread - self.newversionfunc = newversionfunc # alert to new version UI function - self.startfunc = startfunc # start torrent UI function - self.installfunc = installfunc # install torrent UI function - self.errorfunc = errorfunc # report error UI function - self.infohash = None - self.version = currentversion - self.currentversion = currentversion - self.asked_for_install = False - self.version_site = version_host - if os.name == 'nt': - self.version_site += 'win32/' - if os_version not in ('XP', '2000', '2003'): - self.version_site += 'legacy/' - elif osx: - self.version_site += 'osx/' - self.debug_mode = DEBUG - if test_new_version: - test_new_version = Version.from_str(test_new_version) - self.debug_mode = True - def _hack_get_available(url): - return test_new_version - self._get_available = _hack_get_available - if test_current_version: - self.debug_mode = True - self.currentversion = Version.from_str(test_current_version) - - - def debug(self, message): - if self.debug_mode: - self.threadwrap(self.errorfunc, WARNING, message) - - - def _get_available(self, url): - self.debug('Updater.get_available() hitting url %s' % url) - try: - u = zurllib.urlopen(url) - s = u.read() - s = s.strip() - except: - raise BTFailure(_("Could not get latest version from %s")%url) - try: - assert len(s) == 5 - availableversion = Version.from_str(s) - except: - raise BTFailure(_("Could not parse new version string from %s")%url) - return availableversion - - - def get_available(self): - url = self.version_site + self.currentversion.name() - availableversion = self._get_available(url) - if availableversion.is_beta(): - if availableversion[1] != self.currentversion[1]: - availableversion = self.currentversion - if self.currentversion.is_beta(): - stable_url = self.version_site + 'stable' - available_stable_version = self._get_available(stable_url) - if available_stable_version > availableversion: - availableversion = available_stable_version - self.version = availableversion - self.debug('Updater.get_available() got %s' % str(self.version)) - return self.version - - - def get(self): - self.debug('Version check skipped for RPM package') - return - - try: - self.get_available() - except BTFailure, e: - self.threadwrap(self.errorfunc, WARNING, e) - return - - if self.version <= self.currentversion: - self.debug('Updater.get() not updating old version %s' % str(self.version)) - return - - if not self.can_install(): - self.debug('Updater.get() cannot install on this os') - return - - self.installer_name = self.calc_installer_name() - self.installer_url = self.version_site + self.installer_name + '.torrent' - self.installer_dir = self.calc_installer_dir() - - self.torrentfile = None - torrentfile, terrors = GetTorrent.get_url(self.installer_url) - signature = None - try: - signfile = zurllib.urlopen(self.installer_url + '.sign') - except: - self.debug('Updater.get() failed to get signfile %s.sign' % self.installer_url) - else: - try: - signature = pickle.load(signfile) - except: - self.debug('Updater.get() failed to load signfile %s' % signfile) - - if terrors: - self.threadwrap(self.errorfunc, WARNING, '\n'.join(terrors)) - - if torrentfile and signature: - public_key_file = open('@@PKIDIR@@/bittorrent/public.key', 'rb') - public_key = pickle.load(public_key_file) - h = sha(torrentfile).digest() - if public_key.verify(h, signature): - self.torrentfile = torrentfile - b = bdecode(torrentfile) - self.infohash = sha(bencode(b['info'])).digest() - self.total_size = b['info']['length'] - self.debug('Updater.get() got torrent file and signature') - else: - self.debug('Updater.get() torrent file signature failed to verify.') - pass - else: - self.debug('Updater.get() doesn\'t have torrentfile %s and signature %s' % - (str(type(torrentfile)), str(type(signature)))) - - def installer_path(self): - if self.installer_dir is not None: - return os.path.join(self.installer_dir, - self.installer_name) - else: - return None - - def check(self): - t = threading.Thread(target=self._check, - args=()) - t.setDaemon(True) - t.start() - - def _check(self): - if osx: - pool = NSAutoreleasePool.alloc().init() - self.get() - if self.version > self.currentversion: - self.threadwrap(self.newversionfunc, self.version, download_url) - - def can_install(self): - if self.debug_mode: - return True - if os.name == 'nt': - return True - elif osx: - return True - else: - return False - - def calc_installer_name(self): - if os.name == 'nt': - ext = 'exe' - elif osx: - ext = 'dmg' - elif os.name == 'posix' and self.debug_mode: - ext = 'tar.gz' - else: - return - - parts = [app_name, str(self.version)] - if self.version.is_beta(): - parts.append('Beta') - name = '-'.join(parts) - name += '.' + ext - return name - - def set_installer_dir(self, path): - self.installer_dir = path - - def calc_installer_dir(self): - if hasattr(self, 'installer_dir'): - return self.installer_dir - - temp_dir = get_temp_dir() - if temp_dir is not None: - return temp_dir - else: - self.errorfunc(WARNING, - _("Could not find a suitable temporary location to " - "save the %s %s installer.") % (app_name, self.version)) - - def installer_downloaded(self): - if self.installer_path() and os.access(self.installer_path(), os.F_OK): - size = os.stat(self.installer_path())[6] - if size == self.total_size: - return True - else: - #print 'installer is wrong size, is', size, 'should be', self.total_size - return False - else: - #print 'installer does not exist' - return False - - def download(self): - if self.torrentfile is not None: - self.startfunc(self.torrentfile, self.installer_path()) - else: - self.errorfunc(WARNING, _("No torrent file available for %s %s " - "installer.")%(app_name, self.version)) - - def start_install(self): - if not self.asked_for_install: - if self.installer_downloaded(): - self.asked_for_install = True - self.installfunc() - else: - self.errorfunc(WARNING, - _("%s %s installer appears to be incomplete, " - "missing, or corrupt.")%(app_name, - self.version)) - - def launch_installer(self, torrentqueue): - if os.name == 'nt': - spawn(torrentqueue, self.installer_path(), "/S") - else: - self.errorfunc(WARNING, _("Cannot launch installer on this OS")) diff --git a/BitTorrent/PeerID.py b/BitTorrent/PeerID.py deleted file mode 100644 index 9dc9285..0000000 --- a/BitTorrent/PeerID.py +++ /dev/null @@ -1,28 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.0 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Matt Chisholm - -import os -from sha import sha -from time import time -try: - getpid = os.getpid -except AttributeError: - def getpid(): - return 1 - -from BitTorrent import version - -def make_id(): - myid = 'M' + version.split()[0].replace('.', '-') - myid = myid + ('-' * (8-len(myid)))+sha(repr(time())+ ' ' + - str(getpid())).digest()[-6:].encode('hex') - return myid diff --git a/BitTorrent/PiecePicker.py b/BitTorrent/PiecePicker.py deleted file mode 100644 index e22b13a..0000000 --- a/BitTorrent/PiecePicker.py +++ /dev/null @@ -1,138 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -from random import randrange, shuffle, choice - - -class PiecePicker(object): - - def __init__(self, numpieces, config): - self.config = config - self.numpieces = numpieces - self.interests = [range(numpieces)] - self.pos_in_interests = range(numpieces) - self.numinterests = [0] * numpieces - self.have = [False] * numpieces - self.crosscount = [numpieces] - self.started = [] - self.seedstarted = [] - self.numgot = 0 - self.scrambled = range(numpieces) - shuffle(self.scrambled) - - def got_have(self, piece): - numint = self.numinterests[piece] - self.crosscount[numint + self.have[piece]] -= 1 - self.numinterests[piece] += 1 - try: - self.crosscount[numint + 1 + self.have[piece]] += 1 - except IndexError: - self.crosscount.append(1) - if self.have[piece]: - return - if numint == len(self.interests) - 1: - self.interests.append([]) - self._shift_over(piece, self.interests[numint], self.interests[numint + 1]) - - def lost_have(self, piece): - numint = self.numinterests[piece] - self.crosscount[numint + self.have[piece]] -= 1 - self.numinterests[piece] -= 1 - self.crosscount[numint - 1 + self.have[piece]] += 1 - if self.have[piece]: - return - self._shift_over(piece, self.interests[numint], self.interests[numint - 1]) - - def _shift_over(self, piece, l1, l2): - p = self.pos_in_interests[piece] - l1[p] = l1[-1] - self.pos_in_interests[l1[-1]] = p - del l1[-1] - newp = randrange(len(l2) + 1) - if newp == len(l2): - self.pos_in_interests[piece] = len(l2) - l2.append(piece) - else: - old = l2[newp] - self.pos_in_interests[old] = len(l2) - l2.append(old) - l2[newp] = piece - self.pos_in_interests[piece] = newp - - def requested(self, piece, seed = False): - if piece not in self.started: - self.started.append(piece) - if seed and piece not in self.seedstarted: - self.seedstarted.append(piece) - - def complete(self, piece): - assert not self.have[piece] - self.have[piece] = True - self.crosscount[self.numinterests[piece]] -= 1 - try: - self.crosscount[self.numinterests[piece] + 1] += 1 - except IndexError: - self.crosscount.append(1) - self.numgot += 1 - l = self.interests[self.numinterests[piece]] - p = self.pos_in_interests[piece] - l[p] = l[-1] - self.pos_in_interests[l[-1]] = p - del l[-1] - try: - self.started.remove(piece) - self.seedstarted.remove(piece) - except ValueError: - pass - - def next(self, havefunc, seed = False): - bests = None - bestnum = 2 ** 30 - if seed: - s = self.seedstarted - else: - s = self.started - for i in s: - if havefunc(i): - if self.numinterests[i] < bestnum: - bests = [i] - bestnum = self.numinterests[i] - elif self.numinterests[i] == bestnum: - bests.append(i) - if bests: - return choice(bests) - if self.numgot < self.config['rarest_first_cutoff']: - for i in self.scrambled: - if havefunc(i): - return i - return None - for i in xrange(1, min(bestnum, len(self.interests))): - for j in self.interests[i]: - if havefunc(j): - return j - return None - - def am_I_complete(self): - return self.numgot == self.numpieces - - def bump(self, piece): - l = self.interests[self.numinterests[piece]] - pos = self.pos_in_interests[piece] - del l[pos] - l.append(piece) - for i in range(pos,len(l)): - self.pos_in_interests[l[i]] = i - try: - self.started.remove(piece) - self.seedstarted.remove(piece) - except ValueError: - pass diff --git a/BitTorrent/RateLimiter.py b/BitTorrent/RateLimiter.py deleted file mode 100644 index d58f2c4..0000000 --- a/BitTorrent/RateLimiter.py +++ /dev/null @@ -1,190 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Uoti Urpala and Andrew Loewenstern - -from BitTorrent.platform import bttime - -def minctx(a,b): - A = B = 0 - if a.rate > 0: - A = a.offset_amount / a.rate - if b.rate > 0: - B = b.offset_amount / b.rate - if A <= B: - return a - return b - -class Dummy(object): - def __init__(self, next): - self.next_upload = next - def send_partial(self, size): - return 0 - closed = False - -class RateLimitedGroup(object): - def __init__(self, rate, got_exception): - self.got_exception = got_exception - # limiting - self.check_time = 0 - self.lasttime = bttime() - self.offset_amount = 0 - self.set_rate(rate) - # accounting - self.count = 0 - self.counts = [] - - def set_rate(self, new_rate): - self.rate = new_rate * 1024 - self.check_time = 0 - self.offset_amount = 0 - -class MultiRateLimiter(object): - def __init__(self, sched): - self.sched = sched - self.last = None - self.upload_rate = 0 - self.unitsize = 17000 - self.offset_amount = 0 - self.ctxs = [] # list of contexts with connections in the queue - self.ctx_counts = {} # dict conn -> how many connections each context has - - def set_parameters(self, rate, unitsize): - if unitsize > 17000: - # Since data is sent to peers in a round-robin fashion, max one - # full request at a time, setting this higher would send more data - # to peers that use request sizes larger than standard 16 KiB. - # 17000 instead of 16384 to allow room for metadata messages. - unitsize = 17000 - self.upload_rate = rate * 1024 - self.unitsize = unitsize - self.lasttime = bttime() - self.offset_amount = 0 - - def queue(self, conn, ctx): - assert conn.next_upload is None - if ctx not in self.ctxs: - ctx.check_time = 1 - self.ctxs.append(ctx) - self.ctx_counts[ctx] = 1 - else: - self.ctx_counts[ctx] += 1 - - if self.last is None: - self.last = conn - conn.next_upload = conn - self.try_send(True) - else: - conn.next_upload = self.last.next_upload - self.last.next_upload = conn - self.last = conn - - def increase_offset(self, bytes): - self.offset_amount += bytes - - def try_send(self, check_time = False): - t = bttime() - cur = self.last.next_upload - - if self.upload_rate > 0: - self.offset_amount -= (t - self.lasttime) * self.upload_rate - if check_time: - self.offset_amount = max(self.offset_amount, -1 * self.unitsize) - else: - self.offset_amount = 0 - - self.lasttime = t - - for ctx in self.ctxs: - if ctx.rate == 0: - ctx.offset_amount = 0 - ctx.lasttime = t - elif ctx.lasttime != t: - ctx.offset_amount -=(t - ctx.lasttime) * ctx.rate - ctx.lasttime = t - if ctx.check_time: - ctx.offset_amount = max(ctx.offset_amount, -1 * self.unitsize) - - min_offset = reduce(minctx, self.ctxs) - ctx = cur.encoder.context.rlgroup - while self.offset_amount <= 0 and min_offset.offset_amount <= 0: - if ctx.offset_amount <= 0: - try: - bytes = cur.send_partial(self.unitsize) - except KeyboardInterrupt: - raise - except Exception, e: - cur.encoder.context.rlgroup.got_exception(e) - cur = self.last.next_upload - bytes = 0 - - if self.upload_rate > 0: - self.offset_amount += bytes - if ctx.rate > 0: - ctx.offset_amount += bytes - - ctx.count += bytes - - if bytes == 0 or not cur.connection.is_flushed(): - if self.last is cur: - self.last = None - cur.next_upload = None - self.ctx_counts = {} - self.ctxs = [] - break - else: - self.last.next_upload = cur.next_upload - cur.next_upload = None - old = ctx - cur = self.last.next_upload - ctx = cur.encoder.context.rlgroup - self.ctx_counts[old] -= 1 - if self.ctx_counts[old] == 0: - del(self.ctx_counts[old]) - self.ctxs.remove(old) - if min_offset == old: - min_offset = reduce(minctx, self.ctxs) - else: - self.last = cur - cur = cur.next_upload - ctx = cur.encoder.context.rlgroup - min_offset = reduce(minctx, self.ctxs) - else: - self.last = cur - cur = self.last.next_upload - ctx = cur.encoder.context.rlgroup - else: - myDelay = minCtxDelay = 0 - if self.upload_rate > 0: - myDelay = 1.0 * self.offset_amount / self.upload_rate - if min_offset.rate > 0: - minCtxDelay = 1.0 * min_offset.offset_amount / min_offset.rate - delay = max(myDelay, minCtxDelay) - self.sched(self.try_send, delay) - - - def clean_closed(self): - if self.last is None: - return - orig = self.last - if self.last.closed: - self.last = Dummy(self.last.next_upload) - self.last.encoder = orig.encoder - c = self.last - while True: - if c.next_upload is orig: - c.next_upload = self.last - break - if c.next_upload.closed: - o = c.next_upload - c.next_upload = Dummy(c.next_upload.next_upload) - c.next_upload.encoder = o.encoder - c = c.next_upload - diff --git a/BitTorrent/RateMeasure.py b/BitTorrent/RateMeasure.py deleted file mode 100644 index b609330..0000000 --- a/BitTorrent/RateMeasure.py +++ /dev/null @@ -1,63 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -from BitTorrent.platform import bttime - - -class RateMeasure(object): - - def __init__(self, left): - self.start = None - self.last = None - self.rate = 0 - self.remaining = None - self.left = left - self.broke = False - self.got_anything = False - - def data_came_in(self, amount): - if not self.got_anything: - self.got_anything = True - self.start = bttime() - 2 - self.last = self.start - self.left -= amount - return - self.update(bttime(), amount) - - def data_rejected(self, amount): - self.left += amount - - def get_time_left(self): - if not self.got_anything: - return None - t = bttime() - if t - self.last > 15: - self.update(t, 0) - return self.remaining - - def get_size_left(self): - return self.left - - def update(self, t, amount): - self.left -= amount - try: - self.rate = ((self.rate * (self.last - self.start)) + amount) / (t - self.start) - self.last = t - self.remaining = self.left / self.rate - if self.start < self.last - self.remaining: - self.start = self.last - self.remaining - except ZeroDivisionError: - self.remaining = None - if self.broke and self.last - self.start < 20: - self.start = self.last - 20 - if self.last - self.start > 20: - self.broke = True diff --git a/BitTorrent/Rerequester.py b/BitTorrent/Rerequester.py deleted file mode 100644 index 93d9472..0000000 --- a/BitTorrent/Rerequester.py +++ /dev/null @@ -1,293 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen, Uoti Urpala - -from threading import Thread -from socket import error, gethostbyname -from random import random, randrange -from binascii import b2a_hex - -from BitTorrent import version -from BitTorrent.platform import bttime -from BitTorrent.zurllib import urlopen, quote, Request -from BitTorrent.btformats import check_peers -from BitTorrent.bencode import bencode, bdecode -from BitTorrent import BTFailure, INFO, WARNING, ERROR, CRITICAL - - -class Rerequester(object): - - def __init__(self, url, config, sched, howmany, connect, externalsched, - amount_left, up, down, port, myid, infohash, errorfunc, doneflag, - upratefunc, downratefunc, ever_got_incoming, diefunc, sfunc): - self.baseurl = url - self.infohash = infohash - self.peerid = None - self.wanted_peerid = myid - self.port = port - self.url = None - self.config = config - self.last = None - self.trackerid = None - self.announce_interval = 30 * 60 - self.sched = sched - self.howmany = howmany - self.connect = connect - self.externalsched = externalsched - self.amount_left = amount_left - self.up = up - self.down = down - self.errorfunc = errorfunc - self.doneflag = doneflag - self.upratefunc = upratefunc - self.downratefunc = downratefunc - self.ever_got_incoming = ever_got_incoming - self.diefunc = diefunc - self.successfunc = sfunc - self.finish = False - self.current_started = None - self.fail_wait = None - self.last_time = None - self.previous_down = 0 - self.previous_up = 0 - self.tracker_num_peers = None - self.tracker_num_seeds = None - - def _makeurl(self, peerid, port): - return ('%s?info_hash=%s&peer_id=%s&port=%s&key=%s' % - (self.baseurl, quote(self.infohash), quote(peerid), str(port), - b2a_hex(''.join([chr(randrange(256)) for i in xrange(4)])))) - - def change_port(self, peerid, port): - self.wanted_peerid = peerid - self.port = port - self.last = None - self.trackerid = None - self._check() - - def begin(self): - if self.sched: - self.sched(self.begin, 60) - self._check() - - def announce_finish(self): - self.finish = True - self._check() - - def announce_stop(self): - self._announce(2) - - def _check(self): - if self.current_started is not None: - if self.current_started <= bttime() - 58: - self.errorfunc(WARNING, - _("Tracker announce still not complete " - "%d seconds after starting it") % - int(bttime() - self.current_started)) - return - if self.peerid is None: - self.peerid = self.wanted_peerid - self.url = self._makeurl(self.peerid, self.port) - self._announce(0) - return - if self.peerid != self.wanted_peerid: - self._announce(2) - self.peerid = None - self.previous_up = self.up() - self.previous_down = self.down() - return - if self.finish: - self.finish = False - self._announce(1) - return - if self.fail_wait is not None: - if self.last_time + self.fail_wait <= bttime(): - self._announce() - return - if self.last_time > bttime() - self.config['rerequest_interval']: - return - if self.ever_got_incoming(): - getmore = self.howmany() <= self.config['min_peers'] / 3 - else: - getmore = self.howmany() < self.config['min_peers'] - if getmore or bttime() - self.last_time > self.announce_interval: - self._announce() - - def _announce(self, event=None): - self.current_started = bttime() - s = ('%s&uploaded=%s&downloaded=%s&left=%s' % - (self.url, str(self.up() - self.previous_up), - str(self.down() - self.previous_down), str(self.amount_left()))) - if self.last is not None: - s += '&last=' + quote(str(self.last)) - if self.trackerid is not None: - s += '&trackerid=' + quote(str(self.trackerid)) - if self.howmany() >= self.config['max_initiate']: - s += '&numwant=0' - else: - s += '&compact=1' - if event is not None: - s += '&event=' + ['started', 'completed', 'stopped'][event] - t = Thread(target=self._rerequest, args=[s, self.peerid]) - t.setDaemon(True) - t.start() - - # Must destroy all references that could cause reference circles - def cleanup(self): - self.sched = None - self.howmany = None - self.connect = None - self.externalsched = lambda *args: None - self.amount_left = None - self.up = None - self.down = None - self.errorfunc = None - self.upratefunc = None - self.downratefunc = None - self.ever_got_incoming = None - self.diefunc = None - self.successfunc = None - - def _rerequest(self, url, peerid): - if self.config['ip']: - try: - url += '&ip=' + gethostbyname(self.config['ip']) - except Exception, e: - self.errorfunc(WARNING, _("Problem connecting to tracker, gethostbyname failed - ") + str(e)) - request = Request(url) - request.add_header('User-Agent', 'BitTorrent/' + version) - if self.config['tracker_proxy']: - request.set_proxy(self.config['tracker_proxy'], 'http') - try: - h = urlopen(request) - data = h.read() - h.close() - # urllib2 can raise various crap that doesn't have a common base - # exception class especially when proxies are used, at least - # ValueError and stuff from httplib - except Exception, e: - def f(r=_("Problem connecting to tracker - ") + str(e)): - self._postrequest(errormsg=r, peerid=peerid) - else: - def f(): - self._postrequest(data, peerid=peerid) - self.externalsched(f, 0) - - def _fail(self): - if self.fail_wait is None: - self.fail_wait = 50 - else: - self.fail_wait *= 1.4 + random() * .2 - self.fail_wait = min(self.fail_wait, - self.config['max_announce_retry_interval']) - - def _postrequest(self, data=None, errormsg=None, peerid=None): - self.current_started = None - self.last_time = bttime() - if errormsg is not None: - self.errorfunc(WARNING, errormsg) - self._fail() - return - try: - r = bdecode(data) - check_peers(r) - except BTFailure, e: - if data != '': - self.errorfunc(ERROR, _("bad data from tracker - ") + str(e)) - self._fail() - return - if type(r.get('complete')) in (int, long) and \ - type(r.get('incomplete')) in (int, long): - self.tracker_num_seeds = r['complete'] - self.tracker_num_peers = r['incomplete'] - else: - self.tracker_num_seeds = self.tracker_num_peers = None - if r.has_key('failure reason'): - if self.howmany() > 0: - self.errorfunc(ERROR, _("rejected by tracker - ") + - r['failure reason']) - else: - # sched shouldn't be strictly necessary - def die(): - self.diefunc(CRITICAL, - _("Aborting the torrent as it was rejected by " - "the tracker while not connected to any peers. ") + - _(" Message from the tracker: ") + r['failure reason']) - self.sched(die, 0) - self._fail() - else: - self.fail_wait = None - if r.has_key('warning message'): - self.errorfunc(ERROR, _("warning from tracker - ") + - r['warning message']) - self.announce_interval = r.get('interval', self.announce_interval) - self.config['rerequest_interval'] = r.get('min interval', - self.config['rerequest_interval']) - self.trackerid = r.get('tracker id', self.trackerid) - self.last = r.get('last') - p = r['peers'] - peers = [] - if type(p) == str: - for x in xrange(0, len(p), 6): - ip = '.'.join([str(ord(i)) for i in p[x:x+4]]) - port = (ord(p[x+4]) << 8) | ord(p[x+5]) - peers.append((ip, port, None)) - else: - for x in p: - peers.append((x['ip'], x['port'], x.get('peer id'))) - ps = len(peers) + self.howmany() - if ps < self.config['max_initiate']: - if self.doneflag.isSet(): - if r.get('num peers', 1000) - r.get('done peers', 0) > ps * 1.2: - self.last = None - else: - if r.get('num peers', 1000) > ps * 1.2: - self.last = None - for x in peers: - self.connect((x[0], x[1]), x[2]) - if peerid == self.wanted_peerid: - self.successfunc() - self._check() - -class DHTRerequester(Rerequester): - def __init__(self, config, sched, howmany, connect, externalsched, - amount_left, up, down, port, myid, infohash, errorfunc, doneflag, - upratefunc, downratefunc, ever_got_incoming, diefunc, sfunc, dht): - self.dht = dht - Rerequester.__init__(self, "http://localhost/announce", config, sched, howmany, connect, externalsched, - amount_left, up, down, port, myid, infohash, errorfunc, doneflag, - upratefunc, downratefunc, ever_got_incoming, diefunc, sfunc) - - def _announce(self, event=None): - self.current_started = bttime() - self._rerequest("", self.peerid) - - def _rerequest(self, url, peerid): - self.peers = "" - try: - self.dht.getPeersAndAnnounce(self.infohash, self.port, self._got_peers) - except Exception, e: - self._postrequest(errormsg=_("Trackerless lookup failed: ") + str(e), peerid=self.wanted_peerid) - - def _got_peers(self, peers): - if not self.howmany: - return - if not peers: - self._postrequest(bencode({'peers':''}), peerid=self.wanted_peerid) - else: - self._postrequest(bencode({'peers':peers[0]}), peerid=None) - - def _announced_peers(self, nodes): - pass - - def announce_stop(self): - # don't do anything - pass diff --git a/BitTorrent/StatusLight.py b/BitTorrent/StatusLight.py deleted file mode 100644 index 11cbf14..0000000 --- a/BitTorrent/StatusLight.py +++ /dev/null @@ -1,108 +0,0 @@ -from BitTorrent.platform import bttime - -class StatusLight(object): - - initial_state = 'stopped' - - states = { - # state : (stock icon name, tool tip), - 'stopped' : ('bt-status-stopped', - _("Paused")), - 'empty' : ('bt-status-stopped', - _("No torrents")), - 'starting' : ('bt-status-starting', - _("Starting download")), - 'pre-natted': ('bt-status-pre-natted', - _("Starting download")), - 'running' : ('bt-status-running', - _("Running normally")), - 'natted' : ('bt-status-natted', - _("Downloads may be slow:\nProbably firewalled/NATted")), - 'broken' : ('bt-status-broken', - _("Check network connection")), - } - - messages = { - # message : default new state, - 'stop' : 'stopped' , - 'empty' : 'empty' , - 'start' : 'starting' , - 'seen_peers' : 'pre-natted', - 'seen_remote_peers' : 'running' , - 'broken' : 'broken' , - } - - transitions = { - # state : { message : custom new state, }, - 'pre-natted' : { 'start' : 'pre-natted', - 'seen_peers' : 'pre-natted',}, - 'running' : { 'start' : 'running' , - 'seen_peers' : 'running' ,}, - 'natted' : { 'start' : 'natted' , - 'seen_peers' : 'natted' ,}, - 'broken' : { 'start' : 'broken' ,}, - #TODO: add broken transitions - } - - time_to_nat = 60 * 5 # 5 minutes - - def __init__(self): - self.mystate = self.initial_state - self.start_time = None - - def send_message(self, message): - if message not in self.messages.keys(): - #print 'bad message', message - return - new_state = self.messages[message] - if self.transitions.has_key(self.mystate): - if self.transitions[self.mystate].has_key(message): - new_state = self.transitions[self.mystate][message] - - # special pre-natted timeout logic - if new_state == 'pre-natted': - if (self.mystate == 'pre-natted' and - bttime() - self.start_time > self.time_to_nat): - # go to natted state after a while - new_state = 'natted' - elif self.mystate != 'pre-natted': - # start pre-natted timer - self.start_time = bttime() - - if new_state != self.mystate: - #print 'changing state from', self.mystate, 'to', new_state - self.mystate = new_state - self.change_state() - - def change_state(self): - pass - - -import gtk - -class GtkStatusLight(gtk.EventBox, StatusLight): - - def __init__(self, main): - gtk.EventBox.__init__(self) - StatusLight.__init__(self) - self.main = main - self.image = None - self.images = {} - for k,(s,t) in self.states.items(): - i = gtk.Image() - i.set_from_stock(s, gtk.ICON_SIZE_LARGE_TOOLBAR) - i.show() - self.images[k] = i - self.set_size_request(24,24) - self.main.tooltips.set_tip(self, 'tooltip') - self.send_message('stop') - - def change_state(self): - state = self.mystate - assert self.states.has_key(state) - if self.image is not None: - self.remove(self.image) - self.image = self.images[state] - self.add(self.image) - stock, tooltip = self.states[state] - self.main.tooltips.set_tip(self, tooltip) diff --git a/BitTorrent/Storage.py b/BitTorrent/Storage.py deleted file mode 100644 index 8f1d6fb..0000000 --- a/BitTorrent/Storage.py +++ /dev/null @@ -1,274 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -import os -from bisect import bisect_right -from array import array - -from BitTorrent import BTFailure, app_name - - -class FilePool(object): - - def __init__(self, max_files_open): - self.allfiles = {} - self.handlebuffer = None - self.handles = {} - self.whandles = {} - self.set_max_files_open(max_files_open) - - def close_all(self): - failures = {} - for filename, handle in self.handles.iteritems(): - try: - handle.close() - except Exception, e: - failures[self.allfiles[filename]] = e - self.handles.clear() - self.whandles.clear() - if self.handlebuffer is not None: - del self.handlebuffer[:] - for torrent, e in failures.iteritems(): - torrent.got_exception(e) - - def set_max_files_open(self, max_files_open): - if max_files_open <= 0: - max_files_open = 1e100 - self.max_files_open = max_files_open - self.close_all() - if len(self.allfiles) > self.max_files_open: - self.handlebuffer = [] - else: - self.handlebuffer = None - - def add_files(self, files, torrent): - for filename in files: - if filename in self.allfiles: - raise BTFailure(_("File %s belongs to another running torrent") - % filename) - for filename in files: - self.allfiles[filename] = torrent - if self.handlebuffer is None and \ - len(self.allfiles) > self.max_files_open: - self.handlebuffer = self.handles.keys() - - def remove_files(self, files): - for filename in files: - del self.allfiles[filename] - if self.handlebuffer is not None and \ - len(self.allfiles) <= self.max_files_open: - self.handlebuffer = None - - -# Make this a separate function because having this code in Storage.__init__() -# would make python print a SyntaxWarning (uses builtin 'file' before 'global') - -def bad_libc_workaround(): - global file - def file(name, mode = 'r', buffering = None): - return open(name, mode) - -class Storage(object): - - def __init__(self, config, filepool, files, check_only=False): - self.filepool = filepool - self.config = config - self.ranges = [] - self.myfiles = {} - self.tops = {} - self.undownloaded = {} - self.unallocated = {} - total = 0 - for filename, length in files: - self.unallocated[filename] = length - self.undownloaded[filename] = length - if length > 0: - self.ranges.append((total, total + length, filename)) - self.myfiles[filename] = None - total += length - if os.path.exists(filename): - if not os.path.isfile(filename): - raise BTFailure(_("File %s already exists, but is not a " - "regular file") % filename) - l = os.path.getsize(filename) - if l > length and not check_only: - h = file(filename, 'rb+') - h.truncate(length) - h.close() - l = length - self.tops[filename] = l - elif not check_only: - f = os.path.split(filename)[0] - if f != '' and not os.path.exists(f): - os.makedirs(f) - file(filename, 'wb').close() - self.begins = [i[0] for i in self.ranges] - self.total_length = total - if check_only: - return - self.handles = filepool.handles - self.whandles = filepool.whandles - - # Rather implement this as an ugly hack here than change all the - # individual calls. Affects all torrent instances using this module. - if config['bad_libc_workaround']: - bad_libc_workaround() - - def was_preallocated(self, pos, length): - for filename, begin, end in self._intervals(pos, length): - if self.tops.get(filename, 0) < end: - return False - return True - - def get_total_length(self): - return self.total_length - - def _intervals(self, pos, amount): - r = [] - stop = pos + amount - p = bisect_right(self.begins, pos) - 1 - while p < len(self.ranges) and self.ranges[p][0] < stop: - begin, end, filename = self.ranges[p] - r.append((filename, max(pos, begin) - begin, min(end, stop) - begin)) - p += 1 - return r - - def _get_file_handle(self, filename, for_write): - handlebuffer = self.filepool.handlebuffer - if filename in self.handles: - if for_write and filename not in self.whandles: - self.handles[filename].close() - self.handles[filename] = file(filename, 'rb+', 0) - self.whandles[filename] = None - if handlebuffer is not None and handlebuffer[-1] != filename: - handlebuffer.remove(filename) - handlebuffer.append(filename) - else: - if for_write: - self.handles[filename] = file(filename, 'rb+', 0) - self.whandles[filename] = None - else: - self.handles[filename] = file(filename, 'rb', 0) - if handlebuffer is not None: - if len(handlebuffer) >= self.filepool.max_files_open: - oldfile = handlebuffer.pop(0) - if oldfile in self.whandles: # .pop() in python 2.3 - del self.whandles[oldfile] - self.handles[oldfile].close() - del self.handles[oldfile] - handlebuffer.append(filename) - return self.handles[filename] - - def read(self, pos, amount): - r = [] - for filename, pos, end in self._intervals(pos, amount): - h = self._get_file_handle(filename, False) - h.seek(pos) - r.append(h.read(end - pos)) - r = ''.join(r) - if len(r) != amount: - raise BTFailure(_("Short read - something truncated files?")) - return r - - def write(self, pos, s): - # might raise an IOError - total = 0 - for filename, begin, end in self._intervals(pos, len(s)): - h = self._get_file_handle(filename, True) - h.seek(begin) - h.write(s[total: total + end - begin]) - total += end - begin - - def close(self): - error = None - for filename in self.handles.keys(): - if filename in self.myfiles: - try: - self.handles[filename].close() - except Exception, e: - error = e - del self.handles[filename] - if filename in self.whandles: - del self.whandles[filename] - handlebuffer = self.filepool.handlebuffer - if handlebuffer is not None: - handlebuffer = [f for f in handlebuffer if f not in self.myfiles] - self.filepool.handlebuffer = handlebuffer - if error is not None: - raise error - - def write_fastresume(self, resumefile, amount_done): - resumefile.write('BitTorrent resume state file, version 1\n') - resumefile.write(str(amount_done) + '\n') - for x, x, filename in self.ranges: - resumefile.write(str(os.path.getsize(filename)) + ' ' + - str(int(os.path.getmtime(filename))) + '\n') - - def check_fastresume(self, resumefile, return_filelist=False, - piece_size=None, numpieces=None, allfiles=None): - filenames = [name for x, x, name in self.ranges] - if resumefile is not None: - version = resumefile.readline() - if version != 'BitTorrent resume state file, version 1\n': - raise BTFailure(_("Unsupported fastresume file format, " - "maybe from another client version?")) - amount_done = int(resumefile.readline()) - else: - amount_done = size = mtime = 0 - for filename in filenames: - if resumefile is not None: - line = resumefile.readline() - size, mtime = line.split()[:2] # allow adding extra fields - size = int(size) - mtime = int(mtime) - if os.path.exists(filename): - fsize = os.path.getsize(filename) - else: - raise BTFailure(_("Another program appears to have moved, renamed, or deleted the file, " - "or %s may have crashed last time it was run.") % app_name) - if fsize > 0 and mtime != os.path.getmtime(filename): - raise BTFailure(_("Another program appears to have modified the file, " - "or %s may have crashed last time it was run.") % app_name) - if size != fsize: - raise BTFailure(_("Another program appears to have changed the file size, " - "or %s may have crashed last time it was run.") % app_name) - if not return_filelist: - return amount_done - if resumefile is None: - return None - if numpieces < 32768: - typecode = 'h' - else: - typecode = 'l' - try: - r = array(typecode) - r.fromfile(resumefile, numpieces) - except Exception, e: - raise BTFailure(_("Couldn't read fastresume data: ") + str(e) + '.') - for i in range(numpieces): - if r[i] >= 0: - # last piece goes "past the end", doesn't matter - self.downloaded(r[i] * piece_size, piece_size) - if r[i] != -2: - self.allocated(i * piece_size, piece_size) - undl = self.undownloaded - unal = self.unallocated - return amount_done, [undl[x] for x in allfiles], \ - [not unal[x] for x in allfiles] - - def allocated(self, pos, length): - for filename, begin, end in self._intervals(pos, length): - self.unallocated[filename] -= end - begin - - def downloaded(self, pos, length): - for filename, begin, end in self._intervals(pos, length): - self.undownloaded[filename] -= end - begin diff --git a/BitTorrent/StorageWrapper.py b/BitTorrent/StorageWrapper.py deleted file mode 100644 index ac14fdd..0000000 --- a/BitTorrent/StorageWrapper.py +++ /dev/null @@ -1,408 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -from __future__ import division - -from sha import sha -from array import array -from binascii import b2a_hex - -from BitTorrent.bitfield import Bitfield -from BitTorrent import BTFailure, INFO, WARNING, ERROR, CRITICAL - -def toint(s): - return int(b2a_hex(s), 16) - -def tobinary(i): - return (chr(i >> 24) + chr((i >> 16) & 0xFF) + - chr((i >> 8) & 0xFF) + chr(i & 0xFF)) - -NO_PLACE = -1 - -ALLOCATED = -1 -UNALLOCATED = -2 -FASTRESUME_PARTIAL = -3 - -class StorageWrapper(object): - - def __init__(self, storage, config, hashes, piece_size, finished, - statusfunc, flag, data_flunked, infohash, errorfunc, resumefile): - self.numpieces = len(hashes) - self.storage = storage - self.config = config - check_hashes = config['check_hashes'] - self.hashes = hashes - self.piece_size = piece_size - self.data_flunked = data_flunked - self.errorfunc = errorfunc - self.total_length = storage.get_total_length() - self.amount_left = self.total_length - self.partial_mark = "BitTorrent - this part has not been "+\ - "downloaded yet."+infohash+\ - tobinary(config['download_slice_size']) - if self.total_length <= piece_size * (self.numpieces - 1): - raise BTFailure, _("bad data in responsefile - total too small") - if self.total_length > piece_size * self.numpieces: - raise BTFailure, _("bad data in responsefile - total too big") - self.finished = finished - self.numactive = array('H', [0] * self.numpieces) - self.inactive_requests = [1] * self.numpieces - self.amount_inactive = self.total_length - self.endgame = False - self.have = Bitfield(self.numpieces) - self.waschecked = Bitfield(self.numpieces) - if self.numpieces < 32768: - typecode = 'h' - else: - typecode = 'l' - self.places = array(typecode, [NO_PLACE] * self.numpieces) - if not check_hashes: - self.rplaces = array(typecode, range(self.numpieces)) - fastresume = True - else: - self.rplaces = self._load_fastresume(resumefile, typecode) - if self.rplaces is not None: - fastresume = True - else: - self.rplaces = array(typecode, [UNALLOCATED] * self.numpieces) - fastresume = False - self.holepos = 0 - self.stat_numfound = 0 - self.stat_numflunked = 0 - self.stat_numdownloaded = 0 - self.stat_active = {} - self.stat_new = {} - self.stat_dirty = {} - self.download_history = {} - self.failed_pieces = {} - - if self.numpieces == 0: - return - targets = {} - total = 0 - if not fastresume: - for i in xrange(self.numpieces): - if self._waspre(i): - self.rplaces[i] = ALLOCATED - total += 1 - else: - targets[hashes[i]] = i - if total and check_hashes: - statusfunc(_("checking existing file"), 0) - def markgot(piece, pos): - if self.have[piece]: - if piece != pos: - return - self.rplaces[self.places[pos]] = ALLOCATED - self.places[pos] = self.rplaces[pos] = pos - return - self.places[piece] = pos - self.rplaces[pos] = piece - self.have[piece] = True - self.amount_left -= self._piecelen(piece) - self.amount_inactive -= self._piecelen(piece) - self.inactive_requests[piece] = None - if not fastresume: - self.waschecked[piece] = True - self.stat_numfound += 1 - lastlen = self._piecelen(self.numpieces - 1) - partials = {} - for i in xrange(self.numpieces): - if not self._waspre(i): - if self.rplaces[i] != UNALLOCATED: - raise BTFailure(_("--check_hashes 0 or fastresume info " - "doesn't match file state (missing data)")) - continue - elif fastresume: - t = self.rplaces[i] - if t >= 0: - markgot(t, i) - continue - if t == UNALLOCATED: - raise BTFailure(_("Bad fastresume info (files contain more " - "data)")) - if t == ALLOCATED: - continue - if t!= FASTRESUME_PARTIAL: - raise BTFailure(_("Bad fastresume info (illegal value)")) - data = self.storage.read(self.piece_size * i, - self._piecelen(i)) - self._check_partial(i, partials, data) - self.rplaces[i] = ALLOCATED - else: - data = self.storage.read(piece_size * i, self._piecelen(i)) - sh = sha(buffer(data, 0, lastlen)) - sp = sh.digest() - sh.update(buffer(data, lastlen)) - s = sh.digest() - if s == hashes[i]: - markgot(i, i) - elif s in targets and self._piecelen(i) == self._piecelen(targets[s]): - markgot(targets[s], i) - elif not self.have[self.numpieces - 1] and sp == hashes[-1] and (i == self.numpieces - 1 or not self._waspre(self.numpieces - 1)): - markgot(self.numpieces - 1, i) - else: - self._check_partial(i, partials, data) - statusfunc(fractionDone = 1 - self.amount_left / - self.total_length) - if flag.isSet(): - return - self.amount_left_with_partials = self.amount_left - for piece in partials: - if self.places[piece] < 0: - pos = partials[piece][0] - self.places[piece] = pos - self.rplaces[pos] = piece - self._make_partial(piece, partials[piece][1]) - for i in xrange(self.numpieces): - if self.rplaces[i] != UNALLOCATED: - self.storage.allocated(piece_size * i, self._piecelen(i)) - if self.have[i]: - self.storage.downloaded(piece_size * i, self._piecelen(i)) - - def _waspre(self, piece): - return self.storage.was_preallocated(piece * self.piece_size, self._piecelen(piece)) - - def _piecelen(self, piece): - if piece < self.numpieces - 1: - return self.piece_size - else: - return self.total_length - piece * self.piece_size - - def _check_partial(self, pos, partials, data): - index = None - missing = False - marklen = len(self.partial_mark)+4 - for i in xrange(0, len(data) - marklen, - self.config['download_slice_size']): - if data[i:i+marklen-4] == self.partial_mark: - ind = toint(data[i+marklen-4:i+marklen]) - if index is None: - index = ind - parts = [] - if ind >= self.numpieces or ind != index: - return - parts.append(i) - else: - missing = True - if index is not None and missing: - i += self.config['download_slice_size'] - if i < len(data): - parts.append(i) - partials[index] = (pos, parts) - - def _make_partial(self, index, parts): - length = self._piecelen(index) - l = [] - self.inactive_requests[index] = l - x = 0 - self.amount_left_with_partials -= self._piecelen(index) - self.download_history[index] = {} - request_size = self.config['download_slice_size'] - for x in xrange(0, self._piecelen(index), request_size): - partlen = min(request_size, length - x) - if x in parts: - l.append((x, partlen)) - self.amount_left_with_partials += partlen - else: - self.amount_inactive -= partlen - self.download_history[index][x] = None - self.stat_dirty[index] = 1 - - def _initalloc(self, pos, piece): - assert self.rplaces[pos] < 0 - assert self.places[piece] == NO_PLACE - p = self.piece_size * pos - length = self._piecelen(pos) - if self.rplaces[pos] == UNALLOCATED: - self.storage.allocated(p, length) - self.places[piece] = pos - self.rplaces[pos] = piece - # "if self.rplaces[pos] != ALLOCATED:" to skip extra mark writes - mark = self.partial_mark + tobinary(piece) - mark += chr(0xff) * (self.config['download_slice_size'] - len(mark)) - mark *= (length - 1) // len(mark) + 1 - self.storage.write(p, buffer(mark, 0, length)) - - def _move_piece(self, oldpos, newpos): - assert self.rplaces[newpos] < 0 - assert self.rplaces[oldpos] >= 0 - data = self.storage.read(self.piece_size * oldpos, - self._piecelen(newpos)) - self.storage.write(self.piece_size * newpos, data) - if self.rplaces[newpos] == UNALLOCATED: - self.storage.allocated(self.piece_size * newpos, len(data)) - piece = self.rplaces[oldpos] - self.places[piece] = newpos - self.rplaces[oldpos] = ALLOCATED - self.rplaces[newpos] = piece - if not self.have[piece]: - return - data = data[:self._piecelen(piece)] - if sha(data).digest() != self.hashes[piece]: - raise BTFailure(_("data corrupted on disk - " - "maybe you have two copies running?")) - - def _get_free_place(self): - while self.rplaces[self.holepos] >= 0: - self.holepos += 1 - return self.holepos - - def get_amount_left(self): - return self.amount_left - - def do_I_have_anything(self): - return self.amount_left < self.total_length - - def _make_inactive(self, index): - length = self._piecelen(index) - l = [] - x = 0 - request_size = self.config['download_slice_size'] - while x + request_size < length: - l.append((x, request_size)) - x += request_size - l.append((x, length - x)) - self.inactive_requests[index] = l - - def _load_fastresume(self, resumefile, typecode): - if resumefile is not None: - try: - r = array(typecode) - r.fromfile(resumefile, self.numpieces) - return r - except Exception, e: - self.errorfunc(WARNING, _("Couldn't read fastresume data: ") + - str(e)) - return None - - def write_fastresume(self, resumefile): - for i in xrange(self.numpieces): - if self.rplaces[i] >= 0 and not self.have[self.rplaces[i]]: - self.rplaces[i] = FASTRESUME_PARTIAL - self.rplaces.tofile(resumefile) - - def get_have_list(self): - return self.have.tostring() - - def do_I_have(self, index): - return self.have[index] - - def do_I_have_requests(self, index): - return not not self.inactive_requests[index] - - def new_request(self, index): - # returns (begin, length) - if self.inactive_requests[index] == 1: - self._make_inactive(index) - self.numactive[index] += 1 - self.stat_active[index] = 1 - if index not in self.stat_dirty: - self.stat_new[index] = 1 - rs = self.inactive_requests[index] - r = min(rs) - rs.remove(r) - self.amount_inactive -= r[1] - if self.amount_inactive == 0: - self.endgame = True - return r - - def piece_came_in(self, index, begin, piece, source = None): - if self.places[index] < 0: - if self.rplaces[index] == ALLOCATED: - self._initalloc(index, index) - else: - n = self._get_free_place() - if self.places[n] >= 0: - oldpos = self.places[n] - self._move_piece(oldpos, n) - n = oldpos - if self.rplaces[index] < 0 or index == n: - self._initalloc(n, index) - else: - self._move_piece(index, n) - self._initalloc(index, index) - - if index in self.failed_pieces: - old = self.storage.read(self.places[index] * self.piece_size + - begin, len(piece)) - if old != piece: - self.failed_pieces[index][self.download_history[index][begin]]\ - = None - self.download_history.setdefault(index, {}) - self.download_history[index][begin] = source - - self.storage.write(self.places[index] * self.piece_size + begin, piece) - self.stat_dirty[index] = 1 - self.numactive[index] -= 1 - if self.numactive[index] == 0: - del self.stat_active[index] - if index in self.stat_new: - del self.stat_new[index] - if not self.inactive_requests[index] and not self.numactive[index]: - del self.stat_dirty[index] - if sha(self.storage.read(self.piece_size * self.places[index], self._piecelen(index))).digest() == self.hashes[index]: - self.have[index] = True - self.storage.downloaded(index * self.piece_size, - self._piecelen(index)) - self.inactive_requests[index] = None - self.waschecked[index] = True - self.amount_left -= self._piecelen(index) - self.stat_numdownloaded += 1 - for d in self.download_history[index].itervalues(): - if d is not None: - d.good(index) - del self.download_history[index] - if index in self.failed_pieces: - for d in self.failed_pieces[index]: - if d is not None: - d.bad(index) - del self.failed_pieces[index] - if self.amount_left == 0: - self.finished() - else: - self.data_flunked(self._piecelen(index), index) - self.inactive_requests[index] = 1 - self.amount_inactive += self._piecelen(index) - self.stat_numflunked += 1 - - self.failed_pieces[index] = {} - allsenders = {} - for d in self.download_history[index].itervalues(): - allsenders[d] = None - if len(allsenders) == 1: - culprit = allsenders.keys()[0] - if culprit is not None: - culprit.bad(index, bump = True) - del self.failed_pieces[index] # found the culprit already - return False - return True - - def request_lost(self, index, begin, length): - self.inactive_requests[index].append((begin, length)) - self.amount_inactive += length - self.numactive[index] -= 1 - if not self.numactive[index] and index in self.stat_active: - del self.stat_active[index] - if index in self.stat_new: - del self.stat_new[index] - - def get_piece(self, index, begin, length): - if not self.have[index]: - return None - if not self.waschecked[index]: - if sha(self.storage.read(self.piece_size * self.places[index], self._piecelen(index))).digest() != self.hashes[index]: - raise BTFailure, _("told file complete on start-up, but piece failed hash check") - self.waschecked[index] = True - if begin + length > self._piecelen(index): - return None - return self.storage.read(self.piece_size * self.places[index] + begin, length) diff --git a/BitTorrent/TorrentQueue.py b/BitTorrent/TorrentQueue.py deleted file mode 100644 index 4187a80..0000000 --- a/BitTorrent/TorrentQueue.py +++ /dev/null @@ -1,848 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Uoti Urpala - -from __future__ import division - -import os -import sys -import threading -import traceback - - -from BitTorrent import GetTorrent -from BitTorrent.platform import bttime -from BitTorrent.download import Feedback, Multitorrent -from BitTorrent.bencode import bdecode -from BitTorrent.ConvertedMetainfo import ConvertedMetainfo -from BitTorrent.prefs import Preferences -from BitTorrent import BTFailure, BTShutdown, INFO, WARNING, ERROR, CRITICAL -from BitTorrent import configfile -from BitTorrent import FAQ_URL -import BitTorrent - - -RUNNING = 0 -RUN_QUEUED = 1 -QUEUED = 2 -KNOWN = 3 -ASKING_LOCATION = 4 - - -class TorrentInfo(object): - - def __init__(self, config): - self.metainfo = None - self.dl = None - self.state = None - self.completion = None - self.finishtime = None - self.uptotal = 0 - self.uptotal_old = 0 - self.downtotal = 0 - self.downtotal_old = 0 - self.config = config - - def _set_dlpath(self, value): - self.config['save_as'] = value - - def _get_dlpath(self): - return self.config['save_as'] - - dlpath = property(_get_dlpath, _set_dlpath) - - -def decode_position(l, pred, succ, default=None): - if default is None: - default = len(l) - if pred is None and succ is None: - return default - if pred is None: - return 0 - if succ is None: - return len(l) - try: - if l[0] == succ and pred not in l: - return 0 - if l[-1] == pred and succ not in l: - return len(l) - i = l.index(pred) - if l[i+1] == succ: - return i+1 - except (ValueError, IndexError): - pass - return default - - -class TorrentQueue(Feedback): - - def __init__(self, config, ui_options, ipc): - self.ui_options = ui_options - self.ipc = ipc - self.config = config - self.config['def_running_torrents'] = 1 # !@# XXX - self.config['max_running_torrents'] = 100 # !@# XXX - self.doneflag = threading.Event() - self.torrents = {} - self.starting_torrent = None - self.running_torrents = [] - self.queue = [] - self.other_torrents = [] - self.last_save_time = 0 - self.last_version_check = 0 - self.initialized = 0 - - def run(self, ui, ui_wrap, startflag): - try: - self.ui = ui - self.run_ui_task = ui_wrap - self.multitorrent = Multitorrent(self.config, self.doneflag, - self.global_error, listen_fail_ok=True) - self.rawserver = self.multitorrent.rawserver - self.ipc.set_rawserver(self.rawserver) - self.ipc.start(self.external_command) - try: - self._restore_state() - except BTFailure, e: - self.torrents = {} - self.running_torrents = [] - self.queue = [] - self.other_torrents = [] - self.global_error(ERROR, _("Could not load saved state: ")+str(e)) - else: - for infohash in self.running_torrents + self.queue + \ - self.other_torrents: - t = self.torrents[infohash] - if t.dlpath is not None: - t.completion = self.multitorrent.get_completion( - self.config, t.metainfo, t.dlpath) - state = t.state - if state == RUN_QUEUED: - state = RUNNING - self.run_ui_task(self.ui.new_displayed_torrent, infohash, - t.metainfo, t.dlpath, state, t.config, - t.completion, t.uptotal, t.downtotal, ) - self._check_queue() - self.initialized = 1 - startflag.set() - except Exception, e: - # dump a normal exception traceback - traceback.print_exc() - # set the error flag - self.initialized = -1 - # signal the gui thread to stop waiting - startflag.set() - return - - self._queue_loop() - self.multitorrent.rawserver.listen_forever() - if self.doneflag.isSet(): - self.run_ui_task(self.ui.quit) - self.multitorrent.close_listening_socket() - self.ipc.stop() - for infohash in list(self.running_torrents): - t = self.torrents[infohash] - if t.state == RUN_QUEUED: - continue - t.dl.shutdown() - if t.dl is not None: # possibly set to none by failed() - totals = t.dl.get_total_transfer() - t.uptotal = t.uptotal_old + totals[0] - t.downtotal = t.downtotal_old + totals[1] - self._dump_state() - - def _check_version(self): - now = bttime() - if self.last_version_check > 0 and \ - self.last_version_check > now - 24*60*60: - return - self.last_version_check = now - self.run_ui_task(self.ui.check_version) - - def _dump_config(self): - configfile.save_ui_config(self.config, 'bittorrent', - self.ui_options, self.global_error) - for infohash,t in self.torrents.items(): - ec = lambda level, message: self.error(t.metainfo, level, message) - config = t.config.getDict() - if config: - configfile.save_torrent_config(self.config['data_dir'], - infohash, config, ec) - - def _dump_state(self): - self.last_save_time = bttime() - r = [] - def write_entry(infohash, t): - if t.dlpath is None: - assert t.state == ASKING_LOCATION - r.append(infohash.encode('hex') + '\n') - else: - r.append(infohash.encode('hex') + ' ' + str(t.uptotal) + ' ' + - str(t.downtotal)+' '+t.dlpath.encode('string_escape')+'\n') - r.append('BitTorrent UI state file, version 3\n') - r.append('Running torrents\n') - for infohash in self.running_torrents: - write_entry(infohash, self.torrents[infohash]) - r.append('Queued torrents\n') - for infohash in self.queue: - write_entry(infohash, self.torrents[infohash]) - r.append('Known torrents\n') - for infohash in self.other_torrents: - write_entry(infohash, self.torrents[infohash]) - r.append('End\n') - f = None - try: - filename = os.path.join(self.config['data_dir'], 'ui_state') - f = file(filename + '.new', 'wb') - f.write(''.join(r)) - f.close() - if os.access(filename, os.F_OK): - os.remove(filename) # no atomic rename on win32 - os.rename(filename + '.new', filename) - except Exception, e: - self.global_error(ERROR, _("Could not save UI state: ") + str(e)) - if f is not None: - f.close() - - def _restore_state(self): - def decode_line(line): - hashtext = line[:40] - try: - infohash = hashtext.decode('hex') - except: - raise BTFailure(_("Invalid state file contents")) - if len(infohash) != 20: - raise BTFailure(_("Invalid state file contents")) - try: - path = os.path.join(self.config['data_dir'], 'metainfo', - hashtext) - f = file(path, 'rb') - data = f.read() - f.close() - except Exception, e: - try: - f.close() - except: - pass - self.global_error(ERROR, - (_("Error reading file \"%s\".") % path) + - " (" + str(e)+ "), " + - _("cannot restore state completely")) - return None - if infohash in self.torrents: - raise BTFailure(_("Invalid state file (duplicate entry)")) - t = TorrentInfo(Preferences(self.config)) - self.torrents[infohash] = t - try: - t.metainfo = ConvertedMetainfo(bdecode(data)) - except Exception, e: - self.global_error(ERROR, - (_("Corrupt data in \"%s\", cannot restore torrent.") % path) + - '('+str(e)+')') - return None - t.metainfo.reported_errors = True # suppress redisplay on restart - if infohash != t.metainfo.infohash: - self.global_error(ERROR, - (_("Corrupt data in \"%s\", cannot restore torrent.") % path) + - _("(infohash mismatch)")) - return None - if len(line) == 41: - t.dlpath = None - return infohash, t - try: - if version < 2: - t.dlpath = line[41:-1].decode('string_escape') - elif version == 3: - up, down, dlpath = line[41:-1].split(' ', 2) - t.uptotal = t.uptotal_old = int(up) - t.downtotal = t.downtotal_old = int(down) - t.dlpath = dlpath.decode('string_escape') - elif version >= 4: - up, down = line[41:-1].split(' ', 1) - t.uptotal = t.uptotal_old = int(up) - t.downtotal = t.downtotal_old = int(down) - except ValueError: # unpack, int(), decode() - raise BTFailure(_("Invalid state file (bad entry)")) - config = configfile.read_torrent_config(self.config, - self.config['data_dir'], - infohash, self.global_error) - t.config.update(config) - return infohash, t - filename = os.path.join(self.config['data_dir'], 'ui_state') - if not os.path.exists(filename): - return - f = None - try: - f = file(filename, 'rb') - lines = f.readlines() - f.close() - except Exception, e: - if f is not None: - f.close() - raise BTFailure(str(e)) - i = iter(lines) - try: - txt = 'BitTorrent UI state file, version ' - version = i.next() - if not version.startswith(txt): - raise BTFailure(_("Bad UI state file")) - try: - version = int(version[len(txt):-1]) - except: - raise BTFailure(_("Bad UI state file version")) - if version > 4: - raise BTFailure(_("Unsupported UI state file version (from " - "newer client version?)")) - if version < 3: - if i.next() != 'Running/queued torrents\n': - raise BTFailure(_("Invalid state file contents")) - else: - if i.next() != 'Running torrents\n': - raise BTFailure(_("Invalid state file contents")) - while True: - line = i.next() - if line == 'Queued torrents\n': - break - t = decode_line(line) - if t is None: - continue - infohash, t = t - if t.dlpath is None: - raise BTFailure(_("Invalid state file contents")) - t.state = RUN_QUEUED - self.running_torrents.append(infohash) - while True: - line = i.next() - if line == 'Known torrents\n': - break - t = decode_line(line) - if t is None: - continue - infohash, t = t - if t.dlpath is None: - raise BTFailure(_("Invalid state file contents")) - t.state = QUEUED - self.queue.append(infohash) - while True: - line = i.next() - if line == 'End\n': - break - t = decode_line(line) - if t is None: - continue - infohash, t = t - if t.dlpath is None: - t.state = ASKING_LOCATION - else: - t.state = KNOWN - self.other_torrents.append(infohash) - except StopIteration: - raise BTFailure(_("Invalid state file contents")) - - def _queue_loop(self): - if self.doneflag.isSet(): - return - self.rawserver.add_task(self._queue_loop, 20) - now = bttime() - self._check_version() - if self.queue and self.starting_torrent is None: - mintime = now - self.config['next_torrent_time'] * 60 - minratio = self.config['next_torrent_ratio'] / 100 - if self.config['seed_forever']: - minratio = 1e99 - else: - mintime = 0 - minratio = self.config['last_torrent_ratio'] / 100 - if self.config['seed_last_forever']: - minratio = 1e99 - if minratio >= 1e99: - return - for infohash in self.running_torrents: - t = self.torrents[infohash] - myminratio = minratio - if t.dl: - if self.queue and t.dl.config['seed_last_forever']: - myminratio = 1e99 - elif t.dl.config['seed_forever']: - myminratio = 1e99 - if t.state == RUN_QUEUED: - continue - totals = t.dl.get_total_transfer() - # not updated for remaining torrents if one is stopped, who cares - t.uptotal = t.uptotal_old + totals[0] - t.downtotal = t.downtotal_old + totals[1] - if t.finishtime is None or t.finishtime > now - 120: - continue - if t.finishtime > mintime: - if t.uptotal < t.metainfo.total_bytes * myminratio: - continue - self.change_torrent_state(infohash, RUNNING, KNOWN) - break - if self.running_torrents and self.last_save_time < now - 300: - self._dump_state() - - def _check_queue(self): - if self.starting_torrent is not None or self.config['pause']: - return - for infohash in self.running_torrents: - if self.torrents[infohash].state == RUN_QUEUED: - self.starting_torrent = infohash - t = self.torrents[infohash] - t.state = RUNNING - t.finishtime = None - t.dl = self.multitorrent.start_torrent(t.metainfo, t.config, - self, t.dlpath) - return - if not self.queue or len(self.running_torrents) >= \ - self.config['def_running_torrents']: - return - infohash = self.queue.pop(0) - self.starting_torrent = infohash - t = self.torrents[infohash] - assert t.state == QUEUED - t.state = RUNNING - t.finishtime = None - self.running_torrents.append(infohash) - t.dl = self.multitorrent.start_torrent(t.metainfo, t.config, self, - t.dlpath) - self._send_state(infohash) - - def _send_state(self, infohash): - t = self.torrents[infohash] - state = t.state - if state == RUN_QUEUED: - state = RUNNING - pos = None - if state in (KNOWN, RUNNING, QUEUED): - l = self._get_list(state) - if l[-1] != infohash: - pos = l.index(infohash) - self.run_ui_task(self.ui.torrent_state_changed, infohash, t.dlpath, - state, t.completion, t.uptotal_old, t.downtotal_old, pos) - - def _stop_running(self, infohash): - t = self.torrents[infohash] - if t.state == RUN_QUEUED: - self.running_torrents.remove(infohash) - t.state = KNOWN - return True - assert t.state == RUNNING - shutdown_succeded = t.dl.shutdown() - if not shutdown_succeded: - self.run_ui_task(self.ui.open_log) - self.error(t.metainfo, ERROR, "Unable to stop torrent. Please send this application log to bugs@bittorrent.com .") - return False - if infohash == self.starting_torrent: - self.starting_torrent = None - try: - self.running_torrents.remove(infohash) - except ValueError: - self.other_torrents.remove(infohash) - return False - else: - t.state = KNOWN - totals = t.dl.get_total_transfer() - t.uptotal_old += totals[0] - t.uptotal = t.uptotal_old - t.downtotal_old += totals[1] - t.downtotal = t.downtotal_old - t.dl = None - t.completion = self.multitorrent.get_completion(self.config, - t.metainfo, t.dlpath) - return True - - def external_command(self, action, *datas): - if action == 'start_torrent': - assert len(datas) == 2 - self.start_new_torrent_by_name(datas[0], save_as=datas[1]) - elif action == 'show_error': - assert len(datas) == 1 - self.global_error(ERROR, datas[0]) - elif action == 'no-op': - pass - - def remove_torrent(self, infohash): - if infohash not in self.torrents: - return - state = self.torrents[infohash].state - if state == QUEUED: - self.queue.remove(infohash) - elif state in (RUNNING, RUN_QUEUED): - self._stop_running(infohash) - self._check_queue() - else: - self.other_torrents.remove(infohash) - self.run_ui_task(self.ui.removed_torrent, infohash) - del self.torrents[infohash] - - for d in ['metainfo', 'resume']: - filename = os.path.join(self.config['data_dir'], d, - infohash.encode('hex')) - try: - os.remove(filename) - except Exception, e: - self.global_error(WARNING, - (_("Could not delete cached %s file:")%d) + - str(e)) - ec = lambda level, message: self.global_error(level, message) - configfile.remove_torrent_config(self.config['data_dir'], - infohash, ec) - self._dump_state() - - def set_save_location(self, infohash, dlpath): - torrent = self.torrents.get(infohash) - if torrent is None or torrent.state == RUNNING: - return - torrent.dlpath = dlpath - self._dump_config() - torrent.completion = self.multitorrent.get_completion(self.config, - torrent.metainfo, dlpath) - if torrent.state == ASKING_LOCATION: - torrent.state = KNOWN - self.change_torrent_state(infohash, KNOWN, QUEUED) - else: - self._send_state(infohash) - self._dump_state() - - def _get_torrent_then_callback(self, name, save_as=None): - data, errors = GetTorrent.get_quietly(name) - - if data: - self.start_new_torrent(data, save_as) - for error in errors: - self.run_ui_task(self.ui.global_error, ERROR, error) - - def start_new_torrent_by_name(self, name, save_as=None): - t = threading.Thread(target=self._get_torrent_then_callback, - args=(name, save_as,)) - t.setDaemon(True) - t.start() - - def start_new_torrent(self, data, save_as=None): - t = TorrentInfo(Preferences(self.config)) - try: - t.metainfo = ConvertedMetainfo(bdecode(data)) - except Exception, e: - self.global_error(ERROR, _("This is not a valid torrent file. (%s)") - % str(e)) - return - infohash = t.metainfo.infohash - if infohash in self.torrents: - real_state = self.torrents[infohash].state - if real_state in (RUNNING, RUN_QUEUED): - self.error(t.metainfo, ERROR, - _("This torrent (or one with the same contents) is " - "already running.")) - elif real_state == QUEUED: - self.error(t.metainfo, ERROR, - _("This torrent (or one with the same contents) is " - "already waiting to run.")) - elif real_state == ASKING_LOCATION: - pass - elif real_state == KNOWN: - self.change_torrent_state(infohash, KNOWN, newstate=QUEUED) - else: - raise BTFailure(_("Torrent in unknown state %d") % real_state) - return - - path = os.path.join(self.config['data_dir'], 'metainfo', - infohash.encode('hex')) - try: - f = file(path+'.new', 'wb') - f.write(data) - f.close() - if os.access(path, os.F_OK): - os.remove(path) # no atomic rename on win32 - os.rename(path+'.new', path) - except Exception, e: - try: - f.close() - except: - pass - self.global_error(ERROR, _("Could not write file ") + path + - ' (' + str(e) + '), ' + - _("torrent will not be restarted " - "correctly on client restart")) - - config = configfile.read_torrent_config(self.config, - self.config['data_dir'], - infohash, self.global_error) - if config: - t.config.update(config) - if save_as: - self.run_ui_task(self.ui.set_config, 'save_as', save_as) - else: - save_as = None - - self.torrents[infohash] = t - t.state = ASKING_LOCATION - self.other_torrents.append(infohash) - self._dump_state() - self.run_ui_task(self.ui.new_displayed_torrent, infohash, - t.metainfo, save_as, t.state, t.config) - - def show_error(level, text): - self.run_ui_task(self.ui.error, infohash, level, text) - t.metainfo.show_encoding_errors(show_error) - - def set_config(self, option, value, ihash=None): - if not ihash: - oldvalue = self.config[option] - self.config[option] = value - self.multitorrent.set_option(option, value) - if option == 'pause': - if value:# and not oldvalue: - self.set_zero_running_torrents() - elif not value:# and oldvalue: - self._check_queue() - else: - torrent = self.torrents[ihash] - if torrent.state == RUNNING: - torrent.dl.set_option(option, value) - if option in ('forwarded_port', 'maxport'): - torrent.dl.change_port() - torrent.config[option] = value - self._dump_config() - - def request_status(self, infohash, want_spew, want_fileinfo): - torrent = self.torrents.get(infohash) - if torrent is None or torrent.state != RUNNING: - return - status = torrent.dl.get_status(want_spew, want_fileinfo) - if torrent.finishtime is not None: - now = bttime() - uptotal = status['upTotal'] + torrent.uptotal_old - downtotal = status['downTotal'] + torrent.downtotal_old - ulspeed = status['upRate2'] - if self.queue: - ratio = torrent.dl.config['next_torrent_ratio'] / 100 - if torrent.dl.config['seed_forever']: - ratio = 1e99 - else: - ratio = torrent.dl.config['last_torrent_ratio'] / 100 - if torrent.dl.config['seed_last_forever']: - ratio = 1e99 - if ulspeed <= 0 or ratio >= 1e99: - rem = 1e99 - elif downtotal == 0: - rem = (torrent.metainfo.total_bytes * ratio - uptotal) / ulspeed - else: - rem = (downtotal * ratio - uptotal) / ulspeed - if self.queue and not torrent.dl.config['seed_forever']: - rem = min(rem, torrent.finishtime + - torrent.dl.config['next_torrent_time'] * 60 - now) - rem = max(rem, torrent.finishtime + 120 - now) - if rem <= 0: - rem = 1 - if rem >= 1e99: - rem = None - status['timeEst'] = rem - self.run_ui_task(self.ui.update_status, infohash, status) - - def _get_list(self, state): - if state == KNOWN: - return self.other_torrents - elif state == QUEUED: - return self.queue - elif state in (RUNNING, RUN_QUEUED): - return self.running_torrents - assert False - - def change_torrent_state(self, infohash, oldstate, newstate=None, - pred=None, succ=None, replaced=None, force_running=False): - t = self.torrents.get(infohash) - if t is None or (t.state != oldstate and not (t.state == RUN_QUEUED and - oldstate == RUNNING)): - return - if newstate is None: - newstate = oldstate - assert oldstate in (KNOWN, QUEUED, RUNNING) - assert newstate in (KNOWN, QUEUED, RUNNING) - pos = None - if oldstate != RUNNING and newstate == RUNNING and replaced is None: - if len(self.running_torrents) >= (force_running and self.config[ - 'max_running_torrents'] or self.config['def_running_torrents']): - if force_running: - self.global_error(ERROR, - _("Can't run more than %d torrents " - "simultaneously. For more info see the" - " FAQ at %s.")% - (self.config['max_running_torrents'], - FAQ_URL)) - newstate = QUEUED - pos = 0 - l = self._get_list(newstate) - if newstate == oldstate: - origpos = l.index(infohash) - del l[origpos] - if pos is None: - pos = decode_position(l, pred, succ, -1) - if pos == -1 or l == origpos: - l.insert(origpos, infohash) - return - l.insert(pos, infohash) - self._dump_state() - self.run_ui_task(self.ui.reorder_torrent, infohash, pos) - return - if pos is None: - pos = decode_position(l, pred, succ) - if newstate == RUNNING: - newstate = RUN_QUEUED - if replaced and len(self.running_torrents) >= \ - self.config['def_running_torrents']: - t2 = self.torrents.get(replaced) - if t2 is None or t2.state not in (RUNNING, RUN_QUEUED): - return - if self.running_torrents.index(replaced) < pos: - pos -= 1 - if self._stop_running(replaced): - t2.state = QUEUED - self.queue.insert(0, replaced) - self._send_state(replaced) - else: - self.other_torrents.append(replaced) - if oldstate == RUNNING: - if newstate == QUEUED and len(self.running_torrents) <= \ - self.config['def_running_torrents'] and pos == 0: - return - if not self._stop_running(infohash): - if newstate == KNOWN: - self.other_torrents.insert(pos, infohash) - self.run_ui_task(self.ui.reorder_torrent, infohash, pos) - else: - self.other_torrents.append(infohash) - return - else: - self._get_list(oldstate).remove(infohash) - t.state = newstate - l.insert(pos, infohash) - self._check_queue() # sends state if it starts the torrent from queue - if t.state != RUNNING or newstate == RUN_QUEUED: - self._send_state(infohash) - self._dump_state() - - def set_zero_running_torrents(self): - newrun = [] - for infohash in list(self.running_torrents): - t = self.torrents[infohash] - if self._stop_running(infohash): - newrun.append(infohash) - t.state = RUN_QUEUED - else: - self.other_torrents.append(infohash) - self.running_torrents = newrun - - def check_completion(self, infohash, filelist=False): - t = self.torrents.get(infohash) - if t is None: - return - r = self.multitorrent.get_completion(self.config, t.metainfo, - t.dlpath, filelist) - if r is None or not filelist: - self.run_ui_task(self.ui.update_completion, infohash, r) - else: - self.run_ui_task(self.ui.update_completion, infohash, *r) - - def global_error(self, level, text): - self.run_ui_task(self.ui.global_error, level, text) - - # callbacks from torrent instances - - def failed(self, torrent, is_external): - infohash = torrent.infohash - if infohash == self.starting_torrent: - self.starting_torrent = None - self.running_torrents.remove(infohash) - t = self.torrents[infohash] - t.state = KNOWN - if is_external: - t.completion = self.multitorrent.get_completion( - self.config, t.metainfo, t.dlpath) - else: - t.completion = None - totals = t.dl.get_total_transfer() - t.uptotal_old += totals[0] - t.uptotal = t.uptotal_old - t.downtotal_old += totals[1] - t.downtotal = t.downtotal_old - t.dl = None - self.other_torrents.append(infohash) - self._send_state(infohash) - if not self.doneflag.isSet(): - self._check_queue() - self._dump_state() - - def finished(self, torrent): - """called when a download reaches 100%""" - infohash = torrent.infohash - t = self.torrents[infohash] - totals = t.dl.get_total_transfer() - if t.downtotal == 0 and t.downtotal_old == 0 and totals[1] == 0: - self.set_config('seed_forever', True, infohash) - self.set_config('seed_last_forever', True, infohash) - self.request_status(infohash, False, False) - - if infohash == self.starting_torrent: - t = self.torrents[infohash] - if self.queue: - ratio = t.config['next_torrent_ratio'] / 100 - if t.config['seed_forever']: - ratio = 1e99 - msg = _("Not starting torrent as there are other torrents " - "waiting to run, and this one already meets the " - "settings for when to stop seeding.") - else: - ratio = t.config['last_torrent_ratio'] / 100 - if t.config['seed_last_forever']: - ratio = 1e99 - msg = _("Not starting torrent as it already meets the " - "settings for when to stop seeding the last " - "completed torrent.") - if ratio < 1e99 and t.uptotal >= t.metainfo.total_bytes * ratio: - raise BTShutdown(msg) - self.torrents[torrent.infohash].finishtime = bttime() - - def started(self, torrent): - infohash = torrent.infohash - assert infohash == self.starting_torrent - self.starting_torrent = None - self._check_queue() - - def error(self, torrent, level, text): - self.run_ui_task(self.ui.error, torrent.infohash, level, text) - - -class ThreadWrappedQueue(object): - - def __init__(self, wrapped): - self.wrapped = wrapped - - def set_done(self): - self.wrapped.doneflag.set() - # add a dummy task to make sure the thread wakes up and notices flag - def dummy(): - pass - self.wrapped.rawserver.external_add_task(dummy, 0) - -# OW -def _makemethod(methodname): - def wrapper(self, *args, **kws): - def f(): - getattr(self.wrapped, methodname)(*args, **kws) - self.wrapped.rawserver.external_add_task(f, 0) - return wrapper - -# also OW -for methodname in ("request_status set_config start_new_torrent " - "start_new_torrent_by_name remove_torrent set_save_location " - "change_torrent_state check_completion").split(): - setattr(ThreadWrappedQueue, methodname, _makemethod(methodname)) -del _makemethod, methodname diff --git a/BitTorrent/TrayIcon.py b/BitTorrent/TrayIcon.py deleted file mode 100644 index ac7620d..0000000 --- a/BitTorrent/TrayIcon.py +++ /dev/null @@ -1,95 +0,0 @@ -import os - -from BitTorrent import app_name -from BitTorrent.GUI import gtk_wrap -from BitTorrent.platform import image_root - -if os.name == 'nt': - from systray import systray - - class TrayIcon(systray.Control): - def __init__(self, initial_state, toggle_func=None, quit_func=None): - iconpath = os.path.join(image_root, 'bittorrent.ico') - - systray.Control.__init__(self, app_name, iconpath) - - self.toggle_func = toggle_func - self.quit_func = quit_func - self.tooltip_text = None - - self.toggle_state = initial_state - menu_text = self._get_text_for_state(self.toggle_state) - - self.toggle_item = systray.MenuItem(name='toggle', - title=menu_text) - - self.toggle_item.onclick = self.toggle - self.on_double_click = self.toggle - - self.add_menuitem(self.toggle_item) - self.default_menu_index = 1 - - def get_tooltip(self): - return self.tooltip_text - - def set_tooltip(self, tooltip_text): - # ow. - if not hasattr(self, 'systray'): - return - - # FIXME: pysystray bug means this might fail - try: - if self.tooltip_text != tooltip_text: - self.systray.text = tooltip_text - # we set our own cache after sending the value to pysystray, - # since it could fail - self.tooltip_text = tooltip_text - except: - pass - - def on_quit(self, *args): - if self.quit_func is not None: - self._callout(self.quit_func) - - def set_toggle_state(self, b): - # ow. - if not hasattr(self, "systray"): - return - - s = self.systray - self.toggle_state = b - s.menu.items['toggle'].title = self._get_text_for_state(self.toggle_state) - - def _get_text_for_state(self, state): - if state: - text = _("Hide %s") % app_name - else: - text = _("Show %s") % app_name - return text - - def toggle(self, s): - if self.toggle_func is not None: - self._callout(self.toggle_func) - self.set_toggle_state(not self.toggle_state) - - def _callout(self, func): - if callable(func): - gtk_wrap(func) - -else: - # No tray icon for *your* OS ! - class TrayIcon: - def func(*a, **kw): - pass - __init__ = enable = disable = get_tooltip = set_tooltip = set_toggle_state = func - - -if __name__ == '__main__': - import threading - from BitTorrent.platform import install_translation - install_translation() - ti = TrayIcon(True) - th = threading.Thread(target=ti.enable, args=()) - th.start() - from time import sleep - sleep(10) diff --git a/BitTorrent/Uploader.py b/BitTorrent/Uploader.py deleted file mode 100644 index d02bfa8..0000000 --- a/BitTorrent/Uploader.py +++ /dev/null @@ -1,97 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -from BitTorrent.CurrentRateMeasure import Measure - - -class Upload(object): - - def __init__(self, connection, ratelimiter, totalup, totalup2, choker, - storage, max_slice_length, max_rate_period): - self.connection = connection - self.ratelimiter = ratelimiter - self.totalup = totalup - self.totalup2 = totalup2 - self.choker = choker - self.storage = storage - self.max_slice_length = max_slice_length - self.max_rate_period = max_rate_period - self.choked = True - self.unchoke_time = None - self.interested = False - self.buffer = [] - self.measure = Measure(max_rate_period) - if storage.do_I_have_anything(): - connection.send_bitfield(storage.get_have_list()) - - def got_not_interested(self): - if self.interested: - self.interested = False - del self.buffer[:] - self.choker.not_interested(self.connection) - - def got_interested(self): - if not self.interested: - self.interested = True - self.choker.interested(self.connection) - - def get_upload_chunk(self): - if not self.buffer: - return None - index, begin, length = self.buffer.pop(0) - piece = self.storage.get_piece(index, begin, length) - if piece is None: - self.connection.close() - return None - return (index, begin, piece) - - def update_rate(self, bytes): - self.measure.update_rate(bytes) - self.totalup.update_rate(bytes) - self.totalup2.update_rate(bytes) - - def got_request(self, index, begin, length): - if not self.interested or length > self.max_slice_length: - self.connection.close() - return - if not self.connection.choke_sent: - self.buffer.append((index, begin, length)) - if self.connection.next_upload is None and \ - self.connection.connection.is_flushed(): - self.ratelimiter.queue(self.connection, self.connection.encoder.context.rlgroup) - - def got_cancel(self, index, begin, length): - try: - self.buffer.remove((index, begin, length)) - except ValueError: - pass - - def choke(self): - if not self.choked: - self.choked = True - self.connection.send_choke() - - def sent_choke(self): - assert self.choked - del self.buffer[:] - - def unchoke(self, time): - if self.choked: - self.choked = False - self.unchoke_time = time - self.connection.send_unchoke() - - def has_queries(self): - return len(self.buffer) > 0 - - def get_rate(self): - return self.measure.get_rate() diff --git a/BitTorrent/__init__.py b/BitTorrent/__init__.py deleted file mode 100644 index cae9c77..0000000 --- a/BitTorrent/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -app_name = 'NoGooee' -version = '1.0.0' - -import sys -assert sys.version_info >= (2, 2, 1), _("Python %s or newer required") % '2.2.1' -import os -import time - -branch = None -if os.access('.cdv', os.F_OK): - branch = os.path.split(os.path.realpath(os.path.split(sys.argv[0])[0]))[1] - -from BitTorrent.language import languages, language_names -from BitTorrent.platform import get_home_dir, is_frozen_exe - -if os.name == 'posix': - if os.uname()[0] == "Darwin": - from BitTorrent.platform import install_translation - install_translation() - -del sys, get_home_dir, is_frozen_exe - -INFO = 0 -WARNING = 1 -ERROR = 2 -CRITICAL = 3 - -status_dict = {INFO: 'info', - WARNING: 'warning', - ERROR: 'error', - CRITICAL: 'critical'} - -class BTFailure(Exception): - pass - -class BTShutdown(BTFailure): - pass - diff --git a/BitTorrent/bencode.py b/BitTorrent/bencode.py deleted file mode 100644 index 2fcdeb8..0000000 --- a/BitTorrent/bencode.py +++ /dev/null @@ -1,134 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Petru Paler - -from BitTorrent import BTFailure - -def decode_int(x, f): - f += 1 - newf = x.index('e', f) - n = int(x[f:newf]) - if x[f] == '-': - if x[f + 1] == '0': - raise ValueError - elif x[f] == '0' and newf != f+1: - raise ValueError - return (n, newf+1) - -def decode_string(x, f): - colon = x.index(':', f) - n = int(x[f:colon]) - if x[f] == '0' and colon != f+1: - raise ValueError - colon += 1 - return (x[colon:colon+n], colon+n) - -def decode_list(x, f): - r, f = [], f+1 - while x[f] != 'e': - v, f = decode_func[x[f]](x, f) - r.append(v) - return (r, f + 1) - -def decode_dict(x, f): - r, f = {}, f+1 - lastkey = None - while x[f] != 'e': - k, f = decode_string(x, f) - if lastkey >= k: - raise ValueError - lastkey = k - r[k], f = decode_func[x[f]](x, f) - return (r, f + 1) - -decode_func = {} -decode_func['l'] = decode_list -decode_func['d'] = decode_dict -decode_func['i'] = decode_int -decode_func['0'] = decode_string -decode_func['1'] = decode_string -decode_func['2'] = decode_string -decode_func['3'] = decode_string -decode_func['4'] = decode_string -decode_func['5'] = decode_string -decode_func['6'] = decode_string -decode_func['7'] = decode_string -decode_func['8'] = decode_string -decode_func['9'] = decode_string - -def bdecode(x): - try: - r, l = decode_func[x[0]](x, 0) - except (IndexError, KeyError, ValueError): - raise BTFailure, _("not a valid bencoded string") - if l != len(x): - raise BTFailure, _("invalid bencoded value (data after valid prefix)") - return r - -from types import StringType, IntType, LongType, DictType, ListType, TupleType - - -class Bencached(object): - - __slots__ = ['bencoded'] - - def __init__(self, s): - self.bencoded = s - -def encode_bencached(x,r): - r.append(x.bencoded) - -def encode_int(x, r): - r.extend(('i', str(x), 'e')) - -def encode_bool(x, r): - if x: - encode_int(1, r) - else: - encode_int(0, r) - -def encode_string(x, r): - r.extend((str(len(x)), ':', x)) - -def encode_list(x, r): - r.append('l') - for i in x: - encode_func[type(i)](i, r) - r.append('e') - -def encode_dict(x,r): - r.append('d') - ilist = x.items() - ilist.sort() - for k, v in ilist: - r.extend((str(len(k)), ':', k)) - encode_func[type(v)](v, r) - r.append('e') - -encode_func = {} -encode_func[Bencached] = encode_bencached -encode_func[IntType] = encode_int -encode_func[LongType] = encode_int -encode_func[StringType] = encode_string -encode_func[ListType] = encode_list -encode_func[TupleType] = encode_list -encode_func[DictType] = encode_dict - -try: - from types import BooleanType - encode_func[BooleanType] = encode_bool -except ImportError: - pass - -def bencode(x): - r = [] - encode_func[type(x)](x, r) - return ''.join(r) diff --git a/BitTorrent/bitfield.py b/BitTorrent/bitfield.py deleted file mode 100644 index bede74f..0000000 --- a/BitTorrent/bitfield.py +++ /dev/null @@ -1,75 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen, Uoti Urpala, and John Hoffman - -from array import array - -counts = [chr(sum([(i >> j) & 1 for j in xrange(8)])) for i in xrange(256)] -counts = ''.join(counts) - - -class Bitfield: - - def __init__(self, length, bitstring=None): - self.length = length - rlen, extra = divmod(length, 8) - if bitstring is None: - self.numfalse = length - if extra: - self.bits = array('B', chr(0) * (rlen + 1)) - else: - self.bits = array('B', chr(0) * rlen) - else: - if extra: - if len(bitstring) != rlen + 1: - raise ValueError - if (ord(bitstring[-1]) << extra) & 0xFF != 0: - raise ValueError - else: - if len(bitstring) != rlen: - raise ValueError - c = counts - self.numfalse = length - sum(array('B', - bitstring.translate(counts))) - if self.numfalse != 0: - self.bits = array('B', bitstring) - else: - self.bits = None - - def __setitem__(self, index, val): - assert val - pos = index >> 3 - mask = 128 >> (index & 7) - if self.bits[pos] & mask: - return - self.bits[pos] |= mask - self.numfalse -= 1 - if self.numfalse == 0: - self.bits = None - - def __getitem__(self, index): - bits = self.bits - if bits is None: - return 1 - return bits[index >> 3] & 128 >> (index & 7) - - def __len__(self): - return self.length - - def tostring(self): - if self.bits is None: - rlen, extra = divmod(self.length, 8) - r = chr(0xFF) * rlen - if extra: - r += chr((0xFF << (8 - extra)) & 0xFF) - return r - else: - return self.bits.tostring() diff --git a/BitTorrent/btformats.py b/BitTorrent/btformats.py deleted file mode 100644 index 97e62df..0000000 --- a/BitTorrent/btformats.py +++ /dev/null @@ -1,140 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -import re - -from BitTorrent import BTFailure - -allowed_path_re = re.compile(r'^[^/\\.~][^/\\]*$') - -ints = (long, int) - -def check_info(info, check_paths=True): - if type(info) != dict: - raise BTFailure, _("bad metainfo - not a dictionary") - pieces = info.get('pieces') - if type(pieces) != str or len(pieces) % 20 != 0: - raise BTFailure, _("bad metainfo - bad pieces key") - piecelength = info.get('piece length') - if type(piecelength) not in ints or piecelength <= 0: - raise BTFailure, _("bad metainfo - illegal piece length") - name = info.get('name') - if type(name) != str: - raise BTFailure, _("bad metainfo - bad name") - if not allowed_path_re.match(name): - raise BTFailure, _("name %s disallowed for security reasons") % name - if info.has_key('files') == info.has_key('length'): - raise BTFailure, _("single/multiple file mix") - if info.has_key('length'): - length = info.get('length') - if type(length) not in ints or length < 0: - raise BTFailure, _("bad metainfo - bad length") - else: - files = info.get('files') - if type(files) != list: - raise BTFailure, _('bad metainfo - "files" is not a list of files') - for f in files: - if type(f) != dict: - raise BTFailure, _("bad metainfo - file entry must be a dict") - length = f.get('length') - if type(length) not in ints or length < 0: - raise BTFailure, _("bad metainfo - bad length") - path = f.get('path') - if type(path) != list or path == []: - raise BTFailure, _("bad metainfo - bad path") - for p in path: - if type(p) != str: - raise BTFailure, _("bad metainfo - bad path dir") - if check_paths and not allowed_path_re.match(p): - raise BTFailure, _("path %s disallowed for security reasons") % p - f = ['/'.join(x['path']) for x in files] - f.sort() - i = iter(f) - try: - name2 = i.next() - while True: - name1 = name2 - name2 = i.next() - if name2.startswith(name1): - if name1 == name2: - raise BTFailure, _("bad metainfo - duplicate path") - elif name2[len(name1)] == '/': - raise BTFailure(_("bad metainfo - name used as both" - "file and subdirectory name")) - except StopIteration: - pass - -def check_message(message, check_paths=True): - if type(message) != dict: - raise BTFailure, _("bad metainfo - wrong object type") - check_info(message.get('info'), check_paths) - if type(message.get('announce')) != str and type(message.get('nodes')) != list: - raise BTFailure, _("bad metainfo - no announce URL string") - if message.has_key('nodes'): - check_nodes(message.get('nodes')) - -def check_nodes(nodes): - ## note, these strings need changing - for node in nodes: - if type(node) != list: - raise BTFailure, _("bad metainfo - node is not a list") - if len(node) != 2: - raise BTFailure, _("bad metainfo - node list must have only two elements") - host, port = node - if type(host) != str: - raise BTFailure, _("bad metainfo - node host must be a string") - if type(port) != int: - raise BTFailure, _("bad metainfo - node port must be an integer") - -def check_peers(message): - if type(message) != dict: - raise BTFailure - if message.has_key('failure reason'): - if type(message['failure reason']) != str: - raise BTFailure, _("failure reason must be a string") - return - if message.has_key('warning message'): - if type(message['warning message']) != str: - raise BTFailure, _("warning message must be a string") - peers = message.get('peers') - if type(peers) == list: - for p in peers: - if type(p) != dict: - raise BTFailure, _("invalid entry in peer list - peer info must be a dict") - if type(p.get('ip')) != str: - raise BTFailure, _("invalid entry in peer list - peer ip must be a string") - port = p.get('port') - if type(port) not in ints or p <= 0: - raise BTFailure, _("invalid entry in peer list - peer port must be an integer") - if p.has_key('peer id'): - peerid = p.get('peer id') - if type(peerid) != str or len(peerid) != 20: - raise BTFailure, _("invalid entry in peer list - invalid peerid") - elif type(peers) != str or len(peers) % 6 != 0: - raise BTFailure, _("invalid peer list") - interval = message.get('interval', 1) - if type(interval) not in ints or interval <= 0: - raise BTFailure, _("invalid announce interval") - minint = message.get('min interval', 1) - if type(minint) not in ints or minint <= 0: - raise BTFailure, _("invalid min announce interval") - if type(message.get('tracker id', '')) != str: - raise BTFailure, _("invalid tracker id") - npeers = message.get('num peers', 0) - if type(npeers) not in ints or npeers < 0: - raise BTFailure, _("invalid peer count") - dpeers = message.get('done peers', 0) - if type(dpeers) not in ints or dpeers < 0: - raise BTFailure, _("invalid seed count") - last = message.get('last', 0) - if type(last) not in ints or last < 0: - raise BTFailure, _('invalid "last" entry') diff --git a/BitTorrent/configfile.py b/BitTorrent/configfile.py deleted file mode 100644 index 6f4a1e3..0000000 --- a/BitTorrent/configfile.py +++ /dev/null @@ -1,222 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Uoti Urpala and Matt Chisholm - -import os -import sys -import gettext -import locale - -# Python 2.2 doesn't have RawConfigParser -try: - from ConfigParser import RawConfigParser -except ImportError: - from ConfigParser import ConfigParser as RawConfigParser - -from ConfigParser import MissingSectionHeaderError, ParsingError -from BitTorrent import parseargs -from BitTorrent import app_name, version, ERROR, BTFailure -from BitTorrent.platform import get_config_dir, locale_root, is_frozen_exe -from BitTorrent.defaultargs import MYTRUE -from BitTorrent.zurllib import bind_tracker_connection, set_zurllib_rawserver - -MAIN_CONFIG_FILE = 'ui_config' -TORRENT_CONFIG_FILE = 'torrent_config' - -alt_uiname = {'bittorrent':'btdownloadgui', - 'maketorrent':'btmaketorrentgui',} - -def _read_config(filename): - # check for bad config files (Windows corrupts them all the time) - p = RawConfigParser() - fp = None - try: - fp = open(filename) - except IOError: - pass - - if fp is not None: - try: - p.readfp(fp, filename=filename) - except MissingSectionHeaderError: - fp.close() - del fp - bad_config(filename) - except ParsingError: - fp.close() - del fp - bad_config(filename) - else: - fp.close() - return p - - -def _write_config(error_callback, filename, p): - try: - f = file(filename, 'w') - p.write(f) - f.close() - except Exception, e: - try: - f.close() - except: - pass - error_callback(ERROR, _("Could not permanently save options: ")+ - str(e)) - - -def bad_config(filename): - base_bad_filename = filename + '.broken' - bad_filename = base_bad_filename - i = 0 - while os.access(bad_filename, os.F_OK): - bad_filename = base_bad_filename + str(i) - i+=1 - os.rename(filename, bad_filename) - sys.stderr.write(_("Error reading config file. " - "Old config file stored in \"%s\"") % bad_filename) - - -def get_config(defaults, section): - dir_root = get_config_dir() - - if dir_root is None: - return {} - - configdir = os.path.join(dir_root, '.bittorrent') - - if not os.path.isdir(configdir): - try: - os.mkdir(configdir, 0700) - except: - pass - - p = _read_config(os.path.join(configdir, 'config')) - values = {} - if p.has_section(section): - for name, value in p.items(section): - if name in defaults: - values[name] = value - if p.has_section('common'): - for name, value in p.items('common'): - if name in defaults and name not in values: - values[name] = value - if defaults.get('data_dir') == '' and \ - 'data_dir' not in values and os.path.isdir(configdir): - datadir = os.path.join(configdir, 'data') - values['data_dir'] = datadir - parseargs.parse_options(defaults, values) - return values - - -def save_ui_config(defaults, section, save_options, error_callback): - filename = os.path.join(defaults['data_dir'], MAIN_CONFIG_FILE) - p = _read_config(filename) - p.remove_section(section) - if p.has_section(alt_uiname[section]): - p.remove_section(alt_uiname[section]) - p.add_section(section) - for name in save_options: - if defaults.has_key(name): - p.set(section, name, defaults[name]) - else: - err_str = _("Configuration option mismatch: '%s'") % name - if is_frozen_exe: - err_str = _("You must quit %s and reinstall it. (%s)") % (app_name, err_str) - error_callback(ERROR, err_str) - _write_config(error_callback, filename, p) - - -def save_torrent_config(path, infohash, config, error_callback): - section = infohash.encode('hex') - filename = os.path.join(path, TORRENT_CONFIG_FILE) - p = _read_config(filename) - p.remove_section(section) - p.add_section(section) - for key, value in config.items(): - p.set(section, key, value) - _write_config(error_callback, filename, p) - -def read_torrent_config(global_config, path, infohash, error_callback): - section = infohash.encode('hex') - filename = os.path.join(path, TORRENT_CONFIG_FILE) - p = _read_config(filename) - if not p.has_section(section): - return {} - else: - c = {} - for name, value in p.items(section): - if global_config.has_key(name): - t = type(global_config[name]) - if t == bool: - c[name] = value in ('1', 'True', MYTRUE, True) - else: - c[name] = type(global_config[name])(value) - return c - -def remove_torrent_config(path, infohash, error_callback): - section = infohash.encode('hex') - filename = os.path.join(path, TORRENT_CONFIG_FILE) - p = _read_config(filename) - if p.has_section(section): - p.remove_section(section) - _write_config(error_callback, filename, p) - -def parse_configuration_and_args(defaults, uiname, arglist=[], minargs=0, - maxargs=0): - defconfig = dict([(name, value) for (name, value, doc) in defaults]) - if arglist[0:] == ['--version']: - print version - sys.exit(0) - - if arglist[0:] in (['--help'], ['-h'], ['--usage'], ['-?']): - parseargs.printHelp(uiname, defaults) - sys.exit(0) - - presets = get_config(defconfig, uiname) - config, args = parseargs.parseargs(arglist, defaults, minargs, maxargs, - presets) - datadir = config['data_dir'] - if datadir: - if uiname in ('bittorrent', 'maketorrent'): - values = {} - p = _read_config(os.path.join(datadir, MAIN_CONFIG_FILE)) - if not p.has_section(uiname) and p.has_section(alt_uiname[uiname]): - uiname = alt_uiname[uiname] - if p.has_section(uiname): - for name, value in p.items(uiname): - if name in defconfig: - values[name] = value - parseargs.parse_options(defconfig, values) - presets.update(values) - config, args = parseargs.parseargs(arglist, defaults, minargs, - maxargs, presets) - - for d in ('', 'resume', 'metainfo'): - ddir = os.path.join(datadir, d) - try: - if not os.path.exists(ddir): - os.mkdir(ddir, 0700) - except: - pass - - if config['language'] != '': - try: - lang = gettext.translation('bittorrent', locale_root, - languages=[config['language']]) - lang.install() - except IOError: - # don't raise an error, just continue untranslated - sys.stderr.write(_('Could not find translation for language "%s"\n') % - config['language']) - if config.has_key('bind') and ['bind'] != '': - bind_tracker_connection(config['bind']) - return config, args diff --git a/BitTorrent/defaultargs.py b/BitTorrent/defaultargs.py deleted file mode 100644 index 2c7af5c..0000000 --- a/BitTorrent/defaultargs.py +++ /dev/null @@ -1,306 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - - -# False and True are not distinct from 0 and 1 under Python 2.2, -# and we want to handle boolean options differently. -class MyBool(object): - - def __init__(self, value): - self.value = value - - def __repr__(self): - if self.value: - return 'True' - return 'False' - - def __nonzero__(self): - return self.value - -MYTRUE = MyBool(True) -MYFALSE = MyBool(False) - -import os -### add your favorite here -BAD_LIBC_WORKAROUND_DEFAULT = MYFALSE -if os.name == 'posix': - if os.uname()[0] in ['Darwin']: - BAD_LIBC_WORKAROUND_DEFAULT = MYTRUE - -MIN_INCOMPLETE = 100 -if os.name == 'nt': - from BitTorrent.platform import win_version_num - # starting in XP SP2 the incomplete outgoing connection limit was set to 10 - if win_version_num >= (2, 5, 1, 2, 0): - MIN_INCOMPLETE = 10 - -from BitTorrent import languages - -basic_options = [ - ('data_dir', '', - _("directory under which variable data such as fastresume information " - "and GUI state is saved. Defaults to subdirectory 'data' of the " - "bittorrent config directory.")), - ('filesystem_encoding', '', - _("character encoding used on the local filesystem. " - "If left empty, autodetected. " - "Autodetection doesn't work under python versions older than 2.3")), - ('language', '', - _("ISO Language code to use") + ': ' + ', '.join(languages)), - ] - -common_options = [ - ('ip', '', - _("ip to report to the tracker (has no effect unless you are on the same " - "local network as the tracker)")), - ('forwarded_port', 0, - _("world-visible port number if it's different from the one the client " - "listens on locally")), - ('minport', 6881, - _("minimum port to listen on, counts up if unavailable")), - ('maxport', 6999, - _("maximum port to listen on")), - ('bind', '', - _("ip to bind to locally")), - ('display_interval', .5, - _("seconds between updates of displayed information")), - ('rerequest_interval', 5 * 60, - _("minutes to wait between requesting more peers")), - ('min_peers', 20, - _("minimum number of peers to not do rerequesting")), - ('max_initiate', 60, - _("number of peers at which to stop initiating new connections")), - ('max_incomplete', MIN_INCOMPLETE, - _("max number of outgoing incomplete connections")), - ('max_allow_in', 80, - _("maximum number of connections to allow, after this new incoming " - "connections will be immediately closed")), - ('check_hashes', MYTRUE, - _("whether to check hashes on disk")), - ('max_upload_rate', 20, - _("maximum kB/s to upload at, 0 means no limit")), - ('min_uploads', 2, - _("the number of uploads to fill out to with extra optimistic unchokes")), - ('max_files_open', 50, - _("the maximum number of files in a multifile torrent to keep open at a " - "time, 0 means no limit. Used to avoid running out of file descriptors.")), - ('start_trackerless_client', MYTRUE, - _("Initialize a trackerless client. This must be enabled in order to download trackerless torrents.")), - ('upnp', MYTRUE, - _("Enable automatic port mapping")+' (UPnP)'), - ] - - -rare_options = [ - ('keepalive_interval', 120.0, - _("number of seconds to pause between sending keepalives")), - ('download_slice_size', 2 ** 14, - _("how many bytes to query for per request.")), - ('max_message_length', 2 ** 23, - _("maximum length prefix encoding you'll accept over the wire - larger " - "values get the connection dropped.")), - ('socket_timeout', 300.0, - _("seconds to wait between closing sockets which nothing has been " - "received on")), - ('timeout_check_interval', 60.0, - _("seconds to wait between checking if any connections have timed out")), - ('max_slice_length', 16384, - _("maximum length slice to send to peers, close connection if a larger " - "request is received")), - ('max_rate_period', 20.0, - _("maximum time interval over which to estimate the current upload and download rates")), - ('max_rate_period_seedtime', 100.0, - _("maximum time interval over which to estimate the current seed rate")), - ('max_announce_retry_interval', 1800, - _("maximum time to wait between retrying announces if they keep failing")), - ('snub_time', 30.0, - _("seconds to wait for data to come in over a connection before assuming " - "it's semi-permanently choked")), - ('rarest_first_cutoff', 4, - _("number of downloads at which to switch from random to rarest first")), - ('upload_unit_size', 1380, - _("how many bytes to write into network buffers at once.")), - ('retaliate_to_garbled_data', MYTRUE, - _("refuse further connections from addresses with broken or intentionally " - "hostile peers that send incorrect data")), - ('one_connection_per_ip', MYTRUE, - _("do not connect to several peers that have the same IP address")), - ('peer_socket_tos', 8, - _("if nonzero, set the TOS option for peer connections to this value")), - ('bad_libc_workaround', BAD_LIBC_WORKAROUND_DEFAULT, - _("enable workaround for a bug in BSD libc that makes file reads very slow.")), - ('tracker_proxy', '', - _("address of HTTP proxy to use for tracker connections")), - ('close_with_rst', 0, - _("close connections with RST and avoid the TCP TIME_WAIT state")), - ('twisted', -1, - _("Use Twisted network libraries for network connections. 1 means use twisted, 0 means do not use twisted, -1 means autodetect, and prefer twisted")), - ] - - -def get_defaults(ui): - assert ui in ("bittorrent" , "bittorrent-curses", "bittorrent-console" , - "maketorrent", "maketorrent-console", - "launchmany-curses", "launchmany-console" , - ) - r = [] - - if ui.startswith('bittorrent') or ui.startswith('launchmany'): - r.extend(common_options) - - if ui == 'bittorrent': - r.extend([ - ('save_as', '', - _("file name (for single-file torrents) or directory name (for " - "batch torrents) to save the torrent as, overriding the default " - "name in the torrent. See also --save_in, if neither is " - "specified the user will be asked for save location")), - ('advanced', MYFALSE, - _("display advanced user interface")), - ('next_torrent_time', 300, - _("the maximum number of minutes to seed a completed torrent " - "before stopping seeding")), - ('next_torrent_ratio', 80, - _("the minimum upload/download ratio, in percent, to achieve " - "before stopping seeding. 0 means no limit.")), - ('last_torrent_ratio', 0, - _("the minimum upload/download ratio, in percent, to achieve " - "before stopping seeding the last torrent. 0 means no limit.")), - ('seed_forever', MYFALSE, - _("Seed each completed torrent indefinitely " - "(until the user cancels it)")), - ('seed_last_forever', MYTRUE, - _("Seed the last torrent indefinitely " - "(until the user cancels it)")), - ('pause', MYFALSE, - _("start downloader in paused state")), - ('start_torrent_behavior', 'replace', - _('specifies how the app should behave when the user manually ' - 'tries to start another torrent: "replace" means always replace ' - 'the running torrent with the new one, "add" means always add ' - 'the running torrent in parallel, and "ask" means ask the user ' - 'each time.')), - ('open_from', '', - 'local directory to look in for .torrent files to open'), - ('ask_for_save', MYFALSE, - 'whether or not to ask for a location to save downloaded files in'), - ('start_minimized', MYFALSE, - _("Start BitTorrent minimized")), - ('new_version', '', - _("override the version provided by the http version check " - "and enable version check debugging mode")), - ('current_version', '', - _("override the current version used in the version check " - "and enable version check debugging mode")), - ('geometry', '', - _("specify window size and position, in the format: " - "WIDTHxHEIGHT+XOFFSET+YOFFSET")), - ]) - - if os.name == 'nt': - r.extend([ - ('launch_on_startup', MYTRUE, - _("Launch BitTorrent when Windows starts")), - ('minimize_to_tray', MYTRUE, - _("Minimize to system tray")), - ]) - - if ui in ('bittorrent-console', 'bittorrent-curses'): - r.append( - ('save_as', '', - _("file name (for single-file torrents) or directory name (for " - "batch torrents) to save the torrent as, overriding the " - "default name in the torrent. See also --save_in"))) - - if ui.startswith('bittorrent'): - r.extend([ - ('max_uploads', -1, - _("the maximum number of uploads to allow at once. -1 means a " - "(hopefully) reasonable number based on --max_upload_rate. " - "The automatic values are only sensible when running one " - "torrent at a time.")), - ('save_in', '', - _("local directory where the torrent contents will be saved. The " - "file (single-file torrents) or directory (batch torrents) will " - "be created under this directory using the default name " - "specified in the .torrent file. See also --save_as.")), - ('responsefile', '', - _("deprecated, do not use")), - ('url', '', - _("deprecated, do not use")), - ('ask_for_save', 0, - _("whether or not to ask for a location to save downloaded files in")), - ]) - - if ui.startswith('launchmany'): - r.extend([ - ('max_uploads', 6, - _("the maximum number of uploads to allow at once. -1 means a " - "(hopefully) reasonable number based on --max_upload_rate. The " - "automatic values are only sensible when running one torrent at " - "a time.")), - ('save_in', '', - _("local directory where the torrents will be saved, using a " - "name determined by --saveas_style. If this is left empty " - "each torrent will be saved under the directory of the " - "corresponding .torrent file")), - ('parse_dir_interval', 60, - _("how often to rescan the torrent directory, in seconds") ), - ('launch_delay', 0, - _("wait this many seconds after noticing a torrent before starting it, to avoid race with tracker")), - ('saveas_style', 4, - _("How to name torrent downloads: " - "1: use name OF torrent file (minus .torrent); " - "2: use name encoded IN torrent file; " - "3: create a directory with name OF torrent file " - "(minus .torrent) and save in that directory using name " - "encoded IN torrent file; " - "4: if name OF torrent file (minus .torrent) and name " - "encoded IN torrent file are identical, use that " - "name (style 1/2), otherwise create an intermediate " - "directory as in style 3; " - "CAUTION: options 1 and 2 have the ability to " - "overwrite files without warning and may present " - "security issues." - ) ), - ('display_path', ui == 'launchmany-console' and MYTRUE or MYFALSE, - _("whether to display the full path or the torrent contents for " - "each torrent") ), - ]) - - if ui.startswith('launchmany') or ui == 'maketorrent': - r.append( - ('torrent_dir', '', - _("directory to look for .torrent files (semi-recursive)")),) - - if ui in ('bittorrent-curses', 'bittorrent-console'): - r.append( - ('spew', MYFALSE, - _("whether to display diagnostic info to stdout"))) - - if ui.startswith('maketorrent'): - r.extend([ - ('piece_size_pow2', 18, - _("which power of two to set the piece size to")), - ('tracker_name', 'http://my.tracker:6969/announce', - _("default tracker name")), - ('tracker_list', '', ''), - ('use_tracker', MYTRUE, - _("if false then make a trackerless torrent, instead of " - "announce URL, use reliable node in form of <ip>:<port> or an " - "empty string to pull some nodes from your routing table")), - ]) - - r.extend(basic_options) - - if ui.startswith('bittorrent') or ui.startswith('launchmany'): - r.extend(rare_options) - - return r diff --git a/BitTorrent/defer.py b/BitTorrent/defer.py deleted file mode 100644 index 4531271..0000000 --- a/BitTorrent/defer.py +++ /dev/null @@ -1,56 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -class Deferred(object): - def __init__(self): - self.callbacks = [] - self.errbacks = [] - self.calledBack = False - self.erredBack = False - self.results = [] - self.failures = [] - - def addCallback(self, cb, args=(), kwargs={}): - assert callable(cb) - self.callbacks.append((cb, args, kwargs)) - if self.calledBack: - self.doCallbacks(self.results, [(cb, args, kwargs)]) - return self - - def addErrback(self, cb, args=(), kwargs={}): - assert callable(cb) - self.errbacks.append((cb, args, kwargs)) - if self.erredBack: - self.doCallbacks(self.failures, [(cb, args, kwargs)]) - return self - - def addCallbacks(self, cb, eb, args=(), kwargs={}, - ebargs=(), ebkwargs={}): - assert callable(cb) - assert callable(eb) - self.addCallback(cb, args, kwargs) - self.addErrback(eb, ebargs, ebkwargs) - - def callback(self, result): - self.results.append(result) - self.calledBack = True - if self.callbacks: - self.doCallbacks([result], self.callbacks) - - def errback(self, failed): - self.failures.append(failed) - self.erredBack = True - if self.errbacks: - self.doCallbacks([failed], self.errbacks) - - def doCallbacks(self, results, callbacks): - for result in results: - for cb, args, kwargs in callbacks: - result = cb(result, *args, **kwargs) diff --git a/BitTorrent/download.py b/BitTorrent/download.py deleted file mode 100644 index d17b685..0000000 --- a/BitTorrent/download.py +++ /dev/null @@ -1,591 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen and Uoti Urpala - -from __future__ import division - -import os -import sys -import threading -import errno -import gc -from sha import sha -from socket import error as socketerror -from random import seed -from time import time -from cStringIO import StringIO -from traceback import print_exc -from math import sqrt - -from BitTorrent.btformats import check_message -from BitTorrent.Choker import Choker -from BitTorrent.Storage import Storage, FilePool -from BitTorrent.StorageWrapper import StorageWrapper -from BitTorrent.Uploader import Upload -from BitTorrent.Downloader import Downloader -from BitTorrent.Encoder import Encoder, SingleportListener -from BitTorrent.zurllib import set_zurllib_rawserver, add_unsafe_thread -from BitTorrent import PeerID - -from BitTorrent.RateLimiter import MultiRateLimiter as RateLimiter -from BitTorrent.RateLimiter import RateLimitedGroup - -from BitTorrent.RawServer_magic import RawServer -from BitTorrent.NatTraversal import NatTraverser -from BitTorrent.Rerequester import Rerequester, DHTRerequester -from BitTorrent.DownloaderFeedback import DownloaderFeedback -from BitTorrent.RateMeasure import RateMeasure -from BitTorrent.CurrentRateMeasure import Measure -from BitTorrent.PiecePicker import PiecePicker -from BitTorrent.ConvertedMetainfo import set_filesystem_encoding -from BitTorrent import version -from BitTorrent import BTFailure, BTShutdown, INFO, WARNING, ERROR, CRITICAL - -from khashmir.utkhashmir import UTKhashmir -from khashmir import const - -class Feedback(object): - - def finished(self, torrent): - pass - - def failed(self, torrent, is_external): - pass - - def error(self, torrent, level, text): - pass - - def exception(self, torrent, text): - self.error(torrent, CRITICAL, text) - - def started(self, torrent): - pass - - -class Multitorrent(object): - - def __init__(self, config, doneflag, errorfunc, listen_fail_ok=False): - self.dht = None - self.config = config - self.errorfunc = errorfunc - self.rawserver = RawServer(doneflag, config, errorfunc=errorfunc, - tos=config['peer_socket_tos']) - set_zurllib_rawserver(self.rawserver) - add_unsafe_thread() - self.nattraverser = NatTraverser(self.rawserver, logfunc=errorfunc) - self.singleport_listener = SingleportListener(self.rawserver, - self.nattraverser) - self.ratelimiter = RateLimiter(self.rawserver.add_task) - self.ratelimiter.set_parameters(config['max_upload_rate'], - config['upload_unit_size']) - self._find_port(listen_fail_ok) - self.filepool = FilePool(config['max_files_open']) - set_filesystem_encoding(config['filesystem_encoding'], - errorfunc) - - - def _find_port(self, listen_fail_ok=True): - e = _("maxport less than minport - no ports to check") - if self.config['minport'] < 1024: - self.config['minport'] = 1024 - for port in xrange(self.config['minport'], self.config['maxport'] + 1): - try: - self.singleport_listener.open_port(port, self.config) - if self.config['start_trackerless_client']: - self.dht = UTKhashmir(self.config['bind'], - self.singleport_listener.get_port(), - self.config['data_dir'], self.rawserver, - int(self.config['max_upload_rate'] * 1024 * 0.01), - rlcount=self.ratelimiter.increase_offset, - config=self.config) - break - except socketerror, e: - pass - else: - if not listen_fail_ok: - raise BTFailure, _("Could not open a listening port: %s.") % str(e) - self.errorfunc(CRITICAL, - _("Could not open a listening port: %s. ") % - str(e) + - _("Check your port range settings.")) - - def close_listening_socket(self): - self.singleport_listener.close_sockets() - - def start_torrent(self, metainfo, config, feedback, filename): - torrent = _SingleTorrent(self.rawserver, self.singleport_listener, - self.ratelimiter, self.filepool, config, self.dht) - torrent.rlgroup = RateLimitedGroup(config['max_upload_rate'], torrent.got_exception) - self.rawserver.add_context(torrent) - def start(): - torrent.start_download(metainfo, feedback, filename) - self.rawserver.external_add_task(start, 0, context=torrent) - return torrent - - def set_option(self, option, value): - self.config[option] = value - if option in ['max_upload_rate', 'upload_unit_size']: - self.ratelimiter.set_parameters(self.config['max_upload_rate'], - self.config['upload_unit_size']) - elif option == 'max_files_open': - self.filepool.set_max_files_open(value) - elif option == 'maxport': - if not self.config['minport'] <= self.singleport_listener.port <= \ - self.config['maxport']: - self._find_port() - - def get_completion(self, config, metainfo, save_path, filelist=False): - if not config['data_dir']: - return None - infohash = metainfo.infohash - if metainfo.is_batch: - myfiles = [os.path.join(save_path, f) for f in metainfo.files_fs] - else: - myfiles = [save_path] - - if metainfo.total_bytes == 0: - if filelist: - return None - return 1 - try: - s = Storage(None, None, zip(myfiles, metainfo.sizes), - check_only=True) - except: - return None - filename = os.path.join(config['data_dir'], 'resume', - infohash.encode('hex')) - try: - f = file(filename, 'rb') - except: - f = None - try: - r = s.check_fastresume(f, filelist, metainfo.piece_length, - len(metainfo.hashes), myfiles) - except: - r = None - if f is not None: - f.close() - if r is None: - return None - if filelist: - return r[0] / metainfo.total_bytes, r[1], r[2] - return r / metainfo.total_bytes - - -class _SingleTorrent(object): - - def __init__(self, rawserver, singleport_listener, ratelimiter, filepool, - config, dht): - self._rawserver = rawserver - self._singleport_listener = singleport_listener - self._ratelimiter = ratelimiter - self._filepool = filepool - self._dht = dht - self._storage = None - self._storagewrapper = None - self._ratemeasure = None - self._upmeasure = None - self._downmeasure = None - self._encoder = None - self._rerequest = None - self._statuscollecter = None - self._announced = False - self._listening = False - self.reserved_ports = [] - self.reported_port = None - self._myfiles = None - self.started = False - self.is_seed = False - self.closed = False - self.infohash = None - self.total_bytes = None - self._doneflag = threading.Event() - self.finflag = threading.Event() - self._hashcheck_thread = None - self._contfunc = None - self._activity = (_("Initial startup"), 0) - self.feedback = None - self.errors = [] - self.rlgroup = None - self.config = config - - def start_download(self, *args, **kwargs): - it = self._start_download(*args, **kwargs) - def cont(): - try: - it.next() - except StopIteration: - self._contfunc = None - def contfunc(): - self._rawserver.external_add_task(cont, 0, context=self) - self._contfunc = contfunc - contfunc() - - def _start_download(self, metainfo, feedback, save_path): - self.feedback = feedback - config = self.config - - self.infohash = metainfo.infohash - self.total_bytes = metainfo.total_bytes - if not metainfo.reported_errors: - metainfo.show_encoding_errors(self._error) - - myid = self._make_id() - seed(myid) - def schedfunc(func, delay): - self._rawserver.add_task(func, delay, context=self) - def externalsched(func, delay): - self._rawserver.external_add_task(func, delay, context=self) - if metainfo.is_batch: - myfiles = [os.path.join(save_path, f) for f in metainfo.files_fs] - else: - myfiles = [save_path] - self._filepool.add_files(myfiles, self) - self._myfiles = myfiles - self._storage = Storage(config, self._filepool, zip(myfiles, - metainfo.sizes)) - resumefile = None - if config['data_dir']: - filename = os.path.join(config['data_dir'], 'resume', - self.infohash.encode('hex')) - if os.path.exists(filename): - try: - resumefile = file(filename, 'rb') - if self._storage.check_fastresume(resumefile) == 0: - resumefile.close() - resumefile = None - except Exception, e: - self._error(WARNING, - _("Could not load fastresume data: %s") % str(e) - + ' ' + _("Will perform full hash check.")) - if resumefile is not None: - resumefile.close() - resumefile = None - def data_flunked(amount, index): - self._ratemeasure.data_rejected(amount) - self._error(INFO, - _("piece %d failed hash check, re-downloading it") - % index) - backthread_exception = [] - def errorfunc(level, text): - def e(): - self._error(level, text) - externalsched(e, 0) - def hashcheck(): - def statusfunc(activity = None, fractionDone = 0): - if activity is None: - activity = self._activity[0] - self._activity = (activity, fractionDone) - try: - self._storagewrapper = StorageWrapper(self._storage, - config, metainfo.hashes, metainfo.piece_length, - self._finished, statusfunc, self._doneflag, data_flunked, - self.infohash, errorfunc, resumefile) - except: - backthread_exception.append(sys.exc_info()) - self._contfunc() - thread = threading.Thread(target = hashcheck) - thread.setDaemon(False) - self._hashcheck_thread = thread - thread.start() - yield None - self._hashcheck_thread = None - if resumefile is not None: - resumefile.close() - if backthread_exception: - a, b, c = backthread_exception[0] - raise a, b, c - - if self._storagewrapper.amount_left == 0: - self._finished() - choker = Choker(config, schedfunc, self.finflag.isSet) - upmeasure = Measure(config['max_rate_period']) - upmeasure_seedtime = Measure(config['max_rate_period_seedtime']) - downmeasure = Measure(config['max_rate_period']) - self._upmeasure = upmeasure - self._upmeasure_seedtime = upmeasure_seedtime - self._downmeasure = downmeasure - self._ratemeasure = RateMeasure(self._storagewrapper. - amount_left_with_partials) - picker = PiecePicker(len(metainfo.hashes), config) - for i in xrange(len(metainfo.hashes)): - if self._storagewrapper.do_I_have(i): - picker.complete(i) - for i in self._storagewrapper.stat_dirty: - picker.requested(i) - def kickpeer(connection): - def kick(): - connection.close() - schedfunc(kick, 0) - def banpeer(ip): - self._encoder.ban(ip) - downloader = Downloader(config, self._storagewrapper, picker, - len(metainfo.hashes), downmeasure, self._ratemeasure.data_came_in, - kickpeer, banpeer) - def make_upload(connection): - return Upload(connection, self._ratelimiter, upmeasure, - upmeasure_seedtime, choker, self._storagewrapper, - config['max_slice_length'], config['max_rate_period']) - - - self.reported_port = self.config['forwarded_port'] - if not self.reported_port: - self.reported_port = self._singleport_listener.get_port(self.change_port) - self.reserved_ports.append(self.reported_port) - - if self._dht: - addContact = self._dht.addContact - else: - addContact = None - self._encoder = Encoder(make_upload, downloader, choker, - len(metainfo.hashes), self._ratelimiter, self._rawserver, - config, myid, schedfunc, self.infohash, self, addContact, self.reported_port) - - self._singleport_listener.add_torrent(self.infohash, self._encoder) - self._listening = True - if metainfo.is_trackerless: - if not self._dht: - self._error(self, CRITICAL, _("Attempt to download a trackerless torrent with trackerless client turned off.")) - return - else: - if len(self._dht.table.findNodes(metainfo.infohash, invalid=False)) < const.K: - for host, port in metainfo.nodes: - self._dht.addContact(host, port) - self._rerequest = DHTRerequester(config, - schedfunc, self._encoder.how_many_connections, - self._encoder.start_connection, externalsched, - self._storagewrapper.get_amount_left, upmeasure.get_total, - downmeasure.get_total, self.reported_port, myid, - self.infohash, self._error, self.finflag, upmeasure.get_rate, - downmeasure.get_rate, self._encoder.ever_got_incoming, - self.internal_shutdown, self._announce_done, self._dht) - else: - self._rerequest = Rerequester(metainfo.announce, config, - schedfunc, self._encoder.how_many_connections, - self._encoder.start_connection, externalsched, - self._storagewrapper.get_amount_left, upmeasure.get_total, - downmeasure.get_total, self.reported_port, myid, - self.infohash, self._error, self.finflag, upmeasure.get_rate, - downmeasure.get_rate, self._encoder.ever_got_incoming, - self.internal_shutdown, self._announce_done) - - self._statuscollecter = DownloaderFeedback(choker, upmeasure.get_rate, - upmeasure_seedtime.get_rate, downmeasure.get_rate, - upmeasure.get_total, downmeasure.get_total, - self._ratemeasure.get_time_left, self._ratemeasure.get_size_left, - self.total_bytes, self.finflag, downloader, self._myfiles, - self._encoder.ever_got_incoming, self._rerequest) - - self._announced = True - if self._dht and len(self._dht.table.findNodes(self.infohash)) == 0: - self._rawserver.add_task(self._dht.findCloseNodes, 5) - self._rawserver.add_task(self._rerequest.begin, 20) - else: - self._rerequest.begin() - self.started = True - if not self.finflag.isSet(): - self._activity = (_("downloading"), 0) - self.feedback.started(self) - - def got_exception(self, e): - is_external = False - if isinstance(e, BTShutdown): - self._error(ERROR, str(e)) - is_external = True - elif isinstance(e, BTFailure): - self._error(CRITICAL, str(e)) - self._activity = ( _("download failed: ") + str(e), 0) - elif isinstance(e, IOError): - msg = 'IO Error ' + str(e) - if e.errno == errno.ENOSPC: - msg = _("IO Error: No space left on disk, " - "or cannot create a file that large:") + str(e) - self._error(CRITICAL, msg) - self._activity = (_("killed by IO error: ") + str(e), 0) - elif isinstance(e, OSError): - self._error(CRITICAL, 'OS Error ' + str(e)) - self._activity = (_("killed by OS error: ") + str(e), 0) - else: - data = StringIO() - print_exc(file=data) - self._error(CRITICAL, data.getvalue(), True) - self._activity = (_("killed by internal exception: ") + str(e), 0) - try: - self._close() - except Exception, e: - self._error(ERROR, - _("Additional error when closing down due to error: ") + - str(e)) - if is_external: - self.feedback.failed(self, True) - return - if self.config['data_dir'] and self._storage is not None: - filename = os.path.join(self.config['data_dir'], 'resume', - self.infohash.encode('hex')) - if os.path.exists(filename): - try: - os.remove(filename) - except Exception, e: - self._error(WARNING, - _("Could not remove fastresume file after " - "failure:") - + str(e)) - self.feedback.failed(self, False) - - def _finished(self): - self.finflag.set() - # Call self._storage.close() to flush buffers and change files to - # read-only mode (when they're possibly reopened). Let exceptions - # from self._storage.close() kill the torrent since files might not - # be correct on disk if file.close() failed. - self._storage.close() - # If we haven't announced yet, normal first announce done later will - # tell the tracker about seed status. - self.is_seed = True - if self._announced: - self._rerequest.announce_finish() - self._activity = (_("seeding"), 1) - if self.config['check_hashes']: - self._save_fastresume(True) - self.feedback.finished(self) - - def _save_fastresume(self, on_finish=False): - if not on_finish and (self.finflag.isSet() or not self.started): - return - if not self.config['data_dir']: - return - if on_finish: # self._ratemeasure might not exist yet - amount_done = self.total_bytes - else: - amount_done = self.total_bytes - self._ratemeasure.get_size_left() - filename = os.path.join(self.config['data_dir'], 'resume', - self.infohash.encode('hex')) - resumefile = None - try: - resumefile = file(filename, 'wb') - self._storage.write_fastresume(resumefile, amount_done) - self._storagewrapper.write_fastresume(resumefile) - resumefile.close() - except Exception, e: - self._error(WARNING, _("Could not write fastresume data: ") + str(e)) - if resumefile is not None: - resumefile.close() - - def shutdown(self): - if self.closed: - return True - try: - self._close() - self._save_fastresume() - self._activity = (_("shut down"), 0) - return True - except Exception, e: - self.got_exception(e) - return False - except: - data = StringIO() - print_exc(file=data) - self._error(WARNING, 'Unable to shutdown:\n'+data.getvalue()) - return False - - def internal_shutdown(self, level, text): - # This is only called when announce fails with no peers, - # don't try to announce again telling we're leaving the torrent - self._announced = False - self._error(level, text) - self.shutdown() - self.feedback.failed(self, True) - - def _close(self): - if self.closed: - return - self.closed = True - self._rawserver.remove_context(self) - self._doneflag.set() - if self._announced: - self._rerequest.announce_stop() - self._rerequest.cleanup() - if self._hashcheck_thread is not None: - self._hashcheck_thread.join() # should die soon after doneflag set - if self._myfiles is not None: - self._filepool.remove_files(self._myfiles) - if self._listening: - self._singleport_listener.remove_torrent(self.infohash) - for port in self.reserved_ports: - self._singleport_listener.release_port(port, self.change_port) - if self._encoder is not None: - self._encoder.close_connections() - if self._storage is not None: - self._storage.close() - self._ratelimiter.clean_closed() - self._rawserver.add_task(gc.collect, 0) - - def get_status(self, spew = False, fileinfo=False): - if self.started and not self.closed: - r = self._statuscollecter.get_statistics(spew, fileinfo) - r['activity'] = self._activity[0] - else: - r = dict(zip(('activity', 'fractionDone'), self._activity)) - return r - - def get_total_transfer(self): - if self._upmeasure is None: - return (0, 0) - return (self._upmeasure.get_total(), self._downmeasure.get_total()) - - def set_option(self, option, value): - if self.closed: - return - if self.config.has_key(option) and self.config[option] == value: - return - self.config[option] = value - if option == 'max_upload_rate': - # make sure counters get reset so new rate applies immediately - self.rlgroup.set_rate(value) - - def change_port(self, new_port = None): - if not self._listening: - return - r = self.config['forwarded_port'] - if r: - for port in self.reserved_ports: - self._singleport_listener.release_port(port) - del self.reserved_ports[:] - if self.reported_port == r: - return - elif new_port is not None: - r = new_port - self.reserved_ports.remove(self.reported_port) - self.reserved_ports.append(r) - elif self._singleport_listener.port != self.reported_port: - r = self._singleport_listener.get_port(self.change_port) - self.reserved_ports.append(r) - else: - return - self.reported_port = r - myid = self._make_id() - self._encoder.my_id = myid - self._rerequest.change_port(myid, r) - - def _announce_done(self): - for port in self.reserved_ports[:-1]: - self._singleport_listener.release_port(port, self.change_port) - del self.reserved_ports[:-1] - - def _make_id(self): - return PeerID.make_id() - - def _error(self, level, text, exception=False): - self.errors.append((time(), level, text)) - if exception: - self.feedback.exception(self, text) - else: - self.feedback.error(self, level, text) diff --git a/BitTorrent/language.py b/BitTorrent/language.py deleted file mode 100644 index f7f72ef..0000000 --- a/BitTorrent/language.py +++ /dev/null @@ -1,202 +0,0 @@ -# -*- coding: UTF-8 -*- -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# http://people.w3.org/rishida/names/languages.html - -language_names = { - 'af' :u'Afrikaans' , 'bg' :u'Български' , - 'da' :u'Dansk' , 'ca' :u'Català' , - 'cs' :u'Čeština' , 'de' :u'Deutsch' , - 'en' :u'English' , 'es' :u'Español' , - 'es_MX':u'Español de Mexico ' , 'fr' :u'Français' , - 'el' :u'Ελληνικά' , 'he' :u'עברית' , - 'hu' :u'Magyar' , 'it' :u'Italiano' , - 'is' :u'Íslenska' , 'ja' :u'日本語' , - 'ko' :u'한국어' ,'nl' :u'Nederlands' , - 'nb_NO':u'Norsk bokmål' , 'pl' :u'Polski' , - 'pt' :u'Português' , 'pt_BR':u'Português do Brasil' , - 'ro' :u'Română' , 'ru' :u'Русский' , - 'sk' :u'Slovenský' , 'sl' :u'Slovensko' , - 'sv' :u'Svenska' , 'tr' :u'Türkçe' , - 'vi' :u'Tiê?ng Viê?t' , - 'zh_CN':u'简体中文' , # Simplified - 'zh_TW':u'繁體中文' , # Traditional - } - -unfinished_language_names = { - 'ar' :u'العربية' , 'bs' :u'Bosanski' , - 'eo' :u'Esperanto' , 'eu' :u'Euskara' , - 'et' :u'Eesti' , 'fi' :u'Suomi' , - 'fa' :u'فارسی' , 'ga' :u'Gaeilge' , - 'gl' :u'Galego' , 'hr' :u'Hrvatski' , - 'hy' :u'Հայերեն' , 'in' :u'Bahasa indonesia' , - 'ka' :u'ქართული ენა', 'lt' :u'Lietuvių' , - 'ms' :u'Bahasa melayu' , 'ml' :u'Malayalam' , - 'sq' :u'Shqipe' , 'th' :u'ภาษาไทย' , - 'tlh' :u'tlhIngan-Hol' , 'uk' :u'Українська' , - 'hi' :u'हिंदी' , 'cy' :u'Cymraeg' , - 'nn_NO':u'Norsk Nynorsk' , 'te' :u' తెలుగు' , - } - -#language_names.update(unfinished_language_names) -languages = language_names.keys() -languages.sort() - -# windows codepage to locale mapping -locale_sucks = { - 0x0436: "af", # Afrikaans - 0x3801: "ar_ae", # Arabic - United Arab Emirates - 0x3C01: "ar_bh", # Arabic - Bahrain - 0x1401: "ar_dz", # Arabic - Algeria - 0x0C01: "ar_eg", # Arabic - Egypt - 0x0801: "ar_iq", # Arabic - Iraq - 0x2C01: "ar_jo", # Arabic - Jordan - 0x3401: "ar_kw", # Arabic - Kuwait - 0x3001: "ar_lb", # Arabic - Lebanon - 0x1001: "ar_ly", # Arabic - Libya - 0x1801: "ar_ma", # Arabic - Morocco - 0x2001: "ar_om", # Arabic - Oman - 0x4001: "ar_qa", # Arabic - Qatar - 0x0401: "ar_sa", # Arabic - Saudi Arabia - 0x2801: "ar_sy", # Arabic - Syria - 0x1C01: "ar_tn", # Arabic - Tunisia - 0x2401: "ar_ye", # Arabic - Yemen - 0x082C: "az_az", # Azeri - Cyrillic - 0x0423: "be", # Belarusian - 0x0402: "bg", # Bulgarian - 0x0403: "ca", # Catalan - 0x0405: "cs", # Czech - 0x0406: "da", # Danish - 0x0007: "de", # German - 0x0C07: "de_at", # German - Austria - 0x0807: "de_ch", # German - Switzerland - 0x0407: "de_de", # German - Germany - 0x1407: "de_li", # German - Liechtenstein - 0x1007: "de_lu", # German - Luxembourg - 0x0408: "el", # Greek - 0x0C09: "en_au", # English - Australia - 0x2809: "en_bz", # English - Belize - 0x1009: "en_ca", # English - Canada - 0x2409: "en_cb", # English - Carribbean - 0x0809: "en_gb", # English - United Kingdom - 0x1809: "en_ie", # English - Ireland - 0x2009: "en_jm", # English - Jamaica - 0x1409: "en_nz", # English - New Zealand - 0x3409: "en_ph", # English - Phillippines - 0x2C09: "en_tt", # English - Trinidad - 0x0409: "en_us", # English - United States - 0x1C09: "en_za", # English - South Africa - 0x000A: "es", # Spanish (added) - 0x2C0A: "es_ar", # Spanish - Argentina - 0x400A: "es_bo", # Spanish - Bolivia - 0x340A: "es_cl", # Spanish - Chile - 0x240A: "es_co", # Spanish - Colombia - 0x140A: "es_cr", # Spanish - Costa Rica - 0x1C0A: "es_do", # Spanish - Dominican Republic - 0x300A: "es_ec", # Spanish - Ecuador - 0x040a: "es_es", # Spanish - Spain - 0x100A: "es_gt", # Spanish - Guatemala - 0x480A: "es_hn", # Spanish - Honduras - 0x080A: "es_mx", # Spanish - Mexico - 0x4C0A: "es_ni", # Spanish - Nicaragua - 0x180A: "es_pa", # Spanish - Panama - 0x280A: "es_pe", # Spanish - Peru - 0x500A: "es_pr", # Spanish - Puerto Rico - 0x3C0A: "es_py", # Spanish - Paraguay - 0x440A: "es_sv", # Spanish - El Salvador - 0x380A: "es_uy", # Spanish - Uruguay - 0x200A: "es_ve", # Spanish - Venezuela - 0x0425: "et", # Estonian - 0x0009: "en", # English (added) - 0x042D: "eu", # Basque - 0x0429: "fa", # Farsi - 0x040B: "fi", # Finnish - 0x0438: "fo", # Faroese - 0x000C: "fr", # French (added) - 0x080C: "fr_be", # French - Belgium - 0x0C0C: "fr_ca", # French - Canada - 0x100C: "fr_ch", # French - Switzerland - 0x040C: "fr_fr", # French - France - 0x140C: "fr_lu", # French - Luxembourg - 0x043C: "gd", # Gaelic - Scotland - 0x083C: "gd_ie", # Gaelic - Ireland - 0x040D: "he", # Hebrew - 0x0439: "hi", # Hindi - 0x041A: "hr", # Croatian - 0x040E: "hu", # Hungarian - 0x042B: "hy", # Armenian - 0x0421: "id", # Indonesian - 0x040F: "is", # Icelandic - 0x0010: "it", # Italian (added) - 0x0810: "it_ch", # Italian - Switzerland - 0x0410: "it_it", # Italian - Italy - 0x0411: "ja", # Japanese - 0x0412: "ko", # Korean - 0x0427: "lt", # Lithuanian - 0x0426: "lv", # Latvian - 0x042F: "mk", # FYRO Macedonian - 0x044E: "mr", # Marathi - 0x083E: "ms_bn", # Malay - Brunei - 0x043E: "ms_my", # Malay - Malaysia - 0x043A: "mt", # Maltese - 0x0013: "nl", # Dutch (added) - 0x0813: "nl_be", # Dutch - Belgium - 0x0413: "nl_nl", # Dutch - The Netherlands - 0x0814: "no_no", # Norwegian - Nynorsk - 0x0414: "nb_no", # Norwegian - Bokmal (?) - 0x0415: "pl", # Polish - 0x0016: "pt", # Portuguese (added) - 0x0416: "pt_br", # Portuguese - Brazil - 0x0816: "pt_pt", # Portuguese - Portugal - 0x0417: "rm", # Raeto-Romance - 0x0418: "ro", # Romanian - Romania - 0x0818: "ro_mo", # Romanian - Moldova - 0x0419: "ru", # Russian - 0x0819: "ru_mo", # Russian - Moldova - 0x044F: "sa", # Sanskrit - 0x042E: "sb", # Sorbian - 0x041B: "sk", # Slovak - 0x0424: "sl", # Slovenian - 0x041C: "sq", # Albanian - 0x081A: "sr_sp", # Serbian - Latin - 0x001D: "sv", # Swedish (added) - 0x081D: "sv_fi", # Swedish - Finland - 0x041D: "sv_se", # Swedish - Sweden - 0x0441: "sw", # Swahili - 0x0430: "sx", # Sutu - 0x0449: "ta", # Tamil - 0x041E: "th", # Thai - 0x0432: "tn", # Setsuana - 0x041F: "tr", # Turkish - 0x0431: "ts", # Tsonga - 0X0444: "tt", # Tatar - 0x0422: "uk", # Ukrainian - 0x0420: "ur", # Urdu - 0x0443: "uz_uz", # Uzbek - Latin - 0x042A: "vi", # Vietnamese - 0x0434: "xh", # Xhosa - 0x043D: "yi", # Yiddish - 0x0804: "zh_cn", # Chinese - China - 0x0C04: "zh_hk", # Chinese - Hong Kong S.A.R. - 0x1404: "zh_mo", # Chinese - Macau S.A.R - 0x1004: "zh_sg", # Chinese - Singapore - 0x0404: "zh_tw", # Chinese - Taiwan - 0x0435: "zu", # Zulu -} - -if __name__ == '__main__': - from sets import Set - internal = Set([x.lower() for x in languages]) - windows = Set(locale_sucks.values()) - if not windows.issuperset(internal): - diff = list(internal.difference(windows)) - diff.sort() - print diff diff --git a/BitTorrent/launchmanycore.py b/BitTorrent/launchmanycore.py deleted file mode 100644 index 955f88a..0000000 --- a/BitTorrent/launchmanycore.py +++ /dev/null @@ -1,261 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Original version written by John Hoffman, heavily modified for different -# multitorrent architecture by Uoti Urpala (over 40% shorter than original) - -import os -from cStringIO import StringIO -from traceback import print_exc - -from BitTorrent import configfile -from BitTorrent.parsedir import parsedir -from BitTorrent.download import Multitorrent, Feedback -from BitTorrent.ConvertedMetainfo import ConvertedMetainfo -from BitTorrent import BTFailure - -from threading import Event -from time import time - - -class LaunchMany(Feedback): - - def __init__(self, config, output, configfile_key): - try: - self.config = config - self.output = output - self.configfile_key = configfile_key - - self.torrent_dir = config['torrent_dir'] - self.torrent_cache = {} - self.file_cache = {} - self.blocked_files = {} - - self.torrent_list = [] - self.downloads = {} - self.doneflag = Event() - - self.hashcheck_queue = [] - self.hashcheck_store = {} - self.hashcheck_current = None - - self.multitorrent = Multitorrent(config, self.doneflag, - self.global_error) - self.rawserver = self.multitorrent.rawserver - - self.rawserver.add_task(self.scan, 0) - self.rawserver.add_task(self.stats, 0) - - try: - import signal - def handler(signum, frame): - self.rawserver.external_add_task(self.read_config, 0) - signal.signal(signal.SIGHUP, handler) - self.rawserver.install_sigint_handler() - except Exception, e: - self.output.message(_("Could not set signal handler: ") + str(e)) - - self.rawserver.listen_forever() - - self.output.message(_("shutting down")) - for infohash in self.torrent_list: - self.output.message(_('dropped "%s"') % - self.torrent_cache[infohash]['path']) - torrent = self.downloads[infohash] - if torrent is not None: - torrent.shutdown() - except: - data = StringIO() - print_exc(file = data) - output.exception(data.getvalue()) - - def scan(self): - self.rawserver.add_task(self.scan, self.config['parse_dir_interval']) - - r = parsedir(self.torrent_dir, self.torrent_cache, - self.file_cache, self.blocked_files, - self.output.message) - - ( self.torrent_cache, self.file_cache, self.blocked_files, - added, removed ) = r - - for infohash, data in removed.items(): - self.output.message(_('dropped "%s"') % data['path']) - self.remove(infohash) - for infohash, data in added.items(): - self.output.message(_('added "%s"' ) % data['path']) - if self.config['launch_delay'] > 0: - self.rawserver.add_task(self.add, self.config['launch_delay'], (infohash, data)) - else: - self.add(infohash, data) - - def stats(self): - self.rawserver.add_task(self.stats, self.config['display_interval']) - data = [] - for infohash in self.torrent_list: - cache = self.torrent_cache[infohash] - if self.config['display_path']: - name = cache['path'] - else: - name = cache['name'] - size = cache['length'] - d = self.downloads[infohash] - progress = '0.0%' - peers = 0 - seeds = 0 - seedsmsg = "S" - dist = 0.0 - uprate = 0.0 - dnrate = 0.0 - upamt = 0 - dnamt = 0 - t = 0 - msg = '' - if d is None: - status = _("waiting for hash check") - else: - stats = d.get_status() - status = stats['activity'] - progress = '%.1f%%' % (int(stats['fractionDone']*1000)/10.0) - if d.started and not d.closed: - s = stats - dist = s['numCopies'] - if d.is_seed: - seeds = 0 # s['numOldSeeds'] - seedsmsg = "s" - else: - if s['numSeeds'] + s['numPeers']: - t = stats['timeEst'] - if t is None: - t = -1 - if t == 0: # unlikely - t = 0.01 - status = _("downloading") - else: - t = -1 - status = _("connecting to peers") - seeds = s['numSeeds'] - dnrate = stats['downRate'] - peers = s['numPeers'] - uprate = stats['upRate'] - upamt = s['upTotal'] - dnamt = s['downTotal'] - if d.errors and (d.closed or d.errors[-1][0] + 300 > time()): - msg = d.errors[-1][2] - - data.append(( name, status, progress, peers, seeds, seedsmsg, dist, - uprate, dnrate, upamt, dnamt, size, t, msg )) - stop = self.output.display(data) - if stop: - self.doneflag.set() - - def remove(self, infohash): - self.torrent_list.remove(infohash) - if self.downloads[infohash] is not None: - self.downloads[infohash].shutdown() - self.was_stopped(infohash) - del self.downloads[infohash] - - def add(self, infohash, data): - self.torrent_list.append(infohash) - self.downloads[infohash] = None - self.hashcheck_queue.append(infohash) - self.hashcheck_store[infohash] = data['metainfo'] - self.check_hashcheck_queue() - - def check_hashcheck_queue(self): - if self.hashcheck_current is not None or not self.hashcheck_queue: - return - self.hashcheck_current = self.hashcheck_queue.pop(0) - metainfo = self.hashcheck_store[self.hashcheck_current] - del self.hashcheck_store[self.hashcheck_current] - filename = self.determine_filename(self.hashcheck_current) - self.downloads[self.hashcheck_current] = self.multitorrent. \ - start_torrent(ConvertedMetainfo(metainfo), - self.config, self, filename) - - def determine_filename(self, infohash): - x = self.torrent_cache[infohash] - name = x['name'] - savein = self.config['save_in'] - isdir = not x['metainfo']['info'].has_key('length') - style = self.config['saveas_style'] - if style == 4: - torrentname = os.path.split(x['path'][:-8])[1] - suggestedname = name - if torrentname == suggestedname: - style = 1 - else: - style = 3 - - if style == 1 or style == 3: - if savein: - saveas = os.path.join(savein,x['file'][:-8]) # strip '.torrent' - else: - saveas = x['path'][:-8] # strip '.torrent' - if style == 3 and not isdir: - saveas = os.path.join(saveas, name) - else: - if savein: - saveas = os.path.join(savein, name) - else: - saveas = os.path.join(os.path.split(x['path'])[0], name) - return saveas - - def was_stopped(self, infohash): - try: - self.hashcheck_queue.remove(infohash) - except: - pass - else: - del self.hashcheck_store[infohash] - if self.hashcheck_current == infohash: - self.hashcheck_current = None - self.check_hashcheck_queue() - - def global_error(self, level, text): - self.output.message(text) - - def exchandler(self, s): - self.output.exception(s) - - def read_config(self): - try: - newvalues = configfile.get_config(self.config, self.configfile_key) - except Exception, e: - self.output.message(_("Error reading config: ") + str(e)) - return - self.output.message(_("Rereading config file")) - self.config.update(newvalues) - # The set_option call can potentially trigger something that kills - # the torrent (when writing this the only possibility is a change in - # max_files_open causing an IOError while closing files), and so - # the self.failed() callback can run during this loop. - for option, value in newvalues.iteritems(): - self.multitorrent.set_option(option, value) - for torrent in self.downloads.values(): - if torrent is not None: - for option, value in newvalues.iteritems(): - torrent.set_option(option, value) - - # rest are callbacks from torrent instances - - def started(self, torrent): - self.hashcheck_current = None - self.check_hashcheck_queue() - - def failed(self, torrent, is_external): - infohash = torrent.infohash - self.was_stopped(infohash) - if self.torrent_cache.has_key(infohash): - self.output.message('DIED: "'+self.torrent_cache[infohash]['path']+'"') - - def exception(self, torrent, text): - self.exchandler(text) diff --git a/BitTorrent/makemetafile.py b/BitTorrent/makemetafile.py deleted file mode 100644 index 798f325..0000000 --- a/BitTorrent/makemetafile.py +++ /dev/null @@ -1,260 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -from __future__ import division - -import os -import sys -from sha import sha -from time import time -from threading import Event - -from BitTorrent.bencode import bencode, bdecode -from BitTorrent.btformats import check_info -from BitTorrent.parseargs import parseargs, printHelp -from BitTorrent import BTFailure - -from khashmir.node import Node -from khashmir.ktable import KTable -from khashmir.util import packPeers, compact_peer_info - -ignore = ['core', 'CVS', 'Thumbs.db', 'desktop.ini'] - -noncharacter_translate = {} -for i in range(0xD800, 0xE000): - noncharacter_translate[i] = None -for i in range(0xFDD0, 0xFDF0): - noncharacter_translate[i] = None -for i in (0xFFFE, 0xFFFF): - noncharacter_translate[i] = None - -del i - -def dummy(v): - pass - -def make_meta_files(url, - files, - flag=Event(), - progressfunc=dummy, - filefunc=dummy, - piece_len_pow2=None, - target=None, - comment=None, - filesystem_encoding=None, - use_tracker=True, - data_dir = None): - if len(files) > 1 and target: - raise BTFailure(_("You can't specify the name of the .torrent file " - "when generating multiple torrents at once")) - - if not filesystem_encoding: - try: - getattr(sys, 'getfilesystemencoding') - except AttributeError: - pass - else: - filesystem_encoding = sys.getfilesystemencoding() - if not filesystem_encoding: - filesystem_encoding = 'ascii' - try: - 'a1'.decode(filesystem_encoding) - except: - raise BTFailure(_('Filesystem encoding "%s" is not supported in this version') - % filesystem_encoding) - files.sort() - ext = '.torrent' - - togen = [] - for f in files: - if not f.endswith(ext): - togen.append(f) - - total = 0 - for f in togen: - total += calcsize(f) - - subtotal = [0] - def callback(x): - subtotal[0] += x - progressfunc(subtotal[0] / total) - for f in togen: - if flag.isSet(): - break - t = os.path.split(f) - if t[1] == '': - f = t[0] - filefunc(f) - if use_tracker: - make_meta_file(f, url, flag=flag, progress=callback, - piece_len_exp=piece_len_pow2, target=target, - comment=comment, encoding=filesystem_encoding) - else: - make_meta_file_dht(f, url, flag=flag, progress=callback, - piece_len_exp=piece_len_pow2, target=target, - comment=comment, encoding=filesystem_encoding, data_dir=data_dir) - - -def make_meta_file(path, url, piece_len_exp, flag=Event(), progress=dummy, - comment=None, target=None, encoding='ascii'): - data = {'announce': url.strip(),'creation date': int(time())} - piece_length = 2 ** piece_len_exp - a, b = os.path.split(path) - if not target: - if b == '': - f = a + '.torrent' - else: - f = os.path.join(a, b + '.torrent') - else: - f = target - info = makeinfo(path, piece_length, flag, progress, encoding) - if flag.isSet(): - return - check_info(info) - h = file(f, 'wb') - - data['info'] = info - if comment: - data['comment'] = comment - h.write(bencode(data)) - h.close() - -def make_meta_file_dht(path, nodes, piece_len_exp, flag=Event(), progress=dummy, - comment=None, target=None, encoding='ascii', data_dir=None): - # if nodes is empty, then get them out of the routing table in data_dir - # else, expect nodes to be a string of comma seperated <ip>:<port> pairs - # this has a lot of duplicated code from make_meta_file - piece_length = 2 ** piece_len_exp - a, b = os.path.split(path) - if not target: - if b == '': - f = a + '.torrent' - else: - f = os.path.join(a, b + '.torrent') - else: - f = target - info = makeinfo(path, piece_length, flag, progress, encoding) - if flag.isSet(): - return - check_info(info) - info_hash = sha(bencode(info)).digest() - - if not nodes: - x = open(os.path.join(data_dir, 'routing_table'), 'rb') - d = bdecode(x.read()) - x.close() - t = KTable(Node().initWithDict({'id':d['id'], 'host':'127.0.0.1','port': 0})) - for n in d['rt']: - t.insertNode(Node().initWithDict(n)) - nodes = [(node.host, node.port) for node in t.findNodes(info_hash) if node.host != '127.0.0.1'] - else: - nodes = [(a[0], int(a[1])) for a in [node.strip().split(":") for node in nodes.split(",")]] - data = {'nodes': nodes, 'creation date': int(time())} - h = file(f, 'wb') - - data['info'] = info - if comment: - data['comment'] = comment - h.write(bencode(data)) - h.close() - - -def calcsize(path): - total = 0 - for s in subfiles(os.path.abspath(path)): - total += os.path.getsize(s[1]) - return total - -def makeinfo(path, piece_length, flag, progress, encoding): - def to_utf8(name): - try: - u = name.decode(encoding) - except Exception, e: - raise BTFailure(_('Could not convert file/directory name "%s" to ' - 'utf-8 (%s). Either the assumed filesystem ' - 'encoding "%s" is wrong or the filename contains ' - 'illegal bytes.') % (name, str(e), encoding)) - if u.translate(noncharacter_translate) != u: - raise BTFailure(_('File/directory name "%s" contains reserved ' - 'unicode values that do not correspond to ' - 'characters.') % name) - return u.encode('utf-8') - path = os.path.abspath(path) - if os.path.isdir(path): - subs = subfiles(path) - subs.sort() - pieces = [] - sh = sha() - done = 0 - fs = [] - totalsize = 0.0 - totalhashed = 0 - for p, f in subs: - totalsize += os.path.getsize(f) - - for p, f in subs: - pos = 0 - size = os.path.getsize(f) - p2 = [to_utf8(name) for name in p] - fs.append({'length': size, 'path': p2}) - h = file(f, 'rb') - while pos < size: - a = min(size - pos, piece_length - done) - sh.update(h.read(a)) - if flag.isSet(): - return - done += a - pos += a - totalhashed += a - - if done == piece_length: - pieces.append(sh.digest()) - done = 0 - sh = sha() - progress(a) - h.close() - if done > 0: - pieces.append(sh.digest()) - return {'pieces': ''.join(pieces), - 'piece length': piece_length, 'files': fs, - 'name': to_utf8(os.path.split(path)[1])} - else: - size = os.path.getsize(path) - pieces = [] - p = 0 - h = file(path, 'rb') - while p < size: - x = h.read(min(piece_length, size - p)) - if flag.isSet(): - return - pieces.append(sha(x).digest()) - p += piece_length - if p > size: - p = size - progress(min(piece_length, size - p)) - h.close() - return {'pieces': ''.join(pieces), - 'piece length': piece_length, 'length': size, - 'name': to_utf8(os.path.split(path)[1])} - -def subfiles(d): - r = [] - stack = [([], d)] - while stack: - p, n = stack.pop() - if os.path.isdir(n): - for s in os.listdir(n): - if s not in ignore and not s.startswith('.'): - stack.append((p + [s], os.path.join(n, s))) - else: - r.append((p, n)) - return r diff --git a/BitTorrent/parseargs.py b/BitTorrent/parseargs.py deleted file mode 100644 index bd38558..0000000 --- a/BitTorrent/parseargs.py +++ /dev/null @@ -1,178 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bill Bumgarner and Bram Cohen - -from types import * -from cStringIO import StringIO - -from BitTorrent.defaultargs import MyBool, MYTRUE -from BitTorrent import BTFailure -from BitTorrent.bencode import bdecode -from BitTorrent.platform import is_frozen_exe - -def makeHelp(uiname, defaults): - ret = '' - ret += (_("Usage: %s ") % uiname) - if uiname.startswith('launchmany'): - ret += _("[OPTIONS] [TORRENTDIRECTORY]\n\n") - ret += _("If a non-option argument is present it's taken as the value\n" - "of the torrent_dir option.\n") - elif uiname == 'bittorrent': - ret += _("[OPTIONS] [TORRENTFILES]\n") - elif uiname.startswith('bittorrent'): - ret += _("[OPTIONS] [TORRENTFILE]\n") - elif uiname.startswith('maketorrent'): - ret += _("[OPTION] TRACKER_URL FILE [FILE]\n") - ret += '\n' - ret += _("arguments are -\n") + formatDefinitions(defaults, 80) - return ret - -def printHelp(uiname, defaults): - if uiname in ('bittorrent','maketorrent') and is_frozen_exe: - from BitTorrent.GUI import HelpWindow - HelpWindow(None, makeHelp(uiname, defaults)) - else: - print makeHelp(uiname, defaults) - -def formatDefinitions(options, COLS): - s = StringIO() - indent = " " * 10 - width = COLS - 11 - - if width < 15: - width = COLS - 2 - indent = " " - - for option in options: - (longname, default, doc) = option - if doc == '': - continue - s.write('--' + longname) - is_boolean = type(default) is MyBool - if is_boolean: - s.write(', --no_' + longname) - else: - s.write(' <arg>') - s.write('\n') - if default is not None: - doc += _(" (defaults to ") + repr(default) + ')' - i = 0 - for word in doc.split(): - if i == 0: - s.write(indent + word) - i = len(word) - elif i + len(word) >= width: - s.write('\n' + indent + word) - i = len(word) - else: - s.write(' ' + word) - i += len(word) + 1 - s.write('\n\n') - return s.getvalue() - -def usage(str): - raise BTFailure(str) - -def format_key(key): - if len(key) == 1: - return '-%s'%key - else: - return '--%s'%key - -def parseargs(argv, options, minargs=None, maxargs=None, presets=None): - config = {} - for option in options: - longname, default, doc = option - config[longname] = default - args = [] - pos = 0 - if presets is None: - presets = {} - else: - presets = presets.copy() - while pos < len(argv): - if argv[pos][:1] != '-': # not a cmdline option - args.append(argv[pos]) - pos += 1 - else: - key, value = None, None - if argv[pos].startswith('--'): # --aaa 1 - if argv[pos].startswith('--no_'): - key = argv[pos][5:] - boolval = False - else: - key = argv[pos][2:] - boolval = True - if key not in config: - raise BTFailure(_("unknown key ") + format_key(key)) - if type(config[key]) is MyBool: # boolean cmd line switch, no value - value = boolval - pos += 1 - else: # --argument value - if pos == len(argv) - 1: - usage(_("parameter passed in at end with no value")) - key, value = argv[pos][2:], argv[pos+1] - pos += 2 - elif argv[pos][:1] == '-': - key = argv[pos][1:2] - if len(argv[pos]) > 2: # -a1 - value = argv[pos][2:] - pos += 1 - else: # -a 1 - if pos == len(argv) - 1: - usage(_("parameter passed in at end with no value")) - value = argv[pos+1] - pos += 2 - else: - raise BTFailure(_("command line parsing failed at ")+argv[pos]) - - presets[key] = value - parse_options(config, presets) - config.update(presets) - for key, value in config.items(): - if value is None: - usage(_("Option %s is required.") % format_key(key)) - if minargs is not None and len(args) < minargs: - usage(_("Must supply at least %d arguments.") % minargs) - if maxargs is not None and len(args) > maxargs: - usage(_("Too many arguments - %d maximum.") % maxargs) - - return (config, args) - -def parse_options(defaults, newvalues): - for key, value in newvalues.iteritems(): - if not defaults.has_key(key): - raise BTFailure(_("unknown key ") + format_key(key)) - try: - t = type(defaults[key]) - if t is MyBool: - if value in ('True', '1', MYTRUE, True): - value = True - else: - value = False - newvalues[key] = value - elif t in (StringType, NoneType): - newvalues[key] = value - elif t in (IntType, LongType): - if value == 'False': - newvalues[key] == 0 - elif value == 'True': - newvalues[key] == 1 - else: - newvalues[key] = int(value) - elif t is FloatType: - newvalues[key] = float(value) - else: - raise TypeError, str(t) - - except ValueError, e: - raise BTFailure(_("wrong format of %s - %s") % (format_key(key), str(e))) - diff --git a/BitTorrent/parsedir.py b/BitTorrent/parsedir.py deleted file mode 100644 index 3b86a03..0000000 --- a/BitTorrent/parsedir.py +++ /dev/null @@ -1,150 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by John Hoffman and Uoti Urpala - -import os -from sha import sha - -from BitTorrent.bencode import bencode, bdecode -from BitTorrent.btformats import check_message - -NOISY = False - -def parsedir(directory, parsed, files, blocked, errfunc, - include_metainfo=True): - if NOISY: - errfunc('checking dir') - dirs_to_check = [directory] - new_files = {} - new_blocked = {} - while dirs_to_check: # first, recurse directories and gather torrents - directory = dirs_to_check.pop() - newtorrents = False - try: - dir_contents = os.listdir(directory) - except (IOError, OSError), e: - errfunc(_("Could not read directory ") + directory) - continue - for f in dir_contents: - if f.endswith('.torrent'): - newtorrents = True - p = os.path.join(directory, f) - try: - new_files[p] = [(os.path.getmtime(p),os.path.getsize(p)),0] - except (IOError, OSError), e: - errfunc(_("Could not stat ") + p + " : " + str(e)) - if not newtorrents: - for f in dir_contents: - p = os.path.join(directory, f) - if os.path.isdir(p): - dirs_to_check.append(p) - - new_parsed = {} - to_add = [] - added = {} - removed = {} - # files[path] = [(modification_time, size), hash], hash is 0 if the file - # has not been successfully parsed - for p,v in new_files.items(): # re-add old items and check for changes - oldval = files.get(p) - if oldval is None: # new file - to_add.append(p) - continue - h = oldval[1] - if oldval[0] == v[0]: # file is unchanged from last parse - if h: - if p in blocked: # parseable + blocked means duplicate - to_add.append(p) # other duplicate may have gone away - else: - new_parsed[h] = parsed[h] - new_files[p] = oldval - else: - new_blocked[p] = None # same broken unparseable file - continue - if p not in blocked and h in parsed: # modified; remove+add - if NOISY: - errfunc(_("removing %s (will re-add)") % p) - removed[h] = parsed[h] - to_add.append(p) - - to_add.sort() - for p in to_add: # then, parse new and changed torrents - new_file = new_files[p] - v = new_file[0] - if new_file[1] in new_parsed: # duplicate - if p not in blocked or files[p][0] != v: - errfunc(_("**warning** %s is a duplicate torrent for %s") % - (p, new_parsed[new_file[1]]['path'])) - new_blocked[p] = None - continue - - if NOISY: - errfunc('adding '+p) - try: - ff = open(p, 'rb') - d = bdecode(ff.read()) - check_message(d) - h = sha(bencode(d['info'])).digest() - new_file[1] = h - if new_parsed.has_key(h): - errfunc(_("**warning** %s is a duplicate torrent for %s") % - (p, new_parsed[h]['path'])) - new_blocked[p] = None - continue - - a = {} - a['path'] = p - f = os.path.basename(p) - a['file'] = f - i = d['info'] - l = 0 - nf = 0 - if i.has_key('length'): - l = i.get('length',0) - nf = 1 - elif i.has_key('files'): - for li in i['files']: - nf += 1 - if li.has_key('length'): - l += li['length'] - a['numfiles'] = nf - a['length'] = l - a['name'] = i.get('name', f) - def setkey(k, d = d, a = a): - if d.has_key(k): - a[k] = d[k] - setkey('failure reason') - setkey('warning message') - setkey('announce-list') - if include_metainfo: - a['metainfo'] = d - except: - errfunc(_("**warning** %s has errors") % p) - new_blocked[p] = None - continue - try: - ff.close() - except: - pass - if NOISY: - errfunc(_("... successful")) - new_parsed[h] = a - added[h] = a - - for p,v in files.iteritems(): # and finally, mark removed torrents - if p not in new_files and p not in blocked: - if NOISY: - errfunc(_("removing %s") % p) - removed[v[1]] = parsed[v[1]] - - if NOISY: - errfunc(_("done checking")) - return (new_parsed, new_files, new_blocked, added, removed) diff --git a/BitTorrent/platform.py b/BitTorrent/platform.py deleted file mode 100644 index 8435397..0000000 --- a/BitTorrent/platform.py +++ /dev/null @@ -1,390 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Matt Chisholm and Uoti Urpala - -# This module is strictly for cross platform compatibility items and -# should not import anything from other BitTorrent modules. - -import os -import re -import sys -import time -import gettext -import locale -if os.name == 'nt': - import _winreg - import win32api - from win32com.shell import shellcon, shell - import win32com.client - import ctypes -elif os.name == 'posix' and os.uname()[0] == 'Darwin': - has_pyobjc = False - try: - from Foundation import NSBundle - has_pyobjc = True - except ImportError: - pass - -from BitTorrent import app_name, version -from BitTorrent.language import locale_sucks - -if sys.platform.startswith('win'): - bttime = time.clock -else: - bttime = time.time - -is_frozen_exe = (os.name == 'nt') and hasattr(sys, 'frozen') and (sys.frozen == 'windows_exe') - -os_name = os.name -os_version = None -if os_name == 'nt': - - class OSVERSIONINFOEX(ctypes.Structure): - _fields_ = [("dwOSVersionInfoSize", ctypes.c_ulong), - ("dwMajorVersion", ctypes.c_ulong), - ("dwMinorVersion", ctypes.c_ulong), - ("dwBuildNumber", ctypes.c_ulong), - ("dwPlatformId", ctypes.c_ulong), - ("szCSDVersion", ctypes.c_char * 128), - ("wServicePackMajor", ctypes.c_ushort), - ("wServicePackMinor", ctypes.c_ushort), - ("wSuiteMask", ctypes.c_ushort), - ("wProductType", ctypes.c_byte), - ("wReserved", ctypes.c_byte), - ] - - o = OSVERSIONINFOEX() - o.dwOSVersionInfoSize = 156 # sizeof(OSVERSIONINFOEX) - - ctypes.windll.kernel32.GetVersionExA(ctypes.byref(o)) - - wh = {(1, 4, 0): "95", - (1, 4, 10): "98", - (1, 4, 90): "ME", - (2, 4, 0): "NT", - (2, 5, 0): "2000", - (2, 5, 1): "XP" , - (2, 5, 2): "2003", - } - - win_version_num = (o.dwPlatformId, o.dwMajorVersion, o.dwMinorVersion, - o.wServicePackMajor, o.wServicePackMinor, o.dwBuildNumber) - - wk = (o.dwPlatformId, o.dwMajorVersion, o.dwMinorVersion) - if wh.has_key(wk): - os_version = wh[wk] - else: - os_version = wh[max(wh.keys())] - sys.stderr.write("Couldn't identify windows version: %s, " - "assuming '%s'\n" % (str(wk), os_version)) - del wh, wk - -elif os_name == 'posix': - os_version = os.uname()[0] - - -def calc_unix_dirs(): - appdir = '%s-%s'%('bittorrent', version) - ip = os.path.join('share', 'pixmaps', appdir) - dp = os.path.join('share', 'doc' , appdir) - lp = os.path.join('share', 'locale') - return ip, dp, lp - -if is_frozen_exe: - app_root = os.path.split(os.path.abspath(win32api.GetModuleFileName(0)))[0] -else: - app_root = os.path.split(os.path.abspath(sys.argv[0]))[0] -doc_root = app_root -osx = False -if os.name == 'posix': - if os.uname()[0] == "Darwin": - doc_root = app_root = app_root.encode('utf8') - if has_pyobjc: - doc_root = NSBundle.mainBundle().resourcePath() - osx = True -image_root = os.path.join(app_root, 'images') -locale_root = os.path.join(app_root, 'locale') - -if not os.access(image_root, os.F_OK) or not os.access(locale_root, os.F_OK): - # we guess that probably we are installed on *nix in this case - # (I have no idea whether this is right or not -- matt) - if app_root[-4:] == '/bin': - # yep, installed on *nix - installed_prefix = app_root[:-4] - image_root, doc_root, locale_root = map( - lambda p: os.path.join(installed_prefix, p), calc_unix_dirs() - ) - -# a cross-platform way to get user's config directory -def get_config_dir(): - shellvars = ['${APPDATA}', '${HOME}', '${USERPROFILE}'] - dir_root = get_dir_root(shellvars) - - if (dir_root is None) and (os.name == 'nt'): - app_dir = get_shell_dir(shellcon.CSIDL_APPDATA) - if app_dir is not None: - dir_root = app_dir - - if dir_root is None and os.name == 'nt': - tmp_dir_root = os.path.split(sys.executable)[0] - if os.access(tmp_dir_root, os.R_OK|os.W_OK): - dir_root = tmp_dir_root - - return dir_root - -def get_cache_dir(): - dir = None - if os.name == 'nt': - dir = get_shell_dir(shellcon.CSIDL_INTERNET_CACHE) - return dir - -def get_home_dir(): - shellvars = ['${HOME}', '${USERPROFILE}'] - dir_root = get_dir_root(shellvars) - - if (dir_root is None) and (os.name == 'nt'): - dir = get_shell_dir(shellcon.CSIDL_PROFILE) - if dir is None: - # there's no clear best fallback here - # MS discourages you from writing directly in the home dir, - # and sometimes (i.e. win98) there isn't one - dir = get_shell_dir(shellcon.CSIDL_DESKTOPDIRECTORY) - - dir_root = dir - - return dir_root - -def get_temp_dir(): - shellvars = ['${TMP}', '${TEMP}'] - dir_root = get_dir_root(shellvars, default_to_home=False) - - #this method is preferred to the envvars - if os.name == 'nt': - try_dir_root = win32api.GetTempPath() - if try_dir_root is not None: - dir_root = try_dir_root - - if dir_root is None: - try_dir_root = None - if os.name == 'nt': - # this should basically never happen. GetTempPath always returns something - try_dir_root = r'C:\WINDOWS\Temp' - elif os.name == 'posix': - try_dir_root = '/tmp' - if (try_dir_root is not None and - os.path.isdir(try_dir_root) and - os.access(try_dir_root, os.R_OK|os.W_OK)): - dir_root = try_dir_root - return dir_root - -def get_dir_root(shellvars, default_to_home=True): - def check_sysvars(x): - y = os.path.expandvars(x) - if y != x and os.path.isdir(y): - return y - return None - - dir_root = None - for d in shellvars: - dir_root = check_sysvars(d) - if dir_root is not None: - break - else: - if default_to_home: - dir_root = os.path.expanduser('~') - if dir_root == '~' or not os.path.isdir(dir_root): - dir_root = None - return dir_root - -# this function is the preferred way to get windows' paths -def get_shell_dir(value): - dir = None - if os.name == 'nt': - try: - dir = shell.SHGetFolderPath(0, value, 0, 0) - dir = dir.encode('mbcs') - except: - pass - return dir - -def get_startup_dir(): - dir = None - if os.name == 'nt': - dir = get_shell_dir(shellcon.CSIDL_STARTUP) - return dir - -def create_shortcut(source, dest, *args): - if os.name == 'nt': - shell = win32com.client.Dispatch("WScript.Shell") - shortcut = shell.CreateShortCut(dest + ".lnk") - shortcut.Targetpath = source - shortcut.Arguments = ' '.join(args) - path, file = os.path.split(source) - shortcut.WorkingDirectory = path - shortcut.save() - else: - # some other os may not support this, but throwing an error is good since - # the function couldn't do what was requested - os.symlink(source, dest) - # linux also can't do args... maybe we should spit out a shell script? - assert not args; - -def remove_shortcut(dest): - if os.name == 'nt': - dest += ".lnk" - os.unlink(dest) - -def path_wrap(path): - return path - -if os.name == 'nt': - def path_wrap(path): - return path.decode('mbcs').encode('utf-8') - -def btspawn(torrentqueue, cmd, *args): - ext = '' - if is_frozen_exe: - ext = '.exe' - path = os.path.join(app_root, cmd+ext) - if not os.access(path, os.F_OK): - if os.access(path+'.py', os.F_OK): - path = path+'.py' - args = [path] + list(args) # $0 - spawn(torrentqueue, *args) - -def spawn(torrentqueue, *args): - if os.name == 'nt': - # do proper argument quoting since exec/spawn on Windows doesn't - bargs = args - args = [] - for a in bargs: - if not a.startswith("/"): - a.replace('"', '\"') - a = '"%s"' % a - args.append(a) - - argstr = ' '.join(args[1:]) - # use ShellExecute instead of spawn*() because we don't want - # handles (like the controlsocket) to be duplicated - win32api.ShellExecute(0, "open", args[0], argstr, None, 1) # 1 == SW_SHOW - else: - if os.access(args[0], os.X_OK): - forkback = os.fork() - if forkback == 0: - if torrentqueue is not None: - #BUG: should we do this? - #torrentqueue.set_done() - torrentqueue.wrapped.ipc.stop() - os.execl(args[0], *args) - else: - #BUG: what should we do here? - pass - -def _gettext_install(domain, localedir=None, languages=None, unicode=False): - # gettext on win32 does not use locale.getdefaultlocale() by default - # other os's will fall through and gettext.find() will do this task - if os_name == 'nt': - # this code is straight out of gettext.find() - if languages is None: - languages = [] - for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): - val = os.environ.get(envar) - if val: - languages = val.split(':') - break - - # this is the important addition - since win32 does not typically - # have any enironment variable set, append the default locale before 'C' - languages.append(locale.getdefaultlocale()[0]) - - if 'C' not in languages: - languages.append('C') - - # this code is straight out of gettext.install - t = gettext.translation(domain, localedir, languages=languages, fallback=True) - t.install(unicode) - - -def language_path(): - config_dir = get_config_dir() - lang_file_name = os.path.join(config_dir, '.bittorrent', 'data', 'language') - return lang_file_name - - -def read_language_file(): - lang = None - - if os.name == 'nt': - # this pulls user-preference language from the installer location - try: - regko = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\BitTorrent") - lang_num = _winreg.QueryValueEx(regko, "Language")[0] - lang_num = int(lang_num) - lang = locale_sucks[lang_num] - except: - pass - else: - lang_file_name = language_path() - if os.access(lang_file_name, os.F_OK|os.R_OK): - mode = 'r' - if sys.version_info >= (2, 3): - mode = 'U' - lang_file = open(lang_file_name, mode) - lang_line = lang_file.readline() - lang_file.close() - if lang_line: - lang = '' - for i in lang_line[:5]: - if not i.isalpha() and i != '_': - break - lang += i - if lang == '': - lang = None - - return lang - - -def write_language_file(lang): - if os.name == 'nt': - regko = _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, "Software\\BitTorrent") - if lang == '': - _winreg.DeleteValue(regko, "Language") - else: - lcid = None - - # I want two-way dicts - for id, code in locale_sucks.iteritems(): - if code.lower() == lang.lower(): - lcid = id - break - if not lcid: - raise KeyError(lang) - - _winreg.SetValueEx(regko, "Language", 0, _winreg.REG_SZ, str(lcid)) - - else: - lang_file_name = language_path() - lang_file = open(lang_file_name, 'w') - lang_file.write(lang) - lang_file.close() - - -def install_translation(): - languages = None - try: - lang = read_language_file() - if lang is not None: - languages = [lang, ] - except: - #pass - from traceback import print_exc - print_exc() - _gettext_install('bittorrent', locale_root, languages=languages) diff --git a/BitTorrent/prefs.py b/BitTorrent/prefs.py deleted file mode 100644 index 37177c2..0000000 --- a/BitTorrent/prefs.py +++ /dev/null @@ -1,89 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - - -class Preferences(object): - def __init__(self, parent=None): - self._parent = None - self._options = {} - if parent: - self._parent = parent - - def initWithDict(self, dict): - self._options = dict - return self - - def getDict(self): - return dict(self._options) - - def getDifference(self): - if self._parent: - return dict([(x, y) for x, y in self._options.items() if y != self._parent.get(x, None)]) - else: - return dict(self._options) - - def __getitem__(self, option): - if self._options.has_key(option): - return self._options[option] - elif self._parent: - return self._parent[option] - return None - - def __setitem__(self, option, value): - self._options.__setitem__(option, value) - - def __len__(self): - l = len(self._options) - if self._parent: - return l + len(self._parent) - else: - return l - - def __delitem__(self, option): - del(self._options[option]) - - def clear(self): self._options.clear() - - def has_key(self, option): - if self._options.has_key(option): - return True - elif self._parent: - return self._parent.has_key(option) - return False - - def keys(self): - l = self._options.keys() - if self._parent: - l += [key for key in self._parent.keys() if key not in l] - return l - - def values(self): - l = self._options.values() - if self._parent: - l += [value for value in self._parent.values() if value not in l] - return l - - def items(self): - l = self._options.items() - if self._parent: - l += [item for item in self._parent.items() if item not in l] - return l - - def __iter__(self): return self.iterkeys() - def __str__(self): return 'Preferences({%s})' % str(self.items()) - def iteritems(self): return self.items().__iter__() - def iterkeys(self): return self.keys().__iter__() - def itervalues(self): return self.values().__iter__() - def update(self, dict): return self._options.update(dict) - - def get(self, key, failobj=None): - if not self.has_key(key): - return failobj - return self[key] diff --git a/BitTorrent/selectpoll.py b/BitTorrent/selectpoll.py deleted file mode 100644 index d01f1ef..0000000 --- a/BitTorrent/selectpoll.py +++ /dev/null @@ -1,68 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen - -from select import select, error -from time import sleep -from types import IntType -from bisect import bisect -POLLIN = 1 -POLLOUT = 2 -POLLERR = 8 -POLLHUP = 16 - - -class poll(object): - - def __init__(self): - self.rlist = [] - self.wlist = [] - - def register(self, f, t): - if type(f) != IntType: - f = f.fileno() - if (t & POLLIN) != 0: - insert(self.rlist, f) - else: - remove(self.rlist, f) - if (t & POLLOUT) != 0: - insert(self.wlist, f) - else: - remove(self.wlist, f) - - def unregister(self, f): - if type(f) != IntType: - f = f.fileno() - remove(self.rlist, f) - remove(self.wlist, f) - - def poll(self, timeout = None): - if self.rlist != [] or self.wlist != []: - r, w, e = select(self.rlist, self.wlist, [], timeout) - else: - sleep(timeout) - return [] - result = [] - for s in r: - result.append((s, POLLIN)) - for s in w: - result.append((s, POLLOUT)) - return result - -def remove(list, item): - i = bisect(list, item) - if i > 0 and list[i-1] == item: - del list[i-1] - -def insert(list, item): - i = bisect(list, item) - if i == 0 or list[i-1] != item: - list.insert(i, item) diff --git a/BitTorrent/track.py b/BitTorrent/track.py deleted file mode 100644 index fe4788f..0000000 --- a/BitTorrent/track.py +++ /dev/null @@ -1,876 +0,0 @@ -# The contents of this file are subject to the BitTorrent Open Source License -# Version 1.1 (the License). You may not copy or use this file, in either -# source code or executable form, except in compliance with the License. You -# may obtain a copy of the License at http://www.bittorrent.com/license/. -# -# Software distributed under the License is distributed on an AS IS basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. - -# Written by Bram Cohen and John Hoffman - -import sys -import os -import re -from time import time, gmtime, strftime, localtime -from random import shuffle -from types import StringType, IntType, LongType, ListType, DictType - -from twisted.web import server -from twisted.web.resource import Resource -from twisted.internet import reactor -from twisted.python import log - -from BitTorrent.parseargs import parseargs, formatDefinitions -from BitTorrent.parsedir import parsedir -from BitTorrent.NatCheck import NatCheck -from BitTorrent.bencode import bencode, bdecode, Bencached -from BitTorrent.zurllib import quote, unquote -from BitTorrent import version - - -defaults = [ - ('port', 80, - _("Port to listen on.")), - ('dfile', '/tmp/dfile.txt', - _("file to store recent downloader info in")), - ('bind', '', - _("ip to bind to locally")), - ('socket_timeout', 15, - _("timeout for closing connections")), - ('close_with_rst', 0, - _("close connections with RST and avoid the TCP TIME_WAIT state")), - ('save_dfile_interval', 5 * 60, - _("seconds between saving dfile")), - ('timeout_downloaders_interval', 45 * 60, - _("seconds between expiring downloaders")), - ('reannounce_interval', 30 * 60, - _("seconds downloaders should wait between reannouncements")), - ('response_size', 50, - _("default number of peers to send an info message to if the " - "client does not specify a number")), - ('timeout_check_interval', 5, - _("time to wait between checking if any connections have timed out")), - ('nat_check', 3, - _("how many times to check if a downloader is behind a NAT " - "(0 = don't check)")), - ('log_nat_checks', 0, - _("whether to add entries to the log for nat-check results")), - ('min_time_between_log_flushes', 3.0, - _("minimum time it must have been since the last flush to do " - "another one")), - ('min_time_between_cache_refreshes', 600.0, - _("minimum time in seconds before a cache is considered stale " - "and is flushed")), - ('allowed_dir', '', - _("only allow downloads for .torrents in this dir (and recursively in " - "subdirectories of directories that have no .torrent files " - "themselves). If set, torrents in this directory show up on " - "infopage/scrape whether they have peers or not")), - ('parse_dir_interval', 60, - _("how often to rescan the torrent directory, in seconds")), - ('allowed_controls', 0, - _("allow special keys in torrents in the allowed_dir to affect " - "tracker access")), - ('show_infopage', 1, - _("whether to display an info page when the tracker's root dir " - "is loaded")), - ('infopage_redirect', '', - _("a URL to redirect the info page to")), - ('show_names', 1, - _("whether to display names from allowed dir")), - ('favicon', '', - _("file containing x-icon data to return when browser requests " - "favicon.ico")), - ('only_local_override_ip', 2, - _("ignore the ip GET parameter from machines which aren't on " - "local network IPs (0 = never, 1 = always, 2 = ignore if NAT " - "checking is not enabled). HTTP proxy headers giving address " - "of original client are treated the same as --ip.")), - ('allow_get', 0, - _("use with allowed_dir; adds a /file?hash={hash} url that " - "allows users to download the torrent file")), - ('keep_dead', 0, - _("keep dead torrents after they expire (so they still show up on your " - "/scrape and web page). Only matters if allowed_dir is not set")), - ('scrape_allowed', 'full', - _("scrape access allowed (can be none, specific or full)")), - ('max_give', 200, - _("maximum number of peers to give with any one request")), - ] - -def statefiletemplate(x): - if type(x) != DictType: - raise ValueError - for cname, cinfo in x.items(): - if cname == 'peers': - for y in cinfo.values(): # The 'peers' key is a dictionary of SHA hashes (torrent ids) - if type(y) != DictType: # ... for the active torrents, and each is a dictionary - raise ValueError - for peerid, info in y.items(): # ... of client ids interested in that torrent - if (len(peerid) != 20): - raise ValueError - if type(info) != DictType: # ... each of which is also a dictionary - raise ValueError # ... which has an IP, a Port, and a Bytes Left count for that client for that torrent - if type(info.get('ip', '')) != StringType: - raise ValueError - port = info.get('port') - if type(port) not in (IntType, LongType) or port < 0: - raise ValueError - left = info.get('left') - if type(left) not in (IntType, LongType) or left < 0: - raise ValueError - elif cname == 'completed': - if (type(cinfo) != DictType): # The 'completed' key is a dictionary of SHA hashes (torrent ids) - raise ValueError # ... for keeping track of the total completions per torrent - for y in cinfo.values(): # ... each torrent has an integer value - if type(y) not in (IntType,LongType): - raise ValueError # ... for the number of reported completions for that torrent - elif cname == 'allowed': - if (type(cinfo) != DictType): # a list of info_hashes and included data - raise ValueError - if x.has_key('allowed_dir_files'): - adlist = [z[1] for z in x['allowed_dir_files'].values()] - for y in cinfo.keys(): # and each should have a corresponding key here - if not y in adlist: - raise ValueError - elif cname == 'allowed_dir_files': - if (type(cinfo) != DictType): # a list of files, their attributes and info hashes - raise ValueError - dirkeys = {} - for y in cinfo.values(): # each entry should have a corresponding info_hash - if not y[1]: - continue - if not x['allowed'].has_key(y[1]): - raise ValueError - if dirkeys.has_key(y[1]): # and each should have a unique info_hash - raise ValueError - dirkeys[y[1]] = 1 - -alas = _("your file may exist elsewhere in the universe\nbut alas, not here\n") - -def isotime(secs = None): - if secs == None: - secs = time() - return strftime('%Y-%m-%d %H:%M UTC', gmtime(secs)) - -http_via_filter = re.compile(' for ([0-9.]+)\Z') - -def _get_forwarded_ip(request): - header = request.getHeader('X-Forwarded-For') - if header: - try: - x,y = header.split(',') - except: - return header - if not is_local_ip(x): - return x - return y - header = request.getHeader('Client-IP') - if header: - return header - header = request.getHeader('Via') - if header: - x = http_via_filter.search(header) - if x: - return x.group(1) - - header = request.getHeader('From') - if header: - return header - return None - -def get_forwarded_ip(request): - x = _get_forwarded_ip(request) - if x is None or not is_valid_ipv4(x) or is_local_ip(x): - return None - return x - -def compact_peer_info(ip, port): - try: - s = ( ''.join([chr(int(i)) for i in ip.split('.')]) - + chr((port & 0xFF00) >> 8) + chr(port & 0xFF) ) - if len(s) != 6: - s = '' - except: - s = '' # not a valid IP, must be a domain name - return s - -def is_valid_ipv4(ip): - a = ip.split('.') - if len(a) != 4: - return False - try: - for x in a: - chr(int(x)) - return True - except: - return False - -def is_local_ip(ip): - try: - v = [int(x) for x in ip.split('.')] - if v[0] == 10 or v[0] == 127 or v[:2] in ([192, 168], [169, 254]): - return True - if v[0] == 172 and v[1] >= 16 and v[1] <= 31: - return True - except ValueError: - return False - -class Tracker(object): - - def __init__(self): - - config, files = parseargs([], defaults, 0, 0) - self.config = config - self.response_size = config['response_size'] - self.max_give = config['max_give'] - self.dfile = config['dfile'] - self.natcheck = config['nat_check'] - favicon = config['favicon'] - self.favicon = None - if favicon: - try: - h = open(favicon,'r') - self.favicon = h.read() - h.close() - except: - log.msg(_("**warning** specified favicon file -- %s -- does not exist.") % favicon) - self.cached = {} # format: infohash: [[time1, l1, s1], [time2, l2, s2], [time3, l3, s3]] - self.cached_t = {} # format: infohash: [time, cache] - self.times = {} - self.state = {} - self.seedcount = {} - - self.only_local_override_ip = config['only_local_override_ip'] - if self.only_local_override_ip == 2: - self.only_local_override_ip = not config['nat_check'] - - if os.path.exists(self.dfile): - try: - h = open(self.dfile, 'rb') - ds = h.read() - h.close() - tempstate = bdecode(ds) - if not tempstate.has_key('peers'): - tempstate = {'peers': tempstate} - statefiletemplate(tempstate) - self.state = tempstate - except: - log.msg(_("**warning** statefile %s corrupt; resetting") % self.dfile) - self.downloads = self.state.setdefault('peers', {}) - self.completed = self.state.setdefault('completed', {}) - - self.becache = {} # format: infohash: [[l1, s1], [l2, s2], [l3, s3]] - for infohash, ds in self.downloads.items(): - self.seedcount[infohash] = 0 - for x,y in ds.items(): - if not y.get('nat',-1): - ip = y.get('given_ip') - if not (ip and self.allow_local_override(y['ip'], ip)): - ip = y['ip'] - self.natcheckOK(infohash,x,ip,y['port'],y['left']) - if not y['left']: - self.seedcount[infohash] += 1 - - for infohash in self.downloads: - self.times[infohash] = {} - for peerid in self.downloads[infohash]: - self.times[infohash][peerid] = 0 - - self.reannounce_interval = config['reannounce_interval'] - self.save_dfile_interval = config['save_dfile_interval'] - self.show_names = config['show_names'] - reactor.callLater(self.save_dfile_interval, self.save_dfile) - self.prevtime = time() - self.timeout_downloaders_interval = config['timeout_downloaders_interval'] - reactor.callLater(self.timeout_downloaders_interval, self.expire_downloaders) - - self.allow_get = config['allow_get'] - - if config['allowed_dir'] != '': - self.allowed_dir = config['allowed_dir'] - self.parse_dir_interval = config['parse_dir_interval'] - self.allowed = self.state.setdefault('allowed',{}) - self.allowed_dir_files = self.state.setdefault('allowed_dir_files',{}) - self.allowed_dir_blocked = {} - self.parse_allowed() - else: - try: - del self.state['allowed'] - except: - pass - try: - del self.state['allowed_dir_files'] - except: - pass - self.allowed = None - - self.uq_broken = unquote('+') != ' ' - self.keep_dead = config['keep_dead'] - - def allow_local_override(self, ip, given_ip): - return is_valid_ipv4(given_ip) and ( - not self.only_local_override_ip or is_local_ip(ip) ) - - def scrapedata(self, infohash, return_name = True): - l = self.downloads[infohash] - n = self.completed.get(infohash, 0) - c = self.seedcount[infohash] - d = len(l) - c - f = {'complete': c, 'incomplete': d, 'downloaded': n} - if return_name and self.show_names and self.allowed is not None: - f['name'] = self.allowed[infohash]['name'] - return (f) - - def add_data(self, infohash, event, ip, paramslist): - peers = self.downloads.setdefault(infohash, {}) - ts = self.times.setdefault(infohash, {}) - self.completed.setdefault(infohash, 0) - self.seedcount.setdefault(infohash, 0) - - def params(key, default = None, l = paramslist): - if l.has_key(key): - return l[key][0] - return default - - myid = params('peer_id','') - if len(myid) != 20: - raise ValueError, 'id not of length 20' - if event not in ['started', 'completed', 'stopped', 'snooped', None]: - raise ValueError, 'invalid event' - port = int(params('port','')) - if port < 0 or port > 65535: - raise ValueError, 'invalid port' - left = int(params('left','')) - if left < 0: - raise ValueError, 'invalid amount left' - - peer = peers.get(myid) - mykey = params('key') - auth = not peer or peer.get('key', -1) == mykey or peer.get('ip') == ip - - gip = params('ip') - local_override = gip and self.allow_local_override(ip, gip) - if local_override: - ip1 = gip - else: - ip1 = ip - if not auth and local_override and self.only_local_override_ip: - auth = True - - if params('numwant') is not None: - rsize = min(int(params('numwant')), self.max_give) - else: - rsize = self.response_size - - if event == 'stopped': - if peer and auth: - self.delete_peer(infohash,myid) - - elif not peer: - ts[myid] = time() - peer = {'ip': ip, 'port': port, 'left': left} - if mykey: - peer['key'] = mykey - if gip: - peer['given ip'] = gip - if port: - if not self.natcheck or (local_override and self.only_local_override_ip): - peer['nat'] = 0 - self.natcheckOK(infohash,myid,ip1,port,left) - else: - NatCheck(self.connectback_result,infohash,myid,ip1,port) - else: - peer['nat'] = 2**30 - if event == 'completed': - self.completed[infohash] += 1 - if not left: - self.seedcount[infohash] += 1 - - peers[myid] = peer - - else: - if not auth: - return rsize # return w/o changing stats - - ts[myid] = time() - if not left and peer['left']: - self.completed[infohash] += 1 - self.seedcount[infohash] += 1 - if not peer.get('nat', -1): - for bc in self.becache[infohash]: - bc[1][myid] = bc[0][myid] - del bc[0][myid] - if peer['left']: - peer['left'] = left - - recheck = False - if ip != peer['ip']: - peer['ip'] = ip - recheck = True - if gip != peer.get('given ip'): - if gip: - peer['given ip'] = gip - elif peer.has_key('given ip'): - del peer['given ip'] - if local_override: - if self.only_local_override_ip: - self.natcheckOK(infohash,myid,ip1,port,left) - else: - recheck = True - - if port and self.natcheck: - if recheck: - if peer.has_key('nat'): - if not peer['nat']: - l = self.becache[infohash] - y = not peer['left'] - for x in l: - del x[y][myid] - del peer['nat'] # restart NAT testing - else: - natted = peer.get('nat', -1) - if natted and natted < self.natcheck: - recheck = True - - if recheck: - NatCheck(self.connectback_result,infohash,myid,ip1,port) - - return rsize - - def peerlist(self, infohash, stopped, is_seed, return_type, rsize): - data = {} # return data - seeds = self.seedcount[infohash] - data['complete'] = seeds - data['incomplete'] = len(self.downloads[infohash]) - seeds - - if ( self.allowed is not None and self.config['allowed_controls'] and - self.allowed[infohash].has_key('warning message') ): - data['warning message'] = self.allowed[infohash]['warning message'] - - data['interval'] = self.reannounce_interval - if stopped or not rsize: # save some bandwidth - data['peers'] = [] - return data - - bc = self.becache.setdefault(infohash,[[{}, {}], [{}, {}], [{}, {}]]) - len_l = len(bc[0][0]) - len_s = len(bc[0][1]) - if not (len_l+len_s): # caches are empty! - data['peers'] = [] - return data - l_get_size = int(float(rsize)*(len_l)/(len_l+len_s)) - cache = self.cached.setdefault(infohash,[None,None,None])[return_type] - if cache: - if cache[0] + self.config['min_time_between_cache_refreshes'] < time(): - cache = None - else: - if ( (is_seed and len(cache[1]) < rsize) - or len(cache[1]) < l_get_size or not cache[1] ): - cache = None - if not cache: - vv = [[],[],[]] - cache = [ time(), - bc[return_type][0].values()+vv[return_type], - bc[return_type][1].values() ] - shuffle(cache[1]) - shuffle(cache[2]) - self.cached[infohash][return_type] = cache - for rr in xrange(len(self.cached[infohash])): - if rr != return_type: - try: - self.cached[infohash][rr][1].extend(vv[rr]) - except: - pass - if len(cache[1]) < l_get_size: - peerdata = cache[1] - if not is_seed: - peerdata.extend(cache[2]) - cache[1] = [] - cache[2] = [] - else: - if not is_seed: - peerdata = cache[2][l_get_size-rsize:] - del cache[2][l_get_size-rsize:] - rsize -= len(peerdata) - else: - peerdata = [] - if rsize: - peerdata.extend(cache[1][-rsize:]) - del cache[1][-rsize:] - if return_type == 2: - peerdata = ''.join(peerdata) - data['peers'] = peerdata - return data - - - def natcheckOK(self, infohash, peerid, ip, port, not_seed): - bc = self.becache.setdefault(infohash,[[{}, {}], [{}, {}], [{}, {}]]) - bc[0][not not_seed][peerid] = Bencached(bencode({'ip': ip, 'port': port, - 'peer id': peerid})) - bc[1][not not_seed][peerid] = Bencached(bencode({'ip': ip, 'port': port})) - bc[2][not not_seed][peerid] = compact_peer_info(ip, port) - - def natchecklog(self, peerid, ip, port, result): - year, month, day, hour, minute, second, a, b, c = localtime(time()) - log.msg('%s - %s [%02d/%3s/%04d:%02d:%02d:%02d] "!natcheck-%s:%i" %i 0 - -' % ( - ip, quote(peerid), day, months[month], year, hour, minute, second, - ip, port, result)) - - def connectback_result(self, result, downloadid, peerid, ip, port): - record = self.downloads.get(downloadid, {}).get(peerid) - if ( record is None - or (record['ip'] != ip and record.get('given ip') != ip) - or record['port'] != port ): - if self.config['log_nat_checks']: - self.natchecklog(peerid, ip, port, 404) - return - if self.config['log_nat_checks']: - if result: - x = 200 - else: - x = 503 - self.natchecklog(peerid, ip, port, x) - if not record.has_key('nat'): - record['nat'] = int(not result) - if result: - self.natcheckOK(downloadid,peerid,ip,port,record['left']) - elif result and record['nat']: - record['nat'] = 0 - self.natcheckOK(downloadid,peerid,ip,port,record['left']) - elif not result: - record['nat'] += 1 - - def save_dfile(self): - # need to arrange for this to be called just before shutdown - log.msg('save_dfile') - reactor.callLater(self.save_dfile_interval, self.save_dfile) - h = open(self.dfile, 'wb') - h.write(bencode(self.state)) - h.close() - - def parse_allowed(self): - log.msg('parse_allowed') - reactor.callLater(self.parse_dir_interval, self.parse_allowed) - - # logging broken .torrent files would be useful but could confuse - # programs parsing log files, so errors are just ignored for now - def ignore(message): - pass - r = parsedir(self.allowed_dir, self.allowed, self.allowed_dir_files, - self.allowed_dir_blocked, ignore,include_metainfo = False) - ( self.allowed, self.allowed_dir_files, self.allowed_dir_blocked, - added, garbage2 ) = r - - for infohash in added: - self.downloads.setdefault(infohash, {}) - self.completed.setdefault(infohash, 0) - self.seedcount.setdefault(infohash, 0) - - self.state['allowed'] = self.allowed - self.state['allowed_dir_files'] = self.allowed_dir_files - - def delete_peer(self, infohash, peerid): - dls = self.downloads[infohash] - peer = dls[peerid] - if not peer['left']: - self.seedcount[infohash] -= 1 - if not peer.get('nat',-1): - l = self.becache[infohash] - y = not peer['left'] - for x in l: - del x[y][peerid] - del self.times[infohash][peerid] - del dls[peerid] - - def expire_downloaders(self): - log.msg('expire_downloaders') - reactor.callLater(self.timeout_downloaders_interval, self.expire_downloaders) - for infohash, peertimes in self.times.items(): - for myid, t in peertimes.items(): - if t < self.prevtime: - self.delete_peer(infohash, myid) - self.prevtime = time() - if (self.keep_dead != 1): - for key, peers in self.downloads.items(): - if len(peers) == 0 and (self.allowed is None or - key not in self.allowed): - del self.times[key] - del self.downloads[key] - del self.seedcount[key] - -class InfoPage(Resource): - def __init__(self, tracker): - Resource.__init__(self) - self.tracker = tracker - - def getChild(self, name, request): - if name in ['', 'index.html', 'index.htm']: - return self - return Resource.getChild(self, name, request) - - def render_GET(self, request): - try: - if not self.tracker.config['show_infopage']: - request.setResponseCode(404, 'Not Found') - request.setHeader('Content-Type', 'text/plain') - request.setHeader('Pragma', 'no-cache') - return alas - - red = self.tracker.config['infopage_redirect'] - if red != '': - request.redirect(red) - request.finish() - return server.NOT_DONE_YET - - request.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' \ - '<html><head><title>BitTorrent download info</title>\n') - if self.tracker.favicon is not None: - request.write('<link rel="shortcut icon" href="/favicon.ico">\n') - request.write('</head>\n<body>\n' \ - '<h3>BitTorrent download info</h3>\n'\ - '<ul>\n' - '<li><strong>tracker version:</strong> %s</li>\n' \ - '<li><strong>server time:</strong> %s</li>\n' \ - '</ul>\n' % (version, isotime())) - if self.tracker.allowed is not None: - if self.tracker.show_names: - names = [ (value['name'], infohash) - for infohash, value in self.tracker.allowed.iteritems()] - else: - names = [(None, infohash) for infohash in self.tracker.allowed] - else: - names = [ (None, infohash) for infohash in self.tracker.downloads] - if not names: - request.write('<p>not tracking any files yet...</p>\n') - else: - names.sort() - tn = 0 - tc = 0 - td = 0 - tt = 0 # Total transferred - ts = 0 # Total size - nf = 0 # Number of files displayed - if self.tracker.allowed is not None and self.tracker.show_names: - request.write('<table summary="files" border="1">\n' \ - '<tr><th>info hash</th><th>torrent name</th><th align="right">size</th><th align="right">complete</th><th align="right">downloading</th><th align="right">downloaded</th><th align="right">transferred</th></tr>\n') - else: - request.write('<table summary="files">\n' \ - '<tr><th>info hash</th><th align="right">complete</th><th align="right">downloading</th><th align="right">downloaded</th></tr>\n') - for name, infohash in names: - l = self.tracker.downloads[infohash] - n = self.tracker.completed.get(infohash, 0) - tn = tn + n - c = self.tracker.seedcount[infohash] - tc = tc + c - d = len(l) - c - td = td + d - nf = nf + 1 - if self.tracker.allowed is not None and self.tracker.show_names: - if self.tracker.allowed.has_key(infohash): - sz = self.tracker.allowed[infohash]['length'] # size - ts = ts + sz - szt = sz * n # Transferred for this torrent - tt = tt + szt - if self.tracker.allow_get == 1: - linkname = '<a href="/file?info_hash=' + quote(infohash) + '">' + name + '</a>' - else: - linkname = name - request.write('<tr><td><code>%s</code></td><td>%s</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td><td align="right">%s</td></tr>\n' \ - % (infohash.encode('hex'), linkname, size_format(sz), c, d, n, size_format(szt))) - else: - request.write('<tr><td><code>%s</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td></tr>\n' \ - % (infohash.encode('hex'), c, d, n)) - ttn = 0 - for i in self.tracker.completed.values(): - ttn = ttn + i - if self.tracker.allowed is not None and self.tracker.show_names: - request.write('<tr><td align="right" colspan="2">%i files</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i/%i</td><td align="right">%s</td></tr>\n' - % (nf, size_format(ts), tc, td, tn, ttn, size_format(tt))) - else: - request.write('<tr><td align="right">%i files</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i/%i</td></tr>\n' - % (nf, tc, td, tn, ttn)) - request.write('</table>\n' \ - '<ul>\n' \ - '<li><em>info hash:</em> SHA1 hash of the "info" section of the metainfo (*.torrent)</li>\n' \ - '<li><em>complete:</em> number of connected clients with the complete file</li>\n' \ - '<li><em>downloading:</em> number of connected clients still downloading</li>\n' \ - '<li><em>downloaded:</em> reported complete downloads (total: current/all)</li>\n' \ - '<li><em>transferred:</em> torrent size * total downloaded (does not include partial transfers)</li>\n' \ - '</ul>\n') - - request.write('</body>\n' \ - '</html>\n') - request.finish() - return server.NOT_DONE_YET - - except: - request.setResponseCode(500, 'Internal Server Error') - log.err() - return 'Server Error' - -class Scrape(Resource): - isLeaf = True - - def __init__(self, tracker): - Resource.__init__(self) - self.tracker = tracker - - def render_GET(self, request): - fs = {} - if request.args.has_key('info_hash'): - if self.tracker.config['scrape_allowed'] not in ['specific', 'full']: - request.setResponseCode(400, 'Not Authorized') - request.setHeader('Content-Type', 'text/plain') - request.setHeader('Pragma', 'no-cache') - return bencode({'failure reason': - _("specific scrape function is not available with this tracker.")}) - for infohash in request.args['info_hash']: - if self.tracker.allowed is not None and infohash not in self.tracker.allowed: - continue - if infohash in self.tracker.downloads: - fs[infohash] = self.tracker.scrapedata(infohash) - else: - if self.tracker.config['scrape_allowed'] != 'full': - request.setResponseCode(400, 'Not Authorized') - request.setHeader('Content-Type', 'text/plain') - request.setHeader('Pragma', 'no-cache') - return bencode({'failure reason': - _("full scrape function is not available with this tracker.")}) - if self.tracker.allowed is not None: - hashes = self.tracker.allowed - else: - hashes = self.tracker.downloads - for infohash in hashes: - fs[infohash] = self.tracker.scrapedata(infohash) - - request.setHeader('Content-Type', 'text/plain') - return bencode({'files': fs}) - -class File(Resource): - isLeaf = True - - def __init__(self, tracker): - Resource.__init__(self) - self.tracker = tracker - - def render_GET(self, request): - if not self.tracker.allow_get: - request.setResponseCode(400, 'Not Authorized') - request.setHeader('Content-Type', 'text/plain') - request.setHeader('Pragma', 'no-cache') - return _("get function is not available with this tracker.") - - infohash = None - if request.args.has_key('info_hash'): - infohash = request.args['info_hash'][0] - - if not self.tracker.allowed.has_key(infohash): - request.setResponseCode(400, 'Not Authorized') - request.setHeader('Content-Type', 'text/plain') - request.setHeader('Pragma', 'no-cache') - return alas - - fname = self.tracker.allowed[infohash]['file'] - fpath = self.tracker.allowed[infohash]['path'] - - request.setHeader('Content-Type', 'application/x-bittorrent') - reuqest.setHeader('Content-Disposition', 'attachment; filename=' + fname) - return open(fpath, 'rb').read() - -class Announce(Resource): - isLeaf = True - - def __init__(self, tracker): - Resource.__init__(self) - self.tracker = tracker - - def render_GET(self, request): - ip = request.getClientIP() - - nip = get_forwarded_ip(request) - if nip and not self.tracker.only_local_override_ip: - ip = nip - - infohash = request.args.get('info_hash', [None])[0] - - if infohash is None: - request.setResponseCode(400, 'Bad Request') - request.setHeader('Content-Type', 'text/plain') - request.setHeader('Pragma', 'no-cache') - return 'info_hash not specified' - - if self.tracker.allowed is not None: - if not self.tracker.allowed.has_key(infohash): - # is 200 really right? - request.setResponseCode(200, 'Not Authorized') - request.setHeader('Content-Type', 'text/plain') - request.setHeader('Pragma', 'no-cache') - return bencode({'failure reason': - _("Requested download is not authorized for use with this tracker.")}) - - if self.tracker.config['allowed_controls']: - if self.tracker.allowed[infohash].has_key('failure reason'): - # is 200 really right? - request.setResponseCode(200, 'Not Authorized') - request.setHeader('Content-Type', 'text/plain') - request.setHeader('Pragma', 'no-cache') - return bencode({'failure reason': self.tracker.allowed[infohash]['failure reason']}) - - event = request.args.get('event', [None])[0] - - rsize = self.tracker.add_data(infohash, event, ip, request.args) - - compact = request.args.get('compact', [None])[0] - - no_peer_id = request.args.get('no_peer_id', [None])[0] - - if compact: - return_type = 2 - elif no_peer_id: - return_type = 1 - else: - return_type = 0 - - left = request.args.get('left', [None])[0] - - data = self.tracker.peerlist(infohash, event == 'stopped', not left, return_type, rsize) - - if request.args.has_key('scrape'): - data['scrape'] = self.tracker.scrapedata(infohash, False) - - request.setHeader('Content-Type', 'text/plain') - request.setHeader('Pragma', 'no-cache') - return bencode(data) - -class FavIcon(Resource): - isLeaf = True - - def __init__(self, tracker): - Resource.__init__(self) - self.tracker = tracker - - def render_GET(self, request): - if self.tracker.favicon is None: - request.setResponseCode(404, 'Not Found') - request.setHeader('Content-Type', 'text/plain') - request.setHeader('Pragma', 'no-cache') - return 'Not Found!' - - request.setHeader('Content-Type', 'image/x-icon') - return self.tracker.favicon - -def size_format(s): - if (s < 1024): - r = str(s) + 'B' - elif (s < 1048576): - r = str(int(s/1024)) + 'KiB' - elif (s < 1073741824): - r = str(int(s/1048576)) + 'MiB' - elif (s < 1099511627776): - r = str(int((s/1073741824.0)*100.0)/100.0) + 'GiB' - else: - r = str(int((s/1099511627776.0)*100.0)/100.0) + 'TiB' - return(r) diff --git a/BitTorrent/zurllib.py b/BitTorrent/zurllib.py deleted file mode 100644 index 8bbe5ba..0000000 --- a/BitTorrent/zurllib.py +++ /dev/null @@ -1,269 +0,0 @@ -# -# zurllib.py -# -# This is (hopefully) a drop-in for urllib which will request gzip/deflate -# compression and then decompress the output if a compressed response is -# received while maintaining the API. -# -# by Robert Stone 2/22/2003 -# extended by Matt Chisholm -# tracker announce --bind support added by Jeremy Evans 11/2005 - -import sys - -import threading -import thread -from BitTorrent import PeerID -user_agent = PeerID.make_id() -del PeerID - -import urllib2 -OldOpenerDirector = urllib2.OpenerDirector - -class MyOpenerDirector(OldOpenerDirector): - def __init__(self): - OldOpenerDirector.__init__(self) - self.addheaders = [('User-agent', user_agent)] - -urllib2.OpenerDirector = MyOpenerDirector - -del urllib2 - -from httplib import HTTPConnection, HTTP -from urllib import * -from urllib2 import * -from gzip import GzipFile -from StringIO import StringIO -import pprint - -DEBUG=0 - -http_bindaddr = None - -# ow ow ow. -# this is here so we can track open http connections in our pending -# connection count. we have to buffer because maybe urllib connections -# start before rawserver does - hopefully not more than 10 of them! -# -# this can all go away when we use a reasonable http client library -# and the connections are managed inside rawserver -class PreRawServerBuffer(object): - def __init__(self): - self.pending_sockets = {} - self.pending_sockets_lock = threading.Lock() - - def _add_pending_connection(self, addr): - # the XP connection rate limiting is unique at the IP level - assert isinstance(addr, str) - self.pending_sockets_lock.acquire() - self.__add_pending_connection(addr) - self.pending_sockets_lock.release() - - def __add_pending_connection(self, addr): - if addr not in self.pending_sockets: - self.pending_sockets[addr] = 1 - else: - self.pending_sockets[addr] += 1 - - def _remove_pending_connection(self, addr): - self.pending_sockets_lock.acquire() - self.__remove_pending_connection(addr) - self.pending_sockets_lock.release() - - def __remove_pending_connection(self, addr): - self.pending_sockets[addr] -= 1 - if self.pending_sockets[addr] <= 0: - del self.pending_sockets[addr] -rawserver = PreRawServerBuffer() - -def bind_tracker_connection(bindaddr): - global http_bindaddr - http_bindaddr = bindaddr - -def set_zurllib_rawserver(new_rawserver): - global rawserver - for addr in rawserver.pending_sockets: - new_rawserver._add_pending_connections(addr) - rawserver._remove_pending_connection(addr) - assert len(rawserver.pending_sockets) == 0 - rawserver = new_rawserver - -unsafe_threads = [] -def add_unsafe_thread(): - global unsafe_threads - unsafe_threads.append(thread.get_ident()) - -class BindingHTTPConnection(HTTPConnection): - def connect(self): - - ident = thread.get_ident() - # never, ever, ever call urlopen from any of these threads - assert ident not in unsafe_threads - - """Connect to the host and port specified in __init__.""" - msg = "getaddrinfo returns an empty list" - for res in socket.getaddrinfo(self.host, self.port, 0, - socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - - addr = sa[0] - # the obvious multithreading problem is avoided by using locks. - # the lock is only acquired during the function call, so there's - # no danger of urllib blocking rawserver. - rawserver._add_pending_connection(addr) - try: - self.sock = socket.socket(af, socktype, proto) - if http_bindaddr: - self.sock.bind((http_bindaddr, 0)) - if self.debuglevel > 0: - print "connect: (%s, %s)" % (self.host, self.port) - self.sock.connect(sa) - except socket.error, msg: - if self.debuglevel > 0: - print 'connect fail:', (self.host, self.port) - if self.sock: - self.sock.close() - self.sock = None - rawserver._remove_pending_connection(addr) - - if self.sock: - break - - if not self.sock: - raise socket.error, msg - -class BindingHTTP(HTTP): - _connection_class = BindingHTTPConnection - -if sys.version_info >= (2,4): - BindingHTTP = BindingHTTPConnection - -class HTTPContentEncodingHandler(HTTPHandler): - """Inherit and add gzip/deflate/etc support to HTTP gets.""" - def http_open(self, req): - # add the Accept-Encoding header to the request - # support gzip encoding (identity is assumed) - req.add_header("Accept-Encoding","gzip") - if DEBUG: - print "Sending:" - print req.headers - print "\n" - fp = self.do_open(BindingHTTP, req) - headers = fp.headers - if DEBUG: - pprint.pprint(headers.dict) - url = fp.url - resp = addinfourldecompress(fp, headers, url) - if hasattr(fp, 'code'): - resp.code = fp.code - if hasattr(fp, 'msg'): - resp.msg = fp.msg - return resp - -class addinfourldecompress(addinfourl): - """Do gzip decompression if necessary. Do addinfourl stuff too.""" - def __init__(self, fp, headers, url): - # we need to do something more sophisticated here to deal with - # multiple values? What about other weird crap like q-values? - # basically this only works for the most simplistic case and will - # break in some other cases, but for now we only care about making - # this work with the BT tracker so.... - if headers.has_key('content-encoding') and headers['content-encoding'] == 'gzip': - if DEBUG: - print "Contents of Content-encoding: " + headers['Content-encoding'] + "\n" - self.gzip = 1 - self.rawfp = fp - fp = GzipStream(fp) - else: - self.gzip = 0 - return addinfourl.__init__(self, fp, headers, url) - - def close(self): - self.fp.close() - if self.gzip: - self.rawfp.close() - - def iscompressed(self): - return self.gzip - -class GzipStream(StringIO): - """Magically decompress a file object. - - This is not the most efficient way to do this but GzipFile() wants - to seek, etc, which won't work for a stream such as that from a socket. - So we copy the whole shebang info a StringIO object, decompress that - then let people access the decompressed output as a StringIO object. - - The disadvantage is memory use and the advantage is random access. - - Will mess with fixing this later. - """ - - def __init__(self,fp): - self.fp = fp - - # this is nasty and needs to be fixed at some point - # copy everything into a StringIO (compressed) - compressed = StringIO() - r = fp.read() - while r: - compressed.write(r) - r = fp.read() - # now, unzip (gz) the StringIO to a string - compressed.seek(0,0) - gz = GzipFile(fileobj = compressed) - str = '' - r = gz.read() - while r: - str += r - r = gz.read() - # close our utility files - compressed.close() - gz.close() - # init our stringio selves with the string - StringIO.__init__(self, str) - del str - - def close(self): - self.fp.close() - return StringIO.close(self) - - -def test(): - """Test this module. - - At the moment this is lame. - """ - - print "Running unit tests.\n" - - def printcomp(fp): - try: - if fp.iscompressed(): - print "GET was compressed.\n" - else: - print "GET was uncompressed.\n" - except: - print "no iscompressed function! this shouldn't happen" - - print "Trying to GET a compressed document...\n" - fp = urlopen('http://a.scarywater.net/hng/index.shtml') - print fp.read() - printcomp(fp) - fp.close() - - print "Trying to GET an unknown document...\n" - fp = urlopen('http://www.otaku.org/') - print fp.read() - printcomp(fp) - fp.close() - - -# -# Install the HTTPContentEncodingHandler that we've defined above. -# -install_opener(build_opener(HTTPContentEncodingHandler, ProxyHandler({}))) - -if __name__ == '__main__': - test() - |