BitTorrent-3.4.2/0042755000202400020240000000000010034142504012225 5ustar brambramBitTorrent-3.4.2/BitTorrent/0042755000202400020240000000000010034142504014321 5ustar brambramBitTorrent-3.4.2/BitTorrent/.cvsignore0100644000202400020240000000000607603763202016324 0ustar brambram*.pyc BitTorrent-3.4.2/BitTorrent/Choker.py0100644000202400020240000002334307730671243016126 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from random import randrange class Choker: def __init__(self, max_uploads, schedule, done = lambda: False, min_uploads = None): self.max_uploads = max_uploads if min_uploads is None: min_uploads = max_uploads self.min_uploads = min_uploads self.schedule = schedule self.connections = [] self.count = 0 self.done = done schedule(self._round_robin, 10) def _round_robin(self): self.schedule(self._round_robin, 10) self.count += 1 if self.count % 3 == 0: for i in xrange(len(self.connections)): u = self.connections[i].get_upload() if u.is_choked() and u.is_interested(): self.connections = self.connections[i:] + self.connections[:i] break self._rechoke() def _snubbed(self, c): if self.done(): return False return c.get_download().is_snubbed() def _rate(self, c): if self.done(): return c.get_upload().get_rate() else: return c.get_download().get_rate() def _rechoke(self): preferred = [] for c in self.connections: if not self._snubbed(c) and c.get_upload().is_interested(): preferred.append((-self._rate(c), c)) preferred.sort() del preferred[self.max_uploads - 1:] preferred = [x[1] for x in preferred] count = len(preferred) hit = False for c in self.connections: u = c.get_upload() if c in preferred: u.unchoke() else: if count < self.min_uploads or not hit: u.unchoke() if u.is_interested(): count += 1 hit = True else: u.choke() def connection_made(self, connection, p = None): if p is None: p = randrange(-2, len(self.connections) + 1) self.connections.insert(max(p, 0), connection) self._rechoke() def connection_lost(self, connection): self.connections.remove(connection) if connection.get_upload().is_interested() and not connection.get_upload().is_choked(): self._rechoke() def interested(self, connection): if not connection.get_upload().is_choked(): self._rechoke() def not_interested(self, connection): if not connection.get_upload().is_choked(): self._rechoke() def change_max_uploads(self, newval): def foo(self=self, newval=newval): self._change_max_uploads(newval) self.schedule(foo, 0); def _change_max_uploads(self, newval): self.max_uploads = newval self._rechoke() class DummyScheduler: def __init__(self): self.s = [] def __call__(self, func, delay): self.s.append((func, delay)) class DummyConnection: def __init__(self, v = 0): self.u = DummyUploader() self.d = DummyDownloader(self) self.v = v def get_upload(self): return self.u def get_download(self): return self.d class DummyDownloader: def __init__(self, c): self.s = False self.c = c def is_snubbed(self): return self.s def get_rate(self): return self.c.v class DummyUploader: def __init__(self): self.i = False self.c = True def choke(self): if not self.c: self.c = True def unchoke(self): if self.c: self.c = False def is_choked(self): return self.c def is_interested(self): return self.i def test_round_robin_with_no_downloads(): s = DummyScheduler() Choker(2, s) assert len(s.s) == 1 assert s.s[0][1] == 10 s.s[0][0]() del s.s[0] assert len(s.s) == 1 assert s.s[0][1] == 10 s.s[0][0]() del s.s[0] s.s[0][0]() del s.s[0] s.s[0][0]() del s.s[0] def test_resort(): s = DummyScheduler() choker = Choker(1, s) c1 = DummyConnection() c2 = DummyConnection(1) c3 = DummyConnection(2) c4 = DummyConnection(3) c2.u.i = True c3.u.i = True choker.connection_made(c1) assert not c1.u.c choker.connection_made(c2, 1) assert not c1.u.c assert not c2.u.c choker.connection_made(c3, 1) assert not c1.u.c assert c2.u.c assert not c3.u.c c2.v = 2 c3.v = 1 choker.connection_made(c4, 1) assert not c1.u.c assert c2.u.c assert not c3.u.c assert not c4.u.c choker.connection_lost(c4) assert not c1.u.c assert c2.u.c assert not c3.u.c s.s[0][0]() assert not c1.u.c assert c2.u.c assert not c3.u.c def test_interest(): s = DummyScheduler() choker = Choker(1, s) c1 = DummyConnection() c2 = DummyConnection(1) c3 = DummyConnection(2) c2.u.i = True c3.u.i = True choker.connection_made(c1) assert not c1.u.c choker.connection_made(c2, 1) assert not c1.u.c assert not c2.u.c choker.connection_made(c3, 1) assert not c1.u.c assert c2.u.c assert not c3.u.c c3.u.i = False choker.not_interested(c3) assert not c1.u.c assert not c2.u.c assert not c3.u.c c3.u.i = True choker.interested(c3) assert not c1.u.c assert c2.u.c assert not c3.u.c choker.connection_lost(c3) assert not c1.u.c assert not c2.u.c def test_robin_interest(): s = DummyScheduler() choker = Choker(1, s) c1 = DummyConnection(0) c2 = DummyConnection(1) c1.u.i = True choker.connection_made(c2) assert not c2.u.c choker.connection_made(c1, 0) assert not c1.u.c assert c2.u.c c1.u.i = False choker.not_interested(c1) assert not c1.u.c assert not c2.u.c c1.u.i = True choker.interested(c1) assert not c1.u.c assert c2.u.c choker.connection_lost(c1) assert not c2.u.c def test_skip_not_interested(): s = DummyScheduler() choker = Choker(1, s) c1 = DummyConnection(0) c2 = DummyConnection(1) c3 = DummyConnection(2) c1.u.i = True c3.u.i = True choker.connection_made(c2) assert not c2.u.c choker.connection_made(c1, 0) assert not c1.u.c assert c2.u.c choker.connection_made(c3, 2) assert not c1.u.c assert c2.u.c assert c3.u.c f = s.s[0][0] f() assert not c1.u.c assert c2.u.c assert c3.u.c f() assert not c1.u.c assert c2.u.c assert c3.u.c f() assert c1.u.c assert c2.u.c assert not c3.u.c def test_connection_lost_no_interrupt(): s = DummyScheduler() choker = Choker(1, s) c1 = DummyConnection(0) c2 = DummyConnection(1) c3 = DummyConnection(2) c1.u.i = True c2.u.i = True c3.u.i = True choker.connection_made(c1) choker.connection_made(c2, 1) choker.connection_made(c3, 2) f = s.s[0][0] f() assert not c1.u.c assert c2.u.c assert c3.u.c f() assert not c1.u.c assert c2.u.c assert c3.u.c f() assert c1.u.c assert not c2.u.c assert c3.u.c f() assert c1.u.c assert not c2.u.c assert c3.u.c f() assert c1.u.c assert not c2.u.c assert c3.u.c choker.connection_lost(c3) assert c1.u.c assert not c2.u.c f() assert not c1.u.c assert c2.u.c choker.connection_lost(c2) assert not c1.u.c def test_connection_made_no_interrupt(): s = DummyScheduler() choker = Choker(1, s) c1 = DummyConnection(0) c2 = DummyConnection(1) c3 = DummyConnection(2) c1.u.i = True c2.u.i = True c3.u.i = True choker.connection_made(c1) choker.connection_made(c2, 1) f = s.s[0][0] assert not c1.u.c assert c2.u.c f() assert not c1.u.c assert c2.u.c f() assert not c1.u.c assert c2.u.c choker.connection_made(c3, 1) assert not c1.u.c assert c2.u.c assert c3.u.c f() assert c1.u.c assert c2.u.c assert not c3.u.c def test_round_robin(): s = DummyScheduler() choker = Choker(1, s) c1 = DummyConnection(0) c2 = DummyConnection(1) c1.u.i = True c2.u.i = True choker.connection_made(c1) choker.connection_made(c2, 1) f = s.s[0][0] assert not c1.u.c assert c2.u.c f() assert not c1.u.c assert c2.u.c f() assert not c1.u.c assert c2.u.c f() assert c1.u.c assert not c2.u.c f() assert c1.u.c assert not c2.u.c f() assert c1.u.c assert not c2.u.c f() assert not c1.u.c assert c2.u.c def test_multi(): s = DummyScheduler() choker = Choker(4, s) c1 = DummyConnection(0) c2 = DummyConnection(0) c3 = DummyConnection(0) c4 = DummyConnection(8) c5 = DummyConnection(0) c6 = DummyConnection(0) c7 = DummyConnection(6) c8 = DummyConnection(0) c9 = DummyConnection(9) c10 = DummyConnection(7) c11 = DummyConnection(10) choker.connection_made(c1, 0) choker.connection_made(c2, 1) choker.connection_made(c3, 2) choker.connection_made(c4, 3) choker.connection_made(c5, 4) choker.connection_made(c6, 5) choker.connection_made(c7, 6) choker.connection_made(c8, 7) choker.connection_made(c9, 8) choker.connection_made(c10, 9) choker.connection_made(c11, 10) c2.u.i = True c4.u.i = True c6.u.i = True c8.u.i = True c10.u.i = True c2.d.s = True c6.d.s = True c8.d.s = True s.s[0][0]() assert not c1.u.c assert not c2.u.c assert not c3.u.c assert not c4.u.c assert not c5.u.c assert not c6.u.c assert c7.u.c assert c8.u.c assert c9.u.c assert not c10.u.c assert c11.u.c BitTorrent-3.4.2/BitTorrent/Connecter.py0100644000202400020240000002506210032204220016603 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from bitfield import Bitfield from binascii import b2a_hex from CurrentRateMeasure import Measure def toint(s): return long(b2a_hex(s), 16) def tobinary(i): return (chr(i >> 24) + chr((i >> 16) & 0xFF) + chr((i >> 8) & 0xFF) + chr(i & 0xFF)) 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) class Connection: def __init__(self, connection, connecter): self.connection = connection self.connecter = connecter self.got_anything = False def get_ip(self): return self.connection.get_ip() def get_id(self): return self.connection.get_id() def close(self): self.connection.close() def is_flushed(self): if self.connecter.rate_capped: return False return self.connection.is_flushed() def is_locally_initiated(self): return self.connection.is_locally_initiated() def send_interested(self): self.connection.send_message(INTERESTED) def send_not_interested(self): self.connection.send_message(NOT_INTERESTED) def send_choke(self): self.connection.send_message(CHOKE) def send_unchoke(self): self.connection.send_message(UNCHOKE) def send_request(self, index, begin, length): self.connection.send_message(REQUEST + tobinary(index) + tobinary(begin) + tobinary(length)) def send_cancel(self, index, begin, length): self.connection.send_message(CANCEL + tobinary(index) + tobinary(begin) + tobinary(length)) def send_piece(self, index, begin, piece): assert not self.connecter.rate_capped self.connecter._update_upload_rate(len(piece)) self.connection.send_message(PIECE + tobinary(index) + tobinary(begin) + piece) def send_bitfield(self, bitfield): self.connection.send_message(BITFIELD + bitfield) def send_have(self, index): self.connection.send_message(HAVE + tobinary(index)) def get_upload(self): return self.upload def get_download(self): return self.download class Connecter: def __init__(self, make_upload, downloader, choker, numpieces, totalup, max_upload_rate = 0, sched = None): self.downloader = downloader self.make_upload = make_upload self.choker = choker self.numpieces = numpieces self.max_upload_rate = max_upload_rate self.sched = sched self.totalup = totalup self.rate_capped = False self.connections = {} def _update_upload_rate(self, amount): self.totalup.update_rate(amount) if self.max_upload_rate > 0 and self.totalup.get_rate_noupdate() > self.max_upload_rate: self.rate_capped = True self.sched(self._uncap, self.totalup.time_until_rate(self.max_upload_rate)) def _uncap(self): self.rate_capped = False while not self.rate_capped: up = None minrate = None for i in self.connections.values(): if not i.upload.is_choked() and i.upload.has_queries() and i.connection.is_flushed(): rate = i.upload.get_rate() if up is None or rate < minrate: up = i.upload minrate = rate if up is None: break up.flushed() if self.totalup.get_rate_noupdate() > self.max_upload_rate: break def change_max_upload_rate(self, newval): def foo(self=self, newval=newval): self._change_max_upload_rate(newval) self.sched(foo, 0); def _change_max_upload_rate(self, newval): self.max_upload_rate = newval self._uncap() def how_many_connections(self): return len(self.connections) def connection_made(self, connection): c = Connection(connection, self) self.connections[connection] = c c.upload = self.make_upload(c) c.download = self.downloader.make_download(c) self.choker.connection_made(c) def connection_lost(self, connection): c = self.connections[connection] d = c.download del self.connections[connection] d.disconnected() self.choker.connection_lost(c) def connection_flushed(self, connection): self.connections[connection].upload.flushed() def got_message(self, connection, message): c = self.connections[connection] t = message[0] if t == BITFIELD and c.got_anything: connection.close() return c.got_anything = True if (t in [CHOKE, UNCHOKE, INTERESTED, NOT_INTERESTED] and len(message) != 1): connection.close() return if t == CHOKE: c.download.got_choke() elif t == UNCHOKE: c.download.got_unchoke() elif t == INTERESTED: c.upload.got_interested() elif t == NOT_INTERESTED: c.upload.got_not_interested() elif t == HAVE: if len(message) != 5: connection.close() return i = toint(message[1:]) if i >= self.numpieces: connection.close() return c.download.got_have(i) elif t == BITFIELD: try: b = Bitfield(self.numpieces, message[1:]) except ValueError: connection.close() return c.download.got_have_bitfield(b) elif t == REQUEST: if len(message) != 13: connection.close() return i = toint(message[1:5]) if i >= self.numpieces: connection.close() return c.upload.got_request(i, toint(message[5:9]), toint(message[9:])) elif t == CANCEL: if len(message) != 13: connection.close() return i = toint(message[1:5]) if i >= self.numpieces: connection.close() return c.upload.got_cancel(i, toint(message[5:9]), toint(message[9:])) elif t == PIECE: if len(message) <= 9: connection.close() return i = toint(message[1:5]) if i >= self.numpieces: connection.close() return if c.download.got_piece(i, toint(message[5:9]), message[9:]): for co in self.connections.values(): co.send_have(i) else: connection.close() class DummyUpload: def __init__(self, events): self.events = events events.append('made upload') def flushed(self): self.events.append('flushed') def got_interested(self): self.events.append('interested') def got_not_interested(self): self.events.append('not interested') def got_request(self, index, begin, length): self.events.append(('request', index, begin, length)) def got_cancel(self, index, begin, length): self.events.append(('cancel', index, begin, length)) class DummyDownload: def __init__(self, events): self.events = events events.append('made download') self.hit = 0 def disconnected(self): self.events.append('disconnected') def got_choke(self): self.events.append('choke') def got_unchoke(self): self.events.append('unchoke') def got_have(self, i): self.events.append(('have', i)) def got_have_bitfield(self, bitfield): self.events.append(('bitfield', bitfield.tostring())) def got_piece(self, index, begin, piece): self.events.append(('piece', index, begin, piece)) self.hit += 1 return self.hit > 1 class DummyDownloader: def __init__(self, events): self.events = events def make_download(self, connection): return DummyDownload(self.events) class DummyConnection: def __init__(self, events): self.events = events def send_message(self, message): self.events.append(('m', message)) class DummyChoker: def __init__(self, events, cs): self.events = events self.cs = cs def connection_made(self, c): self.events.append('made') self.cs.append(c) def connection_lost(self, c): self.events.append('lost') def test_operation(): events = [] cs = [] co = Connecter(lambda c, events = events: DummyUpload(events), DummyDownloader(events), DummyChoker(events, cs), 3, Measure(10)) assert events == [] assert cs == [] dc = DummyConnection(events) co.connection_made(dc) assert len(cs) == 1 cc = cs[0] co.got_message(dc, BITFIELD + chr(0xc0)) co.got_message(dc, CHOKE) co.got_message(dc, UNCHOKE) co.got_message(dc, INTERESTED) co.got_message(dc, NOT_INTERESTED) co.got_message(dc, HAVE + tobinary(2)) co.got_message(dc, REQUEST + tobinary(1) + tobinary(5) + tobinary(6)) co.got_message(dc, CANCEL + tobinary(2) + tobinary(3) + tobinary(4)) co.got_message(dc, PIECE + tobinary(1) + tobinary(0) + 'abc') co.got_message(dc, PIECE + tobinary(1) + tobinary(3) + 'def') co.connection_flushed(dc) cc.send_bitfield(chr(0x60)) cc.send_interested() cc.send_not_interested() cc.send_choke() cc.send_unchoke() cc.send_have(4) cc.send_request(0, 2, 1) cc.send_cancel(1, 2, 3) cc.send_piece(1, 2, 'abc') co.connection_lost(dc) x = ['made upload', 'made download', 'made', ('bitfield', chr(0xC0)), 'choke', 'unchoke', 'interested', 'not interested', ('have', 2), ('request', 1, 5, 6), ('cancel', 2, 3, 4), ('piece', 1, 0, 'abc'), ('piece', 1, 3, 'def'), ('m', HAVE + tobinary(1)), 'flushed', ('m', BITFIELD + chr(0x60)), ('m', INTERESTED), ('m', NOT_INTERESTED), ('m', CHOKE), ('m', UNCHOKE), ('m', HAVE + tobinary(4)), ('m', REQUEST + tobinary(0) + tobinary(2) + tobinary(1)), ('m', CANCEL + tobinary(1) + tobinary(2) + tobinary(3)), ('m', PIECE + tobinary(1) + tobinary(2) + 'abc'), 'disconnected', 'lost'] for a, b in zip (events, x): assert a == b, repr((a, b)) def test_conversion(): assert toint(tobinary(50000)) == 50000 BitTorrent-3.4.2/BitTorrent/CurrentRateMeasure.py0100644000202400020240000000177007737741426020503 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from time import time class Measure: def __init__(self, max_rate_period, fudge = 1): self.max_rate_period = max_rate_period self.ratesince = time() - fudge self.last = self.ratesince self.rate = 0.0 self.total = 0l def update_rate(self, amount): self.total += amount t = time() 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 = time() - self.ratesince return ((self.rate * t) / newrate) - t def get_total(self): return self.total BitTorrent-3.4.2/BitTorrent/Downloader.py0100644000202400020240000004250610032204220016763 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from CurrentRateMeasure import Measure from random import shuffle from time import time from bitfield import Bitfield class SingleDownload: def __init__(self, downloader, connection): self.downloader = downloader self.connection = connection self.choked = True self.interested = False self.active_requests = [] self.measure = Measure(downloader.max_rate_period) self.have = Bitfield(downloader.numpieces) self.last = 0 self.example_interest = None def disconnected(self): self.downloader.downloads.remove(self) for i in xrange(len(self.have)): if self.have[i]: self.downloader.picker.lost_have(i) self._letgo() def _letgo(self): if not self.active_requests: return if self.downloader.storage.is_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 is_choked(self): return self.choked def is_interested(self): return self.interested def got_piece(self, index, begin, piece): try: self.active_requests.remove((index, begin, len(piece))) except ValueError: return False if self.downloader.storage.is_endgame(): self.downloader.all_requests.remove((index, begin, len(piece))) self.last = time() 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): if self.downloader.storage.is_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 self.downloader.picker.bump(index) 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.is_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.downloader.backlog: return if self.downloader.storage.is_endgame(): self.fix_download_endgame() return lost_interests = [] while len(self.active_requests) < self.downloader.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 begin, length = self.downloader.storage.new_request(interest) self.downloader.picker.requested(interest, self.have.numfalse == 0) 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) 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.is_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: return shuffle(want) del want[self.downloader.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 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.is_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): self.have = have for i in xrange(len(self.have)): if self.have[i]: self.downloader.picker.got_have(i) if self.downloader.picker.am_I_complete() and self.have.numfalse == 0: self.connection.close() return if self.downloader.storage.is_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 time() - self.last > self.downloader.snub_time class Downloader: def __init__(self, storage, picker, backlog, max_rate_period, numpieces, downmeasure, snub_time, measurefunc = lambda x: None): self.storage = storage self.picker = picker self.backlog = backlog self.max_rate_period = max_rate_period self.downmeasure = downmeasure self.numpieces = numpieces self.snub_time = snub_time self.measurefunc = measurefunc self.downloads = [] def make_download(self, connection): self.downloads.append(SingleDownload(self, connection)) return self.downloads[-1] class DummyPicker: def __init__(self, num, r): self.stuff = range(num) self.r = r def next(self, wantfunc, seed): for i in self.stuff: if wantfunc(i): return i return None def lost_have(self, pos): self.r.append('lost have') def got_have(self, pos): self.r.append('got have') def requested(self, pos, seed): self.r.append('requested') def complete(self, pos): self.stuff.remove(pos) self.r.append('complete') def am_I_complete(self): return False def bump(self, i): pass class DummyStorage: def __init__(self, remaining, have_endgame = False, numpieces = 1): self.remaining = remaining self.active = [[] for i in xrange(numpieces)] self.endgame = False self.have_endgame = have_endgame def do_I_have_requests(self, index): return self.remaining[index] != [] def request_lost(self, index, begin, length): x = (begin, length) self.active[index].remove(x) self.remaining[index].append(x) self.remaining[index].sort() def piece_came_in(self, index, begin, piece): self.active[index].remove((begin, len(piece))) return True def do_I_have(self, index): return (self.remaining[index] == [] and self.active[index] == []) def new_request(self, index): x = self.remaining[index].pop() for i in self.remaining: if i: break else: self.endgame = True self.active[index].append(x) self.active[index].sort() return x def is_endgame(self): return self.have_endgame and self.endgame class DummyConnection: def __init__(self, events): self.events = events def send_interested(self): self.events.append('interested') def send_not_interested(self): self.events.append('not interested') def send_request(self, index, begin, length): self.events.append(('request', index, begin, length)) def send_cancel(self, index, begin, length): self.events.append(('cancel', index, begin, length)) def test_stops_at_backlog(): ds = DummyStorage([[(0, 2), (2, 2), (4, 2), (6, 2)]]) events = [] d = Downloader(ds, DummyPicker(len(ds.remaining), events), 2, 15, 1, Measure(15), 10) sd = d.make_download(DummyConnection(events)) assert events == [] assert ds.remaining == [[(0, 2), (2, 2), (4, 2), (6, 2)]] assert ds.active == [[]] sd.got_have_bitfield(Bitfield(1, chr(0x80))) assert events == ['got have', 'interested'] del events[:] assert ds.remaining == [[(0, 2), (2, 2), (4, 2), (6, 2)]] assert ds.active == [[]] sd.got_unchoke() assert events == ['requested', ('request', 0, 6, 2), 'requested', ('request', 0, 4, 2)] del events[:] assert ds.remaining == [[(0, 2), (2, 2)]] assert ds.active == [[(4, 2), (6, 2)]] sd.got_piece(0, 4, 'ab') assert events == ['requested', ('request', 0, 2, 2)] del events[:] assert ds.remaining == [[(0, 2)]] assert ds.active == [[(2, 2), (6, 2)]] def test_got_have_single(): ds = DummyStorage([[(0, 2)]]) events = [] d = Downloader(ds, DummyPicker(len(ds.remaining), events), 2, 15, 1, Measure(15), 10) sd = d.make_download(DummyConnection(events)) assert events == [] assert ds.remaining == [[(0, 2)]] assert ds.active == [[]] sd.got_unchoke() assert events == [] assert ds.remaining == [[(0, 2)]] assert ds.active == [[]] sd.got_have(0) assert events == ['got have', 'interested', 'requested', ('request', 0, 0, 2)] del events[:] assert ds.remaining == [[]] assert ds.active == [[(0, 2)]] sd.disconnected() assert events == ['lost have'] def test_choke_clears_active(): ds = DummyStorage([[(0, 2)]]) events = [] d = Downloader(ds, DummyPicker(len(ds.remaining), events), 2, 15, 1, Measure(15), 10) sd1 = d.make_download(DummyConnection(events)) sd2 = d.make_download(DummyConnection(events)) assert events == [] assert ds.remaining == [[(0, 2)]] assert ds.active == [[]] sd1.got_unchoke() sd1.got_have(0) assert events == ['got have', 'interested', 'requested', ('request', 0, 0, 2)] del events[:] assert ds.remaining == [[]] assert ds.active == [[(0, 2)]] sd2.got_unchoke() sd2.got_have(0) assert events == ['got have'] del events[:] assert ds.remaining == [[]] assert ds.active == [[(0, 2)]] sd1.got_choke() assert events == ['interested', 'requested', ('request', 0, 0, 2), 'not interested'] del events[:] assert ds.remaining == [[]] assert ds.active == [[(0, 2)]] sd2.got_piece(0, 0, 'ab') assert events == ['complete', 'not interested'] del events[:] assert ds.remaining == [[]] assert ds.active == [[]] def test_endgame(): ds = DummyStorage([[(0, 2)], [(0, 2)], [(0, 2)]], True, 3) events = [] d = Downloader(ds, DummyPicker(len(ds.remaining), events), 10, 15, 3, Measure(15), 10) ev1 = [] ev2 = [] ev3 = [] ev4 = [] sd1 = d.make_download(DummyConnection(ev1)) sd2 = d.make_download(DummyConnection(ev2)) sd3 = d.make_download(DummyConnection(ev3)) sd1.got_unchoke() sd1.got_have(0) assert ev1 == ['interested', ('request', 0, 0, 2)] del ev1[:] sd2.got_unchoke() sd2.got_have(0) sd2.got_have(1) assert ev2 == ['interested', ('request', 1, 0, 2)] del ev2[:] sd3.got_unchoke() sd3.got_have(0) sd3.got_have(1) sd3.got_have(2) assert (ev3 == ['interested', ('request', 2, 0, 2), ('request', 0, 0, 2), ('request', 1, 0, 2)] or ev3 == ['interested', ('request', 2, 0, 2), ('request', 1, 0, 2), ('request', 0, 0, 2)]) del ev3[:] assert ev2 == [('request', 0, 0, 2)] del ev2[:] sd2.got_piece(0, 0, 'ab') assert ev1 == [('cancel', 0, 0, 2), 'not interested'] del ev1[:] assert ev2 == [] assert ev3 == [('cancel', 0, 0, 2)] del ev3[:] sd3.got_choke() assert ev1 == [] assert ev2 == [] assert ev3 == [] sd3.got_unchoke() assert (ev3 == [('request', 2, 0, 2), ('request', 1, 0, 2)] or ev3 == [('request', 1, 0, 2), ('request', 2, 0, 2)]) del ev3[:] assert ev1 == [] assert ev2 == [] sd4 = d.make_download(DummyConnection(ev4)) sd4.got_have_bitfield([True, True, True]) assert ev4 == ['interested'] del ev4[:] sd4.got_unchoke() assert (ev4 == [('request', 2, 0, 2), ('request', 1, 0, 2)] or ev4 == [('request', 1, 0, 2), ('request', 2, 0, 2)]) assert ev1 == [] assert ev2 == [] assert ev3 == [] def test_stops_at_backlog_endgame(): ds = DummyStorage([[(2, 2), (0, 2)], [(2, 2), (0, 2)], [(0, 2)]], True, 3) events = [] d = Downloader(ds, DummyPicker(len(ds.remaining), events), 3, 15, 3, Measure(15), 10) ev1 = [] ev2 = [] ev3 = [] sd1 = d.make_download(DummyConnection(ev1)) sd2 = d.make_download(DummyConnection(ev2)) sd3 = d.make_download(DummyConnection(ev3)) sd1.got_unchoke() sd1.got_have(0) assert ev1 == ['interested', ('request', 0, 0, 2), ('request', 0, 2, 2)] del ev1[:] sd2.got_unchoke() sd2.got_have(0) assert ev2 == [] sd2.got_have(1) assert ev2 == ['interested', ('request', 1, 0, 2), ('request', 1, 2, 2)] del ev2[:] sd3.got_unchoke() sd3.got_have(2) assert (ev2 == [('request', 0, 0, 2)] or ev2 == [('request', 0, 2, 2)]) n = ev2[0][2] del ev2[:] sd1.got_piece(0, n, 'ab') assert ev1 == [] assert ev2 == [('cancel', 0, n, 2), ('request', 0, 2-n, 2)] BitTorrent-3.4.2/BitTorrent/DownloaderFeedback.py0100644000202400020240000000544707751060505020417 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from time import time class DownloaderFeedback: def __init__(self, choker, add_task, statusfunc, upfunc, downfunc, uptotal, downtotal, remainingfunc, leftfunc, file_length, finflag, interval, spewflag): self.choker = choker self.add_task = add_task self.statusfunc = statusfunc self.upfunc = upfunc self.downfunc = downfunc self.uptotal = uptotal self.downtotal = downtotal self.remainingfunc = remainingfunc self.leftfunc = leftfunc self.file_length = file_length self.finflag = finflag self.interval = interval self.spewflag = spewflag self.lastids = [] self.display() def _rotate(self): cs = self.choker.connections for id in self.lastids: for i in xrange(len(cs)): if cs[i].get_id() == id: return cs[i:] + cs[:i] return cs def collect_spew(self): l = [ ] cs = self._rotate() self.lastids = [c.get_id() for c in cs] for c in cs: rec = {} rec["ip"] = c.get_ip() if c is self.choker.connections[0]: rec["is_optimistic_unchoke"] = 1 else: rec["is_optimistic_unchoke"] = 0 if c.is_locally_initiated(): rec["initiation"] = "local" else: rec["initiation"] = "remote" u = c.get_upload() rec["upload"] = (int(u.measure.get_rate()), u.is_interested(), u.is_choked()) d = c.get_download() rec["download"] = (int(d.measure.get_rate()), d.is_interested(), d.is_choked(), d.is_snubbed()) l.append(rec) return l def display(self): self.add_task(self.display, self.interval) spew = [] if self.finflag.isSet(): status = {"upRate" : self.upfunc(), "upTotal" : self.uptotal() / 1048576.0} if self.spewflag.isSet(): status['spew'] = self.collect_spew() self.statusfunc(status) return timeEst = self.remainingfunc() if self.file_length > 0: fractionDone = (self.file_length - self.leftfunc()) / float(self.file_length) else: fractionDone = 1 status = { "fractionDone" : fractionDone, "downRate" : self.downfunc(), "upRate" : self.upfunc(), "upTotal" : self.uptotal() / 1048576.0, "downTotal" : self.downtotal() / 1048576.0 } if timeEst is not None: status['timeEst'] = timeEst if self.spewflag.isSet(): status['spew'] = self.collect_spew() self.statusfunc(status) BitTorrent-3.4.2/BitTorrent/Encrypter.py0100644000202400020240000004454510024230546016660 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from cStringIO import StringIO from binascii import b2a_hex from socket import error as socketerror protocol_name = 'BitTorrent protocol' def toint(s): return long(b2a_hex(s), 16) def tobinary(i): return (chr(i >> 24) + chr((i >> 16) & 0xFF) + chr((i >> 8) & 0xFF) + chr(i & 0xFF)) # header, reserved, download id, my id, [length, message] class Connection: def __init__(self, Encoder, connection, id, is_local): self.encoder = Encoder self.connection = connection self.id = id self.locally_initiated = is_local self.complete = False self.closed = False self.buffer = StringIO() self.next_len = 1 self.next_func = self.read_header_len if self.locally_initiated: connection.write(chr(len(protocol_name)) + protocol_name + (chr(0) * 8) + self.encoder.download_id) if self.id is not None: connection.write(self.encoder.my_id) def get_ip(self): return self.connection.get_ip() def get_id(self): return self.id def is_locally_initiated(self): return self.locally_initiated def is_flushed(self): return self.connection.is_flushed() def read_header_len(self, s): if ord(s) != len(protocol_name): return None return len(protocol_name), self.read_header def read_header(self, s): if s != protocol_name: return None return 8, self.read_reserved def read_reserved(self, s): return 20, self.read_download_id def read_download_id(self, s): if s != self.encoder.download_id: return None if not self.locally_initiated: self.connection.write(chr(len(protocol_name)) + protocol_name + (chr(0) * 8) + self.encoder.download_id + self.encoder.my_id) return 20, self.read_peer_id def read_peer_id(self, s): if not self.id: if s == self.encoder.my_id: return None for v in self.encoder.connections.values(): if s and v.id == s: return None self.id = s if self.locally_initiated: self.connection.write(self.encoder.my_id) else: self.encoder.everinc = True else: if s != self.id: return None self.complete = True self.encoder.connecter.connection_made(self) return 4, self.read_len def read_len(self, s): l = toint(s) if l > self.encoder.max_len: return None return l, self.read_message def read_message(self, s): if s != '': self.encoder.connecter.got_message(self, s) return 4, self.read_len def read_dead(self, s): return None def close(self): if not self.closed: self.connection.close() self.sever() def sever(self): self.closed = True del self.encoder.connections[self.connection] if self.complete: self.encoder.connecter.connection_lost(self) def send_message(self, message): self.connection.write(tobinary(len(message)) + message) def data_came_in(self, s): while True: if self.closed: return i = self.next_len - self.buffer.tell() if i > len(s): self.buffer.write(s) return self.buffer.write(s[:i]) s = s[i:] m = self.buffer.getvalue() self.buffer.reset() self.buffer.truncate() try: x = self.next_func(m) except: self.next_len, self.next_func = 1, self.read_dead raise if x is None: self.close() return self.next_len, self.next_func = x class Encoder: def __init__(self, connecter, raw_server, my_id, max_len, schedulefunc, keepalive_delay, download_id, max_initiate = 40): self.raw_server = raw_server self.connecter = connecter self.my_id = my_id self.max_len = max_len self.schedulefunc = schedulefunc self.keepalive_delay = keepalive_delay self.download_id = download_id self.max_initiate = max_initiate self.everinc = False self.connections = {} self.spares = [] schedulefunc(self.send_keepalives, keepalive_delay) def send_keepalives(self): self.schedulefunc(self.send_keepalives, self.keepalive_delay) for c in self.connections.values(): if c.complete: c.send_message('') def start_connection(self, dns, id): if id: if id == self.my_id: return for v in self.connections.values(): if v.id == id: return if len(self.connections) >= self.max_initiate: if len(self.spares) < self.max_initiate and dns not in self.spares: self.spares.append(dns) return try: c = self.raw_server.start_connection(dns) self.connections[c] = Connection(self, c, id, True) except socketerror: pass def _start_connection(self, dns, id): def foo(self=self, dns=dns, id=id): self.start_connection(dns, id) self.schedulefunc(foo, 0) def got_id(self, connection): for v in self.connections.values(): if connection is not v and connection.id == v.id: connection.close() return self.connecter.connection_made(connection) def ever_got_incoming(self): return self.everinc def external_connection_made(self, connection): self.connections[connection] = Connection(self, connection, None, False) def connection_flushed(self, connection): c = self.connections[connection] if c.complete: self.connecter.connection_flushed(c) def connection_lost(self, connection): self.connections[connection].sever() while len(self.connections) < self.max_initiate and self.spares: self.start_connection(self.spares.pop(), None) def data_came_in(self, connection, data): self.connections[connection].data_came_in(data) # everything below is for testing class DummyConnecter: def __init__(self): self.log = [] self.close_next = False def connection_made(self, connection): self.log.append(('made', connection)) def connection_lost(self, connection): self.log.append(('lost', connection)) def connection_flushed(self, connection): self.log.append(('flushed', connection)) def got_message(self, connection, message): self.log.append(('got', connection, message)) if self.close_next: connection.close() class DummyRawServer: def __init__(self): self.connects = [] def start_connection(self, dns): c = DummyRawConnection() self.connects.append((dns, c)) return c class DummyRawConnection: def __init__(self): self.closed = False self.data = [] self.flushed = True def get_ip(self): return 'fake.ip' def is_flushed(self): return self.flushed def write(self, data): assert not self.closed self.data.append(data) def close(self): assert not self.closed self.closed = True def pop(self): r = ''.join(self.data) del self.data[:] return r def dummyschedule(a, b): pass def test_messages_in_and_out(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20) assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, 'b' * 20) assert c1.pop() == '' assert len(c.log) == 1 assert c.log[0][0] == 'made' ch = c.log[0][1] del c.log[:] assert rs.connects == [] assert not c1.closed assert ch.get_ip() == 'fake.ip' ch.send_message('abc') assert c1.pop() == chr(0) * 3 + chr(3) + 'abc' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(0) * 3 + chr(3) + 'def') assert c1.pop() == '' assert c.log == [('got', ch, 'def')] del c.log[:] assert rs.connects == [] assert not c1.closed def test_flushed(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20) assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.connection_flushed(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, 'b' * 20) assert c1.pop() == '' assert len(c.log) == 1 assert c.log[0][0] == 'made' ch = c.log[0][1] del c.log[:] assert rs.connects == [] assert not c1.closed assert ch.is_flushed() e.connection_flushed(c1) assert c1.pop() == '' assert c.log == [('flushed', ch)] assert rs.connects == [] assert not c1.closed c1.flushed = False assert not ch.is_flushed() def test_wrong_header_length(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(5) * 30) assert c.log == [] assert c1.closed def test_wrong_header(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + 'a' * len(protocol_name)) assert c.log == [] assert c1.closed def test_wrong_download_id(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + chr(0) * 8 + 'e' * 20) assert c1.pop() == '' assert c.log == [] assert c1.closed def test_wrong_other_id(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) e.start_connection('dns', 'o' * 20) assert c.log == [] assert len(rs.connects) == 1 assert rs.connects[0][0] == 'dns' c1 = rs.connects[0][1] del rs.connects[:] assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + chr(0) * 8 + 'd' * 20 + 'b' * 20) assert c.log == [] assert c1.closed def test_over_max_len(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + chr(0) * 8 + 'd' * 20 + 'o' * 20) assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert len(c.log) == 1 and c.log[0][0] == 'made' ch = c.log[0][1] del c.log[:] assert not c1.closed e.data_came_in(c1, chr(1) + chr(0) * 3) assert c.log == [('lost', ch)] assert c1.closed def test_keepalive(): s = [] def sched(interval, thing, s = s): s.append((interval, thing)) c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, sched, 30, 'd' * 20) assert len(s) == 1 assert s[0][1] == 30 kfunc = s[0][0] del s[:] c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert not c1.closed kfunc() assert c1.pop() == '' assert c.log == [] assert not c1.closed assert s == [(kfunc, 30)] del s[:] e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + chr(0) * 8 + 'd' * 20 + 'o' * 20) assert len(c.log) == 1 and c.log[0][0] == 'made' del c.log[:] assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert not c1.closed kfunc() assert c1.pop() == chr(0) * 4 assert c.log == [] assert not c1.closed def test_swallow_keepalive(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + chr(0) * 8 + 'd' * 20 + 'o' * 20) assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert len(c.log) == 1 and c.log[0][0] == 'made' del c.log[:] assert not c1.closed e.data_came_in(c1, chr(0) * 4) assert c.log == [] assert not c1.closed def test_local_close(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + chr(0) * 8 + 'd' * 20 + 'o' * 20) assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert len(c.log) == 1 and c.log[0][0] == 'made' ch = c.log[0][1] del c.log[:] assert not c1.closed ch.close() assert c.log == [('lost', ch)] del c.log[:] assert c1.closed def test_local_close_in_message_receive(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + chr(0) * 8 + 'd' * 20 + 'o' * 20) assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert len(c.log) == 1 and c.log[0][0] == 'made' ch = c.log[0][1] del c.log[:] assert not c1.closed c.close_next = True e.data_came_in(c1, chr(0) * 3 + chr(4) + 'abcd') assert c.log == [('got', ch, 'abcd'), ('lost', ch)] assert c1.closed def test_remote_close(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + chr(0) * 8 + 'd' * 20 + 'o' * 20) assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert len(c.log) == 1 and c.log[0][0] == 'made' ch = c.log[0][1] del c.log[:] assert not c1.closed e.connection_lost(c1) assert c.log == [('lost', ch)] assert not c1.closed def test_partial_data_in(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + chr(0) * 4) e.data_came_in(c1, chr(0) * 4 + 'd' * 20 + 'c' * 10) e.data_came_in(c1, 'c' * 10) assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert len(c.log) == 1 and c.log[0][0] == 'made' del c.log[:] assert not c1.closed def test_ignore_connect_of_extant(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.external_connection_made(c1) assert c1.pop() == '' assert c.log == [] assert rs.connects == [] assert not c1.closed e.data_came_in(c1, chr(len(protocol_name)) + protocol_name + chr(0) * 8 + 'd' * 20 + 'o' * 20) assert c1.pop() == chr(len(protocol_name)) + protocol_name + \ chr(0) * 8 + 'd' * 20 + 'a' * 20 assert len(c.log) == 1 and c.log[0][0] == 'made' del c.log[:] assert not c1.closed e.start_connection('dns', 'o' * 20) assert c.log == [] assert rs.connects == [] assert not c1.closed def test_ignore_connect_to_self(): c = DummyConnecter() rs = DummyRawServer() e = Encoder(c, rs, 'a' * 20, 500, dummyschedule, 30, 'd' * 20) c1 = DummyRawConnection() e.start_connection('dns', 'a' * 20) assert c.log == [] assert rs.connects == [] assert not c1.closed def test_conversion(): assert toint(tobinary(50000)) == 50000 BitTorrent-3.4.2/BitTorrent/HTTPHandler.py0100644000202400020240000001364607737632376017010 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from cStringIO import StringIO from sys import stdout import time from gzip import GzipFile DEBUG = False weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] class HTTPConnection: def __init__(self, handler, connection): self.handler = handler self.connection = connection self.buf = '' self.closed = False self.done = False self.donereading = False self.next_func = self.read_type def get_ip(self): return self.connection.get_ip() def data_came_in(self, data): if self.donereading or self.next_func is None: return True self.buf += data while True: try: i = self.buf.index('\n') except ValueError: return True val = self.buf[:i] self.buf = self.buf[i+1:] self.next_func = self.next_func(val) if self.donereading: return True if self.next_func is None or self.closed: return False def read_type(self, data): self.header = data.strip() words = data.split() if len(words) == 3: self.command, self.path, garbage = words self.pre1 = False elif len(words) == 2: self.command, self.path = words self.pre1 = True if self.command != 'GET': return None else: return None if self.command not in ('HEAD', 'GET'): return None self.headers = {} return self.read_header def read_header(self, data): data = data.strip() if data == '': self.donereading = True # check for Accept-Encoding: header, pick a if self.headers.has_key('accept-encoding'): ae = self.headers['accept-encoding'] if DEBUG: print "Got Accept-Encoding: " + ae + "\n" else: #identity assumed if no header ae = 'identity' # this eventually needs to support multple acceptable types # q-values and all that fancy HTTP crap # for now assume we're only communicating with our own client if ae.find('gzip') != -1: self.encoding = 'gzip' else: #default to identity. self.encoding = 'identity' r = self.handler.getfunc(self, self.path, self.headers) if r is not None: self.answer(r) return None try: i = data.index(':') except ValueError: return None self.headers[data[:i].strip().lower()] = data[i+1:].strip() if DEBUG: print data[:i].strip() + ": " + data[i+1:].strip() return self.read_header def answer(self, (responsecode, responsestring, headers, data)): if self.closed: return if self.encoding == 'gzip': #transform data using gzip compression #this is nasty but i'm unsure of a better way at the moment compressed = StringIO() gz = GzipFile(fileobj = compressed, mode = 'wb', compresslevel = 9) gz.write(data) gz.close() compressed.seek(0,0) cdata = compressed.read() compressed.close() if len(cdata) >= len(data): self.encoding = 'identity' else: if DEBUG: print "Compressed: %i Uncompressed: %i\n" % (len(cdata),len(data)) data = cdata headers['Content-Encoding'] = 'gzip' # i'm abusing the identd field here, but this should be ok if self.encoding == 'identity': ident = '-' else: ident = self.encoding username = '-' referer = self.headers.get('referer','-') useragent = self.headers.get('user-agent','-') year, month, day, hour, minute, second, a, b, c = time.localtime(time.time()) print '%s %s %s [%02d/%3s/%04d:%02d:%02d:%02d] "%s" %i %i "%s" "%s"' % ( self.connection.get_ip(), ident, username, day, months[month], year, hour, minute, second, self.header, responsecode, len(data), referer, useragent) t = time.time() if t - self.handler.lastflush > self.handler.minflush: self.handler.lastflush = t stdout.flush() self.done = True r = StringIO() r.write('HTTP/1.0 ' + str(responsecode) + ' ' + responsestring + '\r\n') if not self.pre1: headers['Content-Length'] = len(data) for key, value in headers.items(): r.write(key + ': ' + str(value) + '\r\n') r.write('\r\n') if self.command != 'HEAD': r.write(data) self.connection.write(r.getvalue()) if self.connection.is_flushed(): self.connection.shutdown(1) class HTTPHandler: def __init__(self, getfunc, minflush): self.connections = {} self.getfunc = getfunc self.minflush = minflush self.lastflush = time.time() def external_connection_made(self, connection): self.connections[connection] = HTTPConnection(self, connection) def connection_flushed(self, connection): if self.connections[connection].done: connection.shutdown(1) def connection_lost(self, connection): ec = self.connections[connection] ec.closed = True del ec.connection del ec.next_func del self.connections[connection] def data_came_in(self, connection, data): c = self.connections[connection] if not c.data_came_in(data) and not c.closed: c.connection.shutdown(1) BitTorrent-3.4.2/BitTorrent/NatCheck.py0100644000202400020240000000513207730671244016370 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from cStringIO import StringIO from socket import error as socketerror protocol_name = 'BitTorrent protocol' # header, reserved, download id, my id, [length, message] class NatCheck: def __init__(self, resultfunc, downloadid, peerid, ip, port, rawserver): self.resultfunc = resultfunc self.downloadid = downloadid self.peerid = peerid self.ip = ip self.port = port self.closed = False self.buffer = StringIO() self.next_len = 1 self.next_func = self.read_header_len try: self.connection = rawserver.start_connection((ip, port), self) self.connection.write(chr(len(protocol_name)) + protocol_name + (chr(0) * 8) + downloadid) except socketerror: self.answer(False) except IOError: self.answer(False) def answer(self, result): self.closed = True try: self.connection.close() except AttributeError: pass self.resultfunc(result, self.downloadid, self.peerid, self.ip, self.port) def read_header_len(self, s): if ord(s) != len(protocol_name): return None return len(protocol_name), self.read_header def read_header(self, s): if s != protocol_name: return None return 8, self.read_reserved def read_reserved(self, s): return 20, self.read_download_id def read_download_id(self, s): if s != self.downloadid: return None return 20, self.read_peer_id def read_peer_id(self, s): if s != self.peerid: return None self.answer(True) return None def data_came_in(self, connection, s): while True: if self.closed: return i = self.next_len - self.buffer.tell() if i > len(s): self.buffer.write(s) return self.buffer.write(s[:i]) s = s[i:] m = self.buffer.getvalue() self.buffer.reset() self.buffer.truncate() x = self.next_func(m) if x is None: if not self.closed: self.answer(False) return self.next_len, self.next_func = x def connection_lost(self, connection): if not self.closed: self.closed = True self.resultfunc(False, self.downloadid, self.peerid, self.ip, self.port) def connection_flushed(self, connection): pass BitTorrent-3.4.2/BitTorrent/PiecePicker.py0100644000202400020240000001162607737736113017104 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from random import randrange, shuffle, choice class PiecePicker: def __init__(self, numpieces, rarest_first_cutoff = 1): self.rarest_first_cutoff = rarest_first_cutoff self.numpieces = numpieces self.interests = [range(numpieces)] self.pos_in_interests = range(numpieces) self.numinterests = [0] * numpieces self.started = [] self.seedstarted = [] self.numgot = 0 self.scrambled = range(numpieces) shuffle(self.scrambled) def got_have(self, piece): if self.numinterests[piece] is None: return numint = self.numinterests[piece] if numint == len(self.interests) - 1: self.interests.append([]) self.numinterests[piece] += 1 self._shift_over(piece, self.interests[numint], self.interests[numint + 1]) def lost_have(self, piece): if self.numinterests[piece] is None: return numint = self.numinterests[piece] self.numinterests[piece] -= 1 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 self.numinterests[piece] is not None 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] self.numinterests[piece] = None 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.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 def test_requested(): p = PiecePicker(9) p.complete(8) p.got_have(0) p.got_have(2) p.got_have(4) p.got_have(6) p.requested(1) p.requested(1) p.requested(3) p.requested(0) p.requested(6) v = _pull(p) assert v[:2] == [1, 3] or v[:2] == [3, 1] assert v[2:4] == [0, 6] or v[2:4] == [6, 0] assert v[4:] == [2, 4] or v[4:] == [4, 2] def test_change_interest(): p = PiecePicker(9) p.complete(8) p.got_have(0) p.got_have(2) p.got_have(4) p.got_have(6) p.lost_have(2) p.lost_have(6) v = _pull(p) assert v == [0, 4] or v == [4, 0] def test_change_interest2(): p = PiecePicker(9) p.complete(8) p.got_have(0) p.got_have(2) p.got_have(4) p.got_have(6) p.lost_have(2) p.lost_have(6) v = _pull(p) assert v == [0, 4] or v == [4, 0] def test_complete(): p = PiecePicker(1) p.got_have(0) p.complete(0) assert _pull(p) == [] p.got_have(0) p.lost_have(0) def test_rarer_in_started_takes_priority(): p = PiecePicker(3) p.complete(2) p.requested(0) p.requested(1) p.got_have(1) p.got_have(0) p.got_have(0) assert _pull(p) == [1, 0] def test_zero(): assert _pull(PiecePicker(0)) == [] def _pull(pp): r = [] def want(p, r = r): return p not in r while True: n = pp.next(want) if n is None: break r.append(n) return r BitTorrent-3.4.2/BitTorrent/RateMeasure.py0100644000202400020240000000273707730671244017135 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from time import time class RateMeasure: 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 = time() - 2 self.last = self.start self.left -= amount return self.update(time(), amount) def data_rejected(self, amount): self.left += amount def get_time_left(self): if not self.got_anything: return None t = time() 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 BitTorrent-3.4.2/BitTorrent/RawServer.py0100644000202400020240000004365310033434504016624 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from bisect import insort import socket from cStringIO import StringIO from traceback import print_exc from errno import EWOULDBLOCK, ENOBUFS try: from select import poll, error, POLLIN, POLLOUT, POLLERR, POLLHUP timemult = 1000 except ImportError: from selectpoll import poll, error, POLLIN, POLLOUT, POLLERR, POLLHUP timemult = 1 from threading import Thread, Event from time import time, sleep import sys from random import randrange all = POLLIN | POLLOUT class SingleSocket: def __init__(self, raw_server, sock, handler): self.raw_server = raw_server self.socket = sock self.handler = handler self.buffer = [] self.last_hit = time() self.fileno = sock.fileno() self.connected = False def get_ip(self): try: return self.socket.getpeername()[0] except socket.error: return 'no connection' def close(self): sock = self.socket self.socket = None self.buffer = [] del self.raw_server.single_sockets[self.fileno] self.raw_server.poll.unregister(sock) sock.close() def shutdown(self, val): self.socket.shutdown(val) def is_flushed(self): return len(self.buffer) == 0 def write(self, s): assert self.socket is not None self.buffer.append(s) if len(self.buffer) == 1: self.try_write() def try_write(self): if self.connected: try: while self.buffer != []: amount = self.socket.send(self.buffer[0]) if amount != len(self.buffer[0]): if amount != 0: self.buffer[0] = self.buffer[0][amount:] break del self.buffer[0] except socket.error, e: code, msg = e if code != EWOULDBLOCK: self.raw_server.dead_from_write.append(self) return if self.buffer == []: self.raw_server.poll.register(self.socket, POLLIN) else: self.raw_server.poll.register(self.socket, all) def default_error_handler(x): print x class RawServer: def __init__(self, doneflag, timeout_check_interval, timeout, noisy = True, errorfunc = default_error_handler, maxconnects = 55): self.timeout_check_interval = timeout_check_interval self.timeout = timeout self.poll = poll() # {socket: SingleSocket} self.single_sockets = {} self.dead_from_write = [] self.doneflag = doneflag self.noisy = noisy self.errorfunc = errorfunc self.maxconnects = maxconnects self.funcs = [] self.unscheduled_tasks = [] self.add_task(self.scan_for_timeouts, timeout_check_interval) def add_task(self, func, delay): self.unscheduled_tasks.append((func, delay)) def scan_for_timeouts(self): self.add_task(self.scan_for_timeouts, self.timeout_check_interval) t = time() - self.timeout tokill = [] for s in self.single_sockets.values(): if s.last_hit < t: tokill.append(s) for k in tokill: if k.socket is not None: self._close_socket(k) def bind(self, port, bind = '', reuse = False): self.bindaddr = bind server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if reuse: server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.setblocking(0) try: server.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 32) except: pass server.bind((bind, port)) server.listen(5) self.poll.register(server, POLLIN) self.server = server def start_connection(self, dns, handler = None): if handler is None: handler = self.handler sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setblocking(0) sock.bind((self.bindaddr, 0)) try: sock.connect_ex(dns) except socket.error: raise except Exception, e: raise socket.error(str(e)) self.poll.register(sock, POLLIN) s = SingleSocket(self, sock, handler) self.single_sockets[sock.fileno()] = s return s def handle_events(self, events): for sock, event in events: if sock == self.server.fileno(): if event & (POLLHUP | POLLERR) != 0: self.poll.unregister(self.server) self.server.close() self.errorfunc('lost server socket') else: try: newsock, addr = self.server.accept() newsock.setblocking(0) if len(self.single_sockets) >= self.maxconnects: newsock.close() continue nss = SingleSocket(self, newsock, self.handler) self.single_sockets[newsock.fileno()] = nss self.poll.register(newsock, POLLIN) self.handler.external_connection_made(nss) except socket.error: sleep(1) else: s = self.single_sockets.get(sock) if s is None: continue s.connected = True if (event & (POLLHUP | POLLERR)) != 0: self._close_socket(s) continue if (event & POLLIN) != 0: try: s.last_hit = time() data = s.socket.recv(100000) if data == '': self._close_socket(s) else: s.handler.data_came_in(s, data) except socket.error, e: code, msg = e if code != EWOULDBLOCK: self._close_socket(s) continue if (event & POLLOUT) != 0 and s.socket is not None and not s.is_flushed(): s.try_write() if s.is_flushed(): s.handler.connection_flushed(s) def pop_unscheduled(self): try: while True: (func, delay) = self.unscheduled_tasks.pop() insort(self.funcs, (time() + delay, func)) except IndexError: pass def listen_forever(self, handler): self.handler = handler try: while not self.doneflag.isSet(): try: self.pop_unscheduled() if len(self.funcs) == 0: period = 2 ** 30 else: period = self.funcs[0][0] - time() if period < 0: period = 0 events = self.poll.poll(period * timemult) if self.doneflag.isSet(): return while len(self.funcs) > 0 and self.funcs[0][0] <= time(): garbage, func = self.funcs[0] del self.funcs[0] try: func() except KeyboardInterrupt: print_exc() return except: if self.noisy: data = StringIO() print_exc(file = data) self.errorfunc(data.getvalue()) self._close_dead() self.handle_events(events) if self.doneflag.isSet(): return self._close_dead() except error, e: if self.doneflag.isSet(): return # I can't find a coherent explanation for what the behavior should be here, # and people report conflicting behavior, so I'll just try all the possibilities try: code, msg, desc = e except: try: code, msg = e except: code = ENOBUFS if code == ENOBUFS: self.errorfunc("Have to exit due to the TCP stack flaking out") return except KeyboardInterrupt: print_exc() return except: data = StringIO() print_exc(file = data) self.errorfunc(data.getvalue()) finally: for ss in self.single_sockets.values(): ss.close() self.server.close() def _close_dead(self): while len(self.dead_from_write) > 0: old = self.dead_from_write self.dead_from_write = [] for s in old: if s.socket is not None: self._close_socket(s) def _close_socket(self, s): sock = s.socket.fileno() s.socket.close() self.poll.unregister(sock) del self.single_sockets[sock] s.socket = None s.handler.connection_lost(s) # everything below is for testing class DummyHandler: def __init__(self): self.external_made = [] self.data_in = [] self.lost = [] def external_connection_made(self, s): self.external_made.append(s) def data_came_in(self, s, data): self.data_in.append((s, data)) def connection_lost(self, s): self.lost.append(s) def connection_flushed(self, s): pass def sl(rs, handler, port): rs.bind(port) Thread(target = rs.listen_forever, args = [handler]).start() def loop(rs): x = [] def r(rs = rs, x = x): rs.add_task(x[0], .1) x.append(r) rs.add_task(r, .1) beginport = 5000 + randrange(10000) def test_starting_side_close(): try: fa = Event() fb = Event() da = DummyHandler() sa = RawServer(fa, 100, 100) loop(sa) sl(sa, da, beginport) db = DummyHandler() sb = RawServer(fb, 100, 100) loop(sb) sl(sb, db, beginport + 1) sleep(.5) ca = sa.start_connection(('127.0.0.1', beginport + 1)) sleep(1) assert da.external_made == [] assert da.data_in == [] assert da.lost == [] assert len(db.external_made) == 1 cb = db.external_made[0] del db.external_made[:] assert db.data_in == [] assert db.lost == [] ca.write('aaa') cb.write('bbb') sleep(1) assert da.external_made == [] assert da.data_in == [(ca, 'bbb')] del da.data_in[:] assert da.lost == [] assert db.external_made == [] assert db.data_in == [(cb, 'aaa')] del db.data_in[:] assert db.lost == [] ca.write('ccc') cb.write('ddd') sleep(1) assert da.external_made == [] assert da.data_in == [(ca, 'ddd')] del da.data_in[:] assert da.lost == [] assert db.external_made == [] assert db.data_in == [(cb, 'ccc')] del db.data_in[:] assert db.lost == [] ca.close() sleep(1) assert da.external_made == [] assert da.data_in == [] assert da.lost == [] assert db.external_made == [] assert db.data_in == [] assert db.lost == [cb] del db.lost[:] finally: fa.set() fb.set() def test_receiving_side_close(): try: da = DummyHandler() fa = Event() sa = RawServer(fa, 100, 100) loop(sa) sl(sa, da, beginport + 2) db = DummyHandler() fb = Event() sb = RawServer(fb, 100, 100) loop(sb) sl(sb, db, beginport + 3) sleep(.5) ca = sa.start_connection(('127.0.0.1', beginport + 3)) sleep(1) assert da.external_made == [] assert da.data_in == [] assert da.lost == [] assert len(db.external_made) == 1 cb = db.external_made[0] del db.external_made[:] assert db.data_in == [] assert db.lost == [] ca.write('aaa') cb.write('bbb') sleep(1) assert da.external_made == [] assert da.data_in == [(ca, 'bbb')] del da.data_in[:] assert da.lost == [] assert db.external_made == [] assert db.data_in == [(cb, 'aaa')] del db.data_in[:] assert db.lost == [] ca.write('ccc') cb.write('ddd') sleep(1) assert da.external_made == [] assert da.data_in == [(ca, 'ddd')] del da.data_in[:] assert da.lost == [] assert db.external_made == [] assert db.data_in == [(cb, 'ccc')] del db.data_in[:] assert db.lost == [] cb.close() sleep(1) assert da.external_made == [] assert da.data_in == [] assert da.lost == [ca] del da.lost[:] assert db.external_made == [] assert db.data_in == [] assert db.lost == [] finally: fa.set() fb.set() def test_connection_refused(): try: da = DummyHandler() fa = Event() sa = RawServer(fa, 100, 100) loop(sa) sl(sa, da, beginport + 6) sleep(.5) ca = sa.start_connection(('127.0.0.1', beginport + 15)) sleep(1) assert da.external_made == [] assert da.data_in == [] assert da.lost == [ca] del da.lost[:] finally: fa.set() def test_both_close(): try: da = DummyHandler() fa = Event() sa = RawServer(fa, 100, 100) loop(sa) sl(sa, da, beginport + 4) sleep(1) db = DummyHandler() fb = Event() sb = RawServer(fb, 100, 100) loop(sb) sl(sb, db, beginport + 5) sleep(.5) ca = sa.start_connection(('127.0.0.1', beginport + 5)) sleep(1) assert da.external_made == [] assert da.data_in == [] assert da.lost == [] assert len(db.external_made) == 1 cb = db.external_made[0] del db.external_made[:] assert db.data_in == [] assert db.lost == [] ca.write('aaa') cb.write('bbb') sleep(1) assert da.external_made == [] assert da.data_in == [(ca, 'bbb')] del da.data_in[:] assert da.lost == [] assert db.external_made == [] assert db.data_in == [(cb, 'aaa')] del db.data_in[:] assert db.lost == [] ca.write('ccc') cb.write('ddd') sleep(1) assert da.external_made == [] assert da.data_in == [(ca, 'ddd')] del da.data_in[:] assert da.lost == [] assert db.external_made == [] assert db.data_in == [(cb, 'ccc')] del db.data_in[:] assert db.lost == [] ca.close() cb.close() sleep(1) assert da.external_made == [] assert da.data_in == [] assert da.lost == [] assert db.external_made == [] assert db.data_in == [] assert db.lost == [] finally: fa.set() fb.set() def test_normal(): l = [] f = Event() s = RawServer(f, 100, 100) loop(s) sl(s, DummyHandler(), beginport + 7) s.add_task(lambda l = l: l.append('b'), 2) s.add_task(lambda l = l: l.append('a'), 1) s.add_task(lambda l = l: l.append('d'), 4) sleep(1.5) s.add_task(lambda l = l: l.append('c'), 1.5) sleep(3) assert l == ['a', 'b', 'c', 'd'] f.set() def test_catch_exception(): l = [] f = Event() s = RawServer(f, 100, 100, False) loop(s) sl(s, DummyHandler(), beginport + 9) s.add_task(lambda l = l: l.append('b'), 2) s.add_task(lambda: 4/0, 1) sleep(3) assert l == ['b'] f.set() def test_closes_if_not_hit(): try: da = DummyHandler() fa = Event() sa = RawServer(fa, 2, 2) loop(sa) sl(sa, da, beginport + 14) sleep(1) db = DummyHandler() fb = Event() sb = RawServer(fb, 100, 100) loop(sb) sl(sb, db, beginport + 13) sleep(.5) sa.start_connection(('127.0.0.1', beginport + 13)) sleep(1) assert da.external_made == [] assert da.data_in == [] assert da.lost == [] assert len(db.external_made) == 1 del db.external_made[:] assert db.data_in == [] assert db.lost == [] sleep(3.1) assert len(da.lost) == 1 assert len(db.lost) == 1 finally: fa.set() fb.set() def test_does_not_close_if_hit(): try: fa = Event() fb = Event() da = DummyHandler() sa = RawServer(fa, 2, 2) loop(sa) sl(sa, da, beginport + 12) sleep(1) db = DummyHandler() sb = RawServer(fb, 100, 100) loop(sb) sl(sb, db, beginport + 13) sleep(.5) sa.start_connection(('127.0.0.1', beginport + 13)) sleep(1) assert da.external_made == [] assert da.data_in == [] assert da.lost == [] assert len(db.external_made) == 1 cb = db.external_made[0] del db.external_made[:] assert db.data_in == [] assert db.lost == [] cb.write('bbb') sleep(.5) assert da.lost == [] assert db.lost == [] finally: fa.set() fb.set() BitTorrent-3.4.2/BitTorrent/Rerequester.py0100644000202400020240000001302510033434505017201 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from zurllib import urlopen, quote from btformats import check_peers from bencode import bdecode from threading import Thread, Lock from socket import error from time import time from random import randrange from binascii import b2a_hex class Rerequester: def __init__(self, url, interval, sched, howmany, minpeers, connect, externalsched, amount_left, up, down, port, ip, myid, infohash, timeout, errorfunc, maxpeers, doneflag, upratefunc, downratefunc, ever_got_incoming): self.url = ('%s?info_hash=%s&peer_id=%s&port=%s&key=%s' % (url, quote(infohash), quote(myid), str(port), b2a_hex(''.join([chr(randrange(256)) for i in xrange(4)])))) if ip != '': self.url += '&ip=' + quote(ip) self.interval = interval self.last = None self.trackerid = None self.announce_interval = 30 * 60 self.sched = sched self.howmany = howmany self.minpeers = minpeers self.connect = connect self.externalsched = externalsched self.amount_left = amount_left self.up = up self.down = down self.timeout = timeout self.errorfunc = errorfunc self.maxpeers = maxpeers self.doneflag = doneflag self.upratefunc = upratefunc self.downratefunc = downratefunc self.ever_got_incoming = ever_got_incoming self.last_failed = True self.last_time = 0 def c(self): self.sched(self.c, self.interval) if self.ever_got_incoming(): getmore = self.howmany() <= self.minpeers / 3 else: getmore = self.howmany() < self.minpeers if getmore or time() - self.last_time > self.announce_interval: self.announce() def begin(self): self.sched(self.c, self.interval) self.announce(0) def announce(self, event = None): self.last_time = time() s = ('%s&uploaded=%s&downloaded=%s&left=%s' % (self.url, str(self.up()), str(self.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.maxpeers: s += '&numwant=0' else: s += '&compact=1' if event != None: s += '&event=' + ['started', 'completed', 'stopped'][event] set = SetOnce().set def checkfail(self = self, set = set): if set(): if self.last_failed and self.upratefunc() < 100 and self.downratefunc() < 100: self.errorfunc('Problem connecting to tracker - timeout exceeded') self.last_failed = True self.sched(checkfail, self.timeout) Thread(target = self.rerequest, args = [s, set]).start() def rerequest(self, url, set): try: h = urlopen(url) r = h.read() h.close() if set(): def add(self = self, r = r): self.last_failed = False self.postrequest(r) self.externalsched(add, 0) except (IOError, error), e: if set(): def fail(self = self, r = 'Problem connecting to tracker - ' + str(e)): if self.last_failed: self.errorfunc(r) self.last_failed = True self.externalsched(fail, 0) def postrequest(self, data): try: r = bdecode(data) check_peers(r) if r.has_key('failure reason'): self.errorfunc('rejected by tracker - ' + r['failure reason']) else: if r.has_key('warning message'): self.errorfunc('warning from tracker - ' + r['warning message']) self.announce_interval = r.get('interval', self.announce_interval) self.interval = r.get('min interval', self.interval) self.trackerid = r.get('tracker id', self.trackerid) self.last = r.get('last') p = r['peers'] peers = [] if type(p) == type(''): 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.maxpeers: 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]) except ValueError, e: if data != '': self.errorfunc('bad data from tracker - ' + str(e)) class SetOnce: def __init__(self): self.lock = Lock() self.first = True def set(self): try: self.lock.acquire() r = self.first self.first = False return r finally: self.lock.release() BitTorrent-3.4.2/BitTorrent/Storage.py0100644000202400020240000001163507730671244016321 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from sha import sha from bisect import bisect_right class Storage: def __init__(self, files, open, exists, getsize): # can raise IOError and ValueError self.ranges = [] total = 0l so_far = 0l for file, length in files: if length != 0: self.ranges.append((total, total + length, file)) total += length if exists(file): l = getsize(file) if l > length: l = length so_far += l elif not exists(file): open(file, 'wb').close() self.begins = [i[0] for i in self.ranges] self.total_length = total self.handles = {} self.whandles = {} self.tops = {} for file, length in files: if exists(file): l = getsize(file) if l != length: self.handles[file] = open(file, 'rb+') self.whandles[file] = 1 if l > length: self.handles[file].truncate(length) else: self.handles[file] = open(file, 'rb') self.tops[file] = l else: self.handles[file] = open(file, 'wb+') self.whandles[file] = 1 def was_preallocated(self, pos, length): for file, begin, end in self._intervals(pos, length): if self.tops.get(file, 0) < end: return False return True def set_readonly(self): # may raise IOError or OSError for file in self.whandles.keys(): old = self.handles[file] old.flush() old.close() self.handles[file] = open(file, 'rb') 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, file = self.ranges[p] r.append((file, max(pos, begin) - begin, min(end, stop) - begin)) p += 1 return r def read(self, pos, amount): r = [] for file, pos, end in self._intervals(pos, amount): h = self.handles[file] h.seek(pos) r.append(h.read(end - pos)) return ''.join(r) def write(self, pos, s): # might raise an IOError total = 0 for file, begin, end in self._intervals(pos, len(s)): if not self.whandles.has_key(file): self.handles[file].close() self.handles[file] = open(file, 'rb+') self.whandles[file] = 1 h = self.handles[file] h.seek(begin) h.write(s[total: total + end - begin]) total += end - begin def close(self): for h in self.handles.values(): h.close() def lrange(a, b, c): r = [] while a < b: r.append(a) a += c return r # everything below is for testing from fakeopen import FakeOpen def test_Storage_simple(): f = FakeOpen() m = Storage([('a', 5)], f.open, f.exists, f.getsize) assert f.files.keys() == ['a'] m.write(0, 'abc') assert m.read(0, 3) == 'abc' m.write(2, 'abc') assert m.read(2, 3) == 'abc' m.write(1, 'abc') assert m.read(0, 5) == 'aabcc' def test_Storage_multiple(): f = FakeOpen() m = Storage([('a', 5), ('2', 4), ('c', 3)], f.open, f.exists, f.getsize) x = f.files.keys() x.sort() assert x == ['2', 'a', 'c'] m.write(3, 'abc') assert m.read(3, 3) == 'abc' m.write(5, 'ab') assert m.read(4, 3) == 'bab' m.write(3, 'pqrstuvw') assert m.read(3, 8) == 'pqrstuvw' m.write(3, 'abcdef') assert m.read(3, 7) == 'abcdefv' def test_Storage_zero(): f = FakeOpen() Storage([('a', 0)], f.open, f.exists, f.getsize) assert f.files == {'a': []} def test_resume_zero(): f = FakeOpen({'a': ''}) Storage([('a', 0)], f.open, f.exists, f.getsize) assert f.files == {'a': []} def test_Storage_with_zero(): f = FakeOpen() m = Storage([('a', 3), ('b', 0), ('c', 3)], f.open, f.exists, f.getsize) m.write(2, 'abc') assert m.read(2, 3) == 'abc' x = f.files.keys() x.sort() assert x == ['a', 'b', 'c'] assert len(f.files['a']) == 3 assert len(f.files['b']) == 0 def test_Storage_resume(): f = FakeOpen({'a': 'abc'}) m = Storage([('a', 4)], f.open, f.exists, f.getsize) assert f.files.keys() == ['a'] assert m.read(0, 3) == 'abc' def test_Storage_mixed_resume(): f = FakeOpen({'b': 'abc'}) m = Storage([('a', 3), ('b', 4)], f.open, f.exists, f.getsize) x = f.files.keys() x.sort() assert x == ['a', 'b'] assert m.read(3, 3) == 'abc' BitTorrent-3.4.2/BitTorrent/StorageWrapper.py0100644000202400020240000004120510032200346017632 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from sha import sha from threading import Event from bitfield import Bitfield def dummy_status(fractionDone = None, activity = None): pass def dummy_data_flunked(size): pass class StorageWrapper: def __init__(self, storage, request_size, hashes, piece_size, finished, failed, statusfunc = dummy_status, flag = Event(), check_hashes = True, data_flunked = dummy_data_flunked): self.storage = storage self.request_size = request_size self.hashes = hashes self.piece_size = piece_size self.data_flunked = data_flunked self.total_length = storage.get_total_length() self.amount_left = self.total_length if self.total_length <= piece_size * (len(hashes) - 1): raise ValueError, 'bad data from tracker - total too small' if self.total_length > piece_size * len(hashes): raise ValueError, 'bad data from tracker - total too big' self.finished = finished self.failed = failed self.numactive = [0] * len(hashes) self.inactive_requests = [1] * len(hashes) self.amount_inactive = self.total_length self.endgame = False self.have = Bitfield(len(hashes)) self.waschecked = [check_hashes] * len(hashes) self.places = {} self.holes = [] if len(hashes) == 0: finished() return targets = {} total = len(hashes) for i in xrange(len(hashes)): if not self._waspre(i): targets.setdefault(hashes[i], []).append(i) total -= 1 numchecked = 0.0 if total and check_hashes: statusfunc({"activity" : 'checking existing file', "fractionDone" : 0}) def markgot(piece, pos, self = self, check_hashes = check_hashes): self.places[piece] = pos self.have[piece] = True self.amount_left -= self._piecelen(piece) self.amount_inactive -= self._piecelen(piece) self.inactive_requests[piece] = None self.waschecked[piece] = check_hashes lastlen = self._piecelen(len(hashes) - 1) for i in xrange(len(hashes)): if not self._waspre(i): self.holes.append(i) elif not check_hashes: markgot(i, i) else: sh = sha(self.storage.read(piece_size * i, lastlen)) sp = sh.digest() sh.update(self.storage.read(piece_size * i + lastlen, self._piecelen(i) - lastlen)) s = sh.digest() if s == hashes[i]: markgot(i, i) elif targets.get(s) and self._piecelen(i) == self._piecelen(targets[s][-1]): markgot(targets[s].pop(), i) elif not self.have[len(hashes) - 1] and sp == hashes[-1] and (i == len(hashes) - 1 or not self._waspre(len(hashes) - 1)): markgot(len(hashes) - 1, i) else: self.places[i] = i if flag.isSet(): return numchecked += 1 statusfunc({'fractionDone': 1 - float(self.amount_left) / self.total_length}) if self.amount_left == 0: finished() def _waspre(self, piece): return self.storage.was_preallocated(piece * self.piece_size, self._piecelen(piece)) def _piecelen(self, piece): if piece < len(self.hashes) - 1: return self.piece_size else: return self.total_length - piece * self.piece_size 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 = min(self.piece_size, self.total_length - self.piece_size * index) l = [] x = 0 while x + self.request_size < length: l.append((x, self.request_size)) x += self.request_size l.append((x, length - x)) self.inactive_requests[index] = l def is_endgame(self): return self.endgame 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 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): try: return self._piece_came_in(index, begin, piece) except IOError, e: self.failed('IO Error ' + str(e)) return True def _piece_came_in(self, index, begin, piece): if not self.places.has_key(index): n = self.holes.pop(0) if self.places.has_key(n): oldpos = self.places[n] old = self.storage.read(self.piece_size * oldpos, self._piecelen(n)) if self.have[n] and sha(old).digest() != self.hashes[n]: self.failed('data corrupted on disk - maybe you have two copies running?') return True self.storage.write(self.piece_size * n, old) self.places[n] = n if index == oldpos or index in self.holes: self.places[index] = oldpos else: for p, v in self.places.items(): if v == index: break self.places[index] = index self.places[p] = oldpos old = self.storage.read(self.piece_size * index, self.piece_size) self.storage.write(self.piece_size * oldpos, old) elif index in self.holes or index == n: if not self._waspre(n): self.storage.write(self.piece_size * n, self._piecelen(n) * chr(0xFF)) self.places[index] = n else: for p, v in self.places.items(): if v == index: break self.places[index] = index self.places[p] = n old = self.storage.read(self.piece_size * index, self._piecelen(n)) self.storage.write(self.piece_size * n, old) self.storage.write(self.places[index] * self.piece_size + begin, piece) self.numactive[index] -= 1 if not self.inactive_requests[index] and not self.numactive[index]: if sha(self.storage.read(self.piece_size * self.places[index], self._piecelen(index))).digest() == self.hashes[index]: self.have[index] = True self.inactive_requests[index] = None self.waschecked[index] = True self.amount_left -= self._piecelen(index) if self.amount_left == 0: self.finished() else: self.data_flunked(self._piecelen(index)) self.inactive_requests[index] = 1 self.amount_inactive += self._piecelen(index) 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 def get_piece(self, index, begin, length): try: return self._get_piece(index, begin, length) except IOError, e: self.failed('IO Error ' + str(e)) return None 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]: self.failed('told file complete on start-up, but piece failed hash check') return None self.waschecked[index] = True if begin + length > self._piecelen(index): return None return self.storage.read(self.piece_size * self.places[index] + begin, length) class DummyStorage: def __init__(self, total, pre = False, ranges = []): self.pre = pre self.ranges = ranges self.s = chr(0xFF) * total self.done = False def was_preexisting(self): return self.pre def was_preallocated(self, begin, length): for b, l in self.ranges: if begin >= b and begin + length <= b + l: return True return False def get_total_length(self): return len(self.s) def read(self, begin, length): return self.s[begin:begin + length] def write(self, begin, piece): self.s = self.s[:begin] + piece + self.s[begin + len(piece):] def finished(self): self.done = True def test_basic(): ds = DummyStorage(3) sw = StorageWrapper(ds, 2, [sha('abc').digest()], 4, ds.finished, None) assert sw.get_amount_left() == 3 assert not sw.do_I_have_anything() assert sw.get_have_list() == chr(0) assert sw.do_I_have_requests(0) x = [] x.append(sw.new_request(0)) assert sw.do_I_have_requests(0) x.append(sw.new_request(0)) assert not sw.do_I_have_requests(0) x.sort() assert x == [(0, 2), (2, 1)] sw.request_lost(0, 2, 1) del x[-1] assert sw.do_I_have_requests(0) x.append(sw.new_request(0)) assert x == [(0, 2), (2, 1)] assert not sw.do_I_have_requests(0) sw.piece_came_in(0, 0, 'ab') assert not sw.do_I_have_requests(0) assert sw.get_amount_left() == 3 assert not sw.do_I_have_anything() assert sw.get_have_list() == chr(0) assert not ds.done sw.piece_came_in(0, 2, 'c') assert not sw.do_I_have_requests(0) assert sw.get_amount_left() == 0 assert sw.do_I_have_anything() assert sw.get_have_list() == chr(0x80) assert sw.get_piece(0, 0, 3) == 'abc' assert sw.get_piece(0, 1, 2) == 'bc' assert sw.get_piece(0, 0, 2) == 'ab' assert sw.get_piece(0, 1, 1) == 'b' assert ds.done def test_two_pieces(): ds = DummyStorage(4) sw = StorageWrapper(ds, 3, [sha('abc').digest(), sha('d').digest()], 3, ds.finished, None) assert sw.get_amount_left() == 4 assert not sw.do_I_have_anything() assert sw.get_have_list() == chr(0) assert sw.do_I_have_requests(0) assert sw.do_I_have_requests(1) assert sw.new_request(0) == (0, 3) assert sw.get_amount_left() == 4 assert not sw.do_I_have_anything() assert sw.get_have_list() == chr(0) assert not sw.do_I_have_requests(0) assert sw.do_I_have_requests(1) assert sw.new_request(1) == (0, 1) assert sw.get_amount_left() == 4 assert not sw.do_I_have_anything() assert sw.get_have_list() == chr(0) assert not sw.do_I_have_requests(0) assert not sw.do_I_have_requests(1) sw.piece_came_in(0, 0, 'abc') assert sw.get_amount_left() == 1 assert sw.do_I_have_anything() assert sw.get_have_list() == chr(0x80) assert not sw.do_I_have_requests(0) assert not sw.do_I_have_requests(1) assert sw.get_piece(0, 0, 3) == 'abc' assert not ds.done sw.piece_came_in(1, 0, 'd') assert ds.done assert sw.get_amount_left() == 0 assert sw.do_I_have_anything() assert sw.get_have_list() == chr(0xC0) assert not sw.do_I_have_requests(0) assert not sw.do_I_have_requests(1) assert sw.get_piece(1, 0, 1) == 'd' def test_hash_fail(): ds = DummyStorage(4) sw = StorageWrapper(ds, 4, [sha('abcd').digest()], 4, ds.finished, None) assert sw.get_amount_left() == 4 assert not sw.do_I_have_anything() assert sw.get_have_list() == chr(0) assert sw.do_I_have_requests(0) assert sw.new_request(0) == (0, 4) sw.piece_came_in(0, 0, 'abcx') assert sw.get_amount_left() == 4 assert not sw.do_I_have_anything() assert sw.get_have_list() == chr(0) assert sw.do_I_have_requests(0) assert sw.new_request(0) == (0, 4) assert not ds.done sw.piece_came_in(0, 0, 'abcd') assert ds.done assert sw.get_amount_left() == 0 assert sw.do_I_have_anything() assert sw.get_have_list() == chr(0x80) assert not sw.do_I_have_requests(0) def test_lazy_hashing(): ds = DummyStorage(4, ranges = [(0, 4)]) flag = Event() sw = StorageWrapper(ds, 4, [sha('abcd').digest()], 4, ds.finished, lambda x, flag = flag: flag.set(), check_hashes = False) assert sw.get_piece(0, 0, 2) is None assert flag.isSet() def test_lazy_hashing_pass(): ds = DummyStorage(4) flag = Event() sw = StorageWrapper(ds, 4, [sha(chr(0xFF) * 4).digest()], 4, ds.finished, lambda x, flag = flag: flag.set(), check_hashes = False) assert sw.get_piece(0, 0, 2) is None assert not flag.isSet() def test_preexisting(): ds = DummyStorage(4, True, [(0, 4)]) sw = StorageWrapper(ds, 2, [sha(chr(0xFF) * 2).digest(), sha('ab').digest()], 2, ds.finished, None) assert sw.get_amount_left() == 2 assert sw.do_I_have_anything() assert sw.get_have_list() == chr(0x80) assert not sw.do_I_have_requests(0) assert sw.do_I_have_requests(1) assert sw.new_request(1) == (0, 2) assert not ds.done sw.piece_came_in(1, 0, 'ab') assert ds.done assert sw.get_amount_left() == 0 assert sw.do_I_have_anything() assert sw.get_have_list() == chr(0xC0) assert not sw.do_I_have_requests(0) assert not sw.do_I_have_requests(1) def test_total_too_short(): ds = DummyStorage(4) try: StorageWrapper(ds, 4, [sha(chr(0xff) * 4).digest(), sha(chr(0xFF) * 4).digest()], 4, ds.finished, None) raise 'fail' except ValueError: pass def test_total_too_big(): ds = DummyStorage(9) try: sw = StorageWrapper(ds, 4, [sha('qqqq').digest(), sha(chr(0xFF) * 4).digest()], 4, ds.finished, None) raise 'fail' except ValueError: pass def test_end_above_total_length(): ds = DummyStorage(3, True) sw = StorageWrapper(ds, 4, [sha('qqq').digest()], 4, ds.finished, None) assert sw.get_piece(0, 0, 4) == None def test_end_past_piece_end(): ds = DummyStorage(4, True, ranges = [(0, 4)]) sw = StorageWrapper(ds, 4, [sha(chr(0xFF) * 2).digest(), sha(chr(0xFF) * 2).digest()], 2, ds.finished, None) assert ds.done assert sw.get_piece(0, 0, 3) == None from random import shuffle def test_alloc_random(): ds = DummyStorage(101) sw = StorageWrapper(ds, 1, [sha(chr(i)).digest() for i in xrange(101)], 1, ds.finished, None) for i in xrange(100): assert sw.new_request(i) == (0, 1) r = range(100) shuffle(r) for i in r: sw.piece_came_in(i, 0, chr(i)) for i in xrange(100): assert sw.get_piece(i, 0, 1) == chr(i) assert ds.s[:100] == ''.join([chr(i) for i in xrange(100)]) def test_alloc_resume(): ds = DummyStorage(101) sw = StorageWrapper(ds, 1, [sha(chr(i)).digest() for i in xrange(101)], 1, ds.finished, None) for i in xrange(100): assert sw.new_request(i) == (0, 1) r = range(100) shuffle(r) for i in r[:50]: sw.piece_came_in(i, 0, chr(i)) assert ds.s[50:] == chr(0xFF) * 51 ds.ranges = [(0, 50)] sw = StorageWrapper(ds, 1, [sha(chr(i)).digest() for i in xrange(101)], 1, ds.finished, None) for i in r[50:]: sw.piece_came_in(i, 0, chr(i)) assert ds.s[:100] == ''.join([chr(i) for i in xrange(100)]) def test_last_piece_pre(): ds = DummyStorage(3, ranges = [(2, 1)]) ds.s = chr(0xFF) + chr(0xFF) + 'c' sw = StorageWrapper(ds, 2, [sha('ab').digest(), sha('c').digest()], 2, ds.finished, None) assert not sw.do_I_have_requests(1) assert sw.do_I_have_requests(0) def test_not_last_pre(): ds = DummyStorage(3, ranges = [(1, 1)]) ds.s = chr(0xFF) + 'a' + chr(0xFF) sw = StorageWrapper(ds, 1, [sha('a').digest()] * 3, 1, ds.finished, None) assert not sw.do_I_have_requests(1) assert sw.do_I_have_requests(0) assert sw.do_I_have_requests(2) def test_last_piece_not_pre(): ds = DummyStorage(51, ranges = [(50, 1)]) sw = StorageWrapper(ds, 2, [sha('aa').digest()] * 25 + [sha('b').digest()], 2, ds.finished, None) for i in xrange(25): assert sw.new_request(i) == (0, 2) assert sw.new_request(25) == (0, 1) sw.piece_came_in(25, 0, 'b') r = range(25) shuffle(r) for i in r: sw.piece_came_in(i, 0, 'aa') assert ds.done assert ds.s == 'a' * 50 + 'b' BitTorrent-3.4.2/BitTorrent/Uploader.py0100644000202400020240000001757307730671244016477 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from CurrentRateMeasure import Measure class Upload: def __init__(self, connection, choker, storage, max_slice_length, max_rate_period, fudge): self.connection = connection self.choker = choker self.storage = storage self.max_slice_length = max_slice_length self.max_rate_period = max_rate_period self.choked = True self.interested = False self.buffer = [] self.measure = Measure(max_rate_period, fudge) 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 flushed(self): while len(self.buffer) > 0 and self.connection.is_flushed(): index, begin, length = self.buffer[0] del self.buffer[0] piece = self.storage.get_piece(index, begin, length) if piece is None: self.connection.close() return self.measure.update_rate(len(piece)) self.connection.send_piece(index, begin, piece) def got_request(self, index, begin, length): if not self.interested or length > self.max_slice_length: self.connection.close() return if not self.choked: self.buffer.append((index, begin, length)) self.flushed() 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 del self.buffer[:] self.connection.send_choke() def unchoke(self): if self.choked: self.choked = False self.connection.send_unchoke() def is_choked(self): return self.choked def is_interested(self): return self.interested def has_queries(self): return len(self.buffer) > 0 def get_rate(self): return self.measure.get_rate() class DummyConnection: def __init__(self, events): self.events = events self.flushed = False def send_bitfield(self, bitfield): self.events.append(('bitfield', bitfield)) def is_flushed(self): return self.flushed def close(self): self.events.append('closed') def send_piece(self, index, begin, piece): self.events.append(('piece', index, begin, piece)) def send_choke(self): self.events.append('choke') def send_unchoke(self): self.events.append('unchoke') class DummyChoker: def __init__(self, events): self.events = events def interested(self, connection): self.events.append('interested') def not_interested(self, connection): self.events.append('not interested') class DummyStorage: def __init__(self, events): self.events = events def do_I_have_anything(self): self.events.append('do I have') return True def get_have_list(self): self.events.append('get have list') return [False, True] def get_piece(self, index, begin, length): self.events.append(('get piece', index, begin, length)) if length == 4: return None return 'a' * length def test_skip_over_choke(): events = [] dco = DummyConnection(events) dch = DummyChoker(events) ds = DummyStorage(events) u = Upload(dco, dch, ds, 100, 20, 5) assert u.is_choked() assert not u.is_interested() u.got_interested() assert u.is_interested() u.got_request(0, 0, 3) dco.flushed = True u.flushed() assert events == ['do I have', 'get have list', ('bitfield', [False, True]), 'interested'] def test_bad_piece(): events = [] dco = DummyConnection(events) dch = DummyChoker(events) ds = DummyStorage(events) u = Upload(dco, dch, ds, 100, 20, 5) assert u.is_choked() assert not u.is_interested() u.got_interested() assert u.is_interested() u.unchoke() assert not u.is_choked() u.got_request(0, 0, 4) dco.flushed = True u.flushed() assert events == ['do I have', 'get have list', ('bitfield', [False, True]), 'interested', 'unchoke', ('get piece', 0, 0, 4), 'closed'] def test_still_rejected_after_unchoke(): events = [] dco = DummyConnection(events) dch = DummyChoker(events) ds = DummyStorage(events) u = Upload(dco, dch, ds, 100, 20, 5) assert u.is_choked() assert not u.is_interested() u.got_interested() assert u.is_interested() u.unchoke() assert not u.is_choked() u.got_request(0, 0, 3) u.choke() u.unchoke() dco.flushed = True u.flushed() assert events == ['do I have', 'get have list', ('bitfield', [False, True]), 'interested', 'unchoke', 'choke', 'unchoke'] def test_sends_when_flushed(): events = [] dco = DummyConnection(events) dch = DummyChoker(events) ds = DummyStorage(events) u = Upload(dco, dch, ds, 100, 20, 5) u.unchoke() u.got_interested() u.got_request(0, 1, 3) dco.flushed = True u.flushed() u.flushed() assert events == ['do I have', 'get have list', ('bitfield', [False, True]), 'unchoke', 'interested', ('get piece', 0, 1, 3), ('piece', 0, 1, 'aaa')] def test_sends_immediately(): events = [] dco = DummyConnection(events) dch = DummyChoker(events) ds = DummyStorage(events) u = Upload(dco, dch, ds, 100, 20, 5) u.unchoke() u.got_interested() dco.flushed = True u.got_request(0, 1, 3) assert events == ['do I have', 'get have list', ('bitfield', [False, True]), 'unchoke', 'interested', ('get piece', 0, 1, 3), ('piece', 0, 1, 'aaa')] def test_cancel(): events = [] dco = DummyConnection(events) dch = DummyChoker(events) ds = DummyStorage(events) u = Upload(dco, dch, ds, 100, 20, 5) u.unchoke() u.got_interested() u.got_request(0, 1, 3) u.got_cancel(0, 1, 3) u.got_cancel(0, 1, 2) u.flushed() dco.flushed = True assert events == ['do I have', 'get have list', ('bitfield', [False, True]), 'unchoke', 'interested'] def test_clears_on_not_interested(): events = [] dco = DummyConnection(events) dch = DummyChoker(events) ds = DummyStorage(events) u = Upload(dco, dch, ds, 100, 20, 5) u.unchoke() u.got_interested() u.got_request(0, 1, 3) u.got_not_interested() dco.flushed = True u.flushed() assert events == ['do I have', 'get have list', ('bitfield', [False, True]), 'unchoke', 'interested', 'not interested'] def test_close_when_sends_on_not_interested(): events = [] dco = DummyConnection(events) dch = DummyChoker(events) ds = DummyStorage(events) u = Upload(dco, dch, ds, 100, 20, 5) u.got_request(0, 1, 3) assert events == ['do I have', 'get have list', ('bitfield', [False, True]), 'closed'] def test_close_over_max_length(): events = [] dco = DummyConnection(events) dch = DummyChoker(events) ds = DummyStorage(events) u = Upload(dco, dch, ds, 100, 20, 5) u.got_interested() u.got_request(0, 1, 101) assert events == ['do I have', 'get have list', ('bitfield', [False, True]), 'interested', 'closed'] def test_no_bitfield_on_start_empty(): events = [] dco = DummyConnection(events) dch = DummyChoker(events) ds = DummyStorage(events) ds.do_I_have_anything = lambda: False u = Upload(dco, dch, ds, 100, 20, 5) assert events == [] BitTorrent-3.4.2/BitTorrent/__init__.py0100644000202400020240000000002210034050651016420 0ustar brambramversion = '3.4.2' BitTorrent-3.4.2/BitTorrent/bencode.py0100644000202400020240000001561410027366775016320 0ustar brambram# Written by Petru Paler # see LICENSE.txt for license information def decode_int(x, f): f += 1 newf = x.index('e', f) try: n = int(x[f:newf]) except (OverflowError, ValueError): n = long(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) try: n = int(x[f:colon]) except (OverflowError, ValueError): n = long(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): raise ValueError if l != len(x): raise ValueError return r def test_bdecode(): try: bdecode('0:0:') assert 0 except ValueError: pass try: bdecode('ie') assert 0 except ValueError: pass try: bdecode('i341foo382e') assert 0 except ValueError: pass assert bdecode('i4e') == 4L assert bdecode('i0e') == 0L assert bdecode('i123456789e') == 123456789L assert bdecode('i-10e') == -10L try: bdecode('i-0e') assert 0 except ValueError: pass try: bdecode('i123') assert 0 except ValueError: pass try: bdecode('') assert 0 except ValueError: pass try: bdecode('i6easd') assert 0 except ValueError: pass try: bdecode('35208734823ljdahflajhdf') assert 0 except ValueError: pass try: bdecode('2:abfdjslhfld') assert 0 except ValueError: pass assert bdecode('0:') == '' assert bdecode('3:abc') == 'abc' assert bdecode('10:1234567890') == '1234567890' try: bdecode('02:xy') assert 0 except ValueError: pass try: bdecode('l') assert 0 except ValueError: pass assert bdecode('le') == [] try: bdecode('leanfdldjfh') assert 0 except ValueError: pass assert bdecode('l0:0:0:e') == ['', '', ''] try: bdecode('relwjhrlewjh') assert 0 except ValueError: pass assert bdecode('li1ei2ei3ee') == [1, 2, 3] assert bdecode('l3:asd2:xye') == ['asd', 'xy'] assert bdecode('ll5:Alice3:Bobeli2ei3eee') == [['Alice', 'Bob'], [2, 3]] try: bdecode('d') assert 0 except ValueError: pass try: bdecode('defoobar') assert 0 except ValueError: pass assert bdecode('de') == {} assert bdecode('d3:agei25e4:eyes4:bluee') == {'age': 25, 'eyes': 'blue'} assert bdecode('d8:spam.mp3d6:author5:Alice6:lengthi100000eee') == {'spam.mp3': {'author': 'Alice', 'length': 100000}} try: bdecode('d3:fooe') assert 0 except ValueError: pass try: bdecode('di1e0:e') assert 0 except ValueError: pass try: bdecode('d1:b0:1:a0:e') assert 0 except ValueError: pass try: bdecode('d1:a0:1:a0:e') assert 0 except ValueError: pass try: bdecode('i03e') assert 0 except ValueError: pass try: bdecode('l01:ae') assert 0 except ValueError: pass try: bdecode('9999:x') assert 0 except ValueError: pass try: bdecode('l0:') assert 0 except ValueError: pass try: bdecode('d0:0:') assert 0 except ValueError: pass try: bdecode('d0:') assert 0 except ValueError: pass try: bdecode('00:') assert 0 except ValueError: pass try: bdecode('l-3:e') assert 0 except ValueError: pass try: bdecode('i-03e') assert 0 except ValueError: pass bdecode('d0:i3ee') 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_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[type(Bencached(0))] = 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_int except ImportError: pass def bencode(x): r = [] encode_func[type(x)](x, r) return ''.join(r) def test_bencode(): assert bencode(4) == 'i4e' assert bencode(0) == 'i0e' assert bencode(-10) == 'i-10e' assert bencode(12345678901234567890L) == 'i12345678901234567890e' assert bencode('') == '0:' assert bencode('abc') == '3:abc' assert bencode('1234567890') == '10:1234567890' assert bencode([]) == 'le' assert bencode([1, 2, 3]) == 'li1ei2ei3ee' assert bencode([['Alice', 'Bob'], [2, 3]]) == 'll5:Alice3:Bobeli2ei3eee' assert bencode({}) == 'de' assert bencode({'age': 25, 'eyes': 'blue'}) == 'd3:agei25e4:eyes4:bluee' assert bencode({'spam.mp3': {'author': 'Alice', 'length': 100000}}) == 'd8:spam.mp3d6:author5:Alice6:lengthi100000eee' assert bencode(Bencached(bencode(3))) == 'i3e' try: bencode({1: 'foo'}) except TypeError: return assert 0 try: import psyco psyco.bind(bdecode) psyco.bind(bencode) except ImportError: pass BitTorrent-3.4.2/BitTorrent/bitfield.py0100644000202400020240000000722510034050651016457 0ustar brambram# Written by Bram Cohen, Uoti Urpala, and John Hoffman # see LICENSE.txt for license information try: True except: True = 1 False = 0 bool = lambda x: not not x try: sum([1]) negsum = lambda a: len(a)-sum(a) except: negsum = lambda a: reduce(lambda x,y: x+(not y), a, 0) def _int_to_booleans(x): r = [] for i in range(8): r.append(bool(x & 0x80)) x <<= 1 return tuple(r) lookup_table = [_int_to_booleans(i) for i in range(256)] reverse_lookup_table = {} for i in xrange(256): reverse_lookup_table[lookup_table[i]] = chr(i) class Bitfield: def __init__(self, length, bitstring = None): self.length = length if bitstring is not None: extra = len(bitstring) * 8 - length if extra < 0 or extra >= 8: raise ValueError t = lookup_table r = [] for c in bitstring: r.extend(t[ord(c)]) if extra > 0: if r[-extra:] != [0] * extra: raise ValueError del r[-extra:] self.array = r self.numfalse = negsum(r) else: self.array = [False] * length self.numfalse = length def __setitem__(self, index, val): val = bool(val) self.numfalse += self.array[index]-val self.array[index] = val def __getitem__(self, index): return self.array[index] def __len__(self): return self.length def tostring(self): booleans = self.array t = reverse_lookup_table s = len(booleans) % 8 r = [ t[tuple(booleans[x:x+8])] for x in xrange(0, len(booleans)-s, 8) ] if s: r += t[tuple(booleans[-s:] + ([0] * (8-s)))] return ''.join(r) def complete(self): return not self.numfalse def test_bitfield(): try: x = Bitfield(7, 'ab') assert False except ValueError: pass try: x = Bitfield(7, 'ab') assert False except ValueError: pass try: x = Bitfield(9, 'abc') assert False except ValueError: pass try: x = Bitfield(0, 'a') assert False except ValueError: pass try: x = Bitfield(1, '') assert False except ValueError: pass try: x = Bitfield(7, '') assert False except ValueError: pass try: x = Bitfield(8, '') assert False except ValueError: pass try: x = Bitfield(9, 'a') assert False except ValueError: pass try: x = Bitfield(7, chr(1)) assert False except ValueError: pass try: x = Bitfield(9, chr(0) + chr(0x40)) assert False except ValueError: pass assert Bitfield(0, '').tostring() == '' assert Bitfield(1, chr(0x80)).tostring() == chr(0x80) assert Bitfield(7, chr(0x02)).tostring() == chr(0x02) assert Bitfield(8, chr(0xFF)).tostring() == chr(0xFF) assert Bitfield(9, chr(0) + chr(0x80)).tostring() == chr(0) + chr(0x80) x = Bitfield(1) assert x.numfalse == 1 x[0] = 1 assert x.numfalse == 0 x[0] = 1 assert x.numfalse == 0 assert x.tostring() == chr(0x80) x = Bitfield(7) assert len(x) == 7 x[6] = 1 assert x.numfalse == 6 assert x.tostring() == chr(0x02) x = Bitfield(8) x[7] = 1 assert x.tostring() == chr(1) x = Bitfield(9) x[8] = 1 assert x.numfalse == 8 assert x.tostring() == chr(0) + chr(0x80) x = Bitfield(8, chr(0xC4)) assert len(x) == 8 assert x.numfalse == 5 assert x.tostring() == chr(0xC4) BitTorrent-3.4.2/BitTorrent/btformats.py0100644000202400020240000000736710027366775016730 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from types import StringType, LongType, IntType, ListType, DictType from re import compile reg = compile(r'^[^/\\.~][^/\\]*$') ints = (LongType, IntType) def check_info(info): if type(info) != DictType: raise ValueError, 'bad metainfo - not a dictionary' pieces = info.get('pieces') if type(pieces) != StringType or len(pieces) % 20 != 0: raise ValueError, 'bad metainfo - bad pieces key' piecelength = info.get('piece length') if type(piecelength) not in ints or piecelength <= 0: raise ValueError, 'bad metainfo - illegal piece length' name = info.get('name') if type(name) != StringType: raise ValueError, 'bad metainfo - bad name' if not reg.match(name): raise ValueError, 'name %s disallowed for security reasons' % name if info.has_key('files') == info.has_key('length'): raise ValueError, 'single/multiple file mix' if info.has_key('length'): length = info.get('length') if type(length) not in ints or length < 0: raise ValueError, 'bad metainfo - bad length' else: files = info.get('files') if type(files) != ListType: raise ValueError for f in files: if type(f) != DictType: raise ValueError, 'bad metainfo - bad file value' length = f.get('length') if type(length) not in ints or length < 0: raise ValueError, 'bad metainfo - bad length' path = f.get('path') if type(path) != ListType or path == []: raise ValueError, 'bad metainfo - bad path' for p in path: if type(p) != StringType: raise ValueError, 'bad metainfo - bad path dir' if not reg.match(p): raise ValueError, 'path %s disallowed for security reasons' % p for i in xrange(len(files)): for j in xrange(i): if files[i]['path'] == files[j]['path']: raise ValueError, 'bad metainfo - duplicate path' def check_message(message): if type(message) != DictType: raise ValueError check_info(message.get('info')) if type(message.get('announce')) != StringType: raise ValueError def check_peers(message): if type(message) != DictType: raise ValueError if message.has_key('failure reason'): if type(message['failure reason']) != StringType: raise ValueError return peers = message.get('peers') if type(peers) == ListType: for p in peers: if type(p) != DictType: raise ValueError if type(p.get('ip')) != StringType: raise ValueError port = p.get('port') if type(port) not in ints or p <= 0: raise ValueError if p.has_key('peer id'): id = p.get('peer id') if type(id) != StringType or len(id) != 20: raise ValueError elif type(peers) != StringType or len(peers) % 6 != 0: raise ValueError interval = message.get('interval', 1) if type(interval) not in ints or interval <= 0: raise ValueError minint = message.get('min interval', 1) if type(minint) not in ints or minint <= 0: raise ValueError if type(message.get('tracker id', '')) != StringType: raise ValueError npeers = message.get('num peers', 0) if type(npeers) not in ints or npeers < 0: raise ValueError dpeers = message.get('done peers', 0) if type(dpeers) not in ints or dpeers < 0: raise ValueError last = message.get('last', 0) if type(last) not in ints or last < 0: raise ValueError BitTorrent-3.4.2/BitTorrent/download.py0100644000202400020240000003076710033434505016516 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from zurllib import urlopen from urlparse import urljoin from btformats import check_message from Choker import Choker from Storage import Storage from StorageWrapper import StorageWrapper from Uploader import Upload from Downloader import Downloader from Connecter import Connecter from Encrypter import Encoder from RawServer import RawServer from Rerequester import Rerequester from DownloaderFeedback import DownloaderFeedback from RateMeasure import RateMeasure from CurrentRateMeasure import Measure from PiecePicker import PiecePicker from bencode import bencode, bdecode from __init__ import version from binascii import b2a_hex from sha import sha from os import path, makedirs from parseargs import parseargs, formatDefinitions from socket import error as socketerror from random import seed from threading import Thread, Event from time import time try: from os import getpid except ImportError: def getpid(): return 1 defaults = [ ('max_uploads', 7, "the maximum number of uploads to allow at once."), ('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."), ('request_backlog', 5, "how many requests to keep in a single pipe at once."), ('max_message_length', 2 ** 23, "maximum length prefix encoding you'll accept over the wire - larger values get the connection dropped."), ('ip', '', "ip to report you have to the tracker."), ('minport', 6881, 'minimum port to listen on, counts up if unavailable'), ('maxport', 6999, 'maximum port to listen on'), ('responsefile', '', 'file the server response was stored in, alternative to url'), ('url', '', 'url to get file from, alternative to responsefile'), ('saveas', '', 'local file name to save the file as, null indicates query user'), ('timeout', 300.0, 'time to wait between closing sockets which nothing has been received on'), ('timeout_check_interval', 60.0, 'time to wait between checking if any connections have timed out'), ('max_slice_length', 2 ** 17, "maximum length slice to send to peers, larger requests are ignored"), ('max_rate_period', 20.0, "maximum amount of time to guess the current rate estimate represents"), ('bind', '', 'ip to bind to locally'), ('upload_rate_fudge', 5.0, 'time equivalent of writing to kernel-level TCP buffer, for rate adjustment'), ('display_interval', .5, 'time between updates of displayed information'), ('rerequest_interval', 5 * 60, 'time to wait between requesting more peers'), ('min_peers', 20, 'minimum number of peers to not do rerequesting'), ('http_timeout', 60, 'number of seconds to wait before assuming that an http connection has timed out'), ('max_initiate', 35, 'number of peers at which to stop initiating new connections'), ('max_allow_in', 55, 'maximum number of connections to allow, after this new incoming connections will be immediately closed'), ('check_hashes', 1, 'whether to check hashes on disk'), ('max_upload_rate', 0, 'maximum kB/s to upload at, 0 means no limit'), ('snub_time', 30.0, "seconds to wait for data to come in over a connection before assuming it's semi-permanently choked"), ('spew', 0, "whether to display diagnostic info to stdout"), ('rarest_first_cutoff', 4, "number of downloads at which to switch from random to rarest first"), ('min_uploads', 4, "the number of uploads to fill out to with extra optimistic unchokes"), ('report_hash_failures', 0, "whether to inform the user that hash failures occur. They're non-fatal."), ] def download(params, filefunc, statusfunc, finfunc, errorfunc, doneflag, cols, pathFunc = None, paramfunc = None, spewflag = Event()): if len(params) == 0: errorfunc('arguments are -\n' + formatDefinitions(defaults, cols)) return try: config, args = parseargs(params, defaults, 0, 1) if args: if config.get('responsefile', None) == None: raise ValueError, 'must have responsefile as arg or parameter, not both' if path.isfile(args[0]): config['responsefile'] = args[0] else: config['url'] = args[0] if (config['responsefile'] == '') == (config['url'] == ''): raise ValueError, 'need responsefile or url' except ValueError, e: errorfunc('error: ' + str(e) + '\nrun with no args for parameter explanations') return try: if config['responsefile'] != '': h = open(config['responsefile'], 'rb') else: h = urlopen(config['url']) response = h.read() h.close() except IOError, e: if config['responsefile'] != '' and config['responsefile'].find('Temporary Internet Files') != -1: errorfunc('BitTorrent was passed a filename that doesn\'t exist. ' + 'Either clear your Temporary Internet Files or right-click the link ' + 'and save the .torrent to disk first.') else: errorfunc('problem getting response info - ' + str(e)) return try: response = bdecode(response) check_message(response) except ValueError, e: errorfunc("got bad file info - " + str(e)) return try: def make(f, forcedir = False): if not forcedir: f = path.split(f)[0] if f != '' and not path.exists(f): makedirs(f) info = response['info'] if info.has_key('length'): file_length = info['length'] file = filefunc(info['name'], file_length, config['saveas'], False) if file is None: return make(file) files = [(file, file_length)] else: file_length = 0 for x in info['files']: file_length += x['length'] file = filefunc(info['name'], file_length, config['saveas'], True) if file is None: return # if this path exists, and no files from the info dict exist, we assume it's a new download and # the user wants to create a new directory with the default name existing = 0 if path.exists(file): for x in info['files']: if path.exists(path.join(file, x['path'][0])): existing = 1 if not existing: file = path.join(file, info['name']) make(file, True) # alert the UI to any possible change in path if pathFunc != None: pathFunc(file) files = [] for x in info['files']: n = file for i in x['path']: n = path.join(n, i) files.append((n, x['length'])) make(n) except OSError, e: errorfunc("Couldn't allocate dir - " + str(e)) return finflag = Event() ann = [None] myid = 'M' + version.replace('.', '-') myid = myid + ('-' * (8 - len(myid))) + b2a_hex(sha(repr(time()) + ' ' + str(getpid())).digest()[-6:]) seed(myid) pieces = [info['pieces'][x:x+20] for x in xrange(0, len(info['pieces']), 20)] def failed(reason, errorfunc = errorfunc, doneflag = doneflag): doneflag.set() if reason is not None: errorfunc(reason) rawserver = RawServer(doneflag, config['timeout_check_interval'], config['timeout'], errorfunc = errorfunc, maxconnects = config['max_allow_in']) try: try: storage = Storage(files, open, path.exists, path.getsize) except IOError, e: errorfunc('trouble accessing files - ' + str(e)) return def finished(finfunc = finfunc, finflag = finflag, ann = ann, storage = storage, errorfunc = errorfunc): finflag.set() try: storage.set_readonly() except (IOError, OSError), e: errorfunc('trouble setting readonly at end - ' + str(e)) if ann[0] is not None: ann[0](1) finfunc() rm = [None] def data_flunked(amount, rm = rm, errorfunc = errorfunc, report_hash_failures = config['report_hash_failures']): if rm[0] is not None: rm[0](amount) if report_hash_failures: errorfunc('a piece failed hash check, re-downloading it') storagewrapper = StorageWrapper(storage, config['download_slice_size'], pieces, info['piece length'], finished, failed, statusfunc, doneflag, config['check_hashes'], data_flunked) except ValueError, e: failed('bad data - ' + str(e)) except IOError, e: failed('IOError - ' + str(e)) if doneflag.isSet(): return e = 'maxport less than minport - no ports to check' for listen_port in xrange(config['minport'], config['maxport'] + 1): try: rawserver.bind(listen_port, config['bind']) break except socketerror, e: pass else: errorfunc("Couldn't listen - " + str(e)) return choker = Choker(config['max_uploads'], rawserver.add_task, finflag.isSet, config['min_uploads']) upmeasure = Measure(config['max_rate_period'], config['upload_rate_fudge']) downmeasure = Measure(config['max_rate_period']) def make_upload(connection, choker = choker, storagewrapper = storagewrapper, max_slice_length = config['max_slice_length'], max_rate_period = config['max_rate_period'], fudge = config['upload_rate_fudge']): return Upload(connection, choker, storagewrapper, max_slice_length, max_rate_period, fudge) ratemeasure = RateMeasure(storagewrapper.get_amount_left()) rm[0] = ratemeasure.data_rejected picker = PiecePicker(len(pieces), config['rarest_first_cutoff']) for i in xrange(len(pieces)): if storagewrapper.do_I_have(i): picker.complete(i) downloader = Downloader(storagewrapper, picker, config['request_backlog'], config['max_rate_period'], len(pieces), downmeasure, config['snub_time'], ratemeasure.data_came_in) connecter = Connecter(make_upload, downloader, choker, len(pieces), upmeasure, config['max_upload_rate'] * 1024, rawserver.add_task) infohash = sha(bencode(info)).digest() encoder = Encoder(connecter, rawserver, myid, config['max_message_length'], rawserver.add_task, config['keepalive_interval'], infohash, config['max_initiate']) rerequest = Rerequester(response['announce'], config['rerequest_interval'], rawserver.add_task, connecter.how_many_connections, config['min_peers'], encoder.start_connection, rawserver.add_task, storagewrapper.get_amount_left, upmeasure.get_total, downmeasure.get_total, listen_port, config['ip'], myid, infohash, config['http_timeout'], errorfunc, config['max_initiate'], doneflag, upmeasure.get_rate, downmeasure.get_rate, encoder.ever_got_incoming) if config['spew']: spewflag.set() DownloaderFeedback(choker, rawserver.add_task, statusfunc, upmeasure.get_rate, downmeasure.get_rate, upmeasure.get_total, downmeasure.get_total, ratemeasure.get_time_left, ratemeasure.get_size_left, file_length, finflag, config['display_interval'], spewflag) # useful info and functions for the UI if paramfunc: paramfunc({ 'max_upload_rate' : connecter.change_max_upload_rate, # change_max_upload_rate() 'max_uploads': choker.change_max_uploads, # change_max_uploads() 'listen_port' : listen_port, # int 'peer_id' : myid, # string 'info_hash' : infohash, # string 'start_connection' : encoder._start_connection # start_connection((, ), ) }) statusfunc({"activity" : 'connecting to peers'}) ann[0] = rerequest.announce rerequest.begin() rawserver.listen_forever(encoder) storage.close() rerequest.announce(2) BitTorrent-3.4.2/BitTorrent/fakeopen.py0100644000202400020240000000430007413652275016475 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from string import join class FakeHandle: def __init__(self, name, fakeopen): self.name = name self.fakeopen = fakeopen self.pos = 0 def flush(self): pass def close(self): pass def seek(self, pos): self.pos = pos def read(self, amount = None): old = self.pos f = self.fakeopen.files[self.name] if self.pos >= len(f): return '' if amount is None: self.pos = len(f) return join(f[old:], '') else: self.pos = min(len(f), old + amount) return join(f[old:self.pos], '') def write(self, s): f = self.fakeopen.files[self.name] while len(f) < self.pos: f.append(chr(0)) self.fakeopen.files[self.name][self.pos : self.pos + len(s)] = list(s) self.pos += len(s) class FakeOpen: def __init__(self, initial = {}): self.files = {} for key, value in initial.items(): self.files[key] = list(value) def open(self, filename, mode): """currently treats everything as rw - doesn't support append""" self.files.setdefault(filename, []) return FakeHandle(filename, self) def exists(self, file): return self.files.has_key(file) def getsize(self, file): return len(self.files[file]) def test_normal(): f = FakeOpen({'f1': 'abcde'}) assert f.exists('f1') assert not f.exists('f2') assert f.getsize('f1') == 5 h = f.open('f1', 'rw') assert h.read(3) == 'abc' assert h.read(1) == 'd' assert h.read() == 'e' assert h.read(2) == '' h.write('fpq') h.seek(4) assert h.read(2) == 'ef' h.write('ghij') h.seek(0) assert h.read() == 'abcdefghij' h.seek(2) h.write('p') h.write('q') assert h.read(1) == 'e' h.seek(1) assert h.read(5) == 'bpqef' h2 = f.open('f2', 'rw') assert h2.read() == '' h2.write('mnop') h2.seek(1) assert h2.read() == 'nop' assert f.exists('f1') assert f.exists('f2') assert f.getsize('f1') == 10 assert f.getsize('f2') == 4 BitTorrent-3.4.2/BitTorrent/parseargs.py0100644000202400020240000000677310027366775016716 0ustar brambram# Written by Bill Bumgarner and Bram Cohen # see LICENSE.txt for license information from types import * from cStringIO import StringIO def formatDefinitions(options, COLS): s = StringIO() indent = " " * 10 width = COLS - 11 if width < 15: width = COLS - 2 indent = " " for (longname, default, doc) in options: s.write('--' + longname + ' \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 ValueError(str) def parseargs(argv, options, minargs = None, maxargs = None): config = {} longkeyed = {} for option in options: longname, default, doc = option longkeyed[longname] = option config[longname] = default options = [] args = [] pos = 0 while pos < len(argv): if argv[pos][:2] != '--': args.append(argv[pos]) pos += 1 else: if pos == len(argv) - 1: usage('parameter passed in at end with no value') key, value = argv[pos][2:], argv[pos+1] pos += 2 if not longkeyed.has_key(key): usage('unknown key --' + key) longname, default, doc = longkeyed[key] try: t = type(config[longname]) if t is NoneType or t is StringType: config[longname] = value elif t in (IntType, LongType): config[longname] = long(value) elif t is FloatType: config[longname] = float(value) else: assert 0 except ValueError, e: usage('wrong format of --%s - %s' % (key, str(e))) for key, value in config.items(): if value is None: usage("Option --%s is required." % key) if minargs is not None and len(args) < minargs: usage("Must supply at least %d args." % minargs) if maxargs is not None and len(args) > maxargs: usage("Too many args - %d max." % maxargs) return (config, args) def test_parseargs(): assert parseargs(('d', '--a', 'pq', 'e', '--b', '3', '--c', '4.5', 'f'), (('a', 'x', ''), ('b', 1, ''), ('c', 2.3, ''))) == ({'a': 'pq', 'b': 3, 'c': 4.5}, ['d', 'e', 'f']) assert parseargs([], [('a', 'x', '')]) == ({'a': 'x'}, []) assert parseargs(['--a', 'x', '--a', 'y'], [('a', '', '')]) == ({'a': 'y'}, []) try: parseargs([], [('a', 'x', '')]) except ValueError: pass try: parseargs(['--a', 'x'], []) except ValueError: pass try: parseargs(['--a'], [('a', 'x', '')]) except ValueError: pass try: parseargs([], [], 1, 2) except ValueError: pass assert parseargs(['x'], [], 1, 2) == ({}, ['x']) assert parseargs(['x', 'y'], [], 1, 2) == ({}, ['x', 'y']) try: parseargs(['x', 'y', 'z'], [], 1, 2) except ValueError: pass try: parseargs(['--a', '2.0'], [('a', 3, '')]) except ValueError: pass try: parseargs(['--a', 'z'], [('a', 2.1, '')]) except ValueError: pass BitTorrent-3.4.2/BitTorrent/selectpoll.py0100644000202400020240000000435707576453313017071 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information 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: 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) def test_remove(): x = [2, 4, 6] remove(x, 2) assert x == [4, 6] x = [2, 4, 6] remove(x, 4) assert x == [2, 6] x = [2, 4, 6] remove(x, 6) assert x == [2, 4] x = [2, 4, 6] remove(x, 5) assert x == [2, 4, 6] x = [2, 4, 6] remove(x, 1) assert x == [2, 4, 6] x = [2, 4, 6] remove(x, 7) assert x == [2, 4, 6] x = [2, 4, 6] remove(x, 5) assert x == [2, 4, 6] x = [] remove(x, 3) assert x == [] def test_insert(): x = [2, 4] insert(x, 1) assert x == [1, 2, 4] x = [2, 4] insert(x, 3) assert x == [2, 3, 4] x = [2, 4] insert(x, 5) assert x == [2, 4, 5] x = [2, 4] insert(x, 2) assert x == [2, 4] x = [2, 4] insert(x, 4) assert x == [2, 4] x = [2, 3, 4] insert(x, 3) assert x == [2, 3, 4] x = [] insert(x, 3) assert x == [3] BitTorrent-3.4.2/BitTorrent/testtest.py0100644000202400020240000000400407730671244016564 0ustar brambram""" A much simpler testing framework than PyUnit tests a module by running all functions in it whose name starts with 'test' a test fails if it raises an exception, otherwise it passes functions are try_all and try_single """ # Written by Bram Cohen # see LICENSE.txt for license information from traceback import print_exc from sys import modules def try_all(excludes = [], excluded_paths=[]): """ tests all imported modules takes an optional list of module names and/or module objects to skip over. modules from files under under any of excluded_paths are also skipped. """ failed = [] for modulename, module in modules.items(): # skip builtins if not hasattr(module, '__file__'): continue # skip modules under any of excluded_paths if [p for p in excluded_paths if module.__file__.startswith(p)]: continue if modulename not in excludes and module not in excludes: try_module(module, modulename, failed) print_failed(failed) def try_single(m): """ tests a single module accepts either a module object or a module name in string form """ if type(m) is str: modulename = m module = __import__(m) else: modulename = str(m) module = m failed = [] try_module(module, modulename, failed) print_failed(failed) def try_module(module, modulename, failed): if not hasattr(module, '__dict__'): return for n, func in module.__dict__.items(): if not callable(func) or n[:4] != 'test': continue name = modulename + '.' + n try: print 'trying ' + name func() print 'passed ' + name except: print_exc() failed.append(name) print 'failed ' + name def print_failed(failed): print if len(failed) == 0: print 'everything passed' else: print 'the following tests failed:' for i in failed: print i BitTorrent-3.4.2/BitTorrent/track.py0100644000202400020240000006003510027366775016022 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information from parseargs import parseargs, formatDefinitions from RawServer import RawServer from HTTPHandler import HTTPHandler from NatCheck import NatCheck from threading import Event from bencode import bencode, bdecode, Bencached from zurllib import urlopen, quote, unquote from urlparse import urlparse from os import rename from os.path import exists, isfile from cStringIO import StringIO from time import time, gmtime, strftime from random import shuffle from sha import sha from types import StringType, IntType, LongType, ListType, DictType from binascii import b2a_hex, a2b_hex, a2b_base64 import sys from __init__ import version defaults = [ ('port', 80, "Port to listen on."), ('dfile', None, 'file to store recent downloader info in'), ('bind', '', 'ip to bind to locally'), ('socket_timeout', 15, 'timeout for closing connections'), ('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, 'number of peers to send in an info message'), ('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)"), ('min_time_between_log_flushes', 3.0, 'minimum time it must have been since the last flush to do another one'), ('allowed_dir', '', 'only allow downloads for .torrents in this dir'), ('parse_allowed_interval', 15, 'minutes between reloading of allowed_dir'), ('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', 1, "ignore the ip GET parameter from machines which aren't on local network IPs"), ('logfile', '', 'file to write the tracker logs, use - for stdout (default)'), ('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)'), ('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 id, info in y.items(): # ... of client ids interested in that torrent if (len(id) != 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): # ... for the number of reported completions for that torrent raise ValueError def parseTorrents(dir): import os a = {} for f in os.listdir(dir): if f[-8:] == '.torrent': try: p = os.path.join(dir,f) d = bdecode(open(p, 'rb').read()) h = sha(bencode(d['info'])).digest() i = d['info'] a[h] = {} a[h]['name'] = i.get('name', f) a[h]['file'] = f a[h]['path'] = p l = 0 if i.has_key('length'): l = i.get('length',0) elif i.has_key('files'): for li in i['files']: if li.has_key('length'): l = l + li['length'] a[h]['length'] = l except: # what now, boss? print "Error parsing " + f, sys.exc_info()[0] return a 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)) def compact_peer_info(ip, port): return ''.join([chr(int(i)) for i in ip.split('.')]) + chr((port & 0xFF00) >> 8) + chr(port & 0xFF) class Tracker: def __init__(self, config, rawserver): self.response_size = config['response_size'] self.dfile = config['dfile'] self.natcheck = config['nat_check'] self.max_give = config['max_give'] self.reannounce_interval = config['reannounce_interval'] self.save_dfile_interval = config['save_dfile_interval'] self.show_names = config['show_names'] self.only_local_override_ip = config['only_local_override_ip'] favicon = config['favicon'] self.favicon = None if favicon: if isfile(favicon): h = open(favicon, 'rb') self.favicon = h.read() h.close() else: print "**warning** specified favicon file -- %s -- does not exist." % favicon self.rawserver = rawserver self.becache1 = {} self.becache2 = {} self.cache1 = {} self.cache2 = {} self.times = {} if exists(self.dfile): h = open(self.dfile, 'rb') ds = h.read() h.close() tempstate = bdecode(ds) else: tempstate = {} if tempstate.has_key('peers'): self.state = tempstate else: self.state = {} self.state['peers'] = tempstate self.downloads = self.state.setdefault('peers', {}) self.completed = self.state.setdefault('completed', {}) statefiletemplate(self.state) for x, dl in self.downloads.items(): self.times[x] = {} for y, dat in dl.items(): self.times[x][y] = 0 if not dat.get('nat',1): ip = dat['ip'] gip = dat.get('given ip') if gip and is_valid_ipv4(gip) and (not self.only_local_override_ip or is_local_ip(ip)): ip = gip self.becache1.setdefault(x,{})[y] = Bencached(bencode({'ip': ip, 'port': dat['port'], 'peer id': y})) self.becache2.setdefault(x,{})[y] = compact_peer_info(ip, dat['port']) rawserver.add_task(self.save_dfile, self.save_dfile_interval) self.prevtime = time() self.timeout_downloaders_interval = config['timeout_downloaders_interval'] rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval) self.logfile = None self.log = None if (config['logfile'] != '') and (config['logfile'] != '-'): try: self.logfile = config['logfile'] self.log = open(self.logfile,'a') sys.stdout = self.log print "# Log Started: ", isotime() except: print "Error trying to redirect stdout to log file:", sys.exc_info()[0] self.allow_get = config['allow_get'] if config['allowed_dir'] != '': self.allowed_dir = config['allowed_dir'] self.parse_allowed_interval = config['parse_allowed_interval'] self.parse_allowed() else: self.allowed = None if unquote('+') != ' ': self.uq_broken = 1 else: self.uq_broken = 0 self.keep_dead = config['keep_dead'] def get(self, connection, path, headers): try: (scheme, netloc, path, pars, query, fragment) = urlparse(path) if self.uq_broken == 1: path = path.replace('+',' ') query = query.replace('+',' ') path = unquote(path)[1:] params = {} for s in query.split('&'): if s != '': i = s.index('=') params[unquote(s[:i])] = unquote(s[i+1:]) except ValueError, e: return (400, 'Bad Request', {'Content-Type': 'text/plain'}, 'you sent me garbage - ' + str(e)) if path == '' or path == 'index.html': s = StringIO() s.write('\n' \ 'BitTorrent download info\n') if self.favicon != None: s.write('\n') s.write('\n\n' \ '

BitTorrent download info

\n'\ '
    \n' '
  • tracker version: %s
  • \n' \ '
  • server time: %s
  • \n' \ '
\n' % (version, isotime())) names = self.downloads.keys() if names: names.sort() tn = 0 tc = 0 td = 0 tt = 0 # Total transferred ts = 0 # Total size nf = 0 # Number of files displayed uc = {} ud = {} if self.allowed != None and self.show_names: s.write('\n' \ '\n') else: s.write('
info hashtorrent namesizecompletedownloadingdownloadedtransferred
\n' \ '\n') for name in names: l = self.downloads[name] n = self.completed.get(name, 0) tn = tn + n lc = [] for i in l.values(): if type(i) == DictType: if i['left'] == 0: lc.append(1) uc[i['ip']] = 1 else: ud[i['ip']] = 1 c = len(lc) tc = tc + c d = len(l) - c td = td + d if self.allowed != None and self.show_names: if self.allowed.has_key(name): nf = nf + 1 sz = self.allowed[name]['length'] # size ts = ts + sz szt = sz * n # Transferred for this torrent tt = tt + szt if self.allow_get == 1: linkname = '' + self.allowed[name]['name'] + '' else: linkname = self.allowed[name]['name'] s.write('\n' \ % (b2a_hex(name), linkname, size_format(sz), c, d, n, size_format(szt))) else: s.write('\n' \ % (b2a_hex(name), c, d, n)) ttn = 0 for i in self.completed.values(): ttn = ttn + i if self.allowed != None and self.show_names: s.write('\n' % (nf, size_format(ts), len(uc), tc, len(ud), td, tn, ttn, size_format(tt))) else: s.write('\n' % (nf, len(uc), tc, len(ud), td, tn, ttn)) s.write('
info hashcompletedownloadingdownloaded
%s%s%s%i%i%i%s
%s%i%i%i
%i files%s%i/%i%i/%i%i/%i%s
%i files%i/%i%i/%i%i/%i
\n' \ '
    \n' \ '
  • info hash: SHA1 hash of the "info" section of the metainfo (*.torrent)
  • \n' \ '
  • complete: number of connected clients with the complete file (total: unique IPs/total connections)
  • \n' \ '
  • downloading: number of connected clients still downloading (total: unique IPs/total connections)
  • \n' \ '
  • downloaded: reported complete downloads (total: current/all)
  • \n' \ '
  • transferred: torrent size * total downloaded (does not include partial transfers)
  • \n' \ '
\n') else: s.write('

not tracking any files yet...

\n') s.write('\n' \ '\n') return (200, 'OK', {'Content-Type': 'text/html; charset=iso-8859-1'}, s.getvalue()) elif path == 'scrape': fs = {} names = [] if params.has_key('info_hash'): if self.downloads.has_key(params['info_hash']): names = [ params['info_hash'] ] # else return nothing else: names = self.downloads.keys() names.sort() for name in names: l = self.downloads[name] n = self.completed.get(name, 0) c = len([1 for i in l.values() if type(i) == DictType and i['left'] == 0]) d = len(l) - c fs[name] = {'complete': c, 'incomplete': d, 'downloaded': n} if (self.allowed is not None) and self.allowed.has_key(name) and self.show_names: fs[name]['name'] = self.allowed[name]['name'] r = {'files': fs} return (200, 'OK', {'Content-Type': 'text/plain'}, bencode(r)) elif (path == 'file') and (self.allow_get == 1) and params.has_key('info_hash') and self.allowed.has_key(params['info_hash']): hash = params['info_hash'] fname = self.allowed[hash]['file'] fpath = self.allowed[hash]['path'] return (200, 'OK', {'Content-Type': 'application/x-bittorrent', 'Content-Disposition': 'attachment; filename=' + fname}, open(fpath, 'rb').read()) elif path == 'favicon.ico' and self.favicon != None: return (200, 'OK', {'Content-Type' : 'image/x-icon'}, self.favicon) if path != 'announce': return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas) try: if not params.has_key('info_hash'): raise ValueError, 'no info hash' if params.has_key('ip') and not is_valid_ipv4(params['ip']): raise ValueError('DNS name or invalid IP address given for IP') infohash = params['info_hash'] if self.allowed != None: if not self.allowed.has_key(infohash): return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason': 'Requested download is not authorized for use with this tracker.'})) ip = connection.get_ip() ip_override = 0 if params.has_key('ip') and is_valid_ipv4(params['ip']) and ( not self.only_local_override_ip or is_local_ip(ip)): ip_override = 1 if params.has_key('event') and params['event'] not in ['started', 'completed', 'stopped']: raise ValueError, 'invalid event' port = long(params.get('port', '')) uploaded = long(params.get('uploaded', '')) downloaded = long(params.get('downloaded', '')) left = long(params.get('left', '')) myid = params.get('peer_id', '') if len(myid) != 20: raise ValueError, 'id not of length 20' rsize = self.response_size if params.has_key('numwant'): rsize = min(long(params['numwant']), self.max_give) except ValueError, e: return (400, 'Bad Request', {'Content-Type': 'text/plain'}, 'you sent me garbage - ' + str(e)) peers = self.downloads.setdefault(infohash, {}) self.completed.setdefault(infohash, 0) ts = self.times.setdefault(infohash, {}) confirm = 0 if peers.has_key(myid): myinfo = peers[myid] if myinfo.has_key('key'): if params.get('key') != myinfo['key']: return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason': 'key did not match key supplied earlier'})) confirm = 1 elif myinfo['ip'] == ip: confirm = 1 else: confirm = 1 if params.get('event', '') != 'stopped' and confirm: ts[myid] = time() if not peers.has_key(myid): peers[myid] = {'ip': ip, 'port': port, 'left': left} if params.has_key('key'): peers[myid]['key'] = params['key'] if params.has_key('ip') and is_valid_ipv4(params['ip']): peers[myid]['given ip'] = params['ip'] mip = ip if ip_override: mip = params['ip'] if not self.natcheck or ip_override: self.becache1.setdefault(infohash,{})[myid] = Bencached(bencode({'ip': mip, 'port': port, 'peer id': myid})) self.becache2.setdefault(infohash,{})[myid] = compact_peer_info(mip, port) else: peers[myid]['left'] = left peers[myid]['ip'] = ip if params.get('event', '') == 'completed': self.completed[infohash] = 1 + self.completed[infohash] if port == 0: peers[myid]['nat'] = 2**30 elif self.natcheck and not ip_override: to_nat = peers[myid].get('nat', -1) if to_nat and to_nat < self.natcheck: NatCheck(self.connectback_result, infohash, myid, ip, port, self.rawserver) else: peers[myid]['nat'] = 0 elif confirm: if peers.has_key(myid): if self.becache1[infohash].has_key(myid): del self.becache1[infohash][myid] del self.becache2[infohash][myid] del peers[myid] del ts[myid] data = {'interval': self.reannounce_interval} if params.get('compact', 0): if rsize == 0: data['peers'] = '' else: cache = self.cache2.setdefault(infohash, []) if len(cache) < rsize: del cache[:] cache.extend(self.becache2.setdefault(infohash, {}).values()) shuffle(cache) del self.cache1.get(infohash, [])[:] data['peers'] = ''.join(cache[-rsize:]) del cache[-rsize:] else: if rsize == 0: data['peers'] = [] else: cache = self.cache1.setdefault(infohash, []) if len(cache) < rsize: del cache[:] cache.extend(self.becache1.setdefault(infohash, {}).values()) shuffle(cache) del self.cache2.get(infohash, [])[:] data['peers'] = cache[-rsize:] del cache[-rsize:] connection.answer((200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode(data))) def connectback_result(self, result, downloadid, peerid, ip, port): record = self.downloads.get(downloadid, {}).get(peerid) if record is None or record['ip'] != ip or record['port'] != port: return if not record.has_key('nat'): record['nat'] = int(not result) else: if result: record['nat'] = 0 else: record['nat'] += 1 if result: self.becache1.setdefault(downloadid,{})[peerid] = Bencached(bencode({'ip': ip, 'port': port, 'peer id': peerid})) self.becache2.setdefault(downloadid,{})[peerid] = compact_peer_info(ip, port) def save_dfile(self): self.rawserver.add_task(self.save_dfile, self.save_dfile_interval) h = open(self.dfile, 'wb') h.write(bencode(self.state)) h.close() def parse_allowed(self): self.rawserver.add_task(self.parse_allowed, self.parse_allowed_interval * 60) self.allowed = parseTorrents(self.allowed_dir) def expire_downloaders(self): for x in self.times.keys(): for myid, t in self.times[x].items(): if t < self.prevtime: if self.becache1.get(x, {}).has_key(myid): del self.becache1[x][myid] del self.becache2[x][myid] del self.times[x][myid] del self.downloads[x][myid] self.prevtime = time() if (self.keep_dead != 1): for key, value in self.downloads.items(): if len(value) == 0: del self.times[key] del self.downloads[key] self.rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval) def is_valid_ipv4(ip): try: x = compact_peer_info(ip, 0) if len(x) != 6: return False except (ValueError, IndexError): return False return True def is_local_ip(ip): try: v = [long(x) for x in ip.split('.')] if v[0] == 10 or v[0] == 127 or v[:2] in ([192, 168], [169, 254]): return 1 if v[0] == 172 and v[1] >= 16 and v[1] <= 31: return 1 except ValueError: return 0 def track(args): if len(args) == 0: print formatDefinitions(defaults, 80) return try: config, files = parseargs(args, defaults, 0, 0) except ValueError, e: print 'error: ' + str(e) print 'run with no arguments for parameter explanations' return r = RawServer(Event(), config['timeout_check_interval'], config['socket_timeout']) t = Tracker(config, r) r.bind(config['port'], config['bind'], True) r.listen_forever(HTTPHandler(t.get, config['min_time_between_log_flushes'])) t.save_dfile() print '# Shutting down: ' + isotime() def size_format(s): if (s < 1024): r = str(s) + 'B' elif (s < 1048576): r = str(int(s/1024)) + 'KiB' elif (s < 1073741824l): r = str(int(s/1048576)) + 'MiB' elif (s < 1099511627776l): 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) BitTorrent-3.4.2/BitTorrent/zurllib.py0100644000202400020240000001024507676711165016402 0ustar brambram# # 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 # from urllib import * from urllib2 import * from gzip import GzipFile from StringIO import StringIO from __init__ import version import pprint DEBUG=0 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") req.add_header('User-Agent', 'BitTorrent/' + version) if DEBUG: print "Sending:" print req.headers print "\n" fp = HTTPHandler.http_open(self,req) headers = fp.headers if DEBUG: pprint.pprint(headers.dict) url = fp.url return addinfourldecompress(fp, headers, url) 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)) if __name__ == '__main__': test() BitTorrent-3.4.2/.cvsignore0100644000202400020240000000000607603763201014227 0ustar brambram*.pyc BitTorrent-3.4.2/BUILD.windows.txt0100644000202400020240000000124610020544655015324 0ustar brambraminstall Python, version 2.0 or later - http://python.org/ install wxPython - http://wxpython.org/ install py2exe - http://starship.python.net/crew/theller/py2exe/ install the nullsoft installer - http://www.nullsoft.com/free/nsis/ the rest of these instructions can be abbreviated by just running build.bat in a shell, go to the root BitTorrent directory and run this command python winsetup.py py2exe now run nsis on bittorrent.nsi c:\progra~1\nsis\makensis.exe bittorrent.nsi This will create an installer called bittorrent.exe The installer is completely self-contained and will work on any Windows machine, even without the above software having been installed. BitTorrent-3.4.2/INSTALL.unix.txt0100644000202400020240000000141707737623576015111 0ustar brambraminstall Python, version 2.2.1 or later - http://python.org/ install wxPython - http://wxpython.org/ (under debian, you can currently get the above using apt-get install libwxgtk2.2-python from testing and use python 2.1) untar and put a line in /etc/mailcap which is similar to the following, only replace the path to /usr/bin/btdownloadgui.py with the one it's actually in. application/x-bittorrent; /usr/bin/btdownloadgui.py %s; test=test -n "$DISPLAY" You may have to restart your web browser for it to start using BitTorrent. If you're using a web browser which doesn't respect /etc/mailcap you can go into the mimetype configuration for your web browser and manually associate application/x-bittorrent with btdownloadgui.py (with the appropriate path, of course.) BitTorrent-3.4.2/LICENSE.txt0100644000202400020240000000223607505672664014076 0ustar brambramUnless otherwise noted, all files are released under the MIT license, exceptions contain licensing information in them. Copyright (C) 2001-2002 Bram Cohen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The Software is provided "AS IS", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software. BitTorrent-3.4.2/MANIFEST.in0100644000202400020240000000043707641560320013774 0ustar brambram# include anything in here that is not automatically picked up by distutils include BitTorrent/__init__.py # include all top level scripts include btdownloadlibrary.py include btdownloadheadless.py include btdownloadgui.py include btmakemetafile.py include bttrack.py include bttest.py BitTorrent-3.4.2/README.txt0100644000202400020240000001003407606643706013741 0ustar brambramBitTorrent is a tool for distributing files. It's extremely easy to use - downloads are started by clicking on hyperlinks. Whenever more than one person is downloading at once they send pieces of the file(s) to each other, thus relieving the central server's bandwidth burden. Even with many simultaneous downloads, the upload burden on the central server remains quite small, since each new downloader introduces new upload capacity. Windows web browser support is added by running an installer. A prebuilt one is available, but instructions for building it yourself are in BUILD.windows.txt Instructions for Unix installation are in INSTALL.unix.txt To start hosting - 1) start running a tracker First, you need a tracker. If you're on a dynamic IP or otherwise unreliable connection, you should find someone else's tracker and use that. Otherwise, follow the rest of this step. Trackers refer downloaders to each other. The load on the tracker is very small, so you only need one for all your files. To run a tracker, execute the command bttrack.py Here is an example - ./bttrack.py --port 6969 --dfile dstate --dfile is where persistent information is kept on the tracker across invocations. It makes everything start working again immediately if you restart the tracker. A new one will be created if it doesn't exist already. The tracker must be on a net-addressible box, and you must know the ip number or dns name of it. The tracker outputs web logs to standard out. You can get information about the files it's currently serving by getting its index page. 2) create a metainfo file using btmakemetafile.py To generate a metainfo file, run the publish btmakemetafile and give it the file you want metainfo for and the url of the tracker ./btmakemetafile.py myfile.ext http://my.tracker:6969/announce This will generate a file called myfile.ext.torrent Make sure to include the port number in the tracker url if it isn't 80. This command may take a while to scan over the whole file hashing it. The /announce path is special and hard-coded into the tracker. Make sure to give the domain or ip your tracker is on instead of my.tracker. You can use either a dns name or an IP address in the tracker url. 3) associate .torrent with application/x-bittorrent on your web server The way you do this is dependent on the particular web server you're using. You must have a web server which can serve ordinary static files and is addressable from the internet at large. 4) put the newly made .torrent file on your web server Note that the file name you choose on the server must end in .torrent, so it gets associated with the right mimetype. 5) put up a static page which links to the location you uploaded to in step 4 The file you uploaded in step 4 is linked to using an ordinary url. 6) start a downloader as a resume on the complete file You have to run a downloader which already has the complete file, so new downloaders have a place to get it from. Here's an example - ./btdownloadheadless.py --url http://my.server/myfile.torrent --saveas myfile.ext Make sure the saveas argument points to the already complete file. If you're running the complete downloader on the same machine or LAN as the tracker, give a --ip parameter to the complete downloader. The --ip parameter can be either an IP address or DNS name. BitTorrent defaults to port 6881. If it can't use 6881, (probably because another download is happening) it tries 6882, then 6883, etc. It gives up after 6889. 7) you're done! Now you just have to get people downloading! Refer them to the page you created in step 5. BitTorrent can also publish whole directories - simply point btmakemetafile.py at the directory with files in it, they'll be published as one unit. All files in subdirectories will be included, although files and directories named 'CVS' and 'core' are ignored. If you have any questions, try the web site or mailing list - http://bitconjurer.org/BitTorrent/ http://groups.yahoo.com/group/BitTorrent You can also often find me, Bram, in #bittorrent of irc.freenode.net BitTorrent-3.4.2/bittorrent.ico0100644000202400020240000000047607746623504015143 0ustar brambram(( wwwwwwptDDDDDDDDDGDDDD@DDDpDDDDDDDDGtDD@tDDDtDp ?fkZkZfBitTorrent-3.4.2/bittorrent.nsi0100644000202400020240000000315210034050651015131 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information !define VERSION "3.4.2" Outfile BitTorrent-${VERSION}.exe Name BitTorrent SilentInstall silent SetCompressor lzma InstallDir "$PROGRAMFILES\BitTorrent\" Section "Install" SetOutPath $INSTDIR WriteUninstaller "$INSTDIR\uninstall.exe" File dist\*.exe File dist\*.pyd File dist\*.dll File dist\library.zip File redirdonate.html File bittorrent.ico File LICENSE.txt WriteRegStr HKCR .torrent "" bittorrent DeleteRegKey HKCR ".torrent\Content Type" WriteRegStr HKCR "MIME\Database\Content Type\application/x-bittorrent" Extension .torrent WriteRegStr HKCR bittorrent "" "TORRENT File" WriteRegBin HKCR bittorrent EditFlags 00000100 WriteRegStr HKCR "bittorrent\shell" "" open WriteRegStr HKCR "bittorrent\shell\open\command" "" `"$INSTDIR\btdownloadgui.exe" --responsefile "%1"` WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BitTorrent" "DisplayName" "BitTorrent ${VERSION}" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BitTorrent" "UninstallString" '"$INSTDIR\uninstall.exe"' ExecShell open "$INSTDIR\redirdonate.html" Sleep 2000 MessageBox MB_OK "BitTorrent has been successfully installed!$\r$\n$\r$\nTo use BitTorrent, find a web site which uses it and click on the appropriate links." BringToFront SectionEnd Section "Uninstall" DeleteRegKey HKCR .torrent DeleteRegKey HKCR "MIME\Database\Content Type\application/x-bittorrent" DeleteRegKey HKCR bittorrent DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BitTorrent" RMDir /r "$INSTDIR" SectionEnd BitTorrent-3.4.2/btcompletedir.py0100644000202400020240000000213510021414574015435 0ustar brambram#!/usr/bin/env python # Written by Bram Cohen # see LICENSE.txt for license information from os.path import join, split from threading import Event from traceback import print_exc from sys import argv from btmakemetafile import calcsize, make_meta_file, ignore def dummy(x): pass def completedir(files, url, flag = Event(), vc = dummy, fc = dummy, piece_len_pow2 = None): files.sort() ext = '.torrent' togen = [] for f in files: if f[-len(ext):] != ext: togen.append(f) total = 0 for i in togen: total += calcsize(i) subtotal = [0] def callback(x, subtotal = subtotal, total = total, vc = vc): subtotal[0] += x vc(float(subtotal[0]) / total) for i in togen: t = split(i) if t[1] == '': i = t[0] fc(i) try: make_meta_file(i, url, flag = flag, progress = callback, progress_percent=0, piece_len_exp = piece_len_pow2) except ValueError: print_exc() def dc(v): print v if __name__ == '__main__': completedir(argv[2:], argv[1], fc = dc) BitTorrent-3.4.2/btcompletedirgui.py0100644000202400020240000001442410027113052016137 0ustar brambram#!/usr/bin/env python # Written by Bram Cohen # see LICENSE.txt for license information from sys import argv, version from btcompletedir import completedir from threading import Event, Thread from os.path import join, split from sys import argv from wxPython.wx import * from traceback import print_exc wxEVT_INVOKE = wxNewEventType() def EVT_INVOKE(win, func): win.Connect(-1, -1, wxEVT_INVOKE, func) class InvokeEvent(wxPyEvent): def __init__(self, func, args, kwargs): wxPyEvent.__init__(self) self.SetEventType(wxEVT_INVOKE) self.func = func self.args = args self.kwargs = kwargs class DownloadInfo: def __init__(self): frame = wxFrame(None, -1, 'BitTorrent complete dir 1.1', size = wxSize(550, 250)) self.frame = frame frame.SetIcon(wxIcon(join(split(argv[0])[0], 'bittorrent.ico'), wxBITMAP_TYPE_ICO)) panel = wxPanel(frame, -1) gridSizer = wxFlexGridSizer(cols = 2, rows = 2, vgap = 15, hgap = 8) gridSizer.Add(wxStaticText(panel, -1, 'Files to make .torrents for:')) self.dirCtl = wxTextCtrl(panel, -1, '') b = wxBoxSizer(wxHORIZONTAL) b.Add(self.dirCtl, 1, wxEXPAND) b.Add(10, 10, 0, wxEXPAND) button = wxButton(panel, -1, 'add file') b.Add(button, 0, wxEXPAND) EVT_BUTTON(frame, button.GetId(), self.select) b.Add(5, 5, 0, wxEXPAND) c = wxButton(panel, -1, 'add dir') b.Add(c, 0, wxEXPAND) EVT_BUTTON(frame, c.GetId(), self.selectdir) gridSizer.Add(b, 0, wxEXPAND) gridSizer.Add(wxStaticText(panel, -1, 'announce url:')) self.annCtl = wxTextCtrl(panel, -1, 'http://my.tracker:6969/announce') gridSizer.Add(self.annCtl, 0, wxEXPAND) gridSizer.Add(wxStaticText(panel, -1, 'piece size:')) self.piece_length = wxChoice(panel, -1, choices = ['2 ** 21', '2 ** 20', '2 ** 19', '2 ** 18', '2 ** 17', '2 ** 16', '2 ** 15']) self.piece_length.SetSelection(3) gridSizer.Add(self.piece_length) gridSizer.AddGrowableCol(1) border = wxBoxSizer(wxVERTICAL) border.Add(gridSizer, 0, wxEXPAND | wxNORTH | wxEAST | wxWEST, 25) b2 = wxButton(panel, -1, 'make') border.Add(10, 10, 1, wxEXPAND) border.Add(b2, 0, wxALIGN_CENTER | wxSOUTH, 20) EVT_BUTTON(frame, b2.GetId(), self.complete) panel.SetSizer(border) panel.SetAutoLayout(True) def select(self, x): dl = wxFileDialog(self.frame, "Choose files", '/', "", '*.*', wxOPEN | wxMULTIPLE) if dl.ShowModal() == wxID_OK: x = self.dirCtl.GetValue() + ';' + ';'.join(dl.GetPaths()) if x[0] == ';': x = x[1:] self.dirCtl.SetValue(x) def selectdir(self, x): dl = wxDirDialog(self.frame, 'Choose directories', '/') if dl.ShowModal() == wxID_OK: x = self.dirCtl.GetValue() + ';' + dl.GetPath() if x[0] == ';': x = x[1:] self.dirCtl.SetValue(x) def complete(self, x): if self.dirCtl.GetValue() == '': dlg = wxMessageDialog(self.frame, message = 'You must select a directory', caption = 'Error', style = wxOK | wxICON_ERROR) dlg.ShowModal() dlg.Destroy() return try: ps = 21 - self.piece_length.GetSelection() CompleteDir(self.dirCtl.GetValue().split(';'), self.annCtl.GetValue(), ps) except: print_exc() sleep(50000) class CompleteDir: def __init__(self, d, a, pl): self.d = d self.a = a self.pl = pl self.flag = Event() frame = wxFrame(None, -1, 'BitTorrent make .torrent', size = wxSize(550, 250)) self.frame = frame panel = wxPanel(frame, -1) gridSizer = wxFlexGridSizer(cols = 1, vgap = 15, hgap = 8) self.currentLabel = wxStaticText(panel, -1, 'checking file sizes') gridSizer.Add(self.currentLabel, 0, wxEXPAND) self.gauge = wxGauge(panel, -1, range = 1000, style = wxGA_SMOOTH) gridSizer.Add(self.gauge, 0, wxEXPAND) gridSizer.Add(10, 10, 1, wxEXPAND) self.button = wxButton(panel, -1, 'cancel') gridSizer.Add(self.button, 0, wxALIGN_CENTER) gridSizer.AddGrowableRow(2) gridSizer.AddGrowableCol(0) g2 = wxFlexGridSizer(cols = 1, vgap = 15, hgap = 8) g2.Add(gridSizer, 1, wxEXPAND | wxALL, 25) g2.AddGrowableRow(0) g2.AddGrowableCol(0) panel.SetSizer(g2) panel.SetAutoLayout(True) EVT_BUTTON(frame, self.button.GetId(), self.done) EVT_CLOSE(frame, self.done) EVT_INVOKE(frame, self.onInvoke) frame.Show(True) Thread(target = self.complete).start() def complete(self): try: completedir(self.d, self.a, self.flag, self.valcallback, self.filecallback, self.pl) if not self.flag.isSet(): self.currentLabel.SetLabel('Done!') self.gauge.SetValue(1000) self.button.SetLabel('Close') except (OSError, IOError), e: self.currentLabel.SetLabel('Error!') self.button.SetLabel('Close') dlg = wxMessageDialog(self.frame, message = 'Error - ' + str(e), caption = 'Error', style = wxOK | wxICON_ERROR) dlg.ShowModal() dlg.Destroy() def valcallback(self, amount): self.invokeLater(self.onval, [amount]) def onval(self, amount): self.gauge.SetValue(int(amount * 1000)) def filecallback(self, f): self.invokeLater(self.onfile, [f]) def onfile(self, f): self.currentLabel.SetLabel('building ' + join(self.d, f) + '.torrent') def onInvoke(self, event): if not self.flag.isSet(): apply(event.func, event.args, event.kwargs) def invokeLater(self, func, args = [], kwargs = {}): if not self.flag.isSet(): wxPostEvent(self.frame, InvokeEvent(func, args, kwargs)) def done(self, event): self.flag.set() self.frame.Destroy() class btWxApp(wxApp): def OnInit(self): d = DownloadInfo() d.frame.Show(True) self.SetTopWindow(d.frame) return True if __name__ == '__main__': btWxApp().MainLoop() BitTorrent-3.4.2/btdownloadcurses.py0100755000202400020240000002057007733336245016206 0ustar brambram#!/usr/bin/env python # Written by Henry 'Pi' James # see LICENSE.txt for license information from BitTorrent.download import download from threading import Event from os.path import abspath from signal import signal, SIGWINCH from sys import argv, stdout from time import strftime, time def fmttime(n): if n == -1: return 'download not progressing (file not being uploaded by others?)' if n == 0: return 'download complete!' n = int(n) m, s = divmod(n, 60) h, m = divmod(m, 60) if h > 1000000: return 'n/a' return 'finishing in %d:%02d:%02d' % (h, m, s) def commaize(n): s = str(n) commad = s[-3:] while len(s) > 3: s = s[:-3] commad = '%s,%s' % (s[-3:], commad) return commad def fmtsize(n, baseunit = 0, padded = 1): unit = [' B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] i = baseunit while i + 1 < len(unit) and n >= 999: i += 1 n = float(n) / (1 << 10) size = '' if padded: if n < 10: size = ' ' elif n < 100: size = ' ' if i != 0: size += '%.1f %s' % (n, unit[i]) else: if padded: size += '%.0f %s' % (n, unit[i]) else: size += '%.0f %s' % (n, unit[i]) return size def winch_handler(signum, stackframe): global scrwin, scrpan, labelwin, labelpan, fieldw, fieldh, fieldwin, fieldpan # SIGWINCH. Remake the frames! ## Curses Trickery curses.endwin() # delete scrwin somehow? scrwin.refresh() scrwin = curses.newwin(0, 0, 0, 0) scrh, scrw = scrwin.getmaxyx() scrpan = curses.panel.new_panel(scrwin) labelh, labelw, labely, labelx = scrh - 2, 9, 1, 2 labelwin = curses.newwin(labelh, labelw, labely, labelx) labelpan = curses.panel.new_panel(labelwin) fieldh, fieldw, fieldy, fieldx = scrh - 2, scrw - 2 - labelw - 3, 1, labelw + 3 fieldwin = curses.newwin(fieldh, fieldw, fieldy, fieldx) fieldpan = curses.panel.new_panel(fieldwin) prepare_display() # This flag stops the torrent when set. mainkillflag = Event() class CursesDisplayer: def __init__(self, mainerrlist): self.done = 0 self.file = '' self.fileSize = '' self.activity = '' self.status = '' self.progress = '' self.downloadTo = '' self.downRate = '%s/s down' % (fmtsize(0)) self.upRate = '%s/s up ' % (fmtsize(0)) self.upTotal = '%s up ' % (fmtsize(0, 2)) self.downTotal = '%s down' % (fmtsize(0, 2)) self.errors = [] self.globalerrlist = mainerrlist self.last_update_time = 0 def finished(self): self.done = 1 self.activity = 'download succeeded!' self.downRate = '%s/s down' % (fmtsize(0)) self.display({'fractionDone': 1}) def failed(self): global mainkillflag if not mainkillflag.isSet(): self.done = 1 self.activity = 'download failed!' self.downRate = '%s/s down' % (fmtsize(0)) self.display() def error(self, errormsg): errtxt = strftime('[%H:%M:%S] ') + errormsg self.errors.append(errtxt) self.globalerrlist.append(errtxt) # force redraw to get rid of nasty stack backtrace winch_handler(SIGWINCH, 0) self.display() def display(self, dict = {}): if self.last_update_time + 0.1 > time() and dict.get('fractionDone') not in (0.0, 1.0) and not dict.has_key('activity'): return self.last_update_time = time() global mainkillflag fractionDone = dict.get('fractionDone', None) timeEst = dict.get('timeEst', None) downRate = dict.get('downRate', None) upRate = dict.get('upRate', None) downTotal = dict.get('downTotal', None) # total download megs, float upTotal = dict.get('upTotal', None) # total upload megs, float activity = dict.get('activity', None) if activity is not None and not self.done: self.activity = activity elif timeEst is not None: self.activity = fmttime(timeEst) if fractionDone is not None: blocknum = int(fieldw * fractionDone) self.progress = blocknum * '#' + (fieldw - blocknum) * '_' self.status = '%s (%.1f%%)' % (self.activity, fractionDone * 100) else: self.status = self.activity if downRate is not None: self.downRate = '%s/s down' % (fmtsize(float(downRate))) if upRate is not None: self.upRate = '%s/s up ' % (fmtsize(float(upRate))) if upTotal is not None: self.upTotal = '%s up ' % (fmtsize(upTotal, 2)) if downTotal is not None: self.downTotal = '%s down' % (fmtsize(downTotal, 2)) inchar = fieldwin.getch() if inchar == 12: #^L winch_handler(SIGWINCH, 0) elif inchar == ord('q'): # quit mainkillflag.set() self.status = 'shutting down...' try: fieldwin.erase() fieldwin.addnstr(0, 0, self.file, fieldw, curses.A_BOLD) fieldwin.addnstr(1, 0, self.fileSize, fieldw) fieldwin.addnstr(2, 0, self.downloadTo, fieldw) if self.progress: fieldwin.addnstr(3, 0, self.progress, fieldw, curses.A_BOLD) fieldwin.addnstr(4, 0, self.status, fieldw) fieldwin.addnstr(5, 0, self.downRate + ' - ' + self.upRate, fieldw / 2) fieldwin.addnstr(6, 0, self.downTotal + ' - ' + self.upTotal, fieldw / 2) if self.errors: errsize = len(self.errors) for i in range(errsize): if (7 + i) >= fieldh: break fieldwin.addnstr(7 + i, 0, self.errors[errsize - i - 1], fieldw, curses.A_BOLD) else: fieldwin.move(7, 0) except curses.error: pass curses.panel.update_panels() curses.doupdate() def chooseFile(self, default, size, saveas, dir): self.file = default self.fileSize = '%s (%s)' % (commaize(size), fmtsize(size, padded = 0)) if saveas == '': saveas = default self.downloadTo = abspath(saveas) return saveas def run(mainerrlist, params): d = CursesDisplayer(mainerrlist) try: download(params, d.chooseFile, d.display, d.finished, d.error, mainkillflag, fieldw) except KeyboardInterrupt: # ^C to exit.. pass if not d.done: d.failed() def prepare_display(): try: scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' ')) labelwin.addstr(0, 0, 'file:') labelwin.addstr(1, 0, 'size:') labelwin.addstr(2, 0, 'dest:') labelwin.addstr(3, 0, 'progress:') labelwin.addstr(4, 0, 'status:') labelwin.addstr(5, 0, 'speed:') labelwin.addstr(6, 0, 'totals:') labelwin.addstr(7, 0, 'error(s):') except curses.error: pass fieldwin.nodelay(1) curses.panel.update_panels() curses.doupdate() try: import curses import curses.panel scrwin = curses.initscr() curses.noecho() curses.cbreak() except: print 'Textmode GUI initialization failed, cannot proceed.' print print 'This download interface requires the standard Python module ' \ '"curses", which is unfortunately not available for the native ' \ 'Windows port of Python. It is however available for the Cygwin ' \ 'port of Python, running on all Win32 systems (www.cygwin.com).' print print 'You may still use "btdownloadheadless.py" to download.' scrh, scrw = scrwin.getmaxyx() scrpan = curses.panel.new_panel(scrwin) labelh, labelw, labely, labelx = scrh - 2, 9, 1, 2 labelwin = curses.newwin(labelh, labelw, labely, labelx) labelpan = curses.panel.new_panel(labelwin) fieldh, fieldw, fieldy, fieldx = scrh - 2, scrw - 2 - labelw - 3, 1, labelw + 3 fieldwin = curses.newwin(fieldh, fieldw, fieldy, fieldx) fieldpan = curses.panel.new_panel(fieldwin) prepare_display() signal(SIGWINCH, winch_handler) if __name__ == '__main__': mainerrlist = [] try: run(mainerrlist, argv[1:]) finally: curses.nocbreak() curses.echo() curses.endwin() if len(mainerrlist) != 0: print "These errors occurred during execution:" for error in mainerrlist: print error BitTorrent-3.4.2/btdownloadgui.py0100755000202400020240000002603410027113052015442 0ustar brambram#!/usr/bin/env python # Written by Bram Cohen and Myers Carpenter # see LICENSE.txt for license information from sys import argv from BitTorrent import version from BitTorrent.download import download from btdownloadheadless import print_spew from threading import Event, Thread from os.path import join, split, exists from os import getcwd from wxPython.wx import * from time import strftime, time from webbrowser import open_new from traceback import print_exc def hours(n): if n == -1: return '' if n == 0: return 'complete!' n = int(n) h, r = divmod(n, 60 * 60) m, sec = divmod(r, 60) if h > 1000000: return '' if h > 0: return '%d hour %02d min %02d sec' % (h, m, sec) else: return '%d min %02d sec' % (m, sec) wxEVT_INVOKE = wxNewEventType() def EVT_INVOKE(win, func): win.Connect(-1, -1, wxEVT_INVOKE, func) class InvokeEvent(wxPyEvent): def __init__(self): wxPyEvent.__init__(self) self.SetEventType(wxEVT_INVOKE) class DownloadInfoFrame: def __init__(self, flag): frame = wxFrame(None, -1, 'BitTorrent ' + version + ' download', size = wxSize(400, 250)) self.frame = frame self.flag = flag self.uiflag = Event() self.fin = False self.last_update_time = 0 self.showing_error = False self.event = InvokeEvent() self.funclist = [] panel = wxPanel(frame, -1) colSizer = wxFlexGridSizer(cols = 1, vgap = 3) fnsizer = wxBoxSizer(wxHORIZONTAL) self.fileNameText = wxStaticText(panel, -1, '', style = wxALIGN_LEFT) fnsizer.Add(self.fileNameText, 1, wxALIGN_BOTTOM) self.aboutText = wxStaticText(panel, -1, 'about', style = wxALIGN_RIGHT) self.aboutText.SetForegroundColour('Blue') self.aboutText.SetFont(wxFont(14, wxNORMAL, wxNORMAL, wxNORMAL, True)) fnsizer.Add(self.aboutText, 0, wxEXPAND) colSizer.Add(fnsizer, 0, wxEXPAND) self.gauge = wxGauge(panel, -1, range = 1000, style = wxGA_SMOOTH) colSizer.Add(self.gauge, 0, wxEXPAND) gridSizer = wxFlexGridSizer(cols = 2, vgap = 3, hgap = 8) gridSizer.Add(wxStaticText(panel, -1, 'Estimated time left:')) self.timeEstText = wxStaticText(panel, -1, '') gridSizer.Add(self.timeEstText, 0, wxEXPAND) gridSizer.Add(wxStaticText(panel, -1, 'Download to:')) self.fileDestText = wxStaticText(panel, -1, '') gridSizer.Add(self.fileDestText, 0, wxEXPAND) gridSizer.AddGrowableCol(1) rategridSizer = wxFlexGridSizer(cols = 4, vgap = 3, hgap = 8) rategridSizer.Add(wxStaticText(panel, -1, 'Download rate:')) self.downRateText = wxStaticText(panel, -1, '') rategridSizer.Add(self.downRateText, 0, wxEXPAND) rategridSizer.Add(wxStaticText(panel, -1, 'Downloaded:')) self.downTotalText = wxStaticText(panel, -1, '') rategridSizer.Add(self.downTotalText, 0, wxEXPAND) rategridSizer.Add(wxStaticText(panel, -1, 'Upload rate:')) self.upRateText = wxStaticText(panel, -1, '') rategridSizer.Add(self.upRateText, 0, wxEXPAND) rategridSizer.Add(wxStaticText(panel, -1, 'Uploaded:')) self.upTotalText = wxStaticText(panel, -1, '') rategridSizer.Add(self.upTotalText, 0, wxEXPAND) rategridSizer.AddGrowableCol(1) rategridSizer.AddGrowableCol(3) colSizer.Add(gridSizer, 0, wxEXPAND) colSizer.Add(rategridSizer, 0, wxEXPAND) colSizer.Add(50, 50, 0, wxEXPAND) self.cancelButton = wxButton(panel, -1, 'Cancel') colSizer.Add(self.cancelButton, 0, wxALIGN_CENTER) colSizer.AddGrowableCol(0) colSizer.AddGrowableRow(3) border = wxBoxSizer(wxHORIZONTAL) border.Add(colSizer, 1, wxEXPAND | wxALL, 4) panel.SetSizer(border) panel.SetAutoLayout(True) EVT_LEFT_DOWN(self.aboutText, self.donate) EVT_CLOSE(frame, self.done) EVT_BUTTON(frame, self.cancelButton.GetId(), self.done) EVT_INVOKE(frame, self.onInvoke) self.frame.SetIcon(wxIcon(join(split(argv[0])[0], 'bittorrent.ico'), wxBITMAP_TYPE_ICO)) self.frame.Show() def donate(self, event): Thread(target = self.donate2).start() def donate2(self): open_new('http://bitconjurer.org/BitTorrent/donate.html') def onInvoke(self, event): while not self.uiflag.isSet() and self.funclist: func, args, kwargs = self.funclist.pop(0) apply(func, args, kwargs) def invokeLater(self, func, args = [], kwargs = {}): if not self.uiflag.isSet(): self.funclist.append((func, args, kwargs)) if len(self.funclist) == 1: wxPostEvent(self.frame, self.event) def updateStatus(self, d): if (self.last_update_time + 0.1 < time() and not self.showing_error) or d.get('fractionDone') in (0.0, 1.0) or d.has_key('activity'): self.invokeLater(self.onUpdateStatus, [d]) def onUpdateStatus(self, d): try: if d.has_key('spew'): print_spew(d['spew']) activity = d.get('activity') fractionDone = d.get('fractionDone') timeEst = d.get('timeEst') downRate = d.get('downRate') upRate = d.get('upRate') downTotal = d.get('downTotal') upTotal = d.get('upTotal') if activity is not None and not self.fin: self.timeEstText.SetLabel(activity) if fractionDone is not None and not self.fin: self.gauge.SetValue(int(fractionDone * 1000)) self.frame.SetTitle('%d%% %s - BitTorrent %s' % (int(fractionDone*100), self.filename, version)) if timeEst is not None: self.timeEstText.SetLabel(hours(timeEst)) if downRate is not None: self.downRateText.SetLabel('%.0f KiB/s' % (float(downRate) / (1 << 10))) if upRate is not None: self.upRateText.SetLabel('%.0f KiB/s' % (float(upRate) / (1 << 10))) if downTotal is not None: self.downTotalText.SetLabel('%.1f M' % (downTotal)) if upTotal is not None: self.upTotalText.SetLabel('%.1f M' % (upTotal)) self.last_update_time = time() except: print_exc() def finished(self): self.fin = True self.invokeLater(self.onFinishEvent) def failed(self): self.fin = True self.invokeLater(self.onFailEvent) def error(self, errormsg): if not self.showing_error: self.invokeLater(self.onErrorEvent, [errormsg]) def onFinishEvent(self): self.timeEstText.SetLabel('Download Succeeded!') self.cancelButton.SetLabel('Close') self.gauge.SetValue(1000) self.frame.SetTitle('%s - Upload - BitTorrent %s' % (self.filename, version)) self.downRateText.SetLabel('') def onFailEvent(self): self.timeEstText.SetLabel('Failed!') self.cancelButton.SetLabel('Close') self.gauge.SetValue(0) self.downRateText.SetLabel('') def onErrorEvent(self, errormsg): self.showing_error = True dlg = wxMessageDialog(self.frame, message = errormsg, caption = 'Download Error', style = wxOK | wxICON_ERROR) dlg.Fit() dlg.Center() dlg.ShowModal() self.showing_error = False def chooseFile(self, default, size, saveas, dir): f = Event() bucket = [None] self.invokeLater(self.onChooseFile, [default, bucket, f, size, dir, saveas]) f.wait() return bucket[0] def onChooseFile(self, default, bucket, f, size, dir, saveas): if not saveas: if dir: dl = wxDirDialog(self.frame, 'Choose a directory to save to, pick a partial download to resume', join(getcwd(), default), style = wxDD_DEFAULT_STYLE | wxDD_NEW_DIR_BUTTON) else: dl = wxFileDialog(self.frame, 'Choose file to save as, pick a partial download to resume', '', default, '*', wxSAVE) if dl.ShowModal() != wxID_OK: self.done(None) f.set() return saveas = dl.GetPath() bucket[0] = saveas self.fileNameText.SetLabel('%s (%.1f MB)' % (default, float(size) / (1 << 20))) self.timeEstText.SetLabel('Starting up...') self.fileDestText.SetLabel(saveas) self.filename = default self.frame.SetTitle(default + '- BitTorrent ' + version) f.set() def newpath(self, path): self.fileDestText.SetLabel(path) def done(self, event): self.uiflag.set() self.flag.set() self.frame.Destroy() class btWxApp(wxApp): def __init__(self, x, params): self.params = params wxApp.__init__(self, x) def OnInit(self): doneflag = Event() d = DownloadInfoFrame(doneflag) self.SetTopWindow(d.frame) thread = Thread(target = next, args = [self.params, d, doneflag]) thread.setDaemon(False) thread.start() return 1 def run(params): try: app = btWxApp(0, params) app.MainLoop() except: print_exc() def next(params, d, doneflag): try: p = join(split(argv[0])[0], 'donated') if not exists(p) and long(time()) % 3 == 0: open_new('http://bitconjurer.org/BitTorrent/donate.html') dlg = wxMessageDialog(d.frame, 'BitTorrent is Donation supported software. ' + 'Please go to the donation page (which should be appearing for you now) and make a donation from there. ' + 'Or you can click no and donate later.\n\nHave you made a donation yet?', 'Donate!', wxYES_NO | wxICON_INFORMATION | wxNO_DEFAULT) if dlg.ShowModal() == wxID_YES: dlg.Destroy() dlg = wxMessageDialog(d.frame, 'Thanks for your donation! You will no longer be shown donation requests.\n\n' + "If you haven't actually made a donation and are feeling guilty (as you should!) you can always get to " + "the donation page by clicking the 'about' link in the upper-right corner of the main BitTorrent window and " + 'donating from there.', 'Thanks!', wxOK) dlg.ShowModal() dlg.Destroy() try: open(p, 'wb').close() except IOError, e: dlg = wxMessageDialog(d.frame, "Sorry, but I couldn't set the flag to not ask you for donations in the future - " + str(e), 'Sorry!', wxOK | wxICON_ERROR) dlg.ShowModal() dlg.Destroy() else: dlg.Destroy() download(params, d.chooseFile, d.updateStatus, d.finished, d.error, doneflag, 100, d.newpath) if not d.fin: d.failed() except: print_exc() if __name__ == '__main__': run(argv[1:]) BitTorrent-3.4.2/btdownloadheadless.py0100755000202400020240000001115207733336245016466 0ustar brambram#!/usr/bin/env python # Written by Bram Cohen # see LICENSE.txt for license information from BitTorrent.download import download from threading import Event from os.path import abspath from sys import argv, stdout from cStringIO import StringIO from time import time def hours(n): if n == -1: return '' if n == 0: return 'complete!' n = long(n) h, r = divmod(n, 60 * 60) m, sec = divmod(r, 60) if h > 1000000: return '' if h > 0: return '%d hour %02d min %02d sec' % (h, m, sec) else: return '%d min %02d sec' % (m, sec) class HeadlessDisplayer: def __init__(self): self.done = False self.file = '' self.percentDone = '' self.timeEst = '' self.downloadTo = '' self.downRate = '' self.upRate = '' self.downTotal = '' self.upTotal = '' self.errors = [] self.last_update_time = 0 def finished(self): self.done = True self.percentDone = '100' self.timeEst = 'Download Succeeded!' self.downRate = '' self.display({}) def failed(self): self.done = True self.percentDone = '0' self.timeEst = 'Download Failed!' self.downRate = '' self.display({}) def error(self, errormsg): self.errors.append(errormsg) self.display({}) def display(self, dict): if self.last_update_time + 0.1 > time() and dict.get('fractionDone') not in (0.0, 1.0) and not dict.has_key('activity'): return self.last_update_time = time() if dict.has_key('spew'): print_spew(dict['spew']) if dict.has_key('fractionDone'): self.percentDone = str(float(int(dict['fractionDone'] * 1000)) / 10) if dict.has_key('timeEst'): self.timeEst = hours(dict['timeEst']) if dict.has_key('activity') and not self.done: self.timeEst = dict['activity'] if dict.has_key('downRate'): self.downRate = '%.2f kB/s' % (float(dict['downRate']) / (1 << 10)) if dict.has_key('upRate'): self.upRate = '%.2f kB/s' % (float(dict['upRate']) / (1 << 10)) if dict.has_key('upTotal'): self.upTotal = '%.1f MiB' % (dict['upTotal']) if dict.has_key('downTotal'): self.downTotal = '%.1f MiB' % (dict['downTotal']) print '\n\n' for err in self.errors: print 'ERROR:\n' + err + '\n' print 'saving: ', self.file print 'percent done: ', self.percentDone print 'time left: ', self.timeEst print 'download to: ', self.downloadTo if self.downRate != '': print 'download rate: ', self.downRate if self.upRate != '': print 'upload rate: ', self.upRate if self.downTotal != '': print 'download total:', self.downTotal if self.upTotal != '': print 'upload total: ', self.upTotal stdout.flush() def chooseFile(self, default, size, saveas, dir): self.file = '%s (%.1f MB)' % (default, float(size) / (1 << 20)) if saveas != '': default = saveas self.downloadTo = abspath(default) return default def newpath(self, path): self.downloadTo = path def print_spew(spew): s = StringIO() s.write('\n\n\n') for c in spew: s.write('%20s ' % c['ip']) if c['initiation'] == 'local': s.write('l') else: s.write('r') rate, interested, choked = c['upload'] s.write(' %10s ' % str(int(rate))) if c['is_optimistic_unchoke']: s.write('*') else: s.write(' ') if interested: s.write('i') else: s.write(' ') if choked: s.write('c') else: s.write(' ') rate, interested, choked, snubbed = c['download'] s.write(' %10s ' % str(int(rate))) if interested: s.write('i') else: s.write(' ') if choked: s.write('c') else: s.write(' ') if snubbed: s.write('s') else: s.write(' ') s.write('\n') print s.getvalue() def run(params): try: import curses curses.initscr() cols = curses.COLS curses.endwin() except: cols = 80 h = HeadlessDisplayer() download(params, h.chooseFile, h.display, h.finished, h.error, Event(), cols, h.newpath) if not h.done: h.failed() if __name__ == '__main__': run(argv[1:]) BitTorrent-3.4.2/btdownloadlibrary.py0100644000202400020240000000072107662353364016341 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information import BitTorrent.download from threading import Event def dummychoose(default, size, saveas, dir): return saveas def dummydisplay(dict): pass def dummyerror(message): pass def download(url, file): ev = Event() def fin(ev = ev): ev.set() BitTorrent.download.download(['--url', url, '--saveas', file], dummychoose, dummydisplay, fin, dummyerror, ev, 80) BitTorrent-3.4.2/btlaunchmany.py0100755000202400020240000002312407731512343015300 0ustar brambram#!/usr/bin/env python # Written by Michael Janssen (jamuraa at base0 dot net) # originally heavily borrowed code from btlaunchmany.py by Bram Cohen # and btdownloadcurses.py written by Henry 'Pi' James # now not so much. # fmttime and fmtsize stolen from btdownloadcurses. # see LICENSE.txt for license information from BitTorrent.download import download from threading import Thread, Event, Lock from os import listdir from os.path import abspath, join, exists, getsize from sys import argv, stdout, exit from time import sleep import traceback def fmttime(n): if n == -1: return '(no seeds?)' if n == 0: return 'complete' n = int(n) m, s = divmod(n, 60) h, m = divmod(m, 60) if h > 1000000: return 'n/a' return '%d:%02d:%02d' % (h, m, s) def fmtsize(n, baseunit = 0, padded = 1): unit = [' B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] i = baseunit while i + 1 < len(unit) and n >= 999: i += 1 n = float(n) / (1 << 10) size = '' if padded: if n < 10: size = ' ' elif n < 100: size = ' ' if i != 0: size += '%.1f %s' % (n, unit[i]) else: if padded: size += '%.0f %s' % (n, unit[i]) else: size += '%.0f %s' % (n, unit[i]) return size def dummy(*args, **kwargs): pass threads = {} ext = '.torrent' print 'btlaunchmany starting..' filecheck = Lock() def dropdir_mainloop(d, params): deadfiles = [] global threads, status while 1: files = listdir(d) # new files for file in files: if file[-len(ext):] == ext: if file not in threads.keys() + deadfiles: threads[file] = {'kill': Event(), 'try': 1} print 'New torrent: %s' % file stdout.flush() threads[file]['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file) threads[file]['thread'].start() # files with multiple tries for file, threadinfo in threads.items(): if threadinfo.get('timeout') == 0: # Zero seconds left, try and start the thing again. threadinfo['try'] = threadinfo['try'] + 1 threadinfo['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file) threadinfo['thread'].start() threadinfo['timeout'] = -1 elif threadinfo.get('timeout') > 0: # Decrement our counter by 1 threadinfo['timeout'] = threadinfo['timeout'] - 1 elif not threadinfo['thread'].isAlive(): # died without permission # if it was checking the file, it isn't anymore. if threadinfo.get('checking', None): filecheck.release() if threadinfo.get('try') == 6: # Died on the sixth try? You're dead. deadfiles.append(file) print '%s died 6 times, added to dead list' % fil stdout.flush() del threads[file] else: del threadinfo['thread'] threadinfo['timeout'] = 10 # dealing with files that dissapear if file not in files: print 'Torrent file dissapeared, killing %s' % file stdout.flush() if threadinfo.get('timeout', -1) == -1: threadinfo['kill'].set() threadinfo['thread'].join() # if this thread was filechecking, open it up if threadinfo.get('checking', None): filecheck.release() del threads[file] for file in deadfiles: # if the file dissapears, remove it from our dead list if file not in files: deadfiles.remove(file) sleep(1) def display_thread(displaykiller): interval = 1.0 global threads, status while 1: # display file info if (displaykiller.isSet()): break totalup = 0 totaldown = 0 totaluptotal = 0.0 totaldowntotal = 0.0 for file, threadinfo in threads.items(): uprate = threadinfo.get('uprate', 0) downrate = threadinfo.get('downrate', 0) uptxt = fmtsize(uprate, padded = 0) downtxt = fmtsize(downrate, padded = 0) uptotal = threadinfo.get('uptotal', 0.0) downtotal = threadinfo.get('downtotal', 0.0) uptotaltxt = fmtsize(uptotal, baseunit = 2, padded = 0) downtotaltxt = fmtsize(downtotal, baseunit = 2, padded = 0) filename = threadinfo.get('savefile', file) if threadinfo.get('timeout', 0) > 0: trys = threadinfo.get('try', 1) timeout = threadinfo.get('timeout') print '%s: try %d died, retry in %d' % (filename, trys, timeout) else: status = threadinfo.get('status','') print '%s: Spd: %s/%s Tot: %s/%s [%s]' % (filename, uptxt, downtxt, uptotaltxt, downtotaltxt, status) totalup += uprate totaldown += downrate totaluptotal += uptotal totaldowntotal += downtotal # display totals line totaluptxt = fmtsize(totalup, padded = 0) totaldowntxt = fmtsize(totaldown, padded = 0) totaluptotaltxt = fmtsize(totaluptotal, baseunit = 2, padded = 0) totaldowntotaltxt = fmtsize(totaldowntotal, baseunit = 2, padded = 0) print 'All: Spd: %s/%s Tot: %s/%s' % (totaluptxt, totaldowntxt, totaluptotaltxt, totaldowntotaltxt) print stdout.flush() sleep(interval) class StatusUpdater: def __init__(self, file, params, name): self.file = file self.params = params self.name = name self.myinfo = threads[name] self.done = 0 self.checking = 0 self.activity = 'starting' self.display() self.myinfo['errors'] = [] def download(self): download(self.params + ['--responsefile', self.file], self.choose, self.display, self.finished, self.err, self.myinfo['kill'], 80) print 'Torrent %s stopped' % self.file stdout.flush() def finished(self): self.done = 1 self.myinfo['done'] = 1 self.activity = 'complete' self.display({'fractionDone' : 1}) def err(self, msg): self.myinfo['errors'].append(msg) self.display() def failed(self): self.activity = 'failed' self.display() def choose(self, default, size, saveas, dir): self.myinfo['downfile'] = default self.myinfo['filesize'] = fmtsize(size) if saveas == '': saveas = default # it asks me where I want to save it before checking the file.. self.myinfo['savefile'] = self.file[:-len(ext)] if exists(self.file[:-len(ext)]) and (getsize(self.file[:-len(ext)]) > 0): # file will get checked while (not filecheck.acquire(0) and not self.myinfo['kill'].isSet()): self.myinfo['status'] = 'disk wait' sleep(0.1) if not self.myinfo['kill'].isSet(): self.myinfo['checking'] = 1 self.checking = 1 return self.file[:-len(ext)] def display(self, dict = {}): fractionDone = dict.get('fractionDone', None) timeEst = dict.get('timeEst', None) activity = dict.get('activity', None) global status if activity is not None and not self.done: if activity == 'checking existing file': self.activity = 'disk check' elif activity == 'connecting to peers': self.activity = 'connecting' else: self.activity = activity elif timeEst is not None: self.activity = fmttime(timeEst) if fractionDone is not None: self.myinfo['status'] = '%s %.0f%%' % (self.activity, fractionDone * 100) else: self.myinfo['status'] = self.activity if self.activity != 'checking existing file' and self.checking: # we finished checking our files. filecheck.release() self.checking = 0 self.myinfo['checking'] = 0 if dict.has_key('upRate'): self.myinfo['uprate'] = dict['upRate'] if dict.has_key('downRate'): self.myinfo['downrate'] = dict['downRate'] if dict.has_key('upTotal'): self.myinfo['uptotal'] = dict['upTotal'] if dict.has_key('downTotal'): self.myinfo['downtotal'] = dict['downTotal'] if __name__ == '__main__': if (len(argv) < 2): print """Usage: btlaunchmany.py - directory to look for .torrent files (non-recursive) - options to be applied to all torrents (see btdownloadheadless.py) """ exit(-1) try: displaykiller = Event() displaythread = Thread(target = display_thread, name = 'display', args = [displaykiller]) displaythread.start() dropdir_mainloop(argv[1], argv[2:]) except KeyboardInterrupt: print '^C caught! Killing torrents..' for file, threadinfo in threads.items(): status = 'Killing torrent %s' % file threadinfo['kill'].set() threadinfo['thread'].join() del threads[file] displaykiller.set() displaythread.join() except: traceback.print_exc() BitTorrent-3.4.2/btlaunchmanycurses.py0100755000202400020240000003405307731512343016530 0ustar brambram#!/usr/bin/env python # Written by Michael Janssen (jamuraa at base0 dot net) # originally heavily borrowed code from btlaunchmany.py by Bram Cohen # and btdownloadcurses.py written by Henry 'Pi' James # now not so much. # fmttime and fmtsize stolen from btdownloadcurses. # see LICENSE.txt for license information from BitTorrent.download import download from threading import Thread, Event, Lock from os import listdir from os.path import abspath, join, exists, getsize from sys import argv, stdout, exit from time import sleep from signal import signal, SIGWINCH import traceback def fmttime(n): if n == -1: return 'download not progressing (no seeds?)' if n == 0: return 'download complete!' n = int(n) m, s = divmod(n, 60) h, m = divmod(m, 60) if h > 1000000: return 'n/a' return 'finishing in %d:%02d:%02d' % (h, m, s) def fmtsize(n, baseunit = 0, padded = 1): unit = [' B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] i = baseunit while i + 1 < len(unit) and n >= 999: i += 1 n = float(n) / (1 << 10) size = '' if padded: if n < 10: size = ' ' elif n < 100: size = ' ' if i != 0: size += '%.1f %s' % (n, unit[i]) else: if padded: size += '%.0f %s' % (n, unit[i]) else: size += '%.0f %s' % (n, unit[i]) return size def dummy(*args, **kwargs): pass threads = {} ext = '.torrent' status = 'btlaunchmany starting..' filecheck = Lock() mainquitflag = Event() def cleanup_and_quit(): status = 'Killing torrents..' for file, threadinfo in threads.items(): status = 'Killing torrent %s' % file threadinfo['kill'].set() threadinfo['thread'].join() del threads[file] displaykiller.set() displaythread.join() def dropdir_mainloop(d, params): deadfiles = [] global threads, status, mainquitflag while 1: files = listdir(d) # new files for file in files: if file[-len(ext):] == ext: if file not in threads.keys() + deadfiles: threads[file] = {'kill': Event(), 'try': 1} status = 'New torrent: %s' % file threads[file]['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file) threads[file]['thread'].start() # files with multiple tries for file, threadinfo in threads.items(): if threadinfo.get('timeout') == 0: # Zero seconds left, try and start the thing again. threadinfo['try'] = threadinfo['try'] + 1 threadinfo['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file) threadinfo['thread'].start() threadinfo['timeout'] = -1 elif threadinfo.get('timeout') > 0: # Decrement our counter by 1 threadinfo['timeout'] = threadinfo['timeout'] - 1 elif not threadinfo['thread'].isAlive(): # died without permission # if it was checking the file, it's not anymore if threadinfo.get('checking', None): filecheck.release() if threadinfo.get('try') == 6: # Died on the sixth try? You're dead. deadfiles.append(file) status = '%s died 6 times, added to dead list' % file del threads[file] else: del threadinfo['thread'] threadinfo['timeout'] = 10 # dealing with files that dissapear if file not in files: status = 'Gone torrent: %s' % file if threadinfo.get('timeout', -1) == -1: threadinfo['kill'].set() threadinfo['thread'].join() # if it had the lock, unlock it if threadinfo.get('checking', None): filecheck.release() del threads[file] for file in deadfiles: # if the file dissapears, remove it from our dead list if file not in files: deadfiles.remove(file) if mainquitflag.isSet(): cleanup_and_quit() break sleep(1) def display_thread(displaykiller, mainquitflag): interval = 0.1 global threads, status while 1: inchar = mainwin.getch(); if inchar == 12: # ^L winch_handler() elif inchar == ord('q'): # quit mainquitflag.set() # display file info if (displaykiller.isSet()): break mainwin.erase() winpos = 0 totalup = 0 totaldown = 0 totaluptotal = 0.0 totaldowntotal = 0.0 for file, threadinfo in threads.items(): uprate = threadinfo.get('uprate', 0) downrate = threadinfo.get('downrate', 0) uptxt = '%s/s' % fmtsize(uprate) downtxt = '%s/s' % fmtsize(downrate) uptotal = threadinfo.get('uptotal', 0.0) downtotal = threadinfo.get('downtotal', 0.0) uptotaltxt = fmtsize(uptotal, baseunit = 2) downtotaltxt = fmtsize(downtotal, baseunit = 2) filesize = threadinfo.get('filesize', 'N/A') mainwin.addnstr(winpos, 0, threadinfo.get('savefile', file), mainwinw - 28, curses.A_BOLD) mainwin.addnstr(winpos, mainwinw - 30, filesize, 8) mainwin.addnstr(winpos, mainwinw - 21, downtxt, 10) mainwin.addnstr(winpos, mainwinw - 10, uptxt, 10) winpos = winpos + 1 mainwin.addnstr(winpos, 0, '^--- ', 5) if threadinfo.get('timeout', 0) > 0: mainwin.addnstr(winpos, 6, 'Try %d: died, retrying in %d' % (threadinfo.get('try', 1), threadinfo.get('timeout')), mainwinw - 21) else: mainwin.addnstr(winpos, 6, threadinfo.get('status',''), mainwinw - 21) mainwin.addnstr(winpos, mainwinw - 21, downtotaltxt, 8) mainwin.addnstr(winpos, mainwinw - 10, uptotaltxt, 8) winpos = winpos + 1 totalup += uprate totaldown += downrate totaluptotal += uptotal totaldowntotal += downtotal # display statusline statuswin.erase() statuswin.addnstr(0, 0, status, mainwinw) # display totals line totaluptxt = '%s/s' % fmtsize(totalup) totaldowntxt = '%s/s' % fmtsize(totaldown) totaluptotaltxt = fmtsize(totaluptotal, baseunit = 2) totaldowntotaltxt = fmtsize(totaldowntotal, baseunit = 2) totalwin.erase() totalwin.addnstr(0, mainwinw - 29, 'Totals:', 7); totalwin.addnstr(0, mainwinw - 21, totaldowntxt, 10) totalwin.addnstr(0, mainwinw - 10, totaluptxt, 10) totalwin.addnstr(1, mainwinw - 21, totaldowntotaltxt, 8) totalwin.addnstr(1, mainwinw - 10, totaluptotaltxt, 8) curses.panel.update_panels() curses.doupdate() sleep(interval) class StatusUpdater: def __init__(self, file, params, name): self.file = file self.params = params self.name = name self.myinfo = threads[name] self.done = 0 self.checking = 0 self.activity = 'starting up...' self.display() self.myinfo['errors'] = [] def download(self): download(self.params + ['--responsefile', self.file], self.choose, self.display, self.finished, self.err, self.myinfo['kill'], 80) status = 'Torrent %s stopped' % self.file def finished(self): self.done = 1 self.myinfo['done'] = 1 self.activity = 'download succeeded!' self.display({'fractionDone' : 1, 'downRate' : 0}) def err(self, msg): self.myinfo['errors'].append(msg) # errors often come with evil tracebacks that mess up our screen. winch_handler() self.display() def failed(self): self.activity = 'download failed!' self.display() def choose(self, default, size, saveas, dir): global filecheck self.myinfo['downfile'] = default self.myinfo['filesize'] = fmtsize(size) if saveas == '': saveas = default # it asks me where I want to save it before checking the file.. if exists(saveas) and (getsize(saveas) > 0): # file will get checked while (not filecheck.acquire(0) and not self.myinfo['kill'].isSet()): self.myinfo['status'] = 'Waiting for disk check...' sleep(0.1) if not self.myinfo['kill'].isSet(): self.checking = 1 self.myinfo['checking'] = 1 self.myinfo['savefile'] = saveas return saveas def display(self, dict = {}): fractionDone = dict.get('fractionDone', None) timeEst = dict.get('timeEst', None) activity = dict.get('activity', None) global filecheck, status if activity is not None and not self.done: self.activity = activity elif timeEst is not None: self.activity = fmttime(timeEst) if fractionDone is not None: self.myinfo['status'] = '%s (%.1f%%)' % (self.activity, fractionDone * 100) else: self.myinfo['status'] = self.activity if self.activity != 'checking existing file' and self.checking: # we finished checking our files. filecheck.release() self.checking = 0 self.myinfo['checking'] = 0 if dict.has_key('upRate'): self.myinfo['uprate'] = dict['upRate'] if dict.has_key('downRate'): self.myinfo['downrate'] = dict['downRate'] if dict.has_key('upTotal'): self.myinfo['uptotal'] = dict['upTotal'] if dict.has_key('downTotal'): self.myinfo['downtotal'] = dict['downTotal'] def prepare_display(): global mainwinw, scrwin, headerwin, totalwin try: scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' ')) headerwin.addnstr(0, 0, 'Filename', mainwinw - 25, curses.A_BOLD) headerwin.addnstr(0, mainwinw - 26, 'Size', 4); headerwin.addnstr(0, mainwinw - 19, 'Download', 8); headerwin.addnstr(0, mainwinw - 6, 'Upload', 6); totalwin.addnstr(0, mainwinw - 27, 'Totals:', 7); except curses.error: pass mainwin.nodelay(1) curses.panel.update_panels() curses.doupdate() def winch_handler(signum = SIGWINCH, stackframe = None): global scrwin, mainwin, mainwinw, headerwin, totalwin, statuswin global scrpan, mainpan, headerpan, totalpan, statuspan # SIGWINCH. Remake the frames! ## Curses Trickery curses.endwin() # delete scrwin somehow? scrwin.refresh() scrwin = curses.newwin(0, 0, 0, 0) scrh, scrw = scrwin.getmaxyx() scrpan = curses.panel.new_panel(scrwin) ### Curses Setup scrh, scrw = scrwin.getmaxyx() scrpan = curses.panel.new_panel(scrwin) mainwinh = scrh - 5 # - 2 (bars) - 1 (debugwin) - 1 (borderwin) - 1 (totalwin) mainwinw = scrw - 4 # - 2 (bars) - 2 (spaces) mainwiny = 2 # + 1 (bar) + 1 (titles) mainwinx = 2 # + 1 (bar) + 1 (space) # + 1 to all windows so we can write at mainwinw mainwin = curses.newwin(mainwinh, mainwinw+1, mainwiny, mainwinx) mainpan = curses.panel.new_panel(mainwin) headerwin = curses.newwin(1, mainwinw+1, 1, mainwinx) headerpan = curses.panel.new_panel(headerwin) totalwin = curses.newwin(2, mainwinw+1, scrh-4, mainwinx) totalpan = curses.panel.new_panel(totalwin) statuswin = curses.newwin(1, mainwinw+1, scrh-2, mainwinx) statuspan = curses.panel.new_panel(statuswin) mainwin.scrollok(0) headerwin.scrollok(0) totalwin.scrollok(0) statuswin.addstr(0, 0, 'window resize: %s x %s' % (scrw, scrh)) statuswin.scrollok(0) prepare_display() if __name__ == '__main__': if (len(argv) < 2): print """Usage: btlaunchmanycurses.py - directory to look for .torrent files (non-recursive) - options to be applied to all torrents (see btdownloadheadless.py) """ exit(-1) dietrace = 0 try: import curses import curses.panel scrwin = curses.initscr() curses.noecho() curses.cbreak() except: print 'Textmode GUI initialization failed, cannot proceed.' exit(-1) try: signal(SIGWINCH, winch_handler) ### Curses Setup scrh, scrw = scrwin.getmaxyx() scrpan = curses.panel.new_panel(scrwin) mainwinh = scrh - 5 # - 2 (bars) - 1 (debugwin) - 1 (borderwin) - 1 (totalwin) mainwinw = scrw - 4 # - 2 (bars) - 2 (spaces) mainwiny = 2 # + 1 (bar) + 1 (titles) mainwinx = 2 # + 1 (bar) + 1 (space) # + 1 to all windows so we can write at mainwinw mainwin = curses.newwin(mainwinh, mainwinw+1, mainwiny, mainwinx) mainpan = curses.panel.new_panel(mainwin) headerwin = curses.newwin(1, mainwinw+1, 1, mainwinx) headerpan = curses.panel.new_panel(headerwin) totalwin = curses.newwin(2, mainwinw+1, scrh-4, mainwinx) totalpan = curses.panel.new_panel(totalwin) statuswin = curses.newwin(1, mainwinw+1, scrh-2, mainwinx) statuspan = curses.panel.new_panel(statuswin) mainwin.scrollok(0) headerwin.scrollok(0) totalwin.scrollok(0) statuswin.addstr(0, 0, 'btlaunchmany started') statuswin.scrollok(0) prepare_display() displaykiller = Event() displaythread = Thread(target = display_thread, name = 'display', args = [displaykiller, mainquitflag]) displaythread.setDaemon(1) displaythread.start() dropdir_mainloop(argv[1], argv[2:]) except KeyboardInterrupt: cleanup_and_quit() except: dietrace = traceback curses.nocbreak() curses.echo() curses.endwin() if dietrace != 0: dietrace.print_exc() BitTorrent-3.4.2/btmakemetafile.py0100755000202400020240000001066210021010705015545 0ustar brambram#!/usr/bin/env python # Written by Bram Cohen # see LICENSE.txt for license information from sys import argv from os.path import getsize, split, join, abspath, isdir from os import listdir from sha import sha from copy import copy from string import strip from BitTorrent.bencode import bencode from BitTorrent.btformats import check_info from BitTorrent.parseargs import parseargs, formatDefinitions from threading import Event from time import time defaults = [ ('piece_size_pow2', 18, "which power of 2 to set the piece size to"), ('comment', '', "optional human-readable comment to put in .torrent"), ('target', '', "optional target file for the torrent") ] ignore = ['core', 'CVS'] def dummy(v): pass def make_meta_file(file, url, piece_len_exp, flag = Event(), progress = dummy, progress_percent=1, comment = None, target = None): piece_length = 2 ** piece_len_exp a, b = split(file) if not target: if b == '': f = a + '.torrent' else: f = join(a, b + '.torrent') else: f = target info = makeinfo(file, piece_length, flag, progress, progress_percent) if flag.isSet(): return check_info(info) h = open(f, 'wb') data = {'info': info, 'announce': strip(url), 'creation date': long(time())} if comment: data['comment'] = comment h.write(bencode(data)) h.close() def calcsize(file): if not isdir(file): return getsize(file) total = 0 for s in subfiles(abspath(file)): total += getsize(s[1]) return total def makeinfo(file, piece_length, flag, progress, progress_percent=1): file = abspath(file) if isdir(file): subs = subfiles(file) subs.sort() pieces = [] sh = sha() done = 0 fs = [] totalsize = 0.0 totalhashed = 0 for p, f in subs: totalsize += getsize(f) for p, f in subs: pos = 0 size = getsize(f) fs.append({'length': size, 'path': p}) h = open(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() if progress_percent: progress(totalhashed / totalsize) else: progress(a) h.close() if done > 0: pieces.append(sh.digest()) return {'pieces': ''.join(pieces), 'piece length': piece_length, 'files': fs, 'name': split(file)[1]} else: size = getsize(file) pieces = [] p = 0 h = open(file, '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 if progress_percent: progress(float(p) / size) else: progress(min(piece_length, size - p)) h.close() return {'pieces': ''.join(pieces), 'piece length': piece_length, 'length': size, 'name': split(file)[1]} def subfiles(d): r = [] stack = [([], d)] while len(stack) > 0: p, n = stack.pop() if isdir(n): for s in listdir(n): if s not in ignore and s[:1] != '.': stack.append((copy(p) + [s], join(n, s))) else: r.append((p, n)) return r def prog(amount): print '%.1f%% complete\r' % (amount * 100), if __name__ == '__main__': if len(argv) < 3: print 'usage is -' print argv[0] + ' file trackerurl [params]' print print formatDefinitions(defaults, 80) else: try: config, args = parseargs(argv[3:], defaults, 0, 0) make_meta_file(argv[1], argv[2], config['piece_size_pow2'], progress = prog, comment = config['comment'], target = config['target']) except ValueError, e: print 'error: ' + str(e) print 'run with no args for parameter explanations' BitTorrent-3.4.2/btreannounce.py0100755000202400020240000000122107635420560015272 0ustar brambram#!/usr/bin/env python # Written by Henry 'Pi' James and Bram Cohen # see LICENSE.txt for license information from sys import argv from BitTorrent.bencode import bencode, bdecode if len(argv) < 3: print '%s http://new.uri:port/announce file1.torrent file2.torrent' % argv[0] print exit(2) # common exit code for syntax error for f in argv[2:]: h = open(f, 'rb') metainfo = bdecode(h.read()) h.close() if metainfo['announce'] != argv[1]: print 'old announce for %s: %s' % (f, metainfo['announce']) metainfo['announce'] = argv[1] h = open(f, 'wb') h.write(bencode(metainfo)) h.close() BitTorrent-3.4.2/btrename.py0100755000202400020240000000153207635420560014411 0ustar brambram#!/usr/bin/env python # Written by Henry 'Pi' James # see LICENSE.txt for license information from sys import * from os.path import * from sha import * from BitTorrent.bencode import * NAME, EXT = splitext(basename(argv[0])) VERSION = '20021119' print '%s %s - change the suggested filename in a .torrent file' % (NAME, VERSION) print if len(argv) != 3: print '%s file.torrent new.filename.ext' % argv[0] print exit(2) # common exit code for syntax error metainfo_file = open(argv[1], 'rb') metainfo = bdecode(metainfo_file.read()) metainfo_file.close() print 'old filename: %s' % metainfo['info']['name'] metainfo['info']['name'] = argv[2] print 'new filename: %s' % metainfo['info']['name'] metainfo_file = open(argv[1], 'wb') metainfo_file.write(bencode(metainfo)) metainfo_file.close print print 'done.' print BitTorrent-3.4.2/btshowmetainfo.py0100755000202400020240000000343710017161000015626 0ustar brambram#!/usr/bin/env python # Written by Henry 'Pi' James and Loring Holden # see LICENSE.txt for license information from sys import * from os.path import * from sha import * from BitTorrent.bencode import * NAME, EXT = splitext(basename(argv[0])) VERSION = '20021207' print '%s %s - decode BitTorrent metainfo files' % (NAME, VERSION) print if len(argv) == 1: print '%s file1.torrent file2.torrent file3.torrent ...' % argv[0] print exit(2) # common exit code for syntax error for metainfo_name in argv[1:]: metainfo_file = open(metainfo_name, 'rb') metainfo = bdecode(metainfo_file.read()) metainfo_file.close() announce = metainfo['announce'] info = metainfo['info'] info_hash = sha(bencode(info)) print 'metainfo file.: %s' % basename(metainfo_name) print 'info hash.....: %s' % info_hash.hexdigest() piece_length = info['piece length'] if info.has_key('length'): # let's assume we just have a file print 'file name.....: %s' % info['name'] file_length = info['length'] name ='file size.....:' else: # let's assume we have a directory structure print 'directory name: %s' % info['name'] print 'files.........: ' file_length = 0; for file in info['files']: path = '' for item in file['path']: if (path != ''): path = path + "/" path = path + item print ' %s (%d)' % (path, file['length']) file_length += file['length'] name = 'archive size..:' piece_number, last_piece_length = divmod(file_length, piece_length) print '%s %i (%i * %i + %i)' \ % (name,file_length, piece_number, piece_length, last_piece_length) print 'announce url..: %s' % announce print BitTorrent-3.4.2/bttest.py0100755000202400020240000000066707640727445014141 0ustar brambram#!/usr/bin/env python # Written by Bram Cohen # see LICENSE.txt for license information from BitTorrent import testtest import bttrack import btmakemetafile import btdownloadheadless def run(): testtest.try_all(['urllib', 'urllib2', 'StringIO', 'random', 'urlparse', 'BaseHTTPServer', 'httplib', 'BitTorrent.RawServer', 'BitTorrent.zurllib', 'base64', 'ftplib', 'gopherlib']) if __name__ == '__main__': run() BitTorrent-3.4.2/bttrack.py0100755000202400020240000000030207635420560014240 0ustar brambram#!/usr/bin/env python # Written by Bram Cohen # see LICENSE.txt for license information from sys import argv from BitTorrent.track import track if __name__ == '__main__': track(argv[1:]) BitTorrent-3.4.2/build.bat0100644000202400020240000000016610024120454014011 0ustar brambramdel /F /S /Q build dist c:\python23\python.exe winsetup.py py2exe "C:\Program Files\NSIS\makensis.exe" bittorrent.nsi BitTorrent-3.4.2/completedir.nsi0100644000202400020240000000207010027113052015237 0ustar brambram# Written by Bram Cohen # see LICENSE.txt for license information Outfile completedir.exe Name completedir SilentInstall silent SetCompressor lzma InstallDir "$PROGRAMFILES\completedir\" Section "Install" SetOutPath $INSTDIR WriteUninstaller "$INSTDIR\uninstall.exe" File dist\btcompletedirgui.exe File dist\*.exe File dist\*.pyd File dist\*.dll File dist\library.zip File bittorrent.ico File LICENSE.txt CreateShortCut "$STARTMENU\Programs\completedir.lnk" "$INSTDIR\btcompletedirgui.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\CompleteDir" "DisplayName" "BitTorrent complete dir 1.1" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\CompleteDir" "UninstallString" '"$INSTDIR\uninstall.exe"' MessageBox MB_OK "Complete dir has been successfully installed! Run it under the Programs in the Start Menu." SectionEnd Section "Uninstall" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\CompleteDir" Delete "$STARTMENU\Programs\completedir.lnk" RMDir /r "$INSTDIR" SectionEnd BitTorrent-3.4.2/credits.txt0100644000202400020240000000065110031567652014434 0ustar brambramThe following people have all helped with BitTorrent in some way - Bill Bumgarner David Creswick Andrew Loewenstern Ross Cohen Jeremy Avnet Greg Broiles Barry Cohen Bram Cohen sayke Steve Jenson Myers Carpenter Francis Crick Petru Paler Jeff Darcy John Gilmore Yann Vernier Pat Mahoney Boris Zbarsky Eric Tiedemann Henry 'Pi' James Loring Holden Robert Stone Michael Janssen John Hoffman Uoti Urpala Kevin Drum Denis Ahrens BitTorrent-3.4.2/redirdonate.html0100644000202400020240000000013207734433313015420 0ustar brambram BitTorrent-3.4.2/setup.py0100755000202400020240000000147507635421521013760 0ustar brambram#!/usr/bin/env python # Written by Bram Cohen # see LICENSE.txt for license information import sys assert sys.version >= '2', "Install Python 2.0 or greater" from distutils.core import setup, Extension import BitTorrent setup( name = "BitTorrent", version = BitTorrent.version, author = "Bram Cohen", author_email = "", url = "http://www.bitconjurer.org/BitTorrent/", license = "MIT", packages = ["BitTorrent"], scripts = ["btdownloadgui.py", "btdownloadheadless.py", "btdownloadlibrary.py", "bttrack.py", "btmakemetafile.py", "btlaunchmany.py", "btcompletedir.py", "btdownloadcurses.py", "btcompletedirgui.py", "btlaunchmanycurses.py", "btmakemetafile.py", "btreannounce.py", "btrename.py", "btshowmetainfo.py", "bttest.py"] ) BitTorrent-3.4.2/wincompletedirsetup.py0100644000202400020240000000035010024652157016707 0ustar brambram#!/usr/bin/env python # Written by Bram Cohen # see LICENSE.txt for license information from distutils.core import setup import py2exe setup(windows=[{'script': 'btcompletedirgui.py', "icon_resources": [(1, "bittorrent.ico")]}]) BitTorrent-3.4.2/winsetup.py0100755000202400020240000000034510024652157014466 0ustar brambram#!/usr/bin/env python # Written by Bram Cohen # see LICENSE.txt for license information from distutils.core import setup import py2exe setup(windows=[{'script': 'btdownloadgui.py', "icon_resources": [(1, "bittorrent.ico")]}]) BitTorrent-3.4.2/debian/0042755000202400020240000000000010034142504013447 5ustar brambramBitTorrent-3.4.2/osx/0042755000202400020240000000000010034142504013036 5ustar brambramBitTorrent-3.4.2/osx/Dutch.lproj/0042755000202400020240000000000010034142504015232 5ustar brambramBitTorrent-3.4.2/osx/Dutch.lproj/DLWindow.nib/0042755000202400020240000000000010034142504017470 5ustar brambramBitTorrent-3.4.2/osx/Dutch.lproj/DLWindow.nib/classes.nib0100644000202400020240000000144307742026620021630 0ustar brambram{ IBClasses = ( { ACTIONS = {cancelDl = id; takeMaxUploadRateFrom = id; takeMaxUploadsFrom = id; }; CLASS = DLWindowController; LANGUAGE = ObjC; OUTLETS = { dlRate = id; dlTotal = id; file = id; lastError = id; "max_upload_rate" = id; "max_uploads" = id; peerStat = id; percentCompleted = id; progressBar = id; timeRemaining = id; ulRate = id; ulTotal = id; }; SUPERCLASS = NSWindowController; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; }BitTorrent-3.4.2/osx/Dutch.lproj/DLWindow.nib/info.nib0100644000202400020240000000073307742026620021127 0ustar brambram IBDocumentLocation 131 84 356 240 0 0 1280 832 IBFramework Version 291.0 IBOpenObjects 51 5 IBSystem Version 6L60 BitTorrent-3.4.2/osx/Dutch.lproj/DLWindow.nib/keyedobjects.nib0100644000202400020240000001642707742026620022656 0ustar brambrambplist00lY$archiverX$versionX$objectsT$top_NSKeyedArchiver (,-348<QXglnqrwx|   !#%+017:<?CJKORTUWYZ []_fgknpqspu vxz{}~}@EFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghiU$null  !"#$%&']NSFontManagerV$classYNSNextOid_NSVisibleWindowsVNSRoot\NSOidsValues_NSClassesValues]NSConnections]NSNamesValues]NSObjectsKeysZNSOidsKeys]NSClassesKeys[NSFramework[NSNamesKeys_NSObjectsValuesb )*+[NSClassName_DLWindowController./01Z$classnameX$classes^NSCustomObject02XNSObject_IBCocoaFramework 567ZNS.objects./9:\NSMutableSet9;2UNSSet 5=>/?@ABCDEFGHIJKLMNOP iky RSTUVWWNSLabel]NSDestinationXNSSource YZ[\]^_`abcdefa_NSNextResponderXNSvFlagsZNSMaxValue\NSDrawMatrixYNSpiFlagsWNSFrame[NSSuperview "#?  Yh^ijkZNSSubviewscb m ./opZNSPSMatrixo2_{{20, 16}, {346, 20}}./st_NSProgressIndicatorsuv2VNSView[NSResponder[progressBar./yz_NSNibOutletConnectory{2^NSNibConnector RSTU}~h a\NSWindowViewYNSWTFlags]NSWindowTitleYNSMinSizeYNSMaxSize\NSWindowRect_NSWindowStyleMask_NSWindowBacking\NSScreenRect]NSWindowClass[NSViewClassg0xefd_{{275, 403}, {386, 153}}_Voortgang binnenhalenXNSWindow YNS.stringTView./_NSMutableString2XNSString 5=W=DSY Z^_aa[NSProtoCell[NSCellClassYNSNumRowsZNSCellSize_NSBackgroundColor_NSIntercellSpacingYNSNumCols]NSSelectedRow]NSMatrixFlagsYNSEnabled]NSSelectedColWNSCellsVNSFont_NSCellBackgroundColor32<061 ;_{{6, 78}, {360, 68}} 5=Ǥˀ&), YNSSupport]NSControlView\NSCellFlags2[NSTitleCell\NSTitleWidth[NSCellFlagsZNSContents% @""B@P VNSSizeVNSNameXNSfFlags!"AP \LucidaGrande./2 $#XBestand:./VNSCell2./ZNSFormCell2\NSActionCell UNSTag'@ (_Percentage binnen: ـ* +ZTijd over: -!A ._Recentste fout:./^NSMutableArray 2WNSArrayY{360, 17}V{0, 0}  ـ4q@ 5VField: WNSColor]NSCatalogName[NSColorName\NSColorSpace:978VSystem\controlColor  WNSWhiteI0.666667./"2 $B1./&'VNSForm&()*uv2XNSMatrixY%NSMatrixYNSControl YZ^_,a-./aC?#>_{{22, 2}, {340, 13}} 23456[NSTextColorB@@A 89"A  ;D0.5./=>_NSTextFieldCell=2./@A[NSTextField@B*uv2\%NSTextFieldY Z^_DaEFGHaIĀPOMNEF_{{6, 44}, {220, 34}} 5=LMNGJ PQـH"B SI_Downloadsnelheid: VQـK XL_Uploadsnelheid:Y{220, 17} \QـQ ^RY Z^_`aabcdaeĀ_^\]TU_{{227, 44}, {159, 34}} 5=hijVY lmـW"B0 oXWtotaal: rmـZ t[Y{159, 17} wmـ` ya_{{1, 9}, {386, 153}}./u|uv2_{{0, 0}, {1280, 832}}Z{286, 175}_{3.40282e+38, 175}./_NSWindowTemplate2Xdelegate RSTU~jVwindow RSTUxl Y^_,mon Yh^i_{{20, 73}, {54, 19}} 23_NSDrawsBackgroundsq@uqApQ0 "A0r t_textBackgroundColor wvYtextColor B0_max_upload_rate RSTU~z Y^_,|{_{{20, 45}, {40, 19}} 23q@}Q4[max_uploads RST_takeMaxUploadRateFrom:./_NSNibControlConnector{2 RST_takeMaxUploadsFrom: SVNSFileXNSMarker_NSToolTipHelpKey_'Maximum Upload Rate in Kilobytes/Second./_NSIBHelpConnector2 S_Number of Simultaneous Uploads RSTUXpeerStat RSTUTfile RSTU_percentCompleted RSTU]timeRemaining RSTUYlastError RSTUiWdlTotal RSTUMVdlRate RSTUNVulRate RSTUjWulTotal 5뀨MjWNia~ x_{{536, 168}, {213, 112}}UPanelWNSPanel 5=_{{1, 1}, {213, 112}}Z{213, 129}_{3.40282e+38, 3.40282e+38}./  2 5aaaa~a 5W~ 5     XNSForm11YNSForm111Q1\File's Owner\NSTextField1VWindow_NSTextField111111WNSForm1 57 57 5&L@iKBNIMHJMOaWAN~jPE?CDGF 5& !"#$%&'()*+,-./0123456789:;<=>?@ABCD€ÀĀŀƀǀȀɀʀˀ̀̀΀πЀрҀӀԀՀր׀؀ـڀۀ܀݀ހVN+]M=_KYF3L^`[40Z\a@<G$JIH>?CA;E./jk^NSIBObjectDataj2mn]IB.objectdata#,5:LQ[ipz "$&(*,.02468:<>GSUWlu%')+-/13579;=?ACEGIZbpy{}#%').09DIaj$1;IS]j~ !&/AHQZegikm*8BPX_wy{}   ! . : E G L N S \ ^ _ p w ~   = C E J _ a v     . 5 = G N k m r     , / 8 ? N W a k   $ - 6 B O \ 3XZoq7@EGIjlq %.5MXmv6JLNSU^`bsxz| 7\egiu .Xauz +<>HY[ctv}"$&(*,.0KQYbkp +-/13579;=?HRTanuSUWY[]_acegikmoqsuwy{} oBitTorrent-3.4.2/osx/Dutch.lproj/Localizable.strings0100644000202400020240000000722407742026620021102 0ustar brambram/* * Dutch translation by Martijn Dekker <martijn@inlv.demon.nl> * 21 June 2003 */ /* transfer rate */ "%2.1f K/s" = "%2.1f k/s"; /* percent dl completed */ "%2.1f%%" = "%2.1f%%"; /* size and filename for dl window tite */ "(%1.1f MB) %@ " = "(%1.1f MB) %@ "; /* one hundred percent */ "100%" = "100%"; /* shut down internet explorer request */ "BitTorrent cannot register as a helper application while Internet Explorer is running. This only needs to be done once. Please quit Internet Explorer before clicking OK." = "BitTorrent kan zich niet als helperprogramma registreren terwijl Internet Explorer open staat. Dit hoeft maar n keer gedaan te worden. Stop Internet Explorer voordat u op OK klikt."; /* save directory instructions */ "Choose directory, choose existing directory to resume." = "Kies een map. Om een sessie te hervatten kiest u een bestaande map."; /* download failed */ "Download Failed!" = "Binnenhalen mislukt!"; /* download completed successfully */ "Download Succeeded." = "Het bestand is binnen."; /* invalid file chose fo generate */ "Invalid File" = "Ongeldig bestand"; /* No comment provided by engineer. */ "Invalid Tracker URL" = "Ongeldige tracker-URL"; /* explanation of helper registration */ "Quit Internet Explorer" = "Stop Internet Explorer"; /* save directory prompt */ "Save" = "Bewaar"; /* save instructions */ "Save, choose an existing file to resume." = "Bewaar. Om een sessie te hervatten kiest u een bestaand bestand."; /* empty file for generate */ "You must drag a file or folder into the generate window first." = "U moet eerst een bestand of map naar het aanmaakvenster slepen."; /* No comment provided by engineer. */ "You must enter the tracker URL. Contact the tracker administrator for the URL." = "U moet de tracker-URL opgeven. Neem daarvoor contact op met de beheerder van de tracker."; BitTorrent-3.4.2/osx/Dutch.lproj/MainMenu.nib/0042755000202400020240000000000010034142504017512 5ustar brambramBitTorrent-3.4.2/osx/Dutch.lproj/MainMenu.nib/classes.nib0100644000202400020240000000137207742026620021653 0ustar brambram{ IBClasses = ( { ACTIONS = { cancelUrl = id; openAbout = id; openGenerator = id; openPrefs = id; openTrackerResponse = id; openURL = id; takeUrl = id; }; CLASS = BTAppController; LANGUAGE = ObjC; OUTLETS = { aboutWindow = NSWindow; generator = id; url = NSTextField; urlWindow = NSWindow; versField = NSTextField; }; SUPERCLASS = NSObject; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; }BitTorrent-3.4.2/osx/Dutch.lproj/MainMenu.nib/info.nib0100644000202400020240000000114507742026620021147 0ustar brambram IBDocumentLocation 292 150 356 240 0 0 1280 832 IBEditorPositions 29 71 557 319 44 0 0 1280 832 IBFramework Version 291.0 IBOpenObjects 203 29 228 IBSystem Version 6L60 BitTorrent-3.4.2/osx/Dutch.lproj/MainMenu.nib/keyedobjects.nib0100644000202400020240000002754507742026621022704 0ustar brambrambplist00Y$archiverX$versionX$objectsT$top_NSKeyedArchiver (,07:<@D_eu|}~   ,12AHILVWX\^abdhopsxy    #$'(+,/0H3679:=>ABCDEFHILNOPSUVWkptutvxz~u~uuuuuuu   ,-./0123456789:i;=>@ABC/DjFGHJU$null  !"#$%&']NSFontManagerV$classYNSNextOid_NSVisibleWindowsVNSRoot\NSOidsValues_NSClassesValues]NSConnections]NSNamesValues]NSObjectsKeysZNSOidsKeys]NSClassesKeys[NSFramework[NSNamesKeys_NSObjectsValuesQO 0ρPN/. )*+[NSClassName -./YNS.string]NSApplication1234Z$classnameX$classes_NSMutableString356XNSStringXNSObject1289^NSCustomObject86 -.;_IBCocoaFramework =>?ZNS.objects12AB\NSMutableSetAC6UNSSet =EFGHIJKLMNOPQRSTUVWXYZ[\]^ #',06;@EIOTYlwŀǀ `abcdWNSLabelXNSSource fghijklmnopqrst]NSMnemonicLoc\NSMixedImage_NSKeyEquivModMaskWNSTitleVNSMenuZNSKeyEquivYNSOnImage  vwixyz{[NSMenuItemsVNSNameր\MinimaliseerQm )^NSResourceNameWNSImage_NSMenuCheckmark12_NSCustomResource6_%NSCustomResource )_NSMenuMixedState12ZNSMenuItem6 -._performMiniaturize:12_NSNibControlConnector6^NSNibConnector `ab fghijklmnoprt_Alles op voorgrondP -._arrangeInFront: `ab]NSDestination" fghijklmnopt ! vwix  _Stop 'BitTorrent'Qq -.Zterminate: `ab&$ fghijklmnopt%^Verberg andere_hideOtherApplications: `ab+( fghijklmnopt)*_Verberg 'BitTorrent'QhUhide: `ab/- fghijklmnopt.ZToon alles_unhideAllApplications: `abȀ51 fghijklmnopt324 vixπՀTKnipQx -.Tcut: `abր:7 fghijklmnopt89TPlakQv -.Vpaste: `ab?< fghijklmnopt=>_Selecteer allesQa -.ZselectAll: `abDA fghijklmnoptBCWKopieerQc -.Ucopy: `abHF fghijklmnoptGYVerwijder -.Vclear: `abNJ fghijklmnoptLKM vix^NSNoAutoenable USluitQw]performClose: `a   SRP )*Q_BTAppControllerXdelegate12_NSNibOutletConnector6 `ab XU fghijklmnoptVW[Open URL...QOXopenURL: `a  kZ !"#$%&'()*+(_NSNextResponderVNSCellXNSvFlagsWNSFrameYNSEnabled[NSSuperviewj[]*\ !-$./0ZNSSubviews_{{25, 60}, {313, 22}} 3456789:;<=>?@_NSBackgroundColorYNSSupport]NSControlView\NSCellFlags2[NSTextColor_NSDrawsBackground[NSCellFlagsZNSContentsia^@fqA BwCDEFGVNSSizeXNSfFlags`"AP_\LucidaGrande12JKVNSFontJ6 MNOPQRSTUWNSColor]NSCatalogName[NSColorName\NSColorSpaceedbcVSystem_textBackgroundColor YPQZ[WNSWhiteB112M]M6 MNOPQ_S`UhgYtextColor YPQc[B012ef_NSTextFieldCelleg"6\NSActionCell12ij[NSTextFieldiklmn6\%NSTextFieldYNSControlVNSView[NSResponderSurl `abq rvm !"#$%&t(uvw(uo!n_{{260, 12}, {84, 32}} z4{56|}~9:=r_NSKeyEquivalent_NSAlternateContents_NSPeriodicDelay^NSButtonFlags2]NSButtonFlags_NSPeriodicInterval_NSAlternateImagets8@qpROK BwCDErYHelveticaQ 12\NSButtonCellg"6]%NSButtonCell12XNSButtonlmn6XtakeUrl: `ab ~x !"#$%&t(v(zy_{{169, 12}, {97, 32}} z4{56|}~9:=}|{XAnnuleer -. -.ZcancelUrl: `a   (\NSWindowViewYNSWTFlags]NSWindowTitleYNSMinSizeYNSMaxSize\NSWindowRect_NSWindowStyleMask_NSWindowBacking\NSScreenRect]NSWindowClass[NSViewClassx_{{402, 360}, {358, 102}}WNSPanel -.TView =EȤr !"#$%&'((_{{22, 85}, {236, 17}} 345679:;=>Ҁ_Voer de BitTorrent-URL in... MNOPQSU\controlColor YPQ[I0.666667 MNOPQ_SU_controlTextColor12^NSMutableArray6WNSArray_{{1, 1}, {358, 102}}12mmn6_{{0, 0}, {1280, 832}}Z{358, 124}_{3.40282e+38, 124}12_NSWindowTemplate6YurlWindow `ab 퀛 fghijklmnoptWOpen...Qo_openTrackerResponse: `ab  fgijklmnot_Over BitTorrent...ZopenAbout: `a  Ā [€À_{{266, 545}, {438, 133}}_About BitTorrent -. !-$.  =E !"$%&'_{{20, 81}, {204, 51}} 345679:;>ZBitTorrent BwCD"B_LucidaGrande-Bold !"$%&'!"_{{29, 64}, {331, 17}} 345679:;=>%&!_$by Bram Cohen (bram@bitconjurer.org) !"$%&')*_{{29, 42}, {358, 14}} 345679:;->%._;Mac OS X version by Andrew Loewenstern (andrew@gigagig.org) BwCD12"A0 !"$%&'45_{{29, 20}, {244, 14}} 345679:;->%8_"http://bitconjurer.org/BitTorrent/ !"$%&';<_{{306, 93}, {115, 17}} 345679:;=?@@U3.0.0_{{1, 1}, {438, 133}}Z{213, 129}_{3.40282e+38, 3.40282e+38}[aboutWindow `a G YversField `abJ Kʀ fghijklmnopMt_Maak Torrent-bestand aan...^openGenerator: `abQ R΀ fghijklmnopTt]Voorkeuren...ZopenPrefs: =XY-4Z[\]^r_`abc defrKRgdhij(Ѐ׀с  fghlimjklmnopnodtYNSSubmenuXNSActionҀ vwixqrsVWijzig^submenuAction: =Ew12jyj6 fghlimklmnop{|}tڀ؀THelp vix܀ =E fghijklmnop{tހ_BitTorrent HelpQ? fghijklmnopt\NSIsDisabled]NSIsSeparator fghlimjklmnoprdtWVenster =Edi fghijklmnoprt^_NSWindowsMenu fghlimklmnopt[Open Recent vwix -. =E fghijklmnoptZClear Menu__NSRecentDocumentsMenu fghijklmnopt fghlimklmnoptXSpelling vix =E fghijklmnopt[Spelling...Q: fghijklmnopt^Check SpellingQ; fghijklmnopt_Check Spelling As You Type fghijklmnopt fghijklmnopt fghlimjklmnopdtWArchief =EȦbaKXMainMenu =EˤhcZ] fghlimjklmnopdt  =EЪR\e_f fghlimjklmnopgt]Voorzieningen vwix؁ =E?__NSServicesMenu fghijklmnopt\_NSAppleMenu[_NSMainMenu fghlimklmnoptTFind vix -. =E!$'* fghijklmnopt WFind...Qf fghijklmnopt"#YFind NextQg fghijklmnopt%&]Find PreviousQd fghijklmnopt()_Use Selection for FindQe fghijklmnopt+,_Scroll to SelectionQj126 =X 4ddZ(d(dh](re(rdrc =X   cdi`ZR[jar =X  !"#$%&'()*+123456789:;<=>?@ABCDEFGHIJKLM -. -.XurlPanel\NSTextField2\NSMenuItem13T1111_NSTextField1111[NSMenuItem1[NSMenuItem4[NSMenuItem3\NSMenuItem10]NSTextField21ZaboutPanel\NSTextField1S121 -.PYls %')JLNcdm   " C E T m ~ " / 1 3 T V X Z g i k p r {     + 8 : < ] _ a i k t z  # & ' - / = N P R T ] _ q z    $ 6 = F N X d f h j l n  &2=?ACHJSdktv{} !$&/4IKMWdgp ":o ')+57@MXfox#,5@QSU $&(*,.0246QYbgpy{"/9NPcl{ %')JLNVXo   4GPaceny{}-/1Ijoq0AFHace}!#)@Kht  ,.<GPS$&(9<?BIXaluz+8Foqs{  #&(IKVo$&GIKZ\}%.;DMV   > K W |  !!!!!+!-!N!Q!T!b!d!!!!!!!!!!!!!"g"p"""#### ####### ###&#)#,#/#2#5#8#;#>#A#D#G#J#M#P#S#V#_#h#q#~###########$$$$$($1$?$K$W$`$b$k$t$}%%#%%%%%%%%%%%%%%%%%%%%%%&&&& &&&&&&& &#&&&)&,&/&2&5&8&;&>&A&D&G&J&M&P&S&V&Y&\&_&b&e&h&k&n&q&t&w&z&}&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&'''''' ' ''''''''''!'#'%''')'+'-'/'1'3'5'7'9';'='?'H'W'\'a'o'qBitTorrent-3.4.2/osx/Dutch.lproj/MainMenu~.nib/0042755000202400020240000000000010034142504017710 5ustar brambramBitTorrent-3.4.2/osx/Dutch.lproj/Metainfo.nib/0042755000202400020240000000000010034142504017543 5ustar brambramBitTorrent-3.4.2/osx/Dutch.lproj/Metainfo.nib/classes.nib0100644000202400020240000000105107742026621021677 0ustar brambram{ IBClasses = ( {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, { ACTIONS = {generate = id; }; CLASS = Generate; LANGUAGE = ObjC; OUTLETS = { announce = id; fileField = id; gButton = id; gWindow = id; iconWell = id; progressMeter = id; subCheck = id; }; SUPERCLASS = NSObject; } ); IBVersion = 1; }BitTorrent-3.4.2/osx/Dutch.lproj/Metainfo.nib/info.nib0100644000202400020240000000070507742026621021202 0ustar brambram IBDocumentLocation 346 227 356 240 0 0 1024 746 IBFramework Version 291.0 IBOpenObjects 13 IBSystem Version 6L60 BitTorrent-3.4.2/osx/Dutch.lproj/Metainfo.nib/objects.nib0100644000202400020240000000523007742026621021676 0ustar brambram typedstream@NSIBObjectDataNSObjectNSCustomObject)@@NSString+GenerateiNSWindowTemplate iiffffi@@@@@cՁpMaak Torrent-bestand aanNSWindowNSMutableStringViewNSView) NSResponder @@@@ffffffffNSMutableArrayNSArray NSTextField NSControl) ]jjicc@NSTextFieldCell> NSActionCellNSCellAiiqA@@@@@NSFont$[36c]LucidaGrandef c i:c@@NSColor@@@SystemtextBackgroundColorff textColor: <rrA@$LucidaGrande $s@,Sleep een bestand of map naar het venster...$LucidaGrande  controlColor?*controlTextColorNSBox*ÙÒ ff@@cccBox QQ@ Tracker-URL:ɯNSButton!^ q q  NSButtonCell?8 Maak aan...̄ ssii@@@@@@[28c]Helvetica ͒!<@@@@Ϩ$$LucidaGrande  ԸK@NSProgressIndicatorܠb NSPSMatrix[12f]XXccddcĒ"ۙے 1Box ͒$XϨ$>Maak een torrent voor alle bestanden/mappen binnen deze map...กH IBDocumentLocation 91 157 356 240 0 0 1024 746 IBFramework Version 291.0 IBOpenObjects 5 IBSystem Version 6L60 BitTorrent-3.4.2/osx/Dutch.lproj/Preferences.nib/keyedobjects.nib0100644000202400020240000001426207742026621023424 0ustar brambrambplist00Y$archiverX$versionX$objectsT$top_NSKeyedArchiver (,-348<GN^_ghk  ,-01237:=>BCDEHIMNOP_acdfghjloprsuw{|8/)U$null  !"#$%&']NSFontManagerV$classYNSNextOid_NSVisibleWindowsVNSRoot\NSOidsValues_NSClassesValues]NSConnections]NSNamesValues]NSObjectsKeysZNSOidsKeys]NSClassesKeys[NSFramework[NSNamesKeys_NSObjectsValues} )*+[NSClassName[Preferences./01Z$classnameX$classes^NSCustomObject02XNSObject_IBCocoaFramework 567ZNS.objects./9:\NSMutableSet9;2UNSSet 5=>.?@ABCDEF #twy{ HIJKLMWNSLabel]NSDestinationXNSSource OPQRSTUVWXYZ[\]YNSSupport]NSControlView\NSCellFlags2[NSTitleCell\NSTitleWidth[NSCellFlagsZNSContents  @"Cq@ P `abcdefVNSSizeVNSNameXNSfFlags"AP \LucidaGrande./ijVNSFonti2lmn opqrstuvwxyz{|}~}[NSProtoCell_NSNextResponder[NSCellClassYNSNumRowsZNSCellSize_NSBackgroundColor_NSIntercellSpacingYNSNumCols]NSSelectedCol]NSSelectedRow]NSMatrixFlagsWNSFrameYNSEnabled[NSSuperviewWNSCells_NSCellBackgroundColora*`;^5_\ ]: OQTUW_)Geef mijn IP-adres aan de tracker op als:./VNSCell2./ZNSFormCell2\NSActionCellRip./_NSNibOutletConnector2^NSNibConnector HIJK OPQRSTUVWY\"BT6881lmn opqrstuvwxyz{}}21/0,- OQTUW^Laagste poort:Wminport HIJK" OPQRSTUVWY\UNSTag T6889 OQTUW!^Hoogste poort:Wmaxport HIJKs$ }\NSWindowViewYNSWTFlags]NSWindowTitleYNSMinSizeYNSMaxSize\NSWindowRect_NSWindowStyleMask_NSWindowBacking\NSScreenRect]NSWindowClass[NSViewClassrpx&pq%o'(_{{362, 280}, {480, 192}}_Voorkeuren BitTorrentXNSWindow YNS.string)TView./_NSMutableString2XNSString mwZNSSubviewsV+n 5=X=_{{381, 12}, {85, 32}} OPQ T!"U#$W%&'()*+_NSKeyEquivalent_NSAlternateContents_NSPeriodicDelay^NSButtonFlags2]NSButtonFlags_NSPeriodicInterval_NSAlternateImageDCB8@@?VBewaar `abcd./AYHelvetica ^ ^./45\NSButtonCell462]%NSButtonCell./89XNSButton82 mwxy};<}HG_{{284, 12}, {97, 32}} OPQ T!"U#?W@&'()*AKJIXAnnuleer ^ ^ mwxy}FG}NM_{{58, 12}, {226, 32}} OPQ T!"U#JWK&'()*LQPO_Herstel fabrieksinstellingen... ^ ^Qm RSTURwVyW}XYZ[ \]^}]NSTransparentYNSOffsets_NSTitlePosition\NSBorderType]NSContentViewYNSBoxType[SXTYW 5=`\ mwybU_{{2, 2}, {452, 1}}./e2_{{12, 118}, {456, 5}}V{0, 0} OQTUW[iZ kSBox./mnUNSBoxm2_{{43, 150}, {390, 22}} 5=qMY{390, 22} OQRSTUVWYt[\]b OQTUWvc mwxyx}yz}mfe_{{20, 129}, {448, 13}} qOPQ}TU~[NSTextColorlh@igo[Bevindt u zich op hetzelfde privnetwerk als de tracker, dan geeft u uw echte IP-adres op. `abce"A  kj_controlTextColor  B0./_NSTextFieldCell2./[NSTextField2\%NSTextField_{{1, 9}, {480, 192}}_{{0, 0}, {1024, 746}}Z{213, 129}_{3.40282e+38, 3.40282e+38}./_NSWindowTemplate2Vwindow HIJvu\loadFactory:./_NSNibControlConnector2 HIJxWcancel: HIJzUsave: HIJK|Xdelegate 5~XM}\./2 5}}}}}X}} 5X\ 5€YNSButton1WNSForm1]NSTextField11VWindowXNSForm11[NSFormCell1\File's OwnerVNSBox1YNSButton2 57 57 5ϯF@M\BXAE}?DC 5ѯ瀒    ./^NSIBObjectData2]IB.objectdata#,5:LQ $1CQ_mx$-@ITVW`mtz".;GRTVX]_dikl}&2<G[pz#(*V_fkt []_acegi~!#Taky  / 8 A K M R [ m t }     < > S U \ q y   % / 9 @ L e g i k   $ 7 9 ; = B D M O Q S Z k m o y A C E G P Y b { }  7EOan| %+4MVYc  !3<EQ^k -4EGO`bhy{ "$&(*,.02468BJX_ht "$&(*,.02468:<>@BDFHJLNPRTVXZ\^`bkzBitTorrent-3.4.2/osx/BTAppController.h0100644000202400020240000000166007754611172016240 0ustar brambram/* BTAppController */ #import #import @interface BTAppController : NSObject { IBOutlet NSTextField *url, *versField; IBOutlet NSWindow *urlWindow, *aboutWindow; NSMutableArray *dlControllers; NSString *version; NSPoint lastPoint; id generator, prefs, prefwindow; } - (IBAction)cancelUrl:(id)sender; - (IBAction)openURL:(id)sender; - (IBAction)openTrackerResponse:(id)sender; - (IBAction)openAbout:(id)sender; - (IBAction)takeUrl:(id)sender; - (void)runWithStr:(NSString *)method:(NSString *)url controller:(id)controller; + (void)runWithDict:(NSDictionary *)dict; // application delegate messages - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; - init; - (NSNotificationCenter *)notificationCenter; - (PyThreadState *)tstate; - (void)setTstate:(PyThreadState *)nstate; - (IBAction)openGenerator:(id)sender; - (IBAction)openPrefs:(id)sender; @end BitTorrent-3.4.2/osx/BTAppController.m0100644000202400020240000001641107737210211016233 0ustar brambram#import "ICHelper.h" #import "BTAppController.h" #import "DLWindowController.h" #import "Generate.h" #import "pystructs.h" #import "callbacks.h" #import "Preferences.h" static PyThreadState *tstate; @implementation BTAppController - init { PyObject *mm, *md, *vers; [super init]; PyRun_SimpleString("from threading import Event;from BitTorrent.download import download;from binascii import *"); mm = PyImport_ImportModule("BitTorrent"); md = PyModule_GetDict(mm); vers = PyDict_GetItemString(md, "version"); version = [[NSString stringWithCString:PyString_AsString(vers)] retain]; tstate = PyEval_SaveThread(); [[[[ICHelper alloc] init] installICHandler:self] autorelease]; lastPoint.x = 0.0; lastPoint.y = 0.0; prefs = [[Preferences alloc] init]; return self; } - (id)loadDLWindow { id controller = [[DLWindowController alloc] init]; [NSBundle loadNibNamed:@"DLWindow" owner:controller]; if(lastPoint.x == 0.0 && lastPoint.y == 0.0) { lastPoint.x = NSMinX([[controller window] frame]); lastPoint.y = NSMaxY([[controller window] frame]); } lastPoint = [[controller window] cascadeTopLeftFromPoint:lastPoint]; [controller showWindow:self]; return controller; } - (IBAction)openPrefs:(id)sender { if (!prefwindow) { [NSBundle loadNibNamed:@"Preferences" owner:prefs]; prefwindow = [prefs window]; } [prefs showWindow:self]; } - (PyThreadState *)tstate { return tstate; } - (void)setTstate:(PyThreadState *)nstate { tstate = nstate; } - (IBAction)cancelUrl:(id)sender { [urlWindow orderOut:self]; } - (IBAction)openURL:(id)sender { [urlWindow makeKeyAndOrderFront:self]; } - (IBAction)openTrackerResponse:(id)sender; { NSOpenPanel *panel = [NSOpenPanel openPanel]; id controller; if([panel runModalForTypes:[NSArray arrayWithObjects:@"torrent", nil]]) { controller = [self loadDLWindow]; [self runWithStr:@"--responsefile":[panel filename] controller:controller]; } } - (IBAction)takeUrl:(id)sender { id controller; [urlWindow orderOut:self]; controller = [self loadDLWindow]; [self runWithStr:@"--url":[url stringValue] controller:controller]; } - (void)runWithStr:(NSString *)method :(NSString *)str controller:(id)controller { NSPort *left, *right; NSConnection *conn; NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:4]; PyObject *flag, *chooseFileFlag; PyObject *mm, *md, *event; left = [NSPort port]; right = [NSPort port]; NSUserDefaults *defaults; defaults = [NSUserDefaults standardUserDefaults]; // create UI side of the connection conn = [[NSConnection alloc] initWithReceivePort:left sendPort:right]; // set the new DLWindowController to be the root [conn setRootObject:controller]; [controller setConnection:conn]; PyEval_RestoreThread(tstate); // get __main__ mm = PyImport_ImportModule("__main__"); md = PyModule_GetDict(mm); // create flag event = PyDict_GetItemString(md, "Event"); flag = PyObject_CallObject(event, NULL); chooseFileFlag = PyObject_CallObject(event, NULL); [controller setFlag:flag]; // controller keeps this reference to flag [controller setChooseFlag:chooseFileFlag]; // controller keeps this reference to flag [dict setObject:right forKey:@"receive"]; [dict setObject:left forKey:@"send"]; [dict setObject:[NSData dataWithBytes:&chooseFileFlag length:sizeof(PyObject *)] forKey:@"chooseFileFlag"]; [dict setObject:[NSData dataWithBytes:&flag length:sizeof(PyObject *)] forKey:@"flag"]; [dict setObject:str forKey:@"str"]; [dict setObject:method forKey:@"method"]; [dict setObject:[NSString stringWithFormat:@"%@", [defaults objectForKey:MINPORT]] forKey:@"minport"]; [dict setObject:[NSString stringWithFormat:@"%@", [defaults objectForKey:MAXPORT]] forKey:@"maxport"]; if (![[defaults objectForKey:IP] isEqualToString:@""]) { [dict setObject:[NSString stringWithFormat:@"%@", [defaults objectForKey:IP]] forKey:@"ip"]; } Py_DECREF(mm); tstate = PyEval_SaveThread(); [NSThread detachNewThreadSelector:@selector(runWithDict:) toTarget:[self class] withObject:dict]; } + (void)runWithDict:(NSDictionary *)dict { NSAutoreleasePool *pool; bt_ProxyObject *proxy; NSString *str, *method; PyObject *chooseFile, *finished, *display, *nerror, *paramfunc, *mm, *md, *dl, *flag, *chooseFileFlag, *ret, *pathUpdated; PyThreadState *ts; const char *minport = [[dict objectForKey:@"minport"] cString]; const char *maxport = [[dict objectForKey:@"maxport"] cString]; pool = [[NSAutoreleasePool alloc] init]; ts = PyThreadState_New(tstate->interp); PyEval_RestoreThread(ts); // get the download function mm = PyImport_ImportModule("__main__"); md = PyModule_GetDict(mm); dl = PyDict_GetItemString(md, "download"); ts = PyEval_SaveThread(); // create proxy, which creates our side of connection [[dict objectForKey:@"chooseFileFlag"] getBytes:&chooseFileFlag]; proxy = (bt_ProxyObject *)bt_getProxy([dict objectForKey:@"receive"], [dict objectForKey:@"send"], (PyObject *)chooseFileFlag); PyEval_RestoreThread(ts); // get callbacks and other args str = [dict objectForKey:@"str"]; method = [dict objectForKey:@"method"]; chooseFile = PyObject_GetAttrString((PyObject *)proxy, "chooseFile"); display = PyObject_GetAttrString((PyObject *)proxy, "display"); finished = PyObject_GetAttrString((PyObject *)proxy, "finished"); pathUpdated = PyObject_GetAttrString((PyObject *)proxy, "pathUpdated"); nerror = PyObject_GetAttrString((PyObject *)proxy, "nerror"); paramfunc = PyObject_GetAttrString((PyObject *)proxy, "paramfunc"); [[dict objectForKey:@"flag"] getBytes:&flag]; // do the download! if([dict objectForKey:@"ip"]) { ret = PyObject_CallFunction(dl, "[ssssssssssss]OOOOOiOO", [method cString], [str UTF8String], "--display_interval", "1.5", "--spew", "1", "--minport", minport, "--maxport", maxport, "--ip", [[dict objectForKey:@"ip"] cString], chooseFile, display, finished, nerror, flag, 80, pathUpdated, paramfunc); } else { ret = PyObject_CallFunction(dl, "[ssssssssss]OOOOOiOO", [method cString], [str UTF8String], "--display_interval", "1.5", "--spew", "1", "--minport", minport, "--maxport", maxport, chooseFile, display, finished, nerror, flag, 80, pathUpdated, paramfunc); } [proxy->dlController dlExited]; // clean up Py_DECREF(mm); Py_DECREF(flag); Py_DECREF(proxy); [pool release]; ts = PyEval_SaveThread(); } - (IBAction)openAbout:(id)sender { [versField setStringValue:version]; [aboutWindow makeKeyAndOrderFront:self]; } - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { id controller = [self loadDLWindow]; [self runWithStr:@"--responsefile" :filename controller:controller]; return TRUE; } - (NSNotificationCenter *)notificationCenter { return [NSNotificationCenter defaultCenter]; } - (IBAction)openGenerator:(id)sender { if (!generator) { generator = [[Generate alloc] init]; } [generator open]; } @end BitTorrent-3.4.2/osx/BTCallbacks.h0100644000202400020240000000105007665604462015331 0ustar brambram#import @protocol BTCallbacks - (oneway void)finished; - (oneway void)error:(in NSString *)str; - (oneway void)display:(in NSData *)dict; - (oneway void)chooseFile:(in NSString *)defaultFile size:(in double)size isDirectory:(in int)dir; - (oneway void)dlExited; - (oneway void)pathUpdated:(in NSString *)newPath; - (oneway void)paramFunc:(in bycopy NSData *)paramDict; - (NSString *)savePath; @end @protocol MetaGenerateCallbacks - (oneway void)progress:(in float)val; - (oneway void)progressFname:(in NSString *)fname; @endBitTorrent-3.4.2/osx/BUILD_INSTRUCTIONS0100644000202400020240000000627407665605474015702 0ustar brambramBuild Instructions For OSX 1. Install Python 2.2 - Make sure ProjectBuilder knows where to find libpython2.2a Update the library's location in Groups&Files/BitTorrent/Frameworks/LinkedFrameworks Update Targets/BitTorrent/BuildSettings/SearchPaths/Libraries - IMPORTANT: You can't use Python.framework Use a regular python install (fink is ok!) 2. Build BitTorrent - click hammer icon The freeze.py script copies the dependent python modules from your python distribution into the app wrapper. I think this script will work independently of your particular python installation but I haven't tested it on any other systems. ** The project configuration could be better. ---------------- HACKING GUIDE The thing that makes BT-OSX not so straightforward is on the Mac you don't create a whole new instance of the app for each download (DL). That means each DL runs in it's own thread. BT uses callbacks for display updates but in Cocoa you con't reliably draw from any thread but the main thread or your app will crash. So what we do is use an NSConnection so messages sent from a DL thread to it's DL window are posted in the main thread on the next turn of the main event loop. This complicates setting up the DL but once things are setup communication between the python threads and the DL window controller is a simple ObjC message send. Basically, a pair of mach ports is created and the DL window is registered in the main thread's connection. Then the ports are passed into the DL thread and the DL thread gets a proxy to the DL window. When the DL thread messages it's window, NSConnection transparently packs up, sends over a Mach port, unpacks, and dispatches the message on the main event loop with the return value sent to the originating thread if necesary. components: BTAppController.m - App delegate, loads the DLWindow nibs and starts DLs DLWindowController.m - each DL window has one of these as it's delegate, it is messaged by the python callbacks to update the DL window BTCallbacks.h - ObjC protocol for messages the DLWindowController - responds to; chooseFile, display, and finished callbacks.. callbacks.m - Python object written in C, it's methods are passed into BitTorrent as callbacks. It encapsulates the connection to the DLWindowController and makes the ObjC calls to it. here's how it goes: main.m initializes the Python interpreter before starting the regular Appkit stuff when BTAppController starts a new DL, it loads the DLWindow nib and instantiates a DLWindowController. BTAppController then makes a pair of NSPorts and an NSConnection and registers the DLWindowController instance as the root object. BTAppController makes Python Event object for cancelling the download, then starts a new thread passing the ports and event flag The new thread uses the ports to create an NSConnection to the DLWindowController, creates a Python proxy object (defined in callbacks.m,) that encapsulates the DLWindowController NSProxy then sets up the arguments for the BT download. Now when BT calls back, the python callback functions message the DLWindowController via the NSConnection, with the messages being dispatched on the main event loop... BitTorrent-3.4.2/osx/DLWindowController.h0100644000202400020240000000421507754611172016760 0ustar brambram/* DLWindowController */ #import #import #import "BTCallbacks.h" @interface DLWindowController : NSWindowController { IBOutlet id dlRate; IBOutlet id lastError; IBOutlet id file; IBOutlet id percentCompleted; IBOutlet id progressBar; IBOutlet id timeRemaining; IBOutlet id ulRate; IBOutlet id max_upload_rate; IBOutlet id max_uploads; IBOutlet id peerStat; IBOutlet id ulTotal; IBOutlet id dlTotal; NSString *timeEst; NSString *savepath; float frac; PyObject *flag, *chooseflag; PyObject *rateFunc, *maxUploadsFunc, *addPeerFunc; double totalsize; NSConnection *conn; NSToolbar *toolBar; NSNetService *publisher; NSNetServiceBrowser *browser; int done; } - (IBAction)cancelDl:(id)sender; - (id)init; - (void)finished; - (void)error:(NSString *)str; - (void)display:(NSData *)dict; - (void)pathUpdated:(NSString *)newPath; - (void)chooseFile:(NSString *)defaultFile size:(double)size isDirectory:(int)dir; - (void)paramFunc:(NSData *)paramDict; - (NSString *)savePath; - (void)dlExited; - (PyObject *)rateFunc; - (PyObject *)maxUploadsFunc; - (void)setFlag:(PyObject *)nflag; - (void)setChooseFlag:(PyObject *)nflag; - (void)setConnection:(NSConnection *)nc; - (void)dealloc; - (IBAction)takeMaxUploadRateFrom:(id)sender; - (IBAction)takeMaxUploadsFrom:(id)sender; - (void)awakeFromNib; - (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo; - (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo; - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType; // toolbar delegate methods - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar; - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar; // rendezvous - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict; - (void)netServiceWillPublish:(NSNetService *)sender; - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing; @end BitTorrent-3.4.2/osx/DLWindowController.m0100644000202400020240000003667607737210212016774 0ustar brambram#import "DLWindowController.h" #import "Tstate.h" #import @interface RateItem : NSToolbarItem { } - validate; @end @implementation RateItem - validate { if ([[self target] rateFunc]) { [self setEnabled:YES]; } else { [self setEnabled:NO]; } return self; } @end @interface MaxUploadsItem : NSToolbarItem { } - validate; @end @implementation MaxUploadsItem - validate { if ([[self target] maxUploadsFunc]) { [self setEnabled:YES]; } else { [self setEnabled:NO]; } return self; } @end @implementation DLWindowController #define LASTDIR @"LastSaveDir" - (id)init { NSUserDefaults *defaults; NSMutableDictionary *appDefaults; [super init]; timeEst = [@"" retain]; conn = nil; done = 0; defaults = [NSUserDefaults standardUserDefaults]; appDefaults = [NSMutableDictionary dictionaryWithObject:NSHomeDirectory() forKey:LASTDIR]; [defaults registerDefaults:appDefaults]; toolBar = [[NSToolbar alloc] initWithIdentifier:@"DLWindow"]; [toolBar setDelegate:self]; [toolBar setVisible:NO]; return self; } - (void)awakeFromNib { [[self window] setToolbar:toolBar]; } - (void)windowWillClose:(NSNotification *)aNotification { [self cancelDl:self]; } - (void)windowDidClose:(NSNotification *)aNotification { [self autorelease]; } - (IBAction)cancelDl:(id)sender { [publisher stop]; [browser stop]; PyEval_RestoreThread([[NSApp delegate] tstate]); PyObject_CallMethod(flag, "set", NULL); [[NSApp delegate] setTstate:PyEval_SaveThread()]; } - (void)setFlag:(PyObject *)nflag { flag = nflag; Py_INCREF(flag); } - (void)setChooseFlag:(PyObject *)nflag { chooseflag = nflag; Py_INCREF(chooseflag); } - (void)setConnection:(NSConnection *)nc { if(conn) { [conn release]; } conn = [nc retain]; } - (NSString *)hours:(long) n { long h, r, m, sec; if (n == -1) return @""; if (n == 0) return @"Complete!"; h = n / (60 * 60); r = n % (60 * 60); m = r / 60; sec = r % 60; if (h > 1000000) return @""; if (h > 0) return [NSString stringWithFormat:@"%d hour(s) %2d min(s) %2d sec(s)", h, m, sec]; else return [NSString stringWithFormat:@"%2d min(s) %2d sec(s)", m, sec]; } - (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { if(returnCode == NSOKButton) { [file setStringValue:[NSString stringWithFormat:NSLocalizedString(@"(%1.1f MiB) %@ ", @"size and filename for dl window tite") , totalsize, [sheet filename]]]; [[self window] setTitleWithRepresentedFilename:[sheet filename]]; [[NSUserDefaults standardUserDefaults] setObject:[sheet directory] forKey:LASTDIR]; savepath = [[sheet filename] retain]; } else { // user cancelled [[self window] performClose:self]; } PyEval_RestoreThread([[NSApp delegate] tstate]); PyObject_CallMethod(chooseflag, "set", NULL); [[NSApp delegate] setTstate:PyEval_SaveThread()]; } - (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { if(returnCode == NSOKButton) { [file setStringValue:[NSString stringWithFormat:NSLocalizedString(@"(%1.1f MiB) %@ ", @"size and filename for dl window tite") , totalsize, [sheet filename]]]; [[self window] setTitleWithRepresentedFilename:[sheet filename]]; [[NSUserDefaults standardUserDefaults] setObject:[sheet directory] forKey:LASTDIR]; savepath = [[sheet filename] retain]; } else { // user cancelled [[self window] performClose:self]; } PyEval_RestoreThread([[NSApp delegate] tstate]); PyObject_CallMethod(chooseflag, "set", NULL); [[NSApp delegate] setTstate:PyEval_SaveThread()]; } - (NSString *)savePath { return savepath; } - (void)chooseFile:(NSString *)defaultFile size:(double)size isDirectory:(int)dir{ id panel; totalsize = size; [[self window] setTitleWithRepresentedFilename:defaultFile]; if(!dir) { panel = [NSSavePanel savePanel]; [panel setTitle:NSLocalizedString(@"Save, choose an existing file to resume.", @"save instructions")]; [panel beginSheetForDirectory:[[NSUserDefaults standardUserDefaults] objectForKey:LASTDIR] file:defaultFile modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; } else { panel = [NSOpenPanel openPanel]; [panel setCanChooseFiles:YES]; [panel setCanChooseDirectories:YES]; [panel setTitle:defaultFile]; [panel setPrompt:NSLocalizedString(@"Save", @"save directory prompt")]; [panel beginSheetForDirectory:[[NSUserDefaults standardUserDefaults] objectForKey:LASTDIR] file:defaultFile modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; } } - (void)pathUpdated:(NSString *)newPath { [file setStringValue:[NSString stringWithFormat:NSLocalizedString(@"(%1.1f MiB) %@ ", @"size and filename for dl window tite") , totalsize, newPath]]; [[self window] setTitleWithRepresentedFilename:newPath]; [savepath release]; savepath = [newPath retain]; } - (void)display:(NSData *)dict { NSString *str, *activity; PyObject *spew, *d, *x; long est; [dict getBytes:&d]; PyEval_RestoreThread([[NSApp delegate] tstate]); activity = nil; if(!done) { if (x = PyDict_GetItemString(d, "activity")) { activity = [NSString stringWithCString:PyString_AsString(x)]; } if (x = PyDict_GetItemString(d, "fractionDone")) { frac = PyFloat_AsDouble(x); } // format dict timeEst here and put in ivar timeEst if (x = PyDict_GetItemString(d, "timeEst")) { est = PyInt_AsLong(x); if(est > 0) { [timeEst release]; timeEst = [[self hours:est] retain]; } } if(activity && ![activity isEqualToString:@""]) { [timeEst release]; timeEst = [activity retain]; } str = [NSString stringWithFormat:NSLocalizedString(@"%2.1f%%", @"percent dl completed"), frac * 100]; [percentCompleted setStringValue:str]; [progressBar setDoubleValue:frac]; [timeRemaining setStringValue:timeEst]; } if (x = PyDict_GetItemString(d, "downRate")) { [dlRate setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%2.1f KiB/s",@"transfer rate"), PyFloat_AsDouble(x) / 1024]]; } if (x = PyDict_GetItemString(d, "downTotal")) { [dlTotal setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%2.1f MiB",@"transfer total"), PyFloat_AsDouble(x)]]; } if (x = PyDict_GetItemString(d, "upRate")) { [ulRate setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%2.1f KiB/s", @"transfer rate"), PyFloat_AsDouble(x) / 1024]]; } if (x = PyDict_GetItemString(d, "upTotal")) { [ulTotal setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%2.1f MiB",@"transfer total"), PyFloat_AsDouble(x)]]; } if (spew = PyDict_GetItemString(d, "spew")){ [peerStat setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Connected to %d peers.", @"num connected peers info string"), PyList_Size(spew)]]; } Py_DECREF(d); [[NSApp delegate] setTstate:PyEval_SaveThread()]; } - (void)paramFunc:(NSData *)paramDict { PyObject *dict, *pid, *ihash, *mm, *md, *he; char *peer_id, *info_hash; int listen_port; PyEval_RestoreThread([[NSApp delegate] tstate]); [paramDict getBytes:&dict]; rateFunc = PyDict_GetItemString(dict, "max_upload_rate"); if (rateFunc) { Py_INCREF(rateFunc); } maxUploadsFunc = PyDict_GetItemString(dict, "max_uploads"); if (maxUploadsFunc) { Py_INCREF(maxUploadsFunc); } addPeerFunc = PyDict_GetItemString(dict, "start_connection"); if (addPeerFunc) { Py_INCREF(addPeerFunc); } // rendezvous mm = PyImport_ImportModule("__main__"); md = PyModule_GetDict(mm); he = PyDict_GetItemString(md, "b2a_hex"); listen_port = PyInt_AsLong(PyDict_GetItemString(dict, "listen_port")); pid = PyObject_CallFunction(he, "O", PyDict_GetItemString(dict, "peer_id")); peer_id = PyString_AsString(pid); ihash = PyObject_CallFunction(he, "O", PyDict_GetItemString(dict, "info_hash")); info_hash = PyString_AsString(ihash); if (listen_port && peer_id && info_hash) { publisher = [[NSNetService alloc] initWithDomain:@"" type:[NSString stringWithFormat:@"_BitTorrent-%s._tcp", info_hash] name:[NSString stringWithCString:peer_id] port:listen_port]; if(publisher) { [publisher setDelegate:self]; [publisher publish]; } browser = [[NSNetServiceBrowser alloc] init]; if(browser) { [browser setDelegate:self]; [browser searchForServicesOfType:[NSString stringWithFormat:@"_BitTorrent-%s._tcp", info_hash] inDomain:@""]; } Py_DECREF(mm); Py_DECREF(pid); Py_DECREF(ihash); } Py_DECREF(dict); [[NSApp delegate] setTstate:PyEval_SaveThread()]; } - (void)finished { done = 1; [timeEst release]; timeEst = [NSLocalizedString(@"Download Succeeded.", @"download completed successfully") retain]; [progressBar setDoubleValue:100.0]; [timeRemaining setStringValue:timeEst]; [dlRate setStringValue:@""]; [percentCompleted setStringValue:NSLocalizedString(@"100%", @"one hundred percent")]; } - (void)dlExited { if(!done) { [progressBar setDoubleValue:0.0]; [timeRemaining setStringValue:NSLocalizedString(@"Download Failed!", @"download failed")]; [dlRate setStringValue:@""]; [ulRate setStringValue:@""]; [percentCompleted setStringValue:@""]; if(publisher) { [publisher stop]; } } } - (void)error:(NSString *)str { [lastError setStringValue:str]; } - (void)dealloc { [conn release]; conn = nil; [timeEst release]; timeEst = nil; PyEval_RestoreThread([[NSApp delegate] tstate]); if (flag) { Py_DECREF(flag); flag = nil; } if (chooseflag) { Py_DECREF(chooseflag); chooseflag = nil; } if(rateFunc) { Py_DECREF(rateFunc); rateFunc = nil; } if(publisher) { [publisher release]; } if(browser) { [browser release]; } [[NSApp delegate] setTstate:PyEval_SaveThread()]; [super dealloc]; } // Services stuff... - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType { if (returnType == nil && ([sendType isEqualToString:NSFilenamesPboardType] ||[sendType isEqualToString:NSStringPboardType])) { return self; } return nil; } - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard types:(NSArray *)types { if ([types containsObject:NSFilenamesPboardType] == NO && [types containsObject:NSStringPboardType] == NO) { return NO; } [pboard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil]; [pboard setPropertyList:[NSArray arrayWithObjects:savepath, nil] forType:NSFilenamesPboardType]; [pboard setString:savepath forType:NSStringPboardType]; return YES; } - (PyObject *)rateFunc { return rateFunc; } - (PyObject *) maxUploadsFunc { return maxUploadsFunc; } - (IBAction)takeMaxUploadRateFrom:(id)sender { PyObject *args; [sender setIntValue:[sender intValue]]; PyEval_RestoreThread([[NSApp delegate] tstate]); args = Py_BuildValue("(i)", [sender intValue] * 1000); PyObject_CallObject(rateFunc, args); Py_DECREF(args); [[NSApp delegate] setTstate:PyEval_SaveThread()]; } - (IBAction)takeMaxUploadsFrom:(id)sender { PyObject *args; int val = [sender intValue]; if (val < 2) { val = 2; } [sender setIntValue:val]; PyEval_RestoreThread([[NSApp delegate] tstate]); args = Py_BuildValue("(i)", val); PyObject_CallObject(maxUploadsFunc, args); Py_DECREF(args); [[NSApp delegate] setTstate:PyEval_SaveThread()]; } // toolbar delegate methods - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar { return (NSArray *)[NSArray arrayWithObjects:@"max_upload_rate", @"max_uploads", nil]; } - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar { return (NSArray *)[NSArray arrayWithObjects:@"max_upload_rate", @"max_uploads", nil]; } - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag { NSToolbarItem *item; NSRect rect; if ([itemIdentifier isEqualTo:@"max_upload_rate"]) { item = [[RateItem alloc] initWithItemIdentifier:@"max_upload_rate"]; [item setView:max_upload_rate]; [item setTarget:self]; [item setAction:@selector(takeMaxUploadRateFrom:)]; [item setLabel:NSLocalizedString(@"Max UL KiB/s", @"max_upload_rate toolbar label")]; [item setPaletteLabel:NSLocalizedString(@"Maximum Upload Rate", @"max_upload_rate toolbar palette label")]; rect = [max_upload_rate frame]; [item setMinSize:rect.size]; [item setEnabled:NO]; } else if ([itemIdentifier isEqualTo:@"max_uploads"]) { item = [[MaxUploadsItem alloc] initWithItemIdentifier:@"max_uploads"]; [item setView:max_uploads]; [item setTarget:self]; [item setAction:@selector(takeMaxUploadsFrom:)]; [item setLabel:NSLocalizedString(@"Num Uploads", @"max_uploads toolbar label")]; [item setPaletteLabel:NSLocalizedString(@"Number of Uploads", @"max_uploads toolbar palette label")]; rect = [max_uploads frame]; [item setMinSize:rect.size]; [item setEnabled:NO]; } return item; } // rendezvous delegate - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict { NSLog(@"Failed to publish..."); } - (void)netServiceWillPublish:(NSNetService *)sender { } - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)peer moreComing:(BOOL)moreComing { [peer setDelegate:self]; [peer resolve]; } - (void)netServiceDidResolveAddress:(NSNetService *)peer { struct sockaddr_in *ip, ip_s; NSData *a; char *str; PyObject *mm, *md, *he, *pid; NSEnumerator *iter = [[peer addresses] objectEnumerator]; PyEval_RestoreThread([[NSApp delegate] tstate]); mm = PyImport_ImportModule("__main__"); md = PyModule_GetDict(mm); he = PyDict_GetItemString(md, "a2b_hex"); while ((a = [iter nextObject]) != nil) { [a getBytes:&ip_s]; ip = &ip_s; union { uint32_t l; u_char b[4]; } addr = { ip->sin_addr.s_addr }; union { uint16_t s; u_char b[2]; } port = { ip->sin_port }; uint16_t PortAsNumber = ((uint16_t)port.b[0]) << 8 | port.b[1]; str = [[NSString stringWithFormat:@"%d.%d.%d.%d", addr.b[0], addr.b[1], addr.b[2], addr.b[3]] cString]; pid = PyObject_CallFunction(he, "s", [[peer name] cString]); if(pid) { PyObject_CallFunction(addPeerFunc, "(si)O", str, PortAsNumber, pid); Py_DECREF(pid); } } Py_DECREF(mm); [[NSApp delegate] setTstate:PyEval_SaveThread()]; } @end BitTorrent-3.4.2/osx/Generate.h0100644000202400020240000000163607754611172014763 0ustar brambram/* Generate */ #import #import #import "BTCallbacks.h" @interface Generate : NSObject { IBOutlet id fileField; IBOutlet id gWindow; IBOutlet id announce; IBOutlet id iconWell; IBOutlet id gButton; IBOutlet id progressMeter; IBOutlet id subCheck; NSString *fname; PyObject *endflag; BOOL done; } - (IBAction)generate:(id)sender; - (void)open; - (IBAction)cancel:(id)sender; - (void)displayFile:(NSString *)f; - (void)savePanelDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo; // drag protocol - (NSDragOperation)draggingEntered:(id )sender; - (void)draggingExited:(id )sender; - (BOOL)performDragOperation:(id )sender; - (void)progress:(float)val; - (void)progressFname:(NSString *)val; - (void)prepareGenerateSaveFile:(NSString *)f; @end BitTorrent-3.4.2/osx/Generate.m0100644000202400020240000002377007737210213014763 0ustar brambram#import "Generate.h" #import "Tstate.h" #import "pystructs.h" #import "callbacks.h" @protocol GCallbacks - (void)endGenerate; @end @implementation Generate #define ANNOUNCEKEY @"AnnounceString" #define GWINKEY @"GenerateFrame" #define COMPLETEDIRKEY @"CompleteDir" - init { NSUserDefaults *defaults; NSMutableDictionary *appDefaults; [super init]; [NSBundle loadNibNamed:@"Metainfo" owner:self]; [gWindow registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]]; defaults = [NSUserDefaults standardUserDefaults]; appDefaults = [NSMutableDictionary dictionaryWithObject:@"" forKey:ANNOUNCEKEY]; [appDefaults setObject:[NSNumber numberWithInt:NSOffState] forKey:COMPLETEDIRKEY]; [appDefaults setObject:[gWindow stringWithSavedFrame] forKey:GWINKEY]; [defaults registerDefaults:appDefaults]; [gWindow setFrameAutosaveName:GWINKEY]; [gWindow setFrameUsingName:GWINKEY]; [announce setStringValue:[defaults objectForKey:ANNOUNCEKEY]]; [subCheck setState:[[defaults objectForKey:COMPLETEDIRKEY] intValue]]; return self; } - (IBAction)generate:(id)sender { NSSavePanel *panel = [NSSavePanel savePanel]; NSArray *a; NSRange range; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // do a bunch of checking // put up alert sheet if error [gButton setEnabled:NO]; if([[announce stringValue] compare:@""] == NSOrderedSame) { NSBeginAlertSheet(NSLocalizedString(@"Invalid Tracker URL", @""), nil, nil, nil, gWindow, nil, nil, nil, nil, NSLocalizedString(@"You must enter the tracker URL. Contact the tracker administrator for the URL.", @"")); } else if (fname == nil) { NSBeginAlertSheet(NSLocalizedString(@"Invalid File", @"invalid file chose fo generate"), nil, nil, nil, gWindow, nil, nil, nil, nil, NSLocalizedString(@"You must drag a file or folder into the generate window first.", @"empty file for generate")); [gButton setEnabled:YES]; } else { [defaults setObject:[announce stringValue] forKey:ANNOUNCEKEY]; [defaults setObject:[NSNumber numberWithInt:[subCheck state]] forKey:COMPLETEDIRKEY]; a = [fname pathComponents]; range.location = 0; range.length = [a count] -1; if ([subCheck isEnabled] && [subCheck state]) { [self prepareGenerateSaveFile:fname]; } else { [panel beginSheetForDirectory:[NSString pathWithComponents:[a subarrayWithRange:range]] file:[[a objectAtIndex:[a count] -1] stringByAppendingString:@".torrent"] modalForWindow:gWindow modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:panel]; } } } - (void)savePanelDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { NSSavePanel *panel = (NSSavePanel *)contextInfo; NSString *f = [panel filename]; if(returnCode == 1) { [self prepareGenerateSaveFile:f]; } else { [gButton setEnabled:YES]; } } - (void) prepareGenerateSaveFile:(NSString *)f { NSConnection *conn; NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:5]; NSPort *left, *right; PyObject *mm, *md, *event; left = [NSPort port]; right = [NSPort port]; conn = [[NSConnection alloc] initWithReceivePort:left sendPort:right]; [conn setRootObject:self]; [dict setObject:right forKey:@"receive"]; [dict setObject:left forKey:@"send"]; [dict setObject:f forKey:@"f"]; [dict setObject:fname forKey:@"fname"]; [dict setObject:[announce stringValue] forKey:@"url"]; // if subCheck is both enabled and checked if ([subCheck isEnabled] && [subCheck state]) { [dict setObject:[NSNumber numberWithInt:1] forKey:@"completedir"]; } else { [dict setObject:[NSNumber numberWithInt:0] forKey:@"completedir"]; } [subCheck setEnabled:NO]; [progressMeter startAnimation:self]; [gWindow unregisterDraggedTypes]; PyEval_RestoreThread([[NSApp delegate] tstate]); mm = PyImport_ImportModule("threading"); md = PyModule_GetDict(mm); event = PyDict_GetItemString(md, "Event"); endflag = PyObject_CallFunction(event, ""); [[NSApp delegate] setTstate:PyEval_SaveThread()]; done = NO; [dict setObject:[NSData dataWithBytes:&endflag length:sizeof(PyObject *)] forKey:@"flag"]; [NSThread detachNewThreadSelector:@selector(doGenerate:) toTarget:[self class] withObject:dict]; [gButton setTitle:NSLocalizedString(@"Cancel", @"Cancel")]; [gButton setAction:@selector(cancel:)]; } - (void)progress:(in float)val { if(!done) { [progressMeter setDoubleValue:val]; [gButton setEnabled:YES]; } } - (void)progressFname:(NSString *)val; { [self displayFile:val]; } - (void)endGenerate { NSFileManager *fm = [NSFileManager defaultManager]; NSWorkspace *wk = [NSWorkspace sharedWorkspace]; NSDictionary *dict; dict = [fm fileAttributesAtPath:fname traverseLink:YES]; [self displayFile:fname]; // if fname is directory and is not file package if ([[dict objectForKey:@"NSFileType"] isEqualToString:@"NSFileTypeDirectory"] && ![wk isFilePackageAtPath:fname]) { [subCheck setEnabled:YES]; } else { [subCheck setEnabled:NO]; } [progressMeter stopAnimation:self]; [gWindow registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]]; [gButton setTitle:NSLocalizedString(@"Generate", @"Generate")]; [gButton setAction:@selector(generate:)]; done = YES; } - (IBAction)cancel:(id)sender { done = YES; PyEval_RestoreThread([[NSApp delegate] tstate]); PyObject_CallMethod(endflag, "set", NULL); [[NSApp delegate] setTstate:PyEval_SaveThread()]; [progressMeter setDoubleValue:0.0]; } + (void)doGenerate:(NSDictionary *)dict { PyObject *mm, *md; PyObject *mmf, *res, *enc, *be, *flag, *display, *displayFname; FILE *desc; NSString *f, *url, *filename; PyThreadState *ts; bt_ProxyObject *proxy; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; id foo; ts = PyThreadState_New([[NSApp delegate] tstate]->interp); PyEval_RestoreThread(ts); f = [dict objectForKey:@"f"]; filename = [dict objectForKey:@"fname"]; url = [dict objectForKey:@"url"]; proxy = (bt_ProxyObject *)bt_getMetaProxy([dict objectForKey:@"receive"], [dict objectForKey:@"send"]); mm = PyImport_ImportModule("BitTorrent.bencode"); md = PyModule_GetDict(mm); be = PyDict_GetItemString(md, "bencode"); [[dict objectForKey:@"flag"] getBytes:&flag]; if ([[dict objectForKey:@"completedir"] intValue] == 0) { mm = PyImport_ImportModule("btmakemetafile"); md = PyModule_GetDict(mm); mmf = PyDict_GetItemString(md, "makeinfo"); display = PyObject_GetAttrString((PyObject *)proxy, "metaprogress"); res = PyObject_CallFunction(mmf, "siOOi", [filename UTF8String], 262144, flag, display, 1); if(res != NULL && res != Py_None) { enc = PyObject_CallFunction(be, "{s:O,s:s}", "info", res, "announce", [url UTF8String]); if(PyErr_Occurred()) PyErr_Print(); else { desc = fopen([f UTF8String], "w"); fwrite(PyString_AsString(enc), sizeof(char), PyString_Size(enc), desc); fclose(desc); if(enc) { Py_DECREF(enc); } } Py_DECREF(res); } } else { mm = PyImport_ImportModule("btcompletedir"); md = PyModule_GetDict(mm); mmf = PyDict_GetItemString(md, "completedir"); display = PyObject_GetAttrString((PyObject *)proxy, "metaprogress"); displayFname = PyObject_GetAttrString((PyObject *)proxy, "fnameprogress"); res = PyObject_CallFunction(mmf, "ssOOOi", [filename UTF8String], [url UTF8String], flag, display, displayFname, 18); if(PyErr_Occurred()) PyErr_Print(); if(res) { Py_DECREF(res); } } ts = PyEval_SaveThread(); foo = (id)[[NSConnection connectionWithReceivePort:[dict objectForKey:@"receive"] sendPort:[dict objectForKey:@"send"]] rootProxy]; [foo setProtocolForProxy:@protocol(GCallbacks)]; [foo endGenerate]; [pool release]; } - (void)open { [gWindow makeKeyAndOrderFront:self]; } - (void)displayFile:(NSString *)f { NSImage *icon; // lookup + display icon icon = [[NSWorkspace sharedWorkspace] iconForFile:f]; [iconWell setImage:icon]; // set path field [fileField setStringValue:f]; } // drag protocol methods - (NSDragOperation)draggingEntered:(id )sender { NSString *f; NSPasteboard *board; NSArray *names; // get path off pasteboard board = [sender draggingPasteboard]; names = [board propertyListForType:@"NSFilenamesPboardType"]; if ([names count] > 0) { f = [names objectAtIndex:0]; [self displayFile:f]; [progressMeter setDoubleValue:0.0]; return NSDragOperationGeneric; } return NSDragOperationNone; } - (void)draggingExited:(id )sender { if (fname == nil) { [iconWell setImage:nil]; [fileField setStringValue:@""]; } else { [self displayFile:fname]; } } - (BOOL)performDragOperation:(id )sender { NSFileManager *fm = [NSFileManager defaultManager]; NSWorkspace *wk = [NSWorkspace sharedWorkspace]; NSDictionary *dict; if(fname != nil) { [fname release]; } fname = [[fileField stringValue] retain]; dict = [fm fileAttributesAtPath:fname traverseLink:YES]; // if fname is directory and is not file package if ([[dict objectForKey:@"NSFileType"] isEqualToString:@"NSFileTypeDirectory"] && ![wk isFilePackageAtPath:fname]) { [subCheck setEnabled:YES]; } else { [subCheck setEnabled:NO]; } // enable subCheck return YES; } @end BitTorrent-3.4.2/osx/ICHelper.h0100644000202400020240000000047007521545314014653 0ustar brambram// // ICHelper.h // BitTorrent // // Created by Dr. Burris T. Ewell on Sat Jul 27 2002. // Copyright (c) 2001 __MyCompanyName__. All rights reserved. // #import #import @interface ICHelper : NSObject { ICInstance ici; } - (id) installICHandler:sender; @end BitTorrent-3.4.2/osx/ICHelper.m0100644000202400020240000000351607603451421014660 0ustar brambram// // ICHelper.m // BitTorrent // // Created by Dr. Burris T. Ewell on Sat Jul 27 2002. // Copyright (c) 2001 __MyCompanyName__. All rights reserved. // #import "ICHelper.h" #import #define EXTENSION "\ptorrent" #define APP "\pBitTorrent" #define MIMETYPE "\papplication/x-bittorrent" #define CREATOR 'BCBC' #define MFILETYPE 'BTMF' @implementation ICHelper - (id) installICHandler:sender { OSStatus err; Handle handle = NewHandle(0); ICAttr attr; ICMapEntry map; err = ICStart(&ici, CREATOR); if(!err) { err = ICFindPrefHandle(ici, "\pMapping", &attr, handle); err = ICMapEntriesFilename(ici, handle, "\pfoo.torrent", &map); if (err == icPrefNotFoundErr) { NSRunAlertPanel(NSLocalizedString(@"Quit Internet Explorer", @"explanation of helper registration"), NSLocalizedString(@"BitTorrent cannot register as a helper application while Internet Explorer is running. This only needs to be done once. Please quit Internet Explorer before clicking OK.", @"shut down internet explorer request"), nil, nil, nil); map.totalLength = kICMapFixedLength + PLstrlen(EXTENSION) + PLstrlen(APP) * 3 + PLstrlen(MIMETYPE); map.fixedLength = kICMapFixedLength; map.version = 3; map.fileType = MFILETYPE; map.fileCreator = CREATOR; map.postCreator = CREATOR; map.flags = kICMapBinaryMask | kICMapDataForkMask | kICMapPostMask; PLstrcpy(map.extension, EXTENSION); PLstrcpy(map.creatorAppName, APP); PLstrcpy(map.postAppName, APP); PLstrcpy(map.MIMEType, MIMETYPE); PLstrcpy(map.entryName, APP); err = ICBegin(ici, icReadWritePerm); err = ICAddMapEntry(ici, handle, &map); err = ICSetPrefHandle(ici, "\pMapping", attr, handle); err = ICEnd(ici); } err = ICStop(ici); } return self; } @end BitTorrent-3.4.2/osx/Preferences.h0100644000202400020240000000100207660353522015453 0ustar brambram/* Preferences */ #import #define MINPORT @"minport" #define MAXPORT @"maxport" #define IP @"ip" #define MINPORT_DEFAULT 6881 #define MAXPORT_DEFAULT 6889 #define IP_DEFAULT @"" @interface Preferences : NSWindowController { IBOutlet id ip; IBOutlet id maxport; IBOutlet id minport; } - (IBAction)cancel:(id)sender; - (IBAction)loadFactory:(id)sender; - (IBAction)save:(id)sender; - (IBAction)resetFields:(id)sender; - (void)windowDidBecomeKey:(NSNotification *)aNotification; @end BitTorrent-3.4.2/osx/Preferences.m0100644000202400020240000000274507660353522015477 0ustar brambram#import "Preferences.h" @implementation Preferences - init { NSUserDefaults *defaults; NSMutableDictionary *appDefaults; [super init]; defaults = [NSUserDefaults standardUserDefaults]; appDefaults = [NSMutableDictionary dictionaryWithObject:IP_DEFAULT forKey:IP]; [appDefaults setObject:[NSNumber numberWithInt:MINPORT_DEFAULT] forKey:MINPORT]; [appDefaults setObject:[NSNumber numberWithInt:MAXPORT_DEFAULT] forKey:MAXPORT]; [defaults registerDefaults:appDefaults]; return self; } - (void)windowDidBecomeKey:(NSNotification *)aNotification { [self resetFields:self]; } - (IBAction)cancel:(id)sender { [self resetFields:self]; [[self window] close]; } - (IBAction)resetFields:(id)sender { NSUserDefaults *defaults; defaults = [NSUserDefaults standardUserDefaults]; [ip setStringValue:[defaults objectForKey:IP]]; [minport setStringValue:[defaults objectForKey:MINPORT]]; [maxport setStringValue:[defaults objectForKey:MAXPORT]]; } - (IBAction)loadFactory:(id)sender { [ip setStringValue:IP_DEFAULT]; [minport setIntValue:MINPORT_DEFAULT]; [maxport setIntValue:MAXPORT_DEFAULT]; } - (IBAction)save:(id)sender { NSUserDefaults *defaults; defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:[ip stringValue] forKey:IP]; [defaults setObject:[minport stringValue] forKey:MINPORT]; [defaults setObject:[maxport stringValue] forKey:MAXPORT]; [[self window] close]; } @end BitTorrent-3.4.2/osx/README.txt0100644000202400020240000000733107754611172014554 0ustar brambram>10.2 Jaguar Required! Sorry... InternetExplorer users: You must launch BitTorrent with InternetExplorer NOT RUNNING in order to register as a helper app. This only needs to be done once. Safari: Safari doesn't automatically hand the torrent file off to BitTorrent. Open the torrent file to start the download. Mozilla: Restart Mozilla after launching BitTorrent for the first time. OmniWeb: OmniWeb doesn't auto-launch BT. Just double-click the .torrent file in the download panel or otherwise open the .torrent file to get things started. Guide to Fast Downloads ----------------------- The name of the game is connecting to as many peers as possible. If you are behind a NAT or firewall, the single thing that will make the biggest difference in your download speed is to reconfigure your NAT/firewall to allow incoming connections on the ports that BT is listening to (it uses a new port for every download, starting at the minimum you specify.) Then all the other peers behind a NAT or firewall will connect to you so that you can download from them. BitTorrent uses "tit for tat" for deciding which peer to upload to. In general terms, the client uploads to the peers that it is downloading from the fastest. This is why there can be a delay after connecting to peers before downloading begins; you have nothing to upload to other peers. The torrent typically bursts to life once your client gets a complete piece or two. If there is excess bandwidth available, perhaps because many peers left their window open, then you can get good download rates without uploading much. If you are on a very fast connection and think you could be downloading faster, try increasing the maximum number of uploads; by uploading to more peers you may end up downloading from more peers. Give the client a few minutes to "settle" after tweaking it. The client uses one upload "slot" to cycle through peers looking for fast downloads and only changes this slot every 30 seconds. Release Notes Version 3.3a 2003/11/07 ---------- Recompiled with XCode, works on Pathner Release Notes Version 3.3 2003/10/10 ---------- Latest BitTorrent: more hard drive friendly file allocation less CPU consumption many tweaks Internationalization: Better handling of extended characters in filenames. Dutch translation contributed by Martijn Dekker Partial French translation contributed by ToShyO Fixed Bugs: opened file descriptor limit removed illegal characters from Rendezvous advertisements, not compatible with 3.2! Release Notes Version 3.2.2a 2003/05/31 ---------- somehow a typo snuck in unnoticed Release Notes Version 3.2.2 2003/05/30 ---------- Latest BitTorrent Fixed bug where opening multiple torrent files at once caused a deadlock New Features: Preferences for minimum/maximum port and IP address Displays number of peers Displays total uploaded / downloaded Adjustable max upload rate and max uploads (not surprisingly, this was the most requested feature) Rendezvous tracking finds peers on the same side of the firewall and allows "trackerless" operation in the local domain Cancel button for torrent generation Release Notes Version 3.1a ---------- Fixed a bug where torrents larger than about 2 gigabytes would fail. These builds do not seem to work on 10.1, the cause is being investigated. For now you need 10.2 "Jaguar" Release Notes Version 3.1 ---------- This release has the latest BitTorrent and also UI for generating torrent files. Checking the "create a torrent file for each file/folder in this folder..." will create a torrent file only if one does not already exist. Also, it only creates torrents for the files/foldes in the top level of the chosen folder. Release Notes Version 3.0 ---------- Initial Mac OS X release BitTorrent-3.4.2/osx/Tstate.h0100644000202400020240000000017107754611172014466 0ustar brambram#import @protocol Tstate - (PyThreadState *)tstate; - (void)setTstate:(PyThreadState *)nstate; @end BitTorrent-3.4.2/osx/callbacks.h0100644000202400020240000000027507660353522015144 0ustar brambram#import "pystructs.h" bt_ProxyObject *bt_getProxy(NSPort *receivePort, NSPort *sendPort, PyObject *chooseFileFlag); bt_ProxyObject *bt_getMetaProxy(NSPort *receivePort, NSPort *sendPort); BitTorrent-3.4.2/osx/callbacks.m0100644000202400020240000001561307754611172015155 0ustar brambram// // callbacks.m // BitTorrent // // Created by Dr. Burris T. Ewell on Tue Apr 30 2002. // Copyright (c) 2001 __MyCompanyName__. All rights reserved. // #import #import #import "BTCallbacks.h" #import "pystructs.h" static PyObject *chooseFile(bt_ProxyObject *self, PyObject *args) { NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; char *def = ""; PyObject *obj, *mm; PyObject *megabyte; char *saveas = NULL; int dir, len; PyObject *res; NSString *str; if (!PyArg_ParseTuple(args, "s#Osi", &def, &len, &obj, &saveas, &dir)) return NULL; megabyte = Py_BuildValue("f", 1048576.0); obj = PyNumber_Divide(obj, megabyte); mm = PyImport_ImportModule("__main__"); str = [NSString stringWithUTF8String:def]; if (!str) { str = [NSString stringWithCString:def length:len]; if (!str) { return NULL;} } Py_BEGIN_ALLOW_THREADS [self->dlController chooseFile:str size:PyFloat_AsDouble(obj) isDirectory:dir]; Py_END_ALLOW_THREADS PyObject_CallMethod(self->chooseFlag, "wait", NULL); Py_BEGIN_ALLOW_THREADS str = [self->dlController savePath]; Py_END_ALLOW_THREADS if(str) { res = PyString_FromString([str UTF8String]); } else { Py_INCREF(Py_None); res = Py_None; } Py_DECREF(obj); Py_DECREF(megabyte); [pool release]; return res; } static PyObject *display(bt_ProxyObject *self, PyObject *args, PyObject *keywds) { PyObject *d; NSData *data; NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; if (!PyArg_ParseTuple(args, "O", &d)) return NULL; Py_INCREF(d); data = [NSData dataWithBytes:&d length:sizeof(PyObject *)]; Py_BEGIN_ALLOW_THREADS [self->dlController display:data]; Py_END_ALLOW_THREADS [pool release]; Py_INCREF(Py_None); return Py_None; } static PyObject *metaprogress(bt_ProxyObject *self, PyObject *args) { NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; float prog; if (!PyArg_ParseTuple(args, "f", &prog)) return NULL; Py_BEGIN_ALLOW_THREADS [self->dlController progress:prog]; [pool release]; Py_END_ALLOW_THREADS Py_INCREF(Py_None); return Py_None; } static PyObject *fnameprogress(bt_ProxyObject *self, PyObject *args) { NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; char *fname; if (!PyArg_ParseTuple(args, "s", &fname)) return NULL; Py_BEGIN_ALLOW_THREADS [self->dlController progressFname:[NSString stringWithUTF8String:fname]]; [pool release]; Py_END_ALLOW_THREADS Py_INCREF(Py_None); return Py_None; } static PyObject *pathUpdated(bt_ProxyObject *self, PyObject *args) { NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; char *fname; if (!PyArg_ParseTuple(args, "s", &fname)) return NULL; Py_BEGIN_ALLOW_THREADS [self->dlController pathUpdated:[NSString stringWithCString:fname]]; [pool release]; Py_END_ALLOW_THREADS Py_INCREF(Py_None); return Py_None; } static PyObject *finished(bt_ProxyObject *self, PyObject *args) { NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; Py_BEGIN_ALLOW_THREADS [self->dlController finished]; Py_END_ALLOW_THREADS [pool release]; Py_INCREF(Py_None); return Py_None; } static PyObject *nerror(bt_ProxyObject *self, PyObject *args) { char *errmsg = NULL; char *BTerr = NULL; NSString *str; NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; if(!PyArg_ParseTuple(args, "s", &BTerr, &errmsg)) return NULL; if(errmsg) str = [NSString stringWithCString:errmsg]; else str = [NSString stringWithCString:BTerr]; Py_BEGIN_ALLOW_THREADS [self->dlController error:str]; Py_END_ALLOW_THREADS [pool release]; Py_INCREF(Py_None); return Py_None; } static PyObject *paramfunc(bt_ProxyObject *self, PyObject *args) { PyObject *d; NSData *data; NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; if(!PyArg_ParseTuple(args, "O", &d)) return NULL; Py_INCREF(d); data = [NSData dataWithBytes:&d length:sizeof(PyObject *)]; Py_BEGIN_ALLOW_THREADS [self->dlController paramFunc:data]; Py_END_ALLOW_THREADS [pool release]; Py_INCREF(Py_None); return Py_None; } // first up is a PythonType to hold the proxy to the DL window staticforward PyTypeObject bt_ProxyType; static void bt_proxy_dealloc(bt_ProxyObject* self) { [self->dlController release]; Py_DECREF(self->chooseFlag); PyObject_Del(self); } static struct PyMethodDef reg_methods[] = { {"display", (PyCFunction)display, METH_VARARGS|METH_KEYWORDS}, {"chooseFile", (PyCFunction)chooseFile, METH_VARARGS}, {"pathUpdated", (PyCFunction)pathUpdated, METH_VARARGS}, {"finished", (PyCFunction)finished, METH_VARARGS}, {"nerror", (PyCFunction)nerror, METH_VARARGS}, {"metaprogress", (PyCFunction)metaprogress, METH_VARARGS}, {"fnameprogress", (PyCFunction)fnameprogress, METH_VARARGS}, {"paramfunc", (PyCFunction)paramfunc, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; static PyObject *proxy_getattr(PyObject *prox, char *name) { return Py_FindMethod(reg_methods, prox, name); } static PyTypeObject bt_ProxyType = { PyObject_HEAD_INIT(NULL) 0, "BT Proxy", sizeof(bt_ProxyObject), 0, (destructor)bt_proxy_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ proxy_getattr, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ }; // given two ports, create a new proxy object bt_ProxyObject *bt_getProxy(NSPort *receivePort, NSPort *sendPort, PyObject *chooseFileFlag) { bt_ProxyObject *proxy; id foo; proxy = PyObject_New(bt_ProxyObject, &bt_ProxyType); foo = (id)[[NSConnection connectionWithReceivePort:receivePort sendPort:sendPort] rootProxy]; [foo setProtocolForProxy:@protocol(BTCallbacks)]; [foo retain]; proxy->dlController = foo; proxy->chooseFlag = chooseFileFlag; Py_INCREF(proxy->chooseFlag); return (bt_ProxyObject *)proxy; } // given two ports, create a new proxy object bt_ProxyObject *bt_getMetaProxy(NSPort *receivePort, NSPort *sendPort) { bt_ProxyObject *proxy; id foo; proxy = PyObject_New(bt_ProxyObject, &bt_ProxyType); foo = (id)[[NSConnection connectionWithReceivePort:receivePort sendPort:sendPort] rootProxy]; [foo setProtocolForProxy:@protocol(MetaGenerateCallbacks)]; [foo retain]; proxy->dlController = foo; return (bt_ProxyObject *)proxy; } BitTorrent-3.4.2/osx/freeze.py0100755000202400020240000000564707754611172014723 0ustar brambram#!/usr/local/bin/python ## this script copies BitTorrent dependent modules into the resource dir in a ## configuration independent way (hopefully) ## this script reportedly doesn't deal well with spaces in the path to your project (who_uses_spaces_anyways?) from os.path import join from os import makedirs, system, environ, listdir, unlink from shutil import copy from compileall import compile_dir import sys sys.prefix='/usr/local' py_path = 'lib/python2.3' so_path = 'lib/python2.3/lib-dynload' ## add dependend modules to one or the other list, depending on the type ## there are probably some extra modules in here that aren't actually used py_modules = ['StringIO', 'UserDict', '__future__', 'atexit', 'base64', 'bisect', 'codecs', 'copy', 'copy_reg', 'dis', 'ftplib', 'inspect', 'getopt', 'getpass', 'gopherlib', 'gzip', 'httplib', 'linecache', 'macpath', 'macurl2path', 'mimetools', 'mimetypes', 'ntpath', 'nturl2path', 'opcode', 'os', 'popen2', 'posixpath', 'pprint', 'pre', 'quopri', 'random', 're', 'repr', 'rfc822', 'socket', 'sre', 'sre_compile', 'sre_constants', 'sre_parse', 'stat', 'string', 'StringIO', 'tempfile', 'termios', 'threading', 'traceback', 'types', 'token', 'tokenize', 'urllib', 'urllib2', 'urlparse', 'uu', 'warnings'] so_modules = ['_random', '_socket', 'binascii', 'cStringIO', 'math', 'md5', 'pcre', 'pwd', 'select', 'sha', 'strop', 'struct', 'time', 'zlib'] res = join(environ['SYMROOT'], '%s.%s/Contents/Resources' % (environ['PRODUCT_NAME'], environ['WRAPPER_EXTENSION'])) py = join(res, 'lib/python2.3') dy = join(py, 'lib-dynload') bt = join(res, 'BitTorrent') try: makedirs(py) except OSError, reason: # ignore errno=17 directory already exists... if reason.errno != 17: raise OSError, reason try: makedirs(dy) except OSError, reason: # ignore errno=17 directory already exists... if reason.errno != 17: raise OSError, reason try: makedirs(bt) except OSError, reason: # ignore errno=17 directory already exists... if reason.errno != 17: raise OSError, reason print "Copying depedent Python modules..." # python lib source = join(sys.prefix, py_path) for module in py_modules: copy(join(source, module +".py"), py) # c modules source = join(sys.prefix, so_path) for module in so_modules: print join(source, module+".so") copy(join(source, module +".so"), dy) # bt modules source = join(environ['SRCROOT'], '../BitTorrent') for f in listdir(source): if f[-3:] == '.py': copy(join(source, f), bt) #copy btmakemetafile.py copy(join(environ['SRCROOT'], "../btmakemetafile.py"), res) #copy btcompletedir.py copy(join(environ['SRCROOT'], "../btcompletedir.py"), res) # compile and remove sources compile_dir(res) for f in listdir(res): if f[-3:] == '.py': unlink(join(res, f)) for f in listdir(bt): if f[-3:] == '.py': unlink(join(bt, f)) for f in listdir(py): if f[-3:] == '.py': unlink(join(py, f)) # strip c modules system("strip -x %s" % join(dy, "*.so")) BitTorrent-3.4.2/osx/main.m0100644000202400020240000000270107754611172014154 0ustar brambram// // main.m // BitTorrent OSX // // Created by Andrew Loewenstern on Mon Apr 29 2002. // Copyright (c) 2001 __MyCompanyName__. All rights reserved. // #import #import #import // external function, registers Python module containing BT callbacks void init_callbacks(); int main(int argc, const char *argv[]) { NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; PyObject *mm, *md, *path; struct rlimit lim; // set the open file limit to 11 if(!getrlimit(RLIMIT_NOFILE, &lim)) { lim.rlim_cur = lim.rlim_max; if(setrlimit(RLIMIT_NOFILE, &lim)) { NSLog(@"Failed to increase open file limit."); } } else { // try something reasonable lim.rlim_cur = 1024; if(setrlimit(RLIMIT_NOFILE, &lim)) { NSLog(@"Failed to increase open file limit."); } } // set up python Py_SetPythonHome((char*)[[[NSBundle mainBundle] resourcePath] cString]); Py_Initialize(); PySys_SetArgv(argc, (char **)argv); PyEval_InitThreads(); // add our resource path to sys.path so we can find the BT modules mm = PyImport_ImportModule("sys"); md = PyModule_GetDict(mm); path = PyDict_GetItemString(md, "path"); PyList_Append(path, PyString_FromString([[[NSBundle mainBundle] resourcePath] cString])); [pool release]; return NSApplicationMain(argc, argv); } BitTorrent-3.4.2/osx/pystructs.h0100644000202400020240000000045107754611172015303 0ustar brambram#import // this is the proxy object that has the callbacks for each DL // encapsulates a connection to the it's DL Window controller typedef struct { PyObject_HEAD id dlController; // NSProxy connection PyObject *chooseFlag; // chooseFileFlag } bt_ProxyObject; BitTorrent-3.4.2/osx/torrent.icns0100644000202400020240000016460207605507624015437 0ustar brambramicnsICN#icl8ЫЫЬϦЫʫХЫϦʬρzUʬ+NzzONU++++zOONЬzUUO*OU{ЫzyUONO*+ЦzzzUOONO*OЬzzUUNTO%*+UyЬzsO1sUUN%*OUzЫzUUNNUN+NNOUzzN+*OyO**%NOUzzЫzUO*ON+N*%**UUOUЦХVzz**$$$OUsUzNzХyz*%**OTOUUUOХzUUVsU$*OyOUNUzzUyON$OUNVOzzyV$*$yyꫦyOUyy+$NNOzzUzUzz*O**zUUzϥzNUzz1$**IUNVzzzOO$$*OUOyЬzzUU*$+N$+NyЭz{z$$*NO*O{ЬЬЦU$NUVyUyyϦʥz*NU{yVzzUyХz*N$UzyUUOil32Ƃ      F3  $8&:C7# !6"Z8-=uaJAI57*:8Y.#WrTO>;=8)+8l#.d?-6@>788cC0>xn@+7-6."# .h);qN:wßdU7)$#$05ADAw;[ȸl:,-&&d|bU3>Qڸɷh(;dlZ>-'1mϾc7>ScT# *Țb"V~51Zzc~\vqx:@~)!$2;Jydt_xn{  '948:40>8;56898Y5GBF=9=9CB>@;?8MZ^UHIFGVHG=@?8i8PF]xbYbSSLDB>L8pMEsťkgZUVRF@8K=Pz]QV[WOI8cT[ʈ]PUPPB20.;q+`iYܴ|mYLB;9BFPS9b[v̈́Y@MMEBRY+/h~ݵrgf[TRN<3>~v͔ofca1/-ep{ť߶vop")/Ocv˽ڻz|(#4UcuԲϸ"07>Oet˷"9GDXf÷1BOrmlƴ4HduZƟųs+;r{Rxu`|KI~g~dVfɚԫQUMtpcjɕıuIIQemk؋}\C^tate̜wפRKWgzsj|`ɩf>5M_}woY^hӽ48O_}xdTNW}\Xp~s:GDFHWoĦQrY;.?LTtݮ~v__I?EUZhբ}x""$"$#*&&'%2MFGKACOGKIGGHFk6WPQNIIJNLJKLLIO8PhnaTSSTcSSLMKFp8ZEdrgs]^XPOHT8RC߾~xcad^PF8I8Y۲aPXfc[R8iQc\NXNSH986Fz-[qX翉uUG?<>MUeh )^X~ؒ[?CEAA_m&0g̃tp[UTS*<;ܳݶrpn+41uwɤ!)32\pۼ%2.8Ynů)41=Rm|޿) -4;\nïު #-I{nؖ"1_Ȉ]ɘӄ+}WȳwX92trTî_޸L RF~hnܰxB PQsvλqܵREjk}f齬־K ?DfnΌhֶu##C[zQZrܐ !A^]ECNۈQZv,0=15OvȼŒC ~ưV5*>Llja_G>G[_m"l8mk ELMMMMMMM MMMMMMMMMMMMMMMMMMMK6tzI  ih32       F`    D[   ( /+.7* *($Nԅ#9O225&#ӄ 4>%"Zq^TJRJ?< 81%% Ԅ:T4&MkePSE=?5@;2'c@c'Sik@8-:@G?96-҄ 0B`kN2-$$/0:<6@498Hr uV#<9EįZ=03;/.5,' $#!#3]I!JnpH=Z´ƸYVR;'%"$""1448CJ!!+klD+;]ӬiD=0*()" %*;D?D!-gA=g|d0,/+$(&=L>! /dXhhvy}QGLJ@<33425! 5~nmmͷkdbVHFHEE?!  Ptp\Lt͞sjZSNKOH!  8iKe_ͯia\YY[!'yqKmewf`]`b! &dnB[[§tifgh! 1TmCMayɴ}tuv! #GqH?bzĺοx! IrNKa}ʮ̼r! "O~KSpβp[! $CtN_MV}šxX (VjzWTE^v˹sQ!Gdnn]>K{kͿ\C!R\RIAdMBDZќt`! g^Tmm4+RwI?bΙn2!>iBWkR1h{yxXG_ؤÖn7!6(5xa_pd83]|tDRzgC!7'+gSZzPNgvSQ{ҩ_!$!*[{OJaVgxrMY|ñt{cB6! 'Hc`'!,AP]QFUs[Ck¡perֿc6! ;9OLhLcg`@hjYEbɰ{M"!!/85^^qnP~]MCEMuǻйyo?! -4qay_gM-*>Gpյrdj!10rn}dT+2(&2\ɵ̮rg7T!3>+4,'%$'?uӿ@,!!"KZml"% #$I`Тop|Y% !=O|=1T\væmbuUwxot/!%F{p0!/9>FmsbYutUvrmwyI!Fx}(4&2.9>Mtpdpx[uwowq"05:1).+.,768B:735=;:[g<369;9624E998439:8867U]$G?BB;59<75D@@=;==?@9;A )NNMWMA:BCDBOLAE?>AB@:>\ԅ#L[__XSIKFGFZQHLH>=?A>3Dc7>EYgnb\WTgPOQIB>?B:9ӄ AZJ9FuȴrhafcZXUXRNGB7ԄElR=Hgîzhj^YWSWTNF6cLzM>9Dkƅ]YOXZ^VSMA҄ -TFK^zʱgTRKJPPTRHK@CDU|%rJ[W`Мo[RTWRPNB71.1AgL!%jdYrΘqnj[KH@<97BCDHRY(!SbR[yb\RIHE?Soopcug!7ITi|sXe˗ȟűq^!#37Lpvha^|ja븰Ȥs! -9KwpXKmh_{䶟ʠW!)G?]\roGXtdv𾘬᱄\J!ZUYyuzWX|cj׸cB'!$XGQ~kshglnǙƲxJJ!$M;uhcum}hs܊Üx]U!A_|zY]mlΫoyψXI!FYiym_ps`Ὑ{ܭxV=!VYjhgy{aӹq`zΤiH!!BDTYTvvguh_^dЭc! =-0VXzr~hWT_a{!$563WVsWXLQWvʈ\o!(:FX_NTMKAFILNO_ˮέ`WH!3eu86AHGDIFTlw龼vT=!Jk_FA27=KCSouą~pVH!3fVB,$@PY_gѪxyqd!'cHM9JTSW_jԫ}t $)$FEHBA DEBFTKHGJMKIsr'OFFILGADGUIEIGHGFCHDCja+ULNMMIGGIHPKHIJLKPIHGS ._^\dYMHLJOLXVMMLLKNKFMfԅ"Liqoc^VVSUTk_RXSKPKLL@McBC?]tsjcbwZX]SPMLNEBӄ LdMAEңzr}tccda\YPI?ԄW~V@Evȳy~hbc[faZO=;;JQR[hp3!>iI[ЯZSJ@??@@DJ]jah!,F^W㫊S@?BED@B@arb!$)B|uwlpmYURQPOT!/)0QĘ|ldfaa]!'I:7tmɩԹwqqsn!.;/1anƺ(.)2Ulʫ!+8)5Ti|ɻ!0-*Roqf͹!8/=Kzb~ ٞr! %2@gמ`iݯт`!$:wrl_i_Ҩ! &G{XQtϩbPԺ湳M!&.)`iFU{]~ٶɐX>!H3I_SIJVnť։`;/!)VKE~qnӫwpݶծzB?!&V6zlvůâjz㹪TJ!H^glirޑ|ʨK7!Ievz`xrc޷֙ʳP>!I?pdo^՗jЯyQ!49FSSytpeVeȩh! "OJϑpKH]g➊!&KQƉxCKCFN柋Rq!.N\T[KC01-<@E[]G=!8k0!"2F4-5IrݺɡE0!Uuɬa?>#18Lzߘ~TH!8nѴE6%@N`a^ɂ٨l!(^ųMN;LYY[etʡ٦h8mk  . 3Z 33 3\ 33 3Z 33 3303C 3J3L3L3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3L1I%Itz[6 .3333333333333333333333333333331%  ics#H????????????????ics8Ь֥ЫzUVOOOрO*OVЬzOUN*zЀUOU%*Us*ON*OUЬV**OUOzzz$OOUUzz**OzPU$OUz饥zyV$*OUO*ONVUЬV*zzUis32  $ -"z !+yyT>0' 4*RF66Uxd ^RB#)6( 'mmwUB( WWd< LRɦN a\~ԿD .objwƬ8 BbJ~zfĘ| QWlrǸx 0WdFx9 /('%$( XʾgnYQ;g%,BQJnYn+wnng`Y_PIXItOHHA9@99@998??8?>)('$"UʾgwgJJg,%%BgwJQn'~}n`_XP_fWOHAA::A@@199@?8>>6;,$#Pʾh'w~JJ;%%%QQggXIBIIAHA@*9GF??E7=.&7/!"MʾhhRJ,3YJYonPIBB;::2$:AAH@GGNF >=(-8;2$#RʾhZR<,%%Joah~YII3;33++22:@G@F??>=<'8;2)'Tʾhx)ZC%,û£~aYYB;33$$3BAOA2$**7.:2/2*, #! #%%!%""$%)/0DheZibi<<CRRCZpҀÁ4wwaYJ;;B;3IIHA:9#*)/42*.+(&$'"!)(,$(%!!&/091<8LT+%ypR--.4>HIDQ!-[y[jj[4-4&&b4=p˼ٻohg`YI:B:29?0..&%)."3'&77105RFK>>E<)&&-5Ưq[LS5D=S[̭Ȫv _22*.;5'-;- ' ++VZHMQE;$55\jS=5S=cjczjqyĦ«~f:#")/(/(! &8\UH@5'-=LjSrcSS=Ljq[zqq«fWAA9@1?LL80)7)0/)"((.',,+1*5X-\\zcLLDcc[jjzzj˵u^HANGNNU\c?1F??>07)//6.54,++6X&ET{r{{TL{E\\jrjrzjzѣvmXWOOHO]]UNMFM?F8>E>7D<;:9>>'.ET\{ccT\zrSzrrһ}m_WW^^]]G FF?FEE>D=C;A99&&.&ETkTcc{cjzzzĽļʻʳv me^eleNGGNMFMLLDJIHG@C&.5kr{k\TrTETrrzqk}ļ}}vvme^WVVGUUMFFMESRIBG&&&&>s=ks\\{>Mdd{kz³~~vmmfm^WWNVNNMLKJII&7&&||ds|dME\d>Mk{k{ɕ }vumm^OWWVNUMSSJI:..dd|s5.EMkE>\{rzۀ •}}vfe^ ]]VU\\UTSYXL'5]lldM>MlsE\{Ļ}vfee^]VV\U[[ZS)'.Ms|]dM]dlTTls˼vmfe ^WV]V\\bba'Fmmlle>UlteUt Ľ}vnf^V]]ccb+>e}}eU.FeleUtĶ~vvn_ff^]dc\c#>U}tMF6>UeUFel|Ľ~~vnnfee^e]]dc6)>6t^F>>Uee^UlǰƿĽļ~~}}vff_ek$/ ??unNG?NVUf^Umu ºοƧwonnmfumtlld&    VGnnNG6?GNf^fuuÀºɁƿ~~nm|tlO  //77f_NG??GNVVnuu}ûƿx~wwnnvut 4'7/?G_nVVG?VNVfnuu~˻ɺ€ƿ~}vv||   /7_~v__G7??Vnu~n~üˬο}vvu '/'_v~_N?G??V^^~u~˳üɀȸ}}u) //VngOGOGOVgVn~~ĽĴʀ wv( .( /7`onVGVGVVg_nv̴ĽÂڀº~ww7  77`ggWGG?OgvͬĽ̽ó{zzzxhg-   (7WgO@GOnnvŭŴĀӁ ó{rcczxaY& /GWwGGO@`gvv݀܀ ó{kjxaR/( /(  (7HgOGWGOOo`Vvg݁$̴{{syiaR* ((/(  (7OWo@GWgwGOWOOgvԭĀĬ{ybbR (  (OowHWWOO`@@7Ow΀ݽĽ qiZR2((//7hhWoo`WHG@GW7GWggww݀ zjiR 0`h`WooW o7@GGO``Owծͽ~\LK (0X7H`wȨhow7O7G`wGw֟Οŵ kLL=S (``7@hxp/owwH@@H`OgggnƾŽu ±dTD55 ( (HP7x`hhwooH@%7`oֶoOO`nΟͽ~~~ d\ED\2 (0Pxp`7OH@7/@7@oowg`OOoށ!Ŧu{kTED0' aPhpxȸ`X`WO@/7HWoogWO@@Go$Ʈ͵dTk4 @axxpPHXhP@07HOO@hwWO@//7`Ƕ͵|elTkA HaXXx8@PhXH@0@00@woOG7@@7Og彭~}m|55S(Php@XpXPhhhH(@H(0PpowWO@7@@Ooս~vn~}et6F58(8phhHapXPpxx@ ( 0@Ppwoo`H7H@@Wǿ߁Ο~v~nuu}m.'$  (XahhH@@aphxph( 00P`xpo `OH@HOWoǀ!Ʈvʹf~um6.T  (aaiPHH@Xxp@ (00HP`pxx ohWH@HO`hw߶ցƮvvŦ~nnnG'X>( 08P8H8@H@pxH( (80Hhxphxo`HH@HH`h߶ΎvVŽ_Vu~G' &a ((8AqqXPaP@xxX@ 0(8H@ppxhW@HWW!ΧƎ_ŭG?V?XX(880 8XyyAayPIaaP@0(0@XxxxPH7HOWWίֶn_gvvƕG_? &a((0 8I8IXqXHXXHHPPH88xhXXP@PWO߶oo_vnvn7?'/ 5q 8 (PiaiIP¢XPXPH88hp0xhXXPHPXoֶwvv/'a 0 0iiIAIIXaqªiPPXiXHpxxxxhPHPXhΧǿonnVG/ /$. 8 88yqPIIPAa‰XaaXahphpphh`PX`XxϷowngVN??7G75i (0iyq8AAXqqXiPipxxhxphX@PX`xȸȷȶ`gwvgVNG77/Xa(Qy8AAPiaҹqPPIPiaqyxpXHHPX`pȷg`owvnvg_VO/7'aY8aiyaQii008QiaP2XPXyPXPHpXppx`gownvvVV/7OI(0QYiQia(YyiyXIP'ҙXHHahaphowowwovv_G77'@i0AyQAaiQiI88IaIYyiQIAIPPqڱyqxPXXaPPxȰ wwwwgVG?'Xb!AyaQIYiI 8QiQIqyaiYAIPXaqyXahxaXaиwg``G''X.88yYA)iyY08aqI8qiqQI8(AX¢¹yihxwgO@7/ z))8zYQ)Qi00IaIiyaiaA QQyqyPIIPPiyyp ȸwwWG/)A0)YI8zIIQIyaAaiaYa8AaúҲaX0IA8AIqyixowgG7(!!80)AY!IAbIjIyiIiqYYaA QqÚ˂aQPIaI AIPyѱowW7/'800(IQ80Q!YzIjYQYyiaqQQYYyӂYAAQQIIAA80IqyȰogoO7.X&!)!0)I)!0)bQbjQbzrrQAQiqYYaQIQA8IPIIAXqioohwwOEQ!1))I)bjjbYYzzjYQQrbYrIA08!08IIAQQiqªɹhhpw`TO)!!!A9AjbjQYbYQYrYA)00!00808AYiq±ɹXhc!b)1AAjjrIYjrrQ8)!)0AIA8AQiۀʹªph`XPXc)Y1ArzQbQj988008)A0))AAIrӀҁppxh@7hXr.1Y1bjrjIjbjA1!11!))!!0)0AAQbӁ!ӲɹhpPH07PLXM!!1B)YrbjYbjrjI91)1)!IIA8)08AIrӲhH@X8@S! !91!)9)AII)!)!)11!!0))00ArYہú P@@H =1111BJkc)!)1!)A1!!9)!!)!)8IYjjہ"êqP8( @0F!ssck{9! 1!!A)!)!A1!)!1AYbr!úyyyiqypH8 !Zss{Zk{sc!!11)1)!!1AYjjrz̀$ijqyiaqyiqqqP08( t{{s{cJ{kR )!!))IzQYYj̀#ģzzzjqyaYiyyqyyqqa80 |JkkZR9!6!1!)!)ArQYQb̊rrbbYzaQYayyiyiq i8( .|B1skscZ9))9!9!919bbQQjjijbjjYYrjQIYiyqayy0 (t1Jkc9!)!A!1A9IQjbjrjzrrjrjYjjjQIiqyyqqiiqqya0(0&e1!sc{scs!!B19'BIQrzYjjbbYYbzjQIbYyyiayiPA5.19sssJ1!)1B919B9BBJkzjj(YbYQYYrbQYYbaaiqiqyq=-BJRsk))!)JZ)11!B9!9B9JBBJBJR{Zcļ jjbbYYQrzjQYrzqiYqiaqicBZs{c!19 19!!B)919BRBc{Rs rrjbrzrYQbjrz qiqyyc>99scZsk! !!)!11)1#91{ZżĴ̼zzrjrjjrzzbbrzzzqqyyyr$+2:++G9+29191*1??880*80>7>DD=76D=CB;A@987=;@@SnoZ+22++:2::+:2$+**11*1?80>7DD>76<5;:9>=7;5:8Zk[++2:2+2:22:2+'2**$19*818188EL>77>77/(/..5@9?=76;?Dba :3:2:2++:3:A2+2+2+1$1@c18818?0>77/=66<654:81<5597Qf:BB:32:2:2::2::+22191\@@?8?88007=6<;A3876598>7==65;:32764316TkB: B33:2+2:22A2GV@@118?808E>>7>77=<;A:386;97;Y`kIPB:IB33+2::AA:2299G@GFF??8?80>D>>==DI<;:>7;925YVjIBIIBIB;;3B AA::+:+9GNGGF?88>>7>DD==IBA98<;86SQnʿiIPIXP II;;3::IB:AH@GMF?M??E>E7D>D==BA:?=597T7PʿiXXPXX_PPIIB3BHAHAG@]N M??MF??8>>7>>DCBA?8<41J2Dʿi3;_XPX_XPXPB;;B:AAH:AHOHsGNG@FMFTFE?>>7>=DCB49<52429xʾh;IIffX_XX_PIPPIPPIIH 2HHAlkGNNGMTME7>D>>DDC;@C520-6eʾh`B3IBv`ggn`g`Y``PYPPIPPHHAOOeeVGGN@MFMFF??87>DDI;:>;3022YʾhGgB33PY`nvnng`n`Y``g``XXPPXWWHHWseVGNNGMFMM?F088>0>>=7>7=CB@=;785@ʾgnP3I3,;3IYvgmf_X_WttWOVU[UMLED=CB@DA840:59lʾg~g`QwJQB;3;JY`ǀ ~wngnnf_(te^^]]NV\GM[[MTTLYRIGEB>;14eʾgngYwJ;;Q;`g`n}vvnvf_u_l^VU[L[SJIG?I?673`ʾg~g`wJB3;BJJ`w`gǀǪvmmfu|me^WW^VVNU [TSRQVLB@;78Zʾh``QQB,JB3;BBggwǀ(Ƹmf__fW^^W^^WWVNV\\UT[SRHFPF7.8ZʾhhhQ>]ʾhQoaJC<34J4CRCZoohкϛgg`X_XPOHO^WW^^k]U[LSQ\LMII?aʾiaZRRC4CJJhaRRoho ~nY`PIHOHO NNVU[ZQUXHIDCW^SLM[W[Y_ozɿi+RaRCRKRKKRZixѻwh``PY`PIAHOWNGGMSQVRNPJF;;61-/+* ,*02=WľiKxZCZKKRxKZZaiôôʳ3ٺogYPYYPPIIHAHGNUULRVMI?A==01*)2-,)**+,).0PqfKyy<=BLPF<@7:8;655B>9>9>:;DEPQNNVu[=bjbSjDS[bˀڀɤwwngnvf#^WNNLR6EB:37;55:CHDDCHH?ONNUUQV_i 4SDb[yy[[S jˀ+ѫ~wngvmmf^WVMSXH3CB?8<;:949??INIINMCIMXYTc% DKSyqyqSSK[KKyS[Ӂˀ0ѝ~~wn`X_^V][SQOFDBGE>92=8288O>=HMRLRQVLPKYU5DLK[bĈyjKSK[K[jbˁ7v__^]\LDIHMLKINHHAHAABDQJCIMdW\PTUN&=0SLSSƈqjjL[SjqŶ̽ȀuOWN?)7=CP\UHNOOUOG :@?DDhmZ_cVS)&-=5=SSrr\Sq[zzĽĶɣ|WAG@80DKDJRQI C>5.&E5>dcr{{krrƾŀǪvvummts]]\\[[b[aZYXWP[SRX>TT5&MMkkǡk{{{Ϳžȳ|t{sk]]cc\[biaa`_]\[ZC.>.5EM5&.EETrkk\kƿغttlkkdjjc[biZgedV[>E>5M>*E5M>]ssTd{{ǿỚɪ|tlsdkdjcba`_^]9>.>6E>.55M>..E||dds{\kȰο{Ϳ૜|mellsdjccbbhg_^:.66>M6&E..5EM||TTdld\sȹθθٳ||ttskjrqiihne66F6FF'6..>FMl|sd]l|\sϰο ݀ ³|tskkrjqphg*>>'F.'6Mes|l||ssϿ³|{srryqxxv+'6.''.'.'>M^}|t]t|tɺɺˀ}uumllskrrqx$'6'>.>^}}lMe||l0ʻv||tltszryy/'//6/>6''F^mmeUUe|le|ɺô}|{tzsryy16F''?6'?F^Ute^^e||tt#ӻ|u||tz //G/' 7??G?^Vn^^fnfuum óπŭ}zGN  ' '?GN7Gvf~n^.f~u}üû; 7''7/GGNNVVvvf^ ffnnüÀֿ.// /7'?NVV_fvnnf^ufn~ۻĽ' 7 /'7GGNVvvvfV_^n~˼ˀ ͽ?//?/7OONNvvff_!nuuóëŶ(//(7 /7@7@OWWng_gvn̂ڀοƿû=W7@@//@GWGG7GOW`wn_n_nnvŵԽó4OW(/HG@OGWG/7@`WwoggVgvĽú~w6@7  //@7(H@GOO`oo`g`ózzļxo/(/7@OO/HHO@H@Wgw``g`wՖӀ˼xh#(/WWh`O/H@H@OOWhg`ogoonv΀ #ìžĭxh(HOW/WhWHHO@OW`hwW`o`owgg̀܁̀ǿppi/PH/H`OHW@HOWo`ogwW``g Ľ ypi=((7HH7WPWWOwowohhW`wWgowնƶԽyp0@HH@0HPwwoo&WWggowwgֶΦ̴Ľƙqcb7@P@0@7PXXxX`wwW;gW`w`ĽعjcSjG@7H0HP xP`π`WW`wg Žк{c\SS 0XHXHPXX`hXpxx`:WWwևogwͭú{s\\r 80@HX(PpwOh`WOH`WWΟwggݽÀªk\cV 0@H00P hИxpoo`WOW`ooo`ހ$ԵĴ˼{k{46H8 (@8@Papp`p`XPX`ho`oo`OOWwՀ ͵Ŵĥ˳tk{988H 00@PhxpX`hph`P`PPXhgW``WgŽܴT\8((08HP8HhxXpxh`H``@HXpwh`W``gοŵĥ}]e]  (H@XX,hxphXPP@0PXhwh`h``o澶ĥUMM$8IP8HPPhxxaaXxH7HPXPhxwhh`hhoƮԴ~}^UF>"sPA8 0PXxyhaaXxppaHHPXXhpxwh`howƎݽ^UNU>dIPI8XPiPhPaaXaHPaXhh`wn vnfVNUM>y8APXPaaqixhXhX@PHapao`hh`ooަvŭ͕f_u^GG/''R5qXaaAaAaaXyaaxxhaPPXhpɨh`WhhooΧv޵_v_G?/ EqXXPAXaXXqq'yhXaahhPXѱpph`poh֯vծ__V?VG T8AAXIIXiya+i⹱qhphhXXѱpphhpoֿƾƵNG7?N?#=y8AA88X0aXaaqq‘yiiqxpa p`hpǀ֟ƀ n_VNGN?LI8Y8IAXXiaaqXy⢱q iyxxhpxpπƮvnfVV__VTA08QAQPXXqڱiqiyp`hxx֧wǿvnf_NVN!58(88iYYaaqڹiiaiq3xhahpxȰϧwoǯvngNVNX5( aQyiyQQYiyyұqiiqiq¹±hppahȰרwwnnOO_?@EYA (IiqiqQ8Aqӱyaiqii򱙪paXpxpȰطv_OVG?=I( QiYyiyiYQayiqqiaiұʑڑhhpxhhɁϨǟwg_VO7635zA0 YyiiqiIQaiiyyqaaiqyڙqyxpxϿw_OOG?4EY)0QqaIiQYyiYiaYIYi⹙ҙyϷϷϿo`WOG/!IIYrjIzÂQQayayyaqqڱʑiiaii ȷϨogO@?MAQIIrbQjiqayYyyya*yҹyyqqaaXXaа ȷgWO>&IAbYIIbzAbYzbbyiqqya yiiaya Xaa±ȠoWWM0.YAQYA8AjjYQjArbzzjqyaiqqqYYiqaiaXPIaȰg`UDT8)AI8AYIjQI8YIzjzjziYj̪rqyiijYYaiaaXiبȷod5A1!1Q)YQIQjQzzrrj<ijzjbYYbIYYiiYii±ѹиxwk(MQQ9A)!)1Yb9AQYzzrrrrbĚrjbQYYIYQ+Yqyѱpxpа{ >9A9AA9!)QIb3jrzĻjQYYQQYQYYbrbYbiyڀɹxphp{X>9!)1))AIAYYbzrQbYQQbIYjbQAQbbj`Xp5JB11J9)9AQz1YzbzzbQQYYQYQ8AQYQYbz¹¹ѩxhhXXpcd91SRJ9B9ZcQAzzrjYQIYYIIjbbI)8QYYbb˪ѡaaxa@Hhc/.!!9BJBBRBZZJJYIbYjQIIAIQIIYAAI9YQAQIYQQYQbr̂˺ªʱ⹘haXhP@@[.J)!1RBRck{JG9AYAIbYIA11II9IYIAQIIQIQYbrzòâʪhaXPaa@De!!9)9B{cJ19?IIAIYIIQIbYYIA99IIQQYbrzۺòaaP@=.JssB1B9))9BI9QYQAQ9AQYIQbbz$ӳӚqhXaXP@4{cs1 91RRJBA9)!99I QYjjrr#ĒyqyXXPPH5ZŬskZ9BJ)1BJ9AIbQ91QYIQbjrjz#䫊zzyiyyXXHP=ZR{{cRJZBJZ11)19B1JAQIAQQYrjjԀӚzzriayyyPX&.9RkŃ{RJRJ99)199)!9BJ!BRR9RYYbjzzzԀ&̊rjiyyyXPXL|ZB {{JJ99!!191BcRZcjjj Լz jjzqyiaS MZZkRBJBRJ11!)9BRZZRZcRcck̂܀%Ĵrzzrzzzjrzzyy[6>cckJR9JcsJ9)JRJZZJZZRcZckcckszězrjzqq¹±zMcs{J9RZ 11RZBBcBRZRZckc{k̴݀zzrzrjz¹r]9ZZ{sB)9JBJ9RJZZJRZRsĬzzzª:HA1HOOHAA]N@@G@G@@FUUFFEELEEKSSYRJJRQWPPNTLKJJHLLkfHOA:AHAHO:HAA::2@G@G@@G@F E?FE>LLYYRDJCIINMLKJIHFEOu]HAHAHAAHAH:A:@#GF@F?F?FETZLLKRKJJDCICHNMLPIHGKTx{PHOHAAHBIHHOWHH:AAH@GNqFFM?FFELELEKDKDJDC B@FKCOA@DHlIPPOHIIPIHHOAHAAG@NGjMFMLLEDKDJJCCGNFKCBGEDMjkPXPPBIBAHBPIPIOHA HHOG@NkNGM?FMFELLDKDJJCBHGFDCA@>CPswkXXPPIPIIXPIIXIBBHOH&@NGNGNMF?F?LEESESSKKJRXVBGMKCHE=FnojXPXPXPIPIIHOHH@GVNGNM UMMLEELLKKRJPHNLKNGDGhj{ʿi X__X_X_XX__XPIPPHOHWNGNNUMFMELLELDSDRJJPOMLJADBiF_ʿi_f_mfm__XPPBIOHO&NGdV\\UMF[TMLLSLLKDRJRQHGLDIEBY=Pʿi3;mmff_ffmmf_f__PXPIPHHWOW^NVVNU\[LLELESKKJRQPGEO@C?aʾhxZRC<<,,4,RCw؁ׅngXPHOWOee^dkkjbbgf]TWYSHC^ʾhNhJ<43%44JZJh~~nn`X_PIOAHWW^elzd\b[`^i^YTSLhʾiRh³aJA=C@=76=@E<<99>?MNTQ]TjX4DĥKKZRpxس~ngm#ee]G@>DBLJ@<@=?8;:9BOKGKBGCHW]bdffj=-q۶qbKjDKRbڅ ٻwnw}vvm^WNG?D6AD6?<@8<;:5?MR N]bfnnkk{-=4yj[S[KKDĵĵ1£wvvf^WVMLJB9=;?8<;??99?DT^YXbbSXbmmrni/&-=ySK=SDKSb̼ໜvgXXOGVTDIB@>=OICY^c\bflb`[kd&-4Db꟏qKKDSD[qbĶĵ+кnXWWVUE>CBBAAHAABCB;N9E?DLK?E??>>K==BS{oma`ga5-&D=LSzqjL[SqߩOGG?>>6&.&5.LcΨkkcrzͯ݀ ޿||{ssyj\b[b[ZaSReVVTSWc*&..&5=L55\k{rjϸ||kkjcb[b[aZ__]\aYX1&&5>&&EE\k׸οͯƿž"пzrkrqcbiphafedca`7&&MT>.&.>>Mȡ{{c{ǰֿƶ޸zszkyrjiiphnskci#..&&TME>T&5E5]ɢ{\kϒހ ΰ{zszrrqpvusrq9...MM55>E5.5Ekk\kȒοǰ{zzrxp}usr'.6>MM>.>'.5>M]]dskdȰǸƯx~}|z+'.6.>>'.6'6M]ɱldsdǀǿ}|#6'M'6'.66FU||d|ssȩހ΂݀ưzzyy'/'''..6>FUmʛ|UttDZ|z/'>'>.FMeʫ|UttѺЁȹ'/6/''./6Ue}ʻ}m^et|lٺـ%'6U6/66?NNe^fee}}t ߀ɺ //?'N?7/ /77VGf^ĥunnu~}}}ˀû?? ' /7 /77G7N~_ĝunfnـʻ$'/7 77 /7?7GN__͎unffnu~~ۀʂɀ û/7?/ 7''GGN_fn~vnf~nu €?/7/?/7'/77GNNV_nfuʀ' / '7/7G7?GO_V~vfnffv~ۀڀǿ//@7/@7@G@GWvngnۀ$@7G7/(/7G7@7@OW`ǟvgvgvvɺ/O((//77@@7/@G``nnVgՀ θۀѫ(7(/7(@7GWW`woogºë (7/@/@@H7OG`gogo`ɺȰӵx#(/H7@77/7/77HO`oogo`wonºɰ˥$//@/@@/7H7HOWog`wWgo``Àĥ$ 0((7//H7@HOwȯ`wow`OOg˂Ҁ ƾy(( (0(OHOOWwowggW`w˃с ׿ͨy((0(00HPȨw`go`gw ĽÂр巂qj/07((7@"HXxXp׏`WWgO`gĽȊrr\j( (0(77H"PXנϯh`howǖΎ ̴ԀrcSS 70@77@HhhXȗ`OwοwĽ́ ϊrcj ( ((07@HPp!`wo`WO`O`翿o`Žɩ{cj6/(8 0@Hhxwo`HO`ζwo``Wӳ¹2((((8@Xxhp`X`hoo`֧ogWGOWҲ+0 ( (0HhX`xphXPXX`Ϸȷ෨o`GOg݀̽ĬʪTT7  (@0PppphHhh@HP`w`OGOW`ԽĽĬÓU]M(00Pa(pxxX@HP7``pȯ`WWOOoέĴʳM>>$ 08( 0HxhphP@HHPXxШ h``W`o ŵ ̴ܬUM>6"l0 ( 8PxpxapP@HXX`xhW`hwƦݦčfNFF66]I0(00(PaʁapXhpahPHHPaPpȸh`WWhƮԵ~u̥^G?F> 50( 80AXҪqqxaaPXHXha4РoOWW`wƶծ_V~V6G//'Ey8(7X0XyayXѹpaHPPaȸШhXO`hw翎ԵfvVG??66/3T8(IIXXPiyyʉaahhpPXḐxhP`ow׀ǟ͎~NNG6G6\I6a8AAayqqy҉pxxpPXаxhpǯ͕G?7?N6E08YI8I(qiqiyڹyypaفиxhx߁ǟƎnVNG?G/6TQAY IAaIʪyqyqXyʁyɹpxǖՀ_NGTY0 YIQâaiayڙyyٹx`XXhxLJ߾n_VG==I( AyiaiyڑyqaaѹɩaXhxǏζvn_GNGX=08QyYYayiaqyiсxpǶngG?G/4=I QyyqQAQyaqyqyphhxǁvV??7/@&Q ayaiIYiiqqaXqyyʢhpxpxȷ߶gVO??/1.A)aqiaAQiqiiYiqyҹ¢ꁉǿwV?OG?;Ej0YiAӊYQqiYaYYQaq±걢֟oWW_O/6.!)0Aj8rYQqiaYYiiұꪁqiqqǿw`OF$>80!rbjIIr̂qiiaYQY҉ iiaiiʑз϶w`WM4.A!00!)Y8QQjĚbqiaaqqqiqqiaaiȷg`M&A)0(rjQQrArYzzrqqiiqyqiXPPAXиȷw`]4&!0IYA)1AbAA8QArzrijyiqyyyjiYaaXPPaڀࠐwk9!!))QI9AbAzijrzۊzrjYYAYaqqaqy{'!)11!Ab9Ib̬zr쳊zbIQAAQYa Ѹpа!<AQIQbzz̼rQYQA8A8AAYbQQYq ⡐x`p41A9IbĬrļrYYQAAQ0AQII0AYYjɘxXPp!!(Iz)I̤rĴbA198AIA08IQAIYQj ɑhXHPpjl)!9B)JcI9z̛ԬbYIA9AbjjY08AQYbrۀhapX77Xj>) !!B1ZJ9RbYrjrIA199A))1!II199IIAQYY Ҁ ٩pPXa8(7L .1!B1JZcJ9AYIIbQ91!)91)!A9199AIAIQYr ʱٹpXPHXH851t!!)BkJ1)!)1!)9AIIA9I91911AAIIQQz"òúê±ɡaP@@H0-.9sʹ9)!!!)QbQI9)!)19A1IQb!˺úªҹpPPHH0-'ݓ{)!))))9BQ1!)!)1911AIr컳ۣòʉXH@&/kͬ{c9BRJJ!!!))!))A91!1IAIjzz"̣ĻqڹXP@H5kZͼ{JJR)BJc)!!)))9)IQYrzzzĻqqò€XI@P--BBkմݓc9JJ91!!!)119B9RbbYj'죚zĻzzòʉPPaSRBJ1!1!!)1)9cRcZYr̀ܚz̳zrʀya[(UcZݬsB!19JR!!!9JZcZRckZZkZck Ԭz̒z˺ҹ[-FZkkռJB19ZsJ1RJBcZJZcZcZckccs 崛zʢXMZZͼŋR9RZZR91RZBBkJZcZZckckcԼz(]BJZsŤZ11!)JRJRBZRccRZRZcZcc%̴Ļêʹt8mk@    E   9  ,n  %6|  *>Y .EZ 1I[ 3KZ 3LZ 3LZ 3M[ 3M[ 3M[ 3M\ 3M\ 3M\ 3M\ 3M\ 3M\ 3M\ 3M[ 3M[ 3M[ 3M[ 3MZ 3MZ 3MZ 3MZ 3MY 3MG 3M'3M$ 3MD 3M' 3M03M7# 3M>( 3MC, 3MG/3MI13MJ23MK33ML33ML33ML33ML33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33MM33LL33LL33KK31II1.EE. *>>*  %6I[itz~~zti[I6%  ,;IU]befffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb]UI;,   ,6>EIKLLMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLKIE>6,  %*.133333333333333333333333333333333333333333333333333333333333333333333333333333331.*%    BitTorrent-3.4.2/osx/waterfall.icns0100644000202400020240000022734607521543131015716 0ustar brambramicns.ics#His32 ss{{sB99)sk{{{ksRJB11)B!)))kB!JB9ZZ1J!k){{J)!cc)11!sZRRck{ƜsskJBBΜνsJ!{cΔc9B/cRcΜc))c)sRZ!!J1JZƜ{s!<1sBkέB!!֥{!sJ!9kc{kJ19)9ZZRBB! 911B19BJJ!kcJJZJJR9B֌kkcRZRcRR9ZRkJkcZ{sZk!BR{JkZR{{RZZJ!{kk{1919ֽ組c1)1kc޽k))R{筵{ZcBB)9{k{ƭ{BJc)R{Js{1B)1RJcJcsƔB!!)1Rc{έcBJ!J)9JƔRc!Bc{{cZBZJZ{ƵkkccRBJ RJRRZZ ))sscRcZRkg9R{sscck{kc)!JJkR{{sks)1JcJRckc1)s{)!)9έޜs)19kkƥs)!9ƀucc!)sƔ9B9)9Rs{1B1!RsZc΄罔9))!Rkk91!!B9!9BsBccZ9cJcν{kccB9s8mkICN#il32 R !))k{s{{s*c{ss{ss{ss{ss!)1)!)!!kss{ {s919J9!)1!):JJ!){{k)9sRJk1B9!)1!!)){!!119Z1)cƌ{RZJBJ91))9{R!)1k1!!ZJ19RJ 991JsR9119))ޥ{91!1BRBJsBR1)BZ!ZRR޽kc19))9ZZ{{s1911)9{!sƵcJB191ckB1911ZRkZR{RJB9ZcRZZ1){{֌{ZcRZc"kk!!!B{BRƜޥ{kckskcZZ!)s1Zƭ֭{{kssJBR!!9BZ֌ֽ{sRRB!)Jc{ƭƭsZ)1)Zc֭{{RBJ11!!kskBJkƭkBB!>JsssJk{έƥcR))RRkZ1csZ1ks)1{B{1)cJ{ƌ)!1){JZc{ZsבֿR!{9sJc{sJcƭνZ{J!s{1ZkJZR֜{s9!91{R1{JB{ƽJ))!19Rs9J޵ֽsZB/!)kc!))c֔νk)Bk!!{kZ1!c޵sZ9B1S1)!19kcJs{)kc{1{)!!BJZƵ{ksJsc{J1s!Rc!sR!!JZ{Ƶ{s1c)BJ9919BBcJZJJB1sJ1))1J1RB1BBJBJRZBJBB9B19*BJJBBJJ!9RRJJB9BBZZBJRRJR9ZcZRJJRZJ RRZkkRR9ZZΌkcZcZBJR!RRZR19RsZR{ޭsscZkZRZRZRZkR!!9ZZJsֽkZZkcZZkkZ!W!JJZcZJZcZcscccsRRc{1BJ1Jsskք{ZkRRc{{ZZRcJB99cJ{cBcZcZ{Z;R!B)19ss{skkkcc{{kss91B!BRJ9ֽΔs{ss{3J9J))1J)cZs絭ƽ{{ss1B!99RRsνրΜkck1199JBZcsέssc)RR1J9Rk{ޭ{ZZ1!)!RJRs{νޔkckRZ)1)1!ZBZkƥ ccRJ!!Jsƌk{ΌƜ{kZRB919119kksR{sRƵέRJB9B!11J)c9cRRkޥRBJB11)JBBRJcs{sΥB1!))!9)!91kJZc{k{sΜc91))9)9RscƔskޔ֥R11!)!J9B)BBcJsRcZޭcJJZ1)JBR9ZccBcc֭skB1)1.RR9ZBZ)Z{քRc)11B!)BsJ9BR1RրssJBccR)!1RRR9BcZޭ{kZB!{1!RJ1BJcZ{֔kscZBJk{BkJBcs֜ZB{B)!99RBckZZRZcc{c{ckcRJ99B)ZB JRRcJRRZcZckc!ZcZZRRJRZZ)ccZ JRZcZRRZcZZ ƽ!1kZs{kZkZZckckckƀ!RJc{ckcRZZcckcckc9JcRBέ{ks{kckck{ckk!!9RJRcZZ{7ssksskssk!!)R9RZRRJckss{{kcs!91)9{kc 猄cZRRckKRB!1JJZJZRZRsckcc)!)1ksƵ{cckc{!1R)!9R1 ε{51J9!)J1kcs絜!))91ZZ{Ƶsc{)B1JRJZkƵ{c!199B9RkεZc!))9BRsε{ccRc!!1!9Bkc ƔkkJJ!)skބsJ1!!){sJ{֥sJƜBB19J)!9)kZZƵZ{J)J9)9)1cJsƄ{޵9B9B99B{RckZZB9))!)Jc{sB1)!)!!)B9ƥZsk+sR99!)1!RcsBksΔ΄k1!! !!)Jƌ9B9JրFBc)!!!)!B1!B΄B1ccJ!JB9!)JZΌsƥR9!31J91BJZZﵔ{scZZ))ZZJk{ށkRR!))B9ZcRRJR JZR{k{kkcZcR91!l8mkich#Hih32K J Jssksksks!)s{s{skB!Rs{*s{{s{{s{ss!!!!!)!!s{s{s{s{B1)9B1!9) )!)11ksskss{{{{{))BJ911)1B)!1!!)))!)!!))R)J{!B!ck9kcZZRJJB{91B!))9)!!)!)Bk!!1)1)s1!1JƵkcZJZRJJB9))11)!)(Js!!))B1J9!ZJνkZkRJB9JBJ9)199Z{B!)11c9!scB9)BJZJB1191)BZccJ1))19B!!)1!)1kscJ))!11JRZB9B99BZsRZ129J!{{)1J)νΜcJ1JZ))JB99RRBJJkJZJ11)19c){9BJsZccJ1)99cZcc{sskB91911!9{cJ9)R{ε祄kBJ!1)91!19ZcZcR1)1919B!!JcRRν猄9!JB91))kkZJ99B991)!!){{{k{{ΌJRckB9B1B9BZR)ZRJ11!)BcZ罜s{sZZRRJRZckc!9))ssZJsבֿ{{ccZRZ,kkZckk)!)9k9{s絔sckckcZcZZ!%!{9scƭεskcsk{kcZBRRZsBcRƽn{ss{{sZBJBJ!1))JcBRc{νƥ{{skcRJ)J!BsJBs{νƽ{ksZJ1 RJRc֥νƵ{{scRB19-!RJR罥scRRJRB!)!!9{ZcJRΥBcJ19B9BB!ZssZ9BcsΜƽZB9B1!1!%)ZZcskRJscνƭZRJ!),{ccRcJcR1ޥޥcR!)!k)B{k9JsB9kƔks993)!R!BkcZJ)9c{{cJZ޵քkB))9!)!!{JkZ)1cs{9Rֽ焌ZJ)1!!cJJZRc{ZR{Ƶ絥c)!!))!)ZBJsZZR{cJZƽcscJB)#!!!!1J1B1kJZRZRƥs{sB!!'1ZsRBB{ZJcƄskcsƜ{c1V!9c11ckcBZcZ9kε{Z!))!!)))11J{ksR{ZJBZRk祔sB1)!!R1R{ZkR))19s絽ƽcZsZ1!)9!ZscJ)))1Rހq޵s9RcR!!1!)1)!!Bc֭99!9ss)9sZ{1!)!Zk֜sZksBBs)B!sB49!!1RRkƵkcsJ{k!)cRkJBs!9JkcZZs{RsskJ!c)sJ0{!!k91911BJRƵskk{Zs{kBB1)RsZ=Z)sJ))191{ƽƭs{k!c11c91Z)1BBRsZk{ZZRRJBB11!!)!)!!)9B!1)!)191911BJRRZRJRZJBB1)1kB!))9B9)191 9BB9JJBk9J11))19B1 9BJBJ9B99JRJ!!BBJBJJ99JBJJ99cBB99BJRk!BJRBBJB99BRJRBBRBRJJRJ!99kZZccZJJBR9RcRJRJRZ!9JZckckZJRJcRRZRRJJRRBRZRZRsJRs!!9{9Jc{sskkcccZcRJZRZRJRcRJJR!)9)BRJBZc֌{sksscZRRZZRkRJR%ZZ!9B1cc9JscεskkcZkckZRRZRZ scRZRRZZ)19{Z9)J{ccZckkcsccZ-cs{{kZRRZZcB99RRRZZ{kRZRZZkk{cckc{s{RZk1!1)!RZJkR {kZksZRcskcck{k{ccRRZZ)B99ZZcc s{{kZJRcs{{cZTRc!!)BB9Rc{ccJsƔckRZRcRRZZs{s{sZRZRZZ1RZB99BRk{kkνZ9RskZRscZZcZ!)19R)!1BR甔{kk{c5Zcck{kskssc)!RRJR9Zε{{ε޽sskskskss{$B)9BZ1BB9BJ9Rskƥ֭έ{{s*{s{1RB911!9BJ19Z{Z絭ƭֵ{4{s{ss!11!9!)!)91JZ{Ƶεν{{{cck)9F!)J9sc{kεֵsckck991cBR)JJc{ck{޵րƽ{skZk!#199)11JckcνƵƽskR)!9)R11BBJsckހXνƔkcZc)!9)JBRJsksֵν{kkckcJR)))BcRBcs{ckƽƽskZZcZB)1R)9B9R{sZc{޽Υ֜scRcZJRB!B9!!BJc{s{{kk{ƥνskcRZJJ9BB!9911B{{k{csRƽƥ{sJB9JJB)199R)JJJZZ9kcc筜ޥcc1BJB1BZ1)9BB{J1c{skRc{{k{έ{cZRcRBBJ9!9!cRR)RJJk{sJZcsֵskB11)BN)19JBB1RB99{ckks{ssֽέ{RJ1!11)BZ9)R)RBZ{Zkskc{csހ3{Υ{ccR1)))1Z1RB1J9RkRcRcs֭sskֽ΄ZBJ1!!!!11B1JskRcsksޥ{{ր,޽{RBJ91!)!!BJ9ZRR{{ck{sZƥD֥kJ9RBB99!))BZ1JJRRkksccskƵcZZB!1J119J B1)sZkkkR3Z޵s֌sc1R1!!!BR!R9B1ZRs{cRRBRZk εքZk{s9!1J1!9JBRRJZJRJBzJc{ε֭ccRcB!!!!)Z{119BZJ9ZRsΌs{ccBJ9{Zc1)1!BJZZBJ119JRZkkք{kJJ9!JBs9!s{ccJ!1)BcZZcsޭ{sskJ{RkB1P)J{9Z)RZZRcckέ{{sƜZcR9RBcs!{JcJ9RRZZ֜ƽ3{JJ9R9B{ZRsJRccksƽsskkckkcZZRZRJ/BBRJ9JRZZJJRRJRRZRZRRccskss{sckscZcZRRJB9Z991)ZBB1199BJZcRBJR JZRRZZcZBZRJBJRZRJ ZZcRRZRZkZ!RRZJJZRZRkRZRRZRZcZ!9JccZZRRZRZRRZRcRkRZZc !!!9)skksskZRJR9RJ!)19B1!!!!9BB!!1JRJRcZJ!B{9BRZRccsk{kZkZBRJcscƵsckkcsst筜{ε֜kc{cJJ1))19J99RRJJBB19!1911!9)!1B)99BZRZB919sZccBcBccZ{cc{{kcRRZksέkcZkkssހ֭{罭c{cJB1!!)!)199JB9JJ9!)9)!!!1JR11Bc9119)B9 JsZZRBZcZZss'{kZcckkRZֵsskcssk޵{޵9ccZBZJ))B!)11)!9919B!)!)11!JB99JR)9B1Z9BBZJJZk{c+k罵skskkZZֵƵsskkssƭƵνRJ9BRB!1B!!1 )!))1!! !B91)J9JR99JR$9B{9BB99Z1cZccssƔ{kks{sc scksƀ΀ޥ΀GscZRJRB9BB)!)!1)!))!1!!11)!191BJ1)BB11RRJ9Z9JBZZkccsZ{祵s k{{{ks{sրε {skZZccZ9BR)9!!))!!1!!B1JJ9B9!))RZRB19)kZB19RBRRZZs޵ksk{sck{{ޭ{ƭ {skcRZR9RJ1!)1!))B9J91*J9BJZRB)Z99)99kZZccs޽kkcks9Ƶ{kcks{ε֭{sε{skRZR1RB199)!9!R)JB))1J199RZB9J9)!cR{k{RRZk{{ֵskksksƽƵkssckεޭ{{ssRRcBBJR11)!!T)!99RJ1BZRB9BBJ)1JZB!)JksksR9Bsֵ{ckskkscZs{sε޽ƀ{cRZJBRJJ911!!1.!!!9B1191RRBB1BJ)ZBJ)!RkZ{k{kZRc{ksskckֵΔޔkks{kk΁ƽ֭Υ{kcZR9 B9)!11!!19)!!)D!91!!11R9RB1991)19{B1!Z{kkskJRckk{{sccks{ޜs{{s{ƭƥ{cRRJBJB911)!11!)1))!F)!11)JB!91ZB)!11BJZ)1RscJkRZ{kZkcZJZk罜֜{TֽֽƵƽscZRJ1B))!)91B))!)!1BB1911!!BB!9)JBJR)!99!!JJZskJ{ƄRRc{c{{css޵Δkkckk νցskRBB)1JBB9191!)!!1))9!!9)9))J99JRBRJJscRkksc{Z{{{c*{ֽ{{ssccZZcֵƽνkZRB99J:BJRBB)191!!1!1JJ!!)B1))1BBR)s)JBcZJJc{BcZ{cc{kss{c {kkc{c ZccƵΥsZZRJJR99JB91I)1))!!!!!!1))!RZ)sB1ZBRZB9BkkZRkBsc{{ks{cksssZZksckcZRJcށεkcZRJ19)>19B11!!!11)9RJcZZ9)BJ9BZJkRJ9ZJ{k{k{kZkέss{kZZckccZk ޭνskkZ RJ9BJ11!!))+!!!)91J1!)9JR9B1!1R)ZRJRkR{{sskEƵ{kcZZcJZZkkZkkƵֽֽ{{sksZRZZcJ919)991)!)!)991BJBRBBR%9B)!)1Zc9BRZ{{sssscƜskcRZZJZR-Zs{ֵs{sֵ{{ZcBJ911!!!!!!!!B)!9)JBB9B9BB9!)RJc3ks{ƽkRZZRRZRZZcscZck{ހν{sks$kskZZR99B1)!!))!!!B11)!)9BJ!!9Bg9!)1))BJBZZc{sRcZRRcJZkcRBRcckƽcZs{ssZJ))!!)!()1JBBJRB!1J9JB11J9)9BR{1Z{c{{cRRZZRZR9BRZRZc{(ƽƽ֭{kkZZskR{kR91)!)119)! !!)k91,RJ9B9ZcRB{{skZRJZZJJkccJ)9RZZcc5έ֥cc{cBJkkssc{RB991!191!!>11!!1!)c1!!9BJBBRBZZJJZJcZkRJJBJRJJZBBJ9ZRBRJZRRZRcs΂/νƭε罜kcZkRBBcRsZ1!9)!)1!))B)!11!1B1J)!1RBRck{J9BZBJcZJB11JJ9JZJBRJJRJRZcs{ƵƥέƥkcZRccBJZRJJscsB!!)!!!))11911!Zk!!9)9B{cJ19?JJBJZJJRJcZZJB99JJRRZcs{޽ƵccRBBJRR9RcJcZ9))1!1!))))!#)11B1JssB1B9))9BJ9RZRBR9BRZJRcc{?ֵ֜skZcZRB9BJB9BZsRZsZkk99!))!!)R{{cs1 91RRJBB9)!99J RZkkssZƔ{s{ZZRRJ911)1)9c{B9cZcB{9))!)1)))ZZƭskZ9BJ)1BJ9BJcR91RZJRcksk{րb筌{{{k{{ZZJRBJ9))!)Bk99Zc)JJ1!)B!!)BBRJ!)ZR{{cRJZBJZ11)19B1JBRJBRRZskkց֜{{skc{{{RZ)9))1B{cRsZ!)=)1JJ!)9){9RkƄ{RJRJ99)199)!9BJ!BRR9RZZck{{{ր>Όskk{{{ZRZRcB9B))9{sk{k{RZJ)!{ZB {{JJ99!!191BcRZckkk ֽ{ kk{s{kcZcZkc9RRs{skkc91RBB)RkRZZkRBJBRJ11!)9BRZZRZcRcck΂ހAƵs{{s{{{ks{{{{cRJRZZsRsRcZcZJB)!>ksZ99BcckJR9JcsJ9)JRJZZJZZRcZckccks{Ɯ{sk{ssƽƵkkZcRcJR%Jk1!sR)9RRcs{J9RZ 11RZBBcBRZRZckc{kހε{{s{sk{ƽ{{cRRZRsZ19RRJ!)))91c9ZZ{sB)9JBJ9RJZZJRZRsƭ{{{ ƭ{{RRJRJ RB1RZBB),)sc!)B))11JccƵ9BRJJBBRJBRRZRΜ.ƽƵ{cRJZJJBBRkJBR)) !{{kcZc!1kkk9BB991BJZJRccZcր ƥ Ƶƀ {J BB99B{sJRB99s{{{s))!!191BkJRJssskR"ZcRcss{ε ƵscZRJB99BZskZ1!R{sB!!1skc11kֆƽ{{skccZcRZJRRJJBJ !119)91)9JJckcJRZZBZJZkkckccZ sZckkckk{{{sskskskkckss{skkskskkss{sckkcckckZckcZRZRJJBJ9B99)J1JB1B9BRJBJBJRZZ{RJRJBRJZZRRckkZRZcZcZc kskkskscZcckkckcZZRZRZRJZRRJRB99B191!!1kJ))BZRB9RR9BcRJZZccRRJBRRZRJcZRRZRZZRZRZcZRZRcck cksckskks{sk{kkskskckRZcZcRcZ RRJBBJB9JB919B991))9!!)!19B1)11JBBJ9JBRJBBJJRZJRJRBRZRZcZccZZcscksk{skkccZZckZccZZccZRJRRZJRJRJ BB9B99B11)9)!kcRRJJB9)19BB9 BBJRJZZJZZRRJRJRZRRJRRZRRZcRZRZZRZRcZcs Jk{kcZRRJBJ9BJBJRRZRJJRJRRJ RRZRJRJRZcZRZccZcZkkccks!csZcZJJBJB99BRJRZZRJJkZJJRJRJJRccR ZRRZcckcZZcckckckk,!))RZRJRRJBJB9J9BJJRZJBJRJRZBRJJBB9JRJRJJRJRJRRJZZkkcRZRZZcs1kkRJRJRJRJRJJRJRBJBJ RRJRJRJRRckZcZRRZRZckck{ƃ!))cZRJRRZZJRZRZRJJRJRZcRRBJJRJRZRRZJRZRZRZRZRZRZcZkZZck !)JZRZRZRRZRZRZJRJJRJZR{ZRZRZR ZZRRZcZcZZcs1RZccZRZRZcZZJRJJRJZRZRZRJ RRZRJZ{ZRZJRZRZZRZRZZRRZk!1ZccZcZZccRJR ZRRZRJJRBJZRJcsZcZRZRZRZRZRZZcZc{)!B)9ZZcZZccZZRZRRcZRRcRJJRZRJZRZRZZRJRJZRRcRccZ ckkRZccZccZkƄ!19RkcZcZcZRZRZRRJRcZRZcZRRZcZcZckcckƁƀ!!!!!))1RkcckkckckcckkcZRZZRZRcZRZZcZ RZRZZRZRcRcZZcZccsƽƽƀƽƂ!))! !)kB91Bksk{s{kkcZZJRZRZ RsckkcZRkcZcZRcZccZZcZcc{ƽƽƁ!)B!!!!1sJ99B{{sskss{{skskkZcZRZRRcZckZccZckZZRZRcZcZZkZckkckccZkkskkcskƽƽ!)1!))9ksZ9BJs{{{sksckkcZcZckckcRc ccZZcckscZRZcRZckZccZkckckcZc kk{{kckckck ss΀!)1ZƥsB)JJ{skkckccZc kZccsZZcZcZccZccZcRZcckckccZccZkZccZcZc{ckckckcZcckcs΀ !!!)1B)JƌJ9)JJs{{{ss{{{skckcc{sZckckkZZRZRcZcZccZckckckckc kkckcckcckck s΃!) !!)1RkB9BRRkΥ{skskckcckkscZckZcZkckkc kZcsccZckccksckƀ!))! )!1ckJZB9BZZsƽ {s{ssk{scksskkskcZcZckZckckcckkckcckskcs))!)%!!)1)!){kZsB9BRJBRcs޽{ {kksks{s{{kcZckcZckcckkccZckskcksk!))BJR9)1skRRJ91JZcsֽ{sks{kks{kkskskckcZckckckcZcssckkcks{skւ$)B1)B)RZ9B1kR9BJ9k{ƽ{{sskssksskss{ksskckcZcZckckcckckkckcsƵ{Zckkcksksks911J911ƭsRJJ9JJBkހ {sskksckss{skkcZckck {ƵkZcZccksks)1BR) 19kƄ{kRB9cJ9Z΄ ֭{{s{skssck{{ kskc{scZcZckccsks{ {sckckckcks!!)1ZcB919kεcJR9J9BZk{skskcZks k{{{ssck{kckckcks skkscksskcs1!19)B9!BΔcZJBB1191ZJ{scZRZcZssks{ss{{sks{{skZckkskskck ss{skssk sk!!!!)1!)!1J6sRB99)99RcRs{{kckZRZJRccks{skskss{{cks{sskskkskk{{ssksks:kscs{!!!!)!1)9skRBJ1JBZsZckkZZRZZRRZZRRck{{k{{scZk{kskkskssk{kkskskskks{kkcs!)!A1!!!)9))!)9RƵ{ZBJBJBJckޭƜ{kkZZcZZRZRZZcZksks{ksk{s{{s{s{cksksks {91!!91)))11JνsBZBBRRks{ƭ{scRcZZRRZZRZcskss{kc{{s{ss{{{s{{kckksccks!kks)!!!1!))!)!1!!!)1BkޥRBJ kJcJRc Ɣ{kZRJZkkskcRRJcZkcckk{kskks{s{{{{skskksccskk{s !!)1!!)1)1B祥BBkkk{skcZcZZsskkZcJRRZccskk{{{ss{{sskck+cksks)19)1!9B!)))B9JֵRRcZ{Ɯ{s{sskRJJRRccZZcckck {{{ {sckksksckkck{!))9)1!)1BB1{{νkRsJRZkΥ{{kcZRJRBRZJZZcZcZk{kksk sksskckck1!))1!)1B9sΜcZcRRJƀ ֵskccZ)RJRRZRZZccZZckskcckkcsskcskc!!!))99B1919)1B΄ZRBZJRZkεέ sccZRccRZRZZRRZskckkskskkcZsk))19)!)9!)19Jk{RRJZJc{kν{cRJJRZcZRZZcRc{sccsckskkcckkcck1J1)1)9))1)!19cJ{޵cRBZRRsր޽{ZJcZcJJRRJcJZRZccRZRcRRZss{sskckkckckkcck4s!!9kcc{kB!))!91)JBRZ{sRcZ{ZRRJBJRZZJZRJRZRJRZc{{ks kksksckccsk!)1J9))9ZRBJ191)1)1&BB{cR{cֵ֜cRRJJBRRJRZJRZJRZR{s{skksks{s{sskkck2))1))11JZZ91!!)!!11BJksk{RkΥޔε֥kkc k{{ccRRZZRccZkkcs{ss{s{{ssks!!99) BB))!))!))!%91BJkkkc省ֽƽν{{{kRkcckZcRZckZcs{{{{BJ)J9)191B)!1)91Rk޵ssk{޽ֽ{kskskkscc{kss{!)RRZB)9))11)9BR99csΔνք{sƵέ{ssksksks {ss{{F)!)))BZZcZcZJ1B91))9B)!)!!JJcsέε޽εֵ{ss{{s{O9)!)!9JRZcZR19!!))1!)!)RZB1)1BBR֭ƥkֽƭƽ{{ {{))!))9RJRZ)'!1)!11))ZRJBZ)9J9c֭csޜ ޽ƵE!)9!J1BJJZsRcZ)!))!1!11RR99BJ919Jsscsֽ֜րր Ƶ9)!1BJcRBJ19J1)!!!)19BRRB1B)19BRΜcck{skֽƵֽ ƽ )<1kcR9!BZB19!!)1)191BB)1!9)9Rcֽsk{kրƵ{{ !1))!)R11)1!!!9)R)9)199JZεk{{ֵނ Ƶ {ssk{!1)JR!91!!)!)1))!)119BJZs֥Z{{ƽ{{k {s{)1)11!1)9!1)B)B!1JRkֵZ{{ށνskksk{ss{!9B)))!)191))119ZkƵsck{s ƽ{sckss{!!!)!1)!!))!!)9!!Z9!!199BRRkcƵk{ ΀E{kscckk{s{s))!!!!!!)B11B)RB91!199ZJkcέ{ss{րƵ{{k-ckss1!)1!9)!!)!1)1BB!)!19!199J9RcΥ{sksƁ{{kZkccsks! )1!!9!191!)#19!99!19B9JRcc֔{skks{ւր ƽ{{s{cZccks!)))9)!))919B1!9))JJRcksƭ{sks{ހր ƽƽ{sskckk!1))91B191B19)199JRRZcƵsk{րށƽƀ sskck)!!!9)1))!)!1 )919J9BJRcZ{kskk{ν{{ssc{91B!11J11B91B9BJBJZ{s ks ցE{s{kccZZkk!!!))9!)!1B9J91)19J9B9BRZcΥ{k{k{{ν{;skkckccs!!!R9!!!))!1R))1199BB91BJccssZkހ ƽƂ罭 {{skkZZRc11!!)!!!!)9)19)B9JZZc{ssk-νεֽ{s{{sskZRJRJJZZ !!!)))!)91B1BBJ9RJckskscրֽƵ{sckkcZRJRJZRZ11)9BJ))!!!)1J9B9919199JRcssksc{ss΀,ֽ޵{{kZZccZkkscZZkRckZ!!)'!!11B1BB19J9JRZskc{Zkscc΀*ֵ{s{cRZcskcckkccskkZ2!!)!)!!!1))911J9BJR{Ƶεc{s{cRRkƽւހεskkZZkkZZcZckZJZR!1!!))')!!)1)RJRRZƥƭ{s{kkZc{փށ޵ssckckccZcZRJZ!!!1!9! !!))1)11JRέ{cksck{ ΂ހ ƌ{s{sZcksZRJJB!))!!!) !)!119))9B"JZ{ZsޔcZZkRck ށ֔{{csZRZcZZRBR ZRJJRB))!))1))!)1)99J"RZޥֵkcks{Μ֔ ֽցΜ{kZZRBRJBBJZJB 9J)1!1!!!91B99BJkkZƵΜƽcR{{ƽƄޔ{kscZcBJRBRJBR1B191B!! !!!)1 )!))19BJRs!c{scZRcRcƵsc$ƽֵks{kZJBJRBJB91BBC!!)!1)9!1BJkƥ{{scJRcƥֽ{sccZ ޽ƜssR1B9BJB9 1B9B))1!!!)! ))))9BZ${kscZcksscƽޭskZJRZl޽sscR9919JB1)99BB9R!)!))1!)!)1JkƄZc{ƄskZRZZcֽν罭ƵscJRkεƽֵZZJcJ1B919B19BK!))!!!!)B1RssskJkkBJRcƥ{cRJRZc!ƽƥεΜZcRZZJB9119B$9191BBJJ!!!!)1!)11Rc(s{{ZBJR9ccsεcZZRRsֵνֽRBBJJB91B1) B9BJJ!!,9!B!19)!1J{kskƜRBJJRZ{֭ƀ ƌkccZcs νֽ絜ZRB99RRcZBR1991 )9JB!!),)!)9s1!)!9R{s{csRBJZZc{ƽkZck{$έΔkRJJ99JBRRJBB91B9919B!)!6cJ1)11)Rc΄csZksckRJJRcRsνƽƽkcZZk&εք޽Ƅ{֭cJBJB9B9JZRJBJ9B9!))19/)!)!!)))1!91)!91BZ֭ss{cƭcRZJZkc^֥ƵsRZZc{ν޵cZZ9J11))199BBJRR9B)B)19199))!)1)9!)1J{9)/Z1Z{c{ZֽscJRRcν֭kZRck{-Ɣ޽k{ZJBB991)191)1991B9199199J9A)!))1)!)191Z9)JJZZRk{{ΌcckksRZ罔{kRcs{ހ2Υ֔RRJ9J9)9Z199BB991B919B9B1!)!!)1)1)cJ6c9BBc{ss{֌s{{sRZƽֵ{ksε֜JB9BR919J99B;99191B911))!!!!!!)B9J19ZJ9J)sksk{޽{{scށֽ{k{ΥΔsZRJBJ11JJ9199BB9 199)991D11!!)9ZZRBZ!JBcJέ{s{sZ{΄{ƽνs{ΜހcRJ9Rc9B991)B91 ))!!!)!BkZZ1!ZJRƥckc{ޜ{{޽ƭ{cZZk{ΌƽscZJBZRB9919!!11)Q1!)B1!)!9RkBJ)!B{kck{ޔ{sccֽέƌcZk{Δֽ{scJRJ1RB91)!!)!!!b1)))))BZB19R{ZZc{kcs{kք{sƔνskJBJ1J11)!)!!I))!!91!)1!)1BBJ!R{{sRBR{cs{s{skk{ƀ΁{ZBB919 1))!))!)!)P!)!)1)!R)R!!c{ckJZkksscZs{{Υks{s{ν罵kZRBB1)91)))!)!!!!!!11B)cskcBRkskkZks{ֽƥƵ{ZBRJB99191)!))!)))!!!!9>!BJk1ZkB֌ZRskZcZZRcsƵﵥޥsZZcR1B1911919!)!)11!1 !)!!)7)1!)1Bk9sZRskcZZkkֵפּskssƭ{cRJ9BJ991)!!!)1!)!)B91!sckJJs΄skkcZRZ֌ kkckkΔֽֽ{cZRJ911!!))!I!!!{1B!11!)Z9RRkƜcskccsssksskcckνkcRJBB1)1)11!)){B)B)1(skRRsBsZ{{ssskks{skZRRBZކֽν{ccZJB91! !!))!!!AJ))!1JZB)1BcBB9RBs{sƵ{ks{{{kkZccZRRcނ祔ƭ{skZRJ99))!=!!))RJ9BcB{Ƶs{ތ{skZZBZcsscs{#ƜƵ{{cZcckZ1! 1!!!)! '!!)11!Bc9Jcέ{s﵌{cJRBBRZcֽsֵZcZc91!!)!2BRJRc{{νsRZRB9B9BBZcRRZs 祔{cs{{sZRJ)19)!!!!!!11B9JcƭsƽsZZRBBR1BRJJ1BZZk$Μ{ZRssR9!!)!!B!!!'J{)JΥsƵcB199BJB19JRBJZRk ΔkZJRssZkR91)! !!)!)s)!9B)JcJ9{Μ֭cZJB9BckkZ19BRZcsއHkcsZ99Zs{ssJ1)!))!!!cB) !!B1ZJ9RcZsksJB199B))1!JJ199JJBRZZ ր(ޭsRZc9)9RJkZƔ1!)!!!J11!B1JZcJ9BZJJcR91!)91)!B9199BJBJRZs7ε޽sZRJZJ99JB99kJ)!!3c{!!)BkJ1)!)1!)9BJJB9J91911BBJJRR{+ƵƽƭƵΥcRBBJ119B91JkZskB)!!R19sε9)!!!)RcRJ9)!)19B1JRc4νƽƭֽsRRJJ11991)1ZsBZscsk1)!!1Zޔ{)!))))9BR1!)!)1911BJsオޥƵΌZJB)19!)!1c{)1cRZB{)!!5!Zkέ{c9BRJJ!!!))!))B91!1JBJk{{$Υƽs޽ZRBJ9B9) !9s19cs!)9))ƵkZν{JJR)BJc)!!)))9 JRZs{{{ƽssƵƀZJBR1991)N9kc{Z!!!εBBkֵޔc9JJ91!!!)119B9RccZk9樂{ƽ{{ƵΌRRcZc911))9s{RR1!ƵRBJ1!1!!)1)9cRcZs΀ޜ{ε{s΀{cZkk9BR{ssk)B)/ZsZcZޭsB!19JR!!!9JZcZRckZZkZck ֭{1Δ{νֽcZJZkk{RRZckcB9!:!{sBBJZkkֽJB19ZsJ1RJBcZJZcZcZckccs絜{'Υsscckc{ZkZckkJc)A){k99BZRZZνƌR9RZZR91RZBBkJZcZZckckcֽ{ƜkcZZccBJZRR4!B9191cBJZsƥZ11!)JRJRBZRccRZRZcZcc*εƽƭνZZRZ cZBccRR9)1{99JB9JJRssƽZ)RZRRJRZRRcュ/Ƶνֽ{cZkZZRRksZJcBB 1{1!911sBB{ƽRRJRRcRckskkck{ Ƶƀ΀εZ RJBZZcJB JƜ11)1)JJZ{RZR{ƔƭccZcsZkƽրրWƵskcZRJJRk{kB)ZR)919!)B{JBsνƵƵ ν{sccZcRJRRJJBB !)119!)1)9ZkZ9BRRBRBJckccZ RZsZcckZkk{{s{ kck{{{ss{sksks{sskckckckccZZcZRRZZJJBJBJB9 B19)1!B!B1)9)11)11J9B99119BBJZRsJBB99BBJBZccRJRZRZRZRRZ ccksskskskckscskcZZRJZRRJJBB1919011)1))cB1RB1)BB!119ZJBJ9JJZRJB91BBRJBRJBBJBJRJJRZRJRJckckkZkskss{{ssk{ss{s{skckRZcRZRZRJB9B911)191)!!)1))!!!))1!!991911)B91199BJ9BB99B9JRcZcZRRZcc{cks{ksksc ZckZZcRZccRZRJBJJRBJBJBB99B99191))!1!t8mk@BitTorrent-3.4.2/osx/BitTorrent.pbproj/0042755000202400020240000000000010034142504016425 5ustar brambramBitTorrent-3.4.2/osx/BitTorrent.pbproj/project.pbxproj0100644000202400020240000005053507754611172021525 0ustar brambram// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 39; objects = { 080E96DCFE201CFB7F000001 = { fileRef = 29B97318FDCFA39411CA2CEA; isa = PBXBuildFile; settings = { }; }; 080E96DDFE201D6D7F000001 = { children = ( F5D01D3D027E224601A845D2, F5D01D3E027E224601A845D2, F5D01D37027E0FE201A845D2, F5D01D38027E0FE201A845D2, 73FFC8050471552300010167, 73FFC8060471552300010167, F5A3C5CA02F3CA7101A845D2, F5A3C5CB02F3CA7101A845D2, F5ACEF980308CD8E01538297, F5ACEF990308CD8E01538297, F5ACEF9D030B4FE301538297, ); isa = PBXGroup; name = Classes; refType = 4; sourceTree = ""; }; 089C165CFE840E0CC02AAC07 = { children = ( 089C165DFE840E0CC02AAC07, 73EB9224052A92EB00D326AE, ); isa = PBXVariantGroup; name = InfoPlist.strings; refType = 4; sourceTree = ""; }; 089C165DFE840E0CC02AAC07 = { expectedFileType = text.plist.strings; fileEncoding = 10; isa = PBXFileReference; name = English; path = English.lproj/InfoPlist.strings; refType = 4; sourceTree = ""; }; //080 //081 //082 //083 //084 //100 //101 //102 //103 //104 1058C7A0FEA54F0111CA2CBB = { children = ( 1058C7A1FEA54F0111CA2CBB, F5A3C5D002F3DA9B01A845D2, ); isa = PBXGroup; name = "Linked Frameworks"; refType = 4; sourceTree = ""; }; 1058C7A1FEA54F0111CA2CBB = { expectedFileType = wrapper.framework; fallbackIsa = PBXFileReference; isa = PBXFrameworkReference; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; refType = 0; sourceTree = ""; }; 1058C7A2FEA54F0111CA2CBB = { children = ( 29B97325FDCFA39411CA2CEA, 29B97324FDCFA39411CA2CEA, ); isa = PBXGroup; name = "Other Frameworks"; refType = 4; sourceTree = ""; }; //100 //101 //102 //103 //104 //170 //171 //172 //173 //174 17587328FF379C6511CA2CBB = { expectedFileType = wrapper.application; fallbackIsa = PBXFileReference; isa = PBXApplicationReference; path = BitTorrent.app; refType = 3; sourceTree = BUILT_PRODUCTS_DIR; }; //170 //171 //172 //173 //174 //190 //191 //192 //193 //194 19C28FACFE9D520D11CA2CBB = { children = ( 17587328FF379C6511CA2CBB, ); isa = PBXGroup; name = Products; refType = 4; sourceTree = ""; }; //190 //191 //192 //193 //194 //290 //291 //292 //293 //294 29B97313FDCFA39411CA2CEA = { buildSettings = { }; buildStyles = ( 4A9504CCFFE6A4B311CA0CBA, 4A9504CDFFE6A4B311CA0CBA, ); hasScannedForEncodings = 1; isa = PBXProject; knownRegions = ( English, Japanese, French, German, Dutch, ); mainGroup = 29B97314FDCFA39411CA2CEA; projectDirPath = ""; targets = ( 29B97326FDCFA39411CA2CEA, ); }; 29B97314FDCFA39411CA2CEA = { children = ( F5A3C5DA02F6923301A845D2, F57B922402F27DF501A845D2, 080E96DDFE201D6D7F000001, 29B97315FDCFA39411CA2CEA, 29B97317FDCFA39411CA2CEA, 29B97323FDCFA39411CA2CEA, 19C28FACFE9D520D11CA2CBB, F55ADF7802F292E001A845D2, 73A43D3803C6C24900000124, F56F7C3602F3544601A845D2, ); isa = PBXGroup; name = BitTorrent; path = ""; refType = 4; sourceTree = ""; }; 29B97315FDCFA39411CA2CEA = { children = ( 29B97316FDCFA39411CA2CEA, F506F6C40287AE7901A845D2, 73D8011003BE3B7900053DF6, F5D01D41027E7D9D01A845D2, F5F1BAA602F0842B01A845D2, ); isa = PBXGroup; name = "Other Sources"; path = ""; refType = 4; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA = { expectedFileType = sourcecode.c.objc; fileEncoding = 5; isa = PBXFileReference; path = main.m; refType = 4; sourceTree = ""; }; 29B97317FDCFA39411CA2CEA = { children = ( 29B97318FDCFA39411CA2CEA, F5D01D34027E0B0201A845D2, F542E1960308BE0301AA9403, 089C165CFE840E0CC02AAC07, 73EB921B052A845700D326AE, 73FFC7F50470806800010167, ); isa = PBXGroup; name = Resources; path = ""; refType = 4; sourceTree = ""; }; 29B97318FDCFA39411CA2CEA = { children = ( 29B97319FDCFA39411CA2CEA, 73EB921E052A916200D326AE, 73EB921F052A929700D326AE, ); isa = PBXVariantGroup; name = MainMenu.nib; path = ""; refType = 4; sourceTree = ""; }; 29B97319FDCFA39411CA2CEA = { expectedFileType = wrapper.nib; isa = PBXFileReference; name = English; path = English.lproj/MainMenu.nib; refType = 4; sourceTree = ""; }; 29B97323FDCFA39411CA2CEA = { children = ( 1058C7A0FEA54F0111CA2CBB, 73DE3209052F4A3900949C8F, 1058C7A2FEA54F0111CA2CBB, ); isa = PBXGroup; name = Frameworks; path = ""; refType = 4; sourceTree = ""; }; 29B97324FDCFA39411CA2CEA = { expectedFileType = wrapper.framework; fallbackIsa = PBXFileReference; isa = PBXFrameworkReference; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; refType = 0; sourceTree = ""; }; 29B97325FDCFA39411CA2CEA = { expectedFileType = wrapper.framework; fallbackIsa = PBXFileReference; isa = PBXFrameworkReference; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; refType = 0; sourceTree = ""; }; 29B97326FDCFA39411CA2CEA = { buildPhases = ( 29B97327FDCFA39411CA2CEA, 29B97328FDCFA39411CA2CEA, 29B9732BFDCFA39411CA2CEA, 29B9732DFDCFA39411CA2CEA, F56F7C3B02F361FF01A845D2, ); buildSettings = { FRAMEWORK_SEARCH_PATHS = ""; HEADER_SEARCH_PATHS = /usr/local/include/; LIBRARY_SEARCH_PATHS = /usr/local/lib/python2.3/config; OPTIMIZATION_CFLAGS = "-O0"; OTHER_CFLAGS = "-Wall -g"; PRODUCT_NAME = BitTorrent; SECTORDER_FLAGS = ""; WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; WRAPPER_EXTENSION = app; }; dependencies = ( ); isa = PBXApplicationTarget; name = BitTorrent; productName = BitTorrent; productReference = 17587328FF379C6511CA2CBB; productSettingsXML = " CFBundleDevelopmentRegion English CFBundleDocumentTypes CFBundleTypeExtensions torrent CFBundleTypeIconFile torrent.icns CFBundleTypeMIMETypes application/x-bittorrent CFBundleTypeName BitTorrent Metainfo File CFBundleTypeOSTypes BTMF CFBundleTypeRole Viewer CFBundleExecutable BitTorrent CFBundleGetInfoString BitTorrent 3.2.2 CFBundleIconFile waterfall.icns CFBundleIdentifier BitTorrent CFBundleInfoDictionaryVersion 6.0 CFBundleName BitTorrent CFBundlePackageType APPL CFBundleShortVersionString 3.2.2 CFBundleSignature BTBC CFBundleVersion 3.3a NSMainNibFile MainMenu NSPrincipalClass NSApplication "; }; 29B97327FDCFA39411CA2CEA = { buildActionMask = 2147483647; files = ( F5D01D3F027E224601A845D2, F506F6C50287AE7901A845D2, F5F1BAA702F0842B01A845D2, F5A3C5CC02F3CA7101A845D2, F5A3C5CD02F3CA7101A845D2, F5ACEF9A0308CD8E01538297, F5ACEF9E030B4FE301538297, 73D8011103BE3B7900053DF6, 73FFC8070471552300010167, ); isa = PBXHeadersBuildPhase; runOnlyForDeploymentPostprocessing = 0; }; 29B97328FDCFA39411CA2CEA = { buildActionMask = 2147483647; files = ( 080E96DCFE201CFB7F000001, F5D01D36027E0B0201A845D2, F56F7C3E02F3731501A845D2, F542E1980308BE0401AA9403, 73A43D3903C6C24A00000124, 73FFC7F70470806900010167, 73EB921D052A845700D326AE, ); isa = PBXResourcesBuildPhase; runOnlyForDeploymentPostprocessing = 0; }; 29B9732BFDCFA39411CA2CEA = { buildActionMask = 2147483647; files = ( 29B9732CFDCFA39411CA2CEA, F5D01D40027E224601A845D2, F5A3C5CE02F3CA7101A845D2, F5A3C5D302F6385901A845D2, F5ACEF9B0308CD8E01538297, 73EF07FB03BA9D8100000104, 73FFC8080471552300010167, ); isa = PBXSourcesBuildPhase; runOnlyForDeploymentPostprocessing = 0; }; 29B9732CFDCFA39411CA2CEA = { fileRef = 29B97316FDCFA39411CA2CEA; isa = PBXBuildFile; settings = { ATTRIBUTES = ( ); }; }; 29B9732DFDCFA39411CA2CEA = { buildActionMask = 2147483647; files = ( F56F7C3C02F372C401A845D2, F5A3C5D102F3DA9B01A845D2, 73DE320A052F4A3900949C8F, ); isa = PBXFrameworksBuildPhase; runOnlyForDeploymentPostprocessing = 0; }; //290 //291 //292 //293 //294 //4A0 //4A1 //4A2 //4A3 //4A4 4A9504CCFFE6A4B311CA0CBA = { buildRules = ( ); buildSettings = { COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; OPTIMIZATION_CFLAGS = "-O0"; ZERO_LINK = YES; }; isa = PBXBuildStyle; name = Development; }; 4A9504CDFFE6A4B311CA0CBA = { buildRules = ( ); buildSettings = { COPY_PHASE_STRIP = YES; GCC_ENABLE_FIX_AND_CONTINUE = NO; OPTIMIZATION_CFLAGS = "-O2"; ZERO_LINK = NO; }; isa = PBXBuildStyle; name = Deployment; }; //4A0 //4A1 //4A2 //4A3 //4A4 //730 //731 //732 //733 //734 73A43D3803C6C24900000124 = { expectedFileType = image.icns; isa = PBXFileReference; path = torrent.icns; refType = 4; sourceTree = ""; }; 73A43D3903C6C24A00000124 = { fileRef = 73A43D3803C6C24900000124; isa = PBXBuildFile; settings = { }; }; 73D8011003BE3B7900053DF6 = { expectedFileType = sourcecode.c.h; fileEncoding = 4; isa = PBXFileReference; path = callbacks.h; refType = 4; sourceTree = ""; }; 73D8011103BE3B7900053DF6 = { fileRef = 73D8011003BE3B7900053DF6; isa = PBXBuildFile; settings = { }; }; 73DE3209052F4A3900949C8F = { expectedFileType = archive.ar; isa = PBXFileReference; name = libpython2.3.a; path = /usr/local/lib/python2.3/config/libpython2.3.a; refType = 0; sourceTree = ""; }; 73DE320A052F4A3900949C8F = { fileRef = 73DE3209052F4A3900949C8F; isa = PBXBuildFile; settings = { }; }; 73EB921B052A845700D326AE = { children = ( 73EB921C052A845700D326AE, 73EB9225052A92F500D326AE, ); isa = PBXVariantGroup; name = Localizable.strings; path = ""; refType = 4; sourceTree = ""; }; 73EB921C052A845700D326AE = { expectedFileType = text.plist.strings; fileEncoding = 30; isa = PBXFileReference; name = English; path = English.lproj/Localizable.strings; refType = 4; sourceTree = ""; }; 73EB921D052A845700D326AE = { fileRef = 73EB921B052A845700D326AE; isa = PBXBuildFile; settings = { }; }; 73EB921E052A916200D326AE = { expectedFileType = wrapper.nib; isa = PBXFileReference; name = Dutch; path = Dutch.lproj/MainMenu.nib; refType = 4; sourceTree = ""; }; 73EB921F052A929700D326AE = { expectedFileType = wrapper.nib; isa = PBXFileReference; name = French; path = French.lproj/MainMenu.nib; refType = 4; sourceTree = ""; }; 73EB9220052A92CE00D326AE = { expectedFileType = wrapper.nib; isa = PBXFileReference; name = Dutch; path = Dutch.lproj/DLWindow.nib; refType = 4; sourceTree = ""; }; 73EB9221052A92D300D326AE = { expectedFileType = wrapper.nib; isa = PBXFileReference; name = French; path = French.lproj/DLWindow.nib; refType = 4; sourceTree = ""; }; 73EB9222052A92DA00D326AE = { expectedFileType = wrapper.nib; isa = PBXFileReference; name = Dutch; path = Dutch.lproj/Metainfo.nib; refType = 4; sourceTree = ""; }; 73EB9223052A92E200D326AE = { expectedFileType = wrapper.nib; isa = PBXFileReference; name = French; path = French.lproj/Metainfo.nib; refType = 4; sourceTree = ""; }; 73EB9224052A92EB00D326AE = { expectedFileType = file; isa = PBXFileReference; name = Dutch; path = Dutch.lproj/InfoPlist.strings; refType = 4; sourceTree = ""; }; 73EB9225052A92F500D326AE = { expectedFileType = text.plist.strings; isa = PBXFileReference; name = Dutch; path = Dutch.lproj/Localizable.strings; refType = 4; sourceTree = ""; }; 73EF07FB03BA9D8100000104 = { fileRef = F5D01D41027E7D9D01A845D2; isa = PBXBuildFile; settings = { }; }; 73FFC7F50470806800010167 = { children = ( 73FFC7F60470806800010167, ); isa = PBXVariantGroup; name = Preferences.nib; path = ""; refType = 4; sourceTree = ""; }; 73FFC7F60470806800010167 = { expectedFileType = wrapper.nib; isa = PBXFileReference; name = English; path = English.lproj/Preferences.nib; refType = 4; sourceTree = ""; }; 73FFC7F70470806900010167 = { fileRef = 73FFC7F50470806800010167; isa = PBXBuildFile; settings = { }; }; 73FFC8050471552300010167 = { expectedFileType = sourcecode.c.h; fileEncoding = 4; isa = PBXFileReference; path = Preferences.h; refType = 4; sourceTree = ""; }; 73FFC8060471552300010167 = { expectedFileType = sourcecode.c.objc; fileEncoding = 4; isa = PBXFileReference; path = Preferences.m; refType = 4; sourceTree = ""; }; 73FFC8070471552300010167 = { fileRef = 73FFC8050471552300010167; isa = PBXBuildFile; settings = { }; }; 73FFC8080471552300010167 = { fileRef = 73FFC8060471552300010167; isa = PBXBuildFile; settings = { }; }; //730 //731 //732 //733 //734 //F50 //F51 //F52 //F53 //F54 F506F6C40287AE7901A845D2 = { expectedFileType = sourcecode.c.h; fileEncoding = 5; isa = PBXFileReference; path = BTCallbacks.h; refType = 4; sourceTree = ""; }; F506F6C50287AE7901A845D2 = { fileRef = F506F6C40287AE7901A845D2; isa = PBXBuildFile; settings = { }; }; F542E1960308BE0301AA9403 = { children = ( F542E1970308BE0301AA9403, 73EB9222052A92DA00D326AE, 73EB9223052A92E200D326AE, ); isa = PBXVariantGroup; name = Metainfo.nib; path = ""; refType = 4; sourceTree = ""; }; F542E1970308BE0301AA9403 = { expectedFileType = wrapper.nib; isa = PBXFileReference; name = English; path = English.lproj/Metainfo.nib; refType = 4; sourceTree = ""; }; F542E1980308BE0401AA9403 = { fileRef = F542E1960308BE0301AA9403; isa = PBXBuildFile; settings = { }; }; F55ADF7802F292E001A845D2 = { expectedFileType = image.icns; isa = PBXFileReference; path = waterfall.icns; refType = 4; sourceTree = ""; }; F56F7C3602F3544601A845D2 = { expectedFileType = text.script.python; fileEncoding = 5; isa = PBXFileReference; path = freeze.py; refType = 4; sourceTree = ""; }; F56F7C3B02F361FF01A845D2 = { buildActionMask = 2147483647; files = ( ); isa = PBXShellScriptBuildPhase; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = $SRCROOT/freeze.py; }; F56F7C3C02F372C401A845D2 = { fileRef = 1058C7A1FEA54F0111CA2CBB; isa = PBXBuildFile; settings = { }; }; F56F7C3E02F3731501A845D2 = { fileRef = F55ADF7802F292E001A845D2; isa = PBXBuildFile; settings = { }; }; F57B922402F27DF501A845D2 = { expectedFileType = text; fileEncoding = 5; isa = PBXFileReference; path = BUILD_INSTRUCTIONS; refType = 2; sourceTree = SOURCE_ROOT; }; F5A3C5CA02F3CA7101A845D2 = { expectedFileType = sourcecode.c.h; fileEncoding = 5; isa = PBXFileReference; path = ICHelper.h; refType = 4; sourceTree = ""; }; F5A3C5CB02F3CA7101A845D2 = { expectedFileType = sourcecode.c.objc; fileEncoding = 5; isa = PBXFileReference; path = ICHelper.m; refType = 4; sourceTree = ""; }; F5A3C5CC02F3CA7101A845D2 = { fileRef = F5A3C5CA02F3CA7101A845D2; isa = PBXBuildFile; settings = { }; }; F5A3C5CD02F3CA7101A845D2 = { fileRef = F5D01D37027E0FE201A845D2; isa = PBXBuildFile; settings = { }; }; F5A3C5CE02F3CA7101A845D2 = { fileRef = F5A3C5CB02F3CA7101A845D2; isa = PBXBuildFile; settings = { }; }; F5A3C5D002F3DA9B01A845D2 = { expectedFileType = wrapper.framework; fallbackIsa = PBXFileReference; isa = PBXFrameworkReference; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; refType = 0; sourceTree = ""; }; F5A3C5D102F3DA9B01A845D2 = { fileRef = F5A3C5D002F3DA9B01A845D2; isa = PBXBuildFile; settings = { }; }; F5A3C5D302F6385901A845D2 = { fileRef = F5D01D38027E0FE201A845D2; isa = PBXBuildFile; settings = { }; }; F5A3C5DA02F6923301A845D2 = { expectedFileType = text; fileEncoding = 5; isa = PBXFileReference; path = README.txt; refType = 2; sourceTree = SOURCE_ROOT; }; F5ACEF980308CD8E01538297 = { expectedFileType = sourcecode.c.h; fileEncoding = 5; isa = PBXFileReference; path = Generate.h; refType = 4; sourceTree = ""; }; F5ACEF990308CD8E01538297 = { expectedFileType = sourcecode.c.objc; fileEncoding = 5; isa = PBXFileReference; path = Generate.m; refType = 4; sourceTree = ""; }; F5ACEF9A0308CD8E01538297 = { fileRef = F5ACEF980308CD8E01538297; isa = PBXBuildFile; settings = { }; }; F5ACEF9B0308CD8E01538297 = { fileRef = F5ACEF990308CD8E01538297; isa = PBXBuildFile; settings = { }; }; F5ACEF9D030B4FE301538297 = { expectedFileType = sourcecode.c.h; fileEncoding = 5; isa = PBXFileReference; path = Tstate.h; refType = 4; sourceTree = ""; }; F5ACEF9E030B4FE301538297 = { fileRef = F5ACEF9D030B4FE301538297; isa = PBXBuildFile; settings = { }; }; F5D01D34027E0B0201A845D2 = { children = ( F5D01D35027E0B0201A845D2, 73EB9220052A92CE00D326AE, 73EB9221052A92D300D326AE, ); isa = PBXVariantGroup; name = DLWindow.nib; path = ""; refType = 4; sourceTree = ""; }; F5D01D35027E0B0201A845D2 = { expectedFileType = wrapper.nib; isa = PBXFileReference; name = English; path = English.lproj/DLWindow.nib; refType = 4; sourceTree = ""; }; F5D01D36027E0B0201A845D2 = { fileRef = F5D01D34027E0B0201A845D2; isa = PBXBuildFile; settings = { }; }; F5D01D37027E0FE201A845D2 = { expectedFileType = sourcecode.c.h; fileEncoding = 5; isa = PBXFileReference; path = DLWindowController.h; refType = 4; sourceTree = ""; }; F5D01D38027E0FE201A845D2 = { expectedFileType = sourcecode.c.objc; fileEncoding = 5; isa = PBXFileReference; path = DLWindowController.m; refType = 4; sourceTree = ""; }; F5D01D3D027E224601A845D2 = { expectedFileType = sourcecode.c.h; fileEncoding = 5; isa = PBXFileReference; path = BTAppController.h; refType = 4; sourceTree = ""; }; F5D01D3E027E224601A845D2 = { expectedFileType = sourcecode.c.objc; fileEncoding = 5; isa = PBXFileReference; path = BTAppController.m; refType = 4; sourceTree = ""; }; F5D01D3F027E224601A845D2 = { fileRef = F5D01D3D027E224601A845D2; isa = PBXBuildFile; settings = { }; }; F5D01D40027E224601A845D2 = { fileRef = F5D01D3E027E224601A845D2; isa = PBXBuildFile; settings = { }; }; F5D01D41027E7D9D01A845D2 = { expectedFileType = sourcecode.c.objc; fileEncoding = 5; isa = PBXFileReference; path = callbacks.m; refType = 4; sourceTree = ""; }; F5F1BAA602F0842B01A845D2 = { expectedFileType = sourcecode.c.h; fileEncoding = 5; isa = PBXFileReference; path = pystructs.h; refType = 4; sourceTree = ""; }; F5F1BAA702F0842B01A845D2 = { fileRef = F5F1BAA602F0842B01A845D2; isa = PBXBuildFile; settings = { }; }; }; rootObject = 29B97313FDCFA39411CA2CEA; } BitTorrent-3.4.2/osx/English.lproj/0042755000202400020240000000000010034142504015554 5ustar brambramBitTorrent-3.4.2/osx/English.lproj/About.nib/0042755000202400020240000000000010034142504017375 5ustar brambramBitTorrent-3.4.2/osx/English.lproj/InfoPlist.strings0100644000202400020240000000126607754611173021120 0ustar brambram/* Localized versions of Info.plist keys */ CFBundleName = "BitTorrent"; CFBundleShortVersionString = "BitTorrent version 3.3a"; CFBundleGetInfoString = "BitTorrent version 3.3a by Bram Cohen, Mac OS-X port by Andrew Loewenstern."; NSHumanReadableCopyright = "Copyright 2001-2003 Bram Cohen, Andrew Loewenstern, distributed under MIT license."; BitTorrent-3.4.2/osx/English.lproj/Localizable.strings0100644000202400020240000001011407737206724021425 0ustar brambram/* transfer rate */ "%2.1f KiB/s" = "%2.1f KiB/s"; /* transfer total */ "%2.1f MiB" = "%2.1f MiB"; /* percent dl completed */ "%2.1f%%" = "%2.1f%%"; /* size and filename for dl window tite */ "(%1.1f MiB) %@ " = "(%1.1f MiB) %@ "; /* one hundred percent */ "100%" = "100%"; /* shut down internet explorer request */ "BitTorrent cannot register as a helper application while Internet Explorer is running. This only needs to be done once. Please quit Internet Explorer before clicking OK." = "BitTorrent cannot register as a helper application while Internet Explorer is running. This only needs to be done once. Please quit Internet Explorer before clicking OK."; /* Cancel */ "Cancel" = "Cancel"; /* num connected peers info string */ "Connected to %d peers." = "Connected to %d peers."; /* download failed */ "Download Failed!" = "Download Failed!"; /* download completed successfully */ "Download Succeeded." = "Download Succeeded."; /* Generate */ "Generate" = "Generate"; /* invalid file chose fo generate */ "Invalid File" = "Invalid File"; /* No comment provided by engineer. */ "Invalid Tracker URL" = "Invalid Tracker URL"; /* max_upload_rate toolbar label */ "Max UL KiB/s" = "Max UL KiB/s"; /* max_upload_rate toolbar palette label */ "Maximum Upload Rate" = "Maximum Upload Rate"; /* max_uploads toolbar label */ "Num Uploads" = "Num Uploads"; /* max_uploads toolbar palette label */ "Number of Uploads" = "Number of Uploads"; /* explanation of helper registration */ "Quit Internet Explorer" = "Quit Internet Explorer"; /* save directory prompt */ "Save" = "Save"; /* save instructions */ "Save, choose an existing file to resume." = "Save, choose an existing file to resume."; /* empty file for generate */ "You must drag a file or folder into the generate window first." = "You must drag a file or folder into the generate window first."; /* No comment provided by engineer. */ "You must enter the tracker URL. Contact the tracker administrator for the URL." = "You must enter the tracker URL. Contact the tracker administrator for the URL."; BitTorrent-3.4.2/osx/English.lproj/DLWindow.nib/0042755000202400020240000000000010034142504020012 5ustar brambramBitTorrent-3.4.2/osx/English.lproj/DLWindow.nib/classes.nib0100644000202400020240000000144307665605602022161 0ustar brambram{ IBClasses = ( { ACTIONS = {cancelDl = id; takeMaxUploadRateFrom = id; takeMaxUploadsFrom = id; }; CLASS = DLWindowController; LANGUAGE = ObjC; OUTLETS = { dlRate = id; dlTotal = id; file = id; lastError = id; "max_upload_rate" = id; "max_uploads" = id; peerStat = id; percentCompleted = id; progressBar = id; timeRemaining = id; ulRate = id; ulTotal = id; }; SUPERCLASS = NSWindowController; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; }BitTorrent-3.4.2/osx/English.lproj/DLWindow.nib/info.nib0100644000202400020240000000073307737206724021462 0ustar brambram IBDocumentLocation 17 451 356 240 0 0 1280 832 IBFramework Version 291.0 IBOpenObjects 5 51 IBSystem Version 6L60 BitTorrent-3.4.2/osx/English.lproj/DLWindow.nib/keyedobjects.nib0100644000202400020240000001640107737206724023201 0ustar brambrambplist00lY$archiverX$versionX$objectsT$top_NSKeyedArchiver (,-348<QXglnqrwx|   !#%+017:<?CJKORTUWYZ []_fgknpqspu vxz{}~}@EFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghiU$null  !"#$%&']NSFontManagerV$classYNSNextOid_NSVisibleWindowsVNSRoot\NSOidsValues_NSClassesValues]NSConnections]NSNamesValues]NSObjectsKeysZNSOidsKeys]NSClassesKeys[NSFramework[NSNamesKeys_NSObjectsValuesb )*+[NSClassName_DLWindowController./01Z$classnameX$classes^NSCustomObject02XNSObject_IBCocoaFramework 567ZNS.objects./9:\NSMutableSet9;2UNSSet 5=>/?@ABCDEFGHIJKLMNOP iky RSTUVWWNSLabel]NSDestinationXNSSource YZ[\]^_`abcdefa_NSNextResponderXNSvFlagsZNSMaxValue\NSDrawMatrixYNSpiFlagsWNSFrame[NSSuperview "#?  Yh^ijkZNSSubviewscb m ./opZNSPSMatrixo2_{{20, 16}, {346, 20}}./st_NSProgressIndicatorsuv2VNSView[NSResponder[progressBar./yz_NSNibOutletConnectory{2^NSNibConnector RSTU}~h a\NSWindowViewYNSWTFlags]NSWindowTitleYNSMinSizeYNSMaxSize\NSWindowRect_NSWindowStyleMask_NSWindowBacking\NSScreenRect]NSWindowClass[NSViewClassg0xefd_{{431, 478}, {386, 153}}_Download ProgressXNSWindow YNS.stringTView./_NSMutableString2XNSString 5=W=DSY Z^_aa[NSProtoCell[NSCellClassYNSNumRowsZNSCellSize_NSBackgroundColor_NSIntercellSpacingYNSNumCols]NSSelectedRow]NSMatrixFlagsYNSEnabled]NSSelectedColWNSCellsVNSFont_NSCellBackgroundColor32<061 ;_{{20, 78}, {346, 68}} 5=Ǥˀ&), YNSSupport]NSControlView\NSCellFlags2[NSTitleCell\NSTitleWidth[NSCellFlagsZNSContents% @""B@P VNSSizeVNSNameXNSfFlags!"AP \LucidaGrande./2 $#UFile:./VNSCell2./ZNSFormCell2\NSActionCell UNSTag'@ (\% Completed: ـ* +_Time Remaining: -!A .[Last Error:./^NSMutableArray 2WNSArrayY{346, 17}V{0, 0}  ـ4q@ 5VField: WNSColor]NSCatalogName[NSColorName\NSColorSpace:978VSystem\controlColor  WNSWhiteI0.666667./"2 $B1./&'VNSForm&()*uv2XNSMatrixY%NSMatrixYNSControl YZ^_,a-./aC?#>_{{22, 2}, {340, 13}} 23456[NSTextColorB@@A 89"A  ;D0.5./=>_NSTextFieldCell=2./@A[NSTextField@B*uv2\%NSTextFieldY Z^_DaEFGHaIĀPOMNEF_{{27, 44}, {199, 34}} 5=LMNGJ PQـH"B SI^Download Rate: VQـK XL\Upload Rate:Y{199, 17} \QـQ ^RY Z^_`aabcdaeĀ_^\]TU_{{234, 44}, {152, 34}} 5=hijVY lmـW"B oXVtotal: rmـZ t[Y{152, 17} wmـ` ya_{{1, 9}, {386, 153}}./u|uv2_{{0, 0}, {1280, 1002}}Z{286, 175}_{3.40282e+38, 175}./_NSWindowTemplate2Xdelegate RSTU~jVwindow RSTUxl Y^_,mon Yh^i_{{20, 73}, {54, 19}} 23_NSDrawsBackgroundsq@uqApQ0 "A0r t_textBackgroundColor wvYtextColor B0_max_upload_rate RSTU~z Y^_,|{_{{20, 45}, {40, 19}} 23q@}Q7[max_uploads RST_takeMaxUploadRateFrom:./_NSNibControlConnector{2 RST_takeMaxUploadsFrom: SVNSFileXNSMarker_NSToolTipHelpKey_'Maximum Upload Rate in Kilobytes/Second./_NSIBHelpConnector2 S_Number of Simultaneous Uploads RSTUXpeerStat RSTUTfile RSTU_percentCompleted RSTU]timeRemaining RSTUYlastError RSTUiWdlTotal RSTUMVdlRate RSTUNVulRate RSTUjWulTotal 5뀨iMj~WNa 5=_{{1, 1}, {213, 112}} x_{{536, 168}, {213, 112}}UPanelWNSPanel Z{213, 129}_{3.40282e+38, 3.40282e+38}./  2 5aaaaa~ 5~W 5     VWindow_NSTextField111111WNSForm1YNSForm111Q1\NSTextField1XNSForm11\File's Owner 57 57 5&D~EjKHCL?MIONaGWAFPM@JBiN 5& !"#$%&'()*+,-./0123456789:;<=>?@ABCD€ÀĀŀƀǀȀɀʀˀ̀̀΀πЀрҀӀԀՀր׀؀ـڀۀ܀݀ހ?E4;@\MIF>N$^K`_C0Aa3Y<+HLGVJ=[]Z./jk^NSIBObjectDataj2mn]IB.objectdata#,5:LQ[ipz "$&(*,.02468:<>GSUWlu%')+-/13579;=?ACEGIZbpy{}#%').09DIaj$1;IS]j~"+=DMVacegi&4>LT[suwy{}    + 7 B D I K P Y [ \ m t {   7 = ? D Y [ h  ! ( 0 : A ^ ` e z |    " + 2 A J T ^ { }    ) 5 B O "GI^`mw!*/13TV[pry7BW`sx 468=?HJL]bdf{} !FOQS_prtBK_duwy&(2CEM^`gxz(13579;=?Z`hq|!#%')0DLVXen{=?ACEGIKMOQSUWY[]_acegikmoqsuwy{}oBitTorrent-3.4.2/osx/English.lproj/MainMenu.nib/0042755000202400020240000000000010034142504020034 5ustar brambramBitTorrent-3.4.2/osx/English.lproj/MainMenu.nib/classes.nib0100644000202400020240000000137207660353522022200 0ustar brambram{ IBClasses = ( { ACTIONS = { cancelUrl = id; openAbout = id; openGenerator = id; openPrefs = id; openTrackerResponse = id; openURL = id; takeUrl = id; }; CLASS = BTAppController; LANGUAGE = ObjC; OUTLETS = { aboutWindow = NSWindow; generator = id; url = NSTextField; urlWindow = NSWindow; versField = NSTextField; }; SUPERCLASS = NSObject; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; }BitTorrent-3.4.2/osx/English.lproj/MainMenu.nib/info.nib0100644000202400020240000000114507742026621021472 0ustar brambram IBDocumentLocation 292 150 356 240 0 0 1280 832 IBEditorPositions 29 70 557 283 44 0 0 1280 832 IBFramework Version 291.0 IBOpenObjects 228 203 29 IBSystem Version 6L60 BitTorrent-3.4.2/osx/English.lproj/MainMenu.nib/keyedobjects.nib0100644000202400020240000002746207742026621023224 0ustar brambrambplist00Y$archiverX$versionX$objectsT$top_NSKeyedArchiver (,07:<@D_eu|}~   ,12AHILVWX\^abdhopsxy    #$'(+,/0H3679:=>ABCDEFHILNOPSUVWkmrvwyz}v~vvvvvvv   ./0j23.45689i:;<=>?@ABDEFGHIJLU$null  !"#$%&']NSFontManagerV$classYNSNextOid_NSVisibleWindowsVNSRoot\NSOidsValues_NSClassesValues]NSConnections]NSNamesValues]NSObjectsKeysZNSOidsKeys]NSClassesKeys[NSFramework[NSNamesKeys_NSObjectsValuesQO 0ρPN/. )*+[NSClassName -./YNS.string]NSApplication1234Z$classnameX$classes_NSMutableString356XNSStringXNSObject1289^NSCustomObject86 -.;_IBCocoaFramework =>?ZNS.objects12AB\NSMutableSetAC6UNSSet =EFGHIJKLMNOPQRSTUVWXYZ[\]^ #',06;@EIOTYlwŀǀ `abcdWNSLabelXNSSource fghijklmnopqrst]NSMnemonicLoc\NSMixedImage_NSKeyEquivModMaskWNSTitleVNSMenuZNSKeyEquivYNSOnImage  vwixyz{[NSMenuItemsVNSName߀XMinimizeQm )^NSResourceNameWNSImage_NSMenuCheckmark12_NSCustomResource6_%NSCustomResource )_NSMenuMixedState12ZNSMenuItem6 -._performMiniaturize:12_NSNibControlConnector6^NSNibConnector `ab fghijklmnoprt_Bring All to FrontP -._arrangeInFront: `ab]NSDestination" fghijklmnopt ! vwixր_Quit BitTorrentQq -.Zterminate: `ab&$ fghijklmnopt%[Hide Others_hideOtherApplications: `ab+( fghijklmnopt)*_Hide BitTorrentQhUhide: `ab/- fghijklmnopt.XShow All_unhideAllApplications: `abȀ51 fghijklmnopt324 vixπSCutQx -.Tcut: `abր:7 fghijklmnopt89UPasteQv -.Vpaste: `ab?< fghijklmnopt=>ZSelect AllQa -.ZselectAll: `abDA fghijklmnoptBCTCopyQc -.Ucopy: `abHF fghijklmnoptGUClear -.Vclear: `abNJ fghijklmnoptLKM vix^NSNoAutoenable UCloseQw]performClose: `a   SRP )*Q_BTAppControllerXdelegate12_NSNibOutletConnector6 `ab XU fghijklmnoptVW[Open URL...QOXopenURL: `a  kZ !"#$%&'()*+(_NSNextResponderVNSCellXNSvFlagsWNSFrameYNSEnabled[NSSuperviewj[]*\ !-$./0ZNSSubviews_{{25, 60}, {313, 22}} 3456789:;<=>?@_NSBackgroundColorYNSSupport]NSControlView\NSCellFlags2[NSTextColor_NSDrawsBackground[NSCellFlagsZNSContentsia^@fqA BwCDEFGVNSSizeXNSfFlags`"AP_\LucidaGrande12JKVNSFontJ6 MNOPQRSTUWNSColor]NSCatalogName[NSColorName\NSColorSpaceedbcVSystem_textBackgroundColor YPQZ[WNSWhiteB112M]M6 MNOPQ_S`UhgYtextColor YPQc[B012ef_NSTextFieldCelleg"6\NSActionCell12ij[NSTextFieldiklmn6\%NSTextFieldYNSControlVNSView[NSResponderSurl `abq rvm !"#$%&t(uvw(uo!n_{{260, 12}, {84, 32}} z4{56|}~9:=r_NSKeyEquivalent_NSAlternateContents_NSPeriodicDelay^NSButtonFlags2]NSButtonFlags_NSPeriodicInterval_NSAlternateImagets8@qpROK BwCDErYHelveticaQ 12\NSButtonCellg"6]%NSButtonCell12XNSButtonlmn6XtakeUrl: `ab ~x !"#$%&t(v(zy_{{176, 12}, {84, 32}} z4{56|}~9:=}|{VCancel -. -.ZcancelUrl: `a   (\NSWindowViewYNSWTFlags]NSWindowTitleYNSMinSizeYNSMaxSize\NSWindowRect_NSWindowStyleMask_NSWindowBacking\NSScreenRect]NSWindowClass[NSViewClassx_{{396, 524}, {358, 102}}WNSPanel -.TView =EȤr !"#$%&'((_{{22, 85}, {236, 17}} 345679:;=>Ҁ_Enter the BitTorrent URL... MNOPQSU\controlColor YPQ[I0.666667 MNOPQ_SU_controlTextColor12^NSMutableArray6WNSArray_{{1, 1}, {358, 102}}12mmn6_{{0, 0}, {1280, 832}}Z{358, 124}_{3.40282e+38, 124}12_NSWindowTemplate6YurlWindow `ab 퀛 fghijklmnoptWOpen...Qo_openTrackerResponse: `ab  fgijklmnot_About BitTorrent...ZopenAbout: `a  Ā [€À_{{266, 545}, {438, 133}}_About BitTorrent -. !-$.  =E !"$%&'_{{20, 81}, {204, 51}} 345679:;>ZBitTorrent BwCD"B_LucidaGrande-Bold !"$%&'!"_{{29, 64}, {331, 17}} 345679:;=>%&!_$by Bram Cohen (bram@bitconjurer.org) !"$%&')*_{{29, 42}, {358, 14}} 345679:;->%._;Mac OS X version by Andrew Loewenstern (andrew@gigagig.org) BwCD12"A0 !"$%&'45_{{29, 20}, {244, 14}} 345679:;->%8_"http://bitconjurer.org/BitTorrent/ !"$%&';<_{{306, 93}, {115, 17}} 345679:;=?@@U3.0.0_{{1, 1}, {438, 133}}Z{213, 129}_{3.40282e+38, 3.40282e+38}[aboutWindow `a G YversField `abJ Kʀ fghijklmnopMt_Generate Torrent File...^openGenerator: `abQ R΀ fghijklmnopTt^Preferences...ZopenPrefs: =XY-4Z[\]^(R_`arbcdefKghidj rЀр׀ۀ؁! fghiljklmnopt]NSIsSeparator fghniojklmnoppqjtYNSSubmenuXNSActionӀ vwixstu+,*^submenuAction: =ExR`gf\ fghiljklmnopt fghniojklmnope{|tـXServices vwix݀ހ -.} =E?__NSServicesMenu12jj6 fghiljklmnopt fghiljklmnopt\_NSAppleMenu fghnioklmnoptXSpelling vix =E fghijklmnopt[Spelling...Q: fghijklmnopt^Check SpellingQ; fghijklmnopt_Check Spelling As You Type fghniojklmnopjtTFile =EbZK fghiljklmnopt fghniojklmnoprjtVWindow -. =Eda fghiljklmnoprt^_NSWindowsMenu -.TEdit =E fghnioklmnoptTFind vix -. =E¥ǁ   fghijklmnoptWFind...Qf fghijklmnopt  YFind NextQg fghijklmnopt ]Find PreviousQd fghijklmnopt_Use Selection for FindQe fghijklmnopt_Scroll to SelectionQj fghnioklmnoptTHelp vix =E fghijklmnopt_BitTorrent HelpQ? fghniojklmnopjt fghnioklmnopt$"#[Open Recent vwix&)% -. =E' fghijklmnopt(ZClear Menu__NSRecentDocumentsMenuXMainMenu =E[^h_[_NSMainMenu12 6 =X 4r(jjj(r(h([jgj^r_ =X rZRdaj^h ]c =X  !"#$%&'()*+,-123456789:;<=>?@ABCDEFGHIJKLM\NSTextField2\NSTextField1 -.1Q7 -.]NSTextField21ZaboutPanelS121 -.7Q1T1111XurlPanel -.\NSMenuItem10 -.7[NSMenuItem3YNSButton1]NSTextField12[NSMenuItem9_NSTextField1111 -.C\File's Owner[NSMenuItem4\NSMenuItem13[NSMenuItem1[NSMenuItem7 =X? =X? =XKMfLNTKSYXV[^rbHgJr d(hMRj`RU]KdZ\W\cO[Ga]^PZeQi_I =XMMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~RSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~S'Hg8I%\QO|912^NSIBObjectData6]IB.objectdata#,5:LQ  2@N\gu 18AJSbgp   !#%')+8@IKMOp~,.02:LUho!#%FHJ_`i{     : < H a r t v    A C E G T V X \ ^ g l y { }    " C E G L N W ] j l n     + - / 1 : < N W ` w ~    # + 5 A C E G I K \ g i k m  %'0AHQSXZ\iry~ &(*4ADM_hu~L^t*5CLU`iz|~,.0anx   ,4=BKTVsuwy (*=FU\d{$&(02IZ\^{}"+<>@ITVXZ\^wy{  $EJLs !#<>@Xy{&CO`bl} ),#,.0ADGJYbw @er!#%46WYv  )0Udmr{')JMPZ\}   9 < ? B G T W Z c f i !! !!!!$!'!0!9! IBDocumentLocation 479 266 356 240 0 0 1280 832 IBFramework Version 291.0 IBOpenObjects 13 IBSystem Version 6L60 BitTorrent-3.4.2/osx/English.lproj/Metainfo.nib/keyedobjects.nib0100644000202400020240000001433407665605603023256 0ustar brambrambplist00 Y$archiverX$versionX$objectsT$top_NSKeyedArchiver (,-348<IPhijkosz    !$%&+,=>ABCDHKNOVYcehilsuwxy{|Ic@oXU$null  !"#$%&']NSFontManagerV$classYNSNextOid_NSVisibleWindowsVNSRoot\NSOidsValues_NSClassesValues]NSConnections]NSNamesValues]NSObjectsKeysZNSOidsKeys]NSClassesKeys[NSFramework[NSNamesKeys_NSObjectsValues' )*+[NSClassNameXGenerate./01Z$classnameX$classes^NSCustomObject02XNSObject_IBCocoaFramework 567ZNS.objects./9:\NSMutableSet9;2UNSSet 5=>7?@ABCDEFGH lnpruwy{} JKLMNOWNSLabel]NSDestinationXNSSourcekj Q RSTUVWXYZ[\]^_`abcdefg\NSWindowViewYNSWTFlags]NSWindowTitleYNSMinSizeYNSMaxSize\NSWindowRect_NSWindowStyleMask_NSWindowBacking\NSScreenRect]NSWindowClass[NSViewClassip gh f _{{203, 615}, {409, 180}}_Generate Torrent FileXNSWindow lmnYNS.stringTView./pq_NSMutableStringpr2XNSString tuvwxy_NSNextResponderZNSSubviewsWNSFrame6e 5={|}~#(2>CMQV^ tv\\VNSCellXNSvFlagsYNSEnabled[NSSuperview"  _{{93, 148}, {304, 19}} |_NSBackgroundColorYNSSupport]NSControlView\NSCellFlags2[NSTextColor_NSDrawsBackground[NSCellFlagsZNSContents!@qAP VNSSizeVNSNameXNSfFlags"A0\LucidaGrande./VNSFont2 WNSColor]NSCatalogName[NSColorName\NSColorSpaceVSystem_textBackgroundColor WNSWhiteB1./2 YtextColor B0./_NSTextFieldCell2\NSActionCell./[NSTextField2\%NSTextFieldYNSControlVNSView[NSResponder tv\d\%$_{{9, 58}, {310, 20}} }_NSTextBezelStyle&A "A@' tv\\*$)_{{7, 111}, {255, 17}} ~-,0+_(Drag a file or folder into the window... "AP /.\controlColor I0.666667 1_controlTextColort uv\dd\]NSTransparentYNSOffsets_NSTitlePosition\NSBorderType]NSContentView[NSTitleCellYNSBoxType=394:8 5= tvw5_{{2, 2}, {381, 1}}./ 2./ ^NSMutableArray 2WNSArray_{{12, 134}, {385, 5}}V{0, 0} <; lmSBox./2./UNSBox2 tv\\@ ?_{{7, 150}, {81, 14}} "#BA\Tracker URL: tv'\()*\LE!D_{{296, 8}, {109, 32}} -./0123456789:;<_NSKeyEquivalent_NSAlternateContents_NSPeriodicDelay^NSButtonFlags2]NSButtonFlags_NSPeriodicInterval_NSAlternateImageKJI8@GF[Generate... ?@HYHelvetica lm lm./EF\NSButtonCellEG2]%NSButtonCell./IJXNSButtonI2 tv'\L)M\ON_{{335, 56}, {64, 64}} -./0124PQRSTUP@$K WX"A  tZ[\v]\^_`ab\ZNSMaxValue\NSDrawMatrixYNSpiFlagsU"#?RT dS./fgZNSPSMatrixf2_{{10, 14}, {282, 20}}./jk_NSProgressIndicatorj2t uv\mnopqrd\W["X\Z 5=tp tvwvY_{{2, 2}, {383, 1}}_{{12, 45}, {387, 5}} z] lm tv'\}~\`__{{8, 87}, {310, 18}} -./01234"8dT:H@BDFHJLNPRTVXZ\^`bdfhjlnpy BitTorrent-3.4.2/osx/English.lproj/Preferences.nib/0042755000202400020240000000000010034142504020564 5ustar brambramBitTorrent-3.4.2/osx/English.lproj/Preferences.nib/classes.nib0100644000202400020240000000060507660353523022727 0ustar brambram{ IBClasses = ( {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, { ACTIONS = {cancel = id; loadFactory = id; save = id; }; CLASS = Preferences; LANGUAGE = ObjC; OUTLETS = {ip = id; maxport = id; minport = id; }; SUPERCLASS = NSWindowController; } ); IBVersion = 1; }BitTorrent-3.4.2/osx/English.lproj/Preferences.nib/info.nib0100644000202400020240000000070507660353523022226 0ustar brambram IBDocumentLocation 126 237 356 240 0 0 1280 1002 IBFramework Version 291.0 IBOpenObjects 5 IBSystem Version 6L60 BitTorrent-3.4.2/osx/English.lproj/Preferences.nib/keyedobjects.nib0100644000202400020240000001407107660353523023747 0ustar brambrambplist00Y$archiverX$versionX$objectsT$top_NSKeyedArchiver (,-348<GN^_ghk  ,-01237:=>BCDEHIMNOP_acdfghjloprsuw{|8/)U$null  !"#$%&']NSFontManagerV$classYNSNextOid_NSVisibleWindowsVNSRoot\NSOidsValues_NSClassesValues]NSConnections]NSNamesValues]NSObjectsKeysZNSOidsKeys]NSClassesKeys[NSFramework[NSNamesKeys_NSObjectsValues} )*+[NSClassName[Preferences./01Z$classnameX$classes^NSCustomObject02XNSObject_IBCocoaFramework 567ZNS.objects./9:\NSMutableSet9;2UNSSet 5=>.?@ABCDEF #twy{ HIJKLMWNSLabel]NSDestinationXNSSource OPQRSTUVWXYZ[\]YNSSupport]NSControlView\NSCellFlags2[NSTitleCell\NSTitleWidth[NSCellFlagsZNSContents  @"Cyq@ P `abcdefVNSSizeVNSNameXNSfFlags"AP \LucidaGrande./ijVNSFonti2lmn opqrstuvwxyz{|}~}[NSProtoCell_NSNextResponder[NSCellClassYNSNumRowsZNSCellSize_NSBackgroundColor_NSIntercellSpacingYNSNumCols]NSSelectedCol]NSSelectedRow]NSMatrixFlagsWNSFrameYNSEnabled[NSSuperviewWNSCells_NSCellBackgroundColora*`;^5_\ ]: OQTUW_'Report my IP address to the tracker as:./VNSCell2./ZNSFormCell2\NSActionCellRip./_NSNibOutletConnector2^NSNibConnector HIJK OPQRSTUVWY\"BT6881lmn opqrstuvwxyz{}}21/0,- OQTUWYLow Port:Wminport HIJK" OPQRSTUVWY\UNSTag T6889 OQTUW!ZHigh Port:Wmaxport HIJKs$ }\NSWindowViewYNSWTFlags]NSWindowTitleYNSMinSizeYNSMaxSize\NSWindowRect_NSWindowStyleMask_NSWindowBacking\NSScreenRect]NSWindowClass[NSViewClassrpx&pq%o'(_{{410, 368}, {480, 192}}_BitTorrent PreferencesXNSWindow YNS.string)TView./_NSMutableString2XNSString mwZNSSubviewsV+n 5=X=_{{382, 12}, {84, 32}} OPQ T!"U#$W%&'()*+_NSKeyEquivalent_NSAlternateContents_NSPeriodicDelay^NSButtonFlags2]NSButtonFlags_NSPeriodicInterval_NSAlternateImageDCB8@@?TSave `abcd./AYHelvetica ^ ^./45\NSButtonCell462]%NSButtonCell./89XNSButton82 mwxy};<}HG_{{298, 12}, {84, 32}} OPQ T!"U#?W@&'()*AKJIVCancel ^ ^ mwxy}FG}NM_{{110, 12}, {188, 32}} OPQ T!"U#JWK&'()*LQPO_Load Factory Settings... ^ ^Qm RSTURwVyW}XYZ[ \]^}]NSTransparentYNSOffsets_NSTitlePosition\NSBorderType]NSContentViewYNSBoxType[SXTYW 5=`\ mwybU_{{2, 2}, {452, 1}}./e2_{{12, 118}, {456, 5}}V{0, 0} OQTUW[iZ kSBox./mnUNSBoxm2_{{20, 150}, {376, 22}} 5=qMY{376, 22} OQRSTUVWYt[\]b OQTUWvc mwxyx}yz}mfe_{{28, 129}, {432, 13}} qOPQ}TU~[NSTextColorlh@ig_SIf you are on the same private network as the tracker, put in your real IP address. `abce"A  kj_controlTextColor  B0./_NSTextFieldCell2./[NSTextField2\%NSTextField_{{1, 9}, {480, 192}}_{{0, 0}, {1280, 1002}}Z{213, 129}_{3.40282e+38, 3.40282e+38}./_NSWindowTemplate2Vwindow HIJvu[loadFactory./_NSNibControlConnector2 HIJxVcancel HIJzTsave HIJK|Xdelegate 5~X}M\./2 5}}}}}}X} 5X\ 5€WNSForm1[NSFormCell1XNSForm11]NSTextField11YNSButton1VNSBox1VWindowYNSButton2\File's Owner 57 57 5ϯBC?\ADEFX}@M 5ѯ瀒    ./^NSIBObjectData2]IB.objectdata#,5:LQ $1CQ_mx$-@ITVW`mtz".;GRTVX]_dikl}&2<G[pz#(*T]dir}  Y[]_aceg|~IV`nx % . 7 A C H Q c j s    2 4 I K R g o }    % / 6 B [ ] _ a y   - / 1 3 8 : C E G I N _ a c m v  5 7 9 ; B K T m o q #1;MZhrsuwy{} 9BEOlnM^cez|~ $/LUhmt 1:?Hcl "+4cl BitTorrent-3.4.2/osx/French.lproj/0042755000202400020240000000000010034142504015370 5ustar brambramBitTorrent-3.4.2/osx/French.lproj/DLWindow.nib/0042755000202400020240000000000010034142504017626 5ustar brambramBitTorrent-3.4.2/osx/French.lproj/DLWindow.nib/classes.nib0100644000202400020240000000144307742026621021767 0ustar brambram{ IBClasses = ( { ACTIONS = {cancelDl = id; takeMaxUploadRateFrom = id; takeMaxUploadsFrom = id; }; CLASS = DLWindowController; LANGUAGE = ObjC; OUTLETS = { dlRate = id; dlTotal = id; file = id; lastError = id; "max_upload_rate" = id; "max_uploads" = id; peerStat = id; percentCompleted = id; progressBar = id; timeRemaining = id; ulRate = id; ulTotal = id; }; SUPERCLASS = NSWindowController; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; }BitTorrent-3.4.2/osx/French.lproj/DLWindow.nib/info.nib0100644000202400020240000000073307742026621021266 0ustar brambram IBDocumentLocation 17 451 356 240 0 0 1280 832 IBFramework Version 291.0 IBOpenObjects 51 5 IBSystem Version 6L60 BitTorrent-3.4.2/osx/French.lproj/DLWindow.nib/objects.nib0100644000202400020240000000624607742026621021771 0ustar brambram typedstream@NSIBObjectDataNSObjectNSCustomObject)@@NSString+DLWindowControlleri NSFormCell) NSActionCellNSCellAii@ @@@@@NSFont$[36c]LucidaGrandef ci:NSFormNSMatrix> NSControl)NSView) NSResponder @@@@ffffffffNSMutableArrayNSArray NUDUDicc@#iiii:::ffffi@@@@@U!A @f@iFichier:@ @i % accompli:@ @iTemps restant:!A @iDernière erreur:NSColor@@@System controlColorff?*q@ @iField:NSProgressIndicatorܤb NSPSMatrix[12f]ZZccddc NSTextField#T T NSTextFieldCell>@$LucidaGrande  ńc@@?:,""@ @ˮ%total:@ @ˮ%total:q@ @%Field: ,""!A @Vitesse descendante:q@ @Field:Vitesse montante:ƒߒƒ-((ȝq@@4$LucidaGrande  textBackgroundColor textColorppI66ȝqA@0充߸膒NSWindowTemplate iiffffi@@@@@cՁ0xProgression du téléchargementNSWindowNSMutableStringViewffff@נŦ˖pxPanelNSPanelView@Ձ󖠦˦핖˙ 󄘘Panel˄ NSForm1111NSForm11 File's Owner℘ NSTextField1߄ NSTextField턘WindowńNSTextField111111NSForm1 NSMutableSetNSSetINSNibOutletConnectorτNSNibConnector progressBar필delegate턘window߄max_upload_rate℘ max_uploadsNSNibControlConnectorߕtakeMaxUploadRateFrom:╄takeMaxUploadsFrom:NSIBHelpConnector߄NSToolTipHelpKey'Maximum Upload Rate in Kilobytes/Second℘NSToolTipHelpKeyNumber of Simultaneous UploadsńpeerStatfilepercentCompleted timeRemaining lastError̈́dlTotalׄdlRateulRateЄulTotal&@i =ŠV +$;>řZ[34Ŧ 0?Ūŕ]\)_@!L'^ŵJŬG%N+`K-a#MůHYFE<ŲICAbIBCocoaFrameworkBitTorrent-3.4.2/osx/French.lproj/MainMenu.nib/0042755000202400020240000000000010034142504017650 5ustar brambramBitTorrent-3.4.2/osx/French.lproj/MainMenu.nib/classes.nib0100644000202400020240000000137207742026621022012 0ustar brambram{ IBClasses = ( { ACTIONS = { cancelUrl = id; openAbout = id; openGenerator = id; openPrefs = id; openTrackerResponse = id; openURL = id; takeUrl = id; }; CLASS = BTAppController; LANGUAGE = ObjC; OUTLETS = { aboutWindow = NSWindow; generator = id; url = NSTextField; urlWindow = NSWindow; versField = NSTextField; }; SUPERCLASS = NSObject; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } ); IBVersion = 1; }BitTorrent-3.4.2/osx/French.lproj/MainMenu.nib/info.nib0100644000202400020240000000111407742026621021302 0ustar brambram IBDocumentLocation 132 147 356 240 0 0 1280 832 IBEditorPositions 29 70 557 320 44 0 0 1280 832 IBFramework Version 291.0 IBOpenObjects 29 228 IBSystem Version 6L60 BitTorrent-3.4.2/osx/French.lproj/MainMenu.nib/objects.nib0100644000202400020240000001440207742026621022004 0ustar brambram typedstream@NSIBObjectDataNSObjectNSCustomObject)@@NSMutableStringNSString+ NSApplicationi5 NSMenuItemNSMenu̔i@@@ÉditionNSMutableArrayNSArray i@@IIi@@@@:i@CouperxNSCustomResource)NSImageNSMenuCheckmarkNSMenuMixedStateCopiercCollervEffacerTout sélectionneraMainMenu BitTorrentsubmenuAction: BitTorrent À propos de BitTorrentPréfèrences...ServicesServices_NSServicesMenuMasquer BitTorrenthMasquer les autres Tout afficherQuiter BitTorrentq _NSAppleMenuFichierׄؠ Ouvrir...oؠOuvrir l'URL...OؠؠFermerwؠؠGénérer un fichier Torrent…Fenêtre鄠 MiniaturisermꠂTout rapporter au premier planm_NSWindowsMenu _NSMainMenuNSView) NSResponder @@@@ffffffff NSTextField NSControl)*<99icc@NSTextFieldCell> NSActionCellNSCellAiiqA@@@@@NSFont$[36c]LucidaGrandef ci:c@@NSColor@@@SystemtextBackgroundColorff textColor:U@Entrez l'URL BitTorrent  controlColor?*controlTextColor NSButton! T T  NSButtonCell?8OK ssii@@@@@@ [28c]Helvetica ! Z Z 8Annuler@ffffNSWindowTemplate iiffffi@@@@@cffxOuvrir l'URL...NSPanelViewffff@f||綖»ѻؖ$g33$@ BitTorrent,[44c]$LucidaGrande-Bold$& #$@ff$!@ IBDocumentLocation 413 270 356 240 0 0 1152 842 IBFramework Version 283.0 IBSystem Version 7B53 BitTorrent-3.4.2/osx/French.lproj/Metainfo.nib/objects.nib0100755000202400020240000000524107742026621022041 0ustar brambram typedstream@NSIBObjectDataNSObjectNSCustomObject)@@NSString+Generatei NSTextField NSControl)NSView) NSResponder @@@@ffffffffNSMutableArrayNSArray  :66icc@NSTextFieldCell> NSActionCellNSCellAiiA@@@@@NSFont$[36c]LucidaGrandef ci:c@@NSColor@@@SystemtextBackgroundColorff textColor:$o@$Déposer un fichier ou un dossier...$LucidaGrande  controlColor?*controlTextColorNSBox*}} ff@@cccBox QQ@Url du tracker:$LucidaGrande NSButton!(m m  NSButtonCell?8 Générer...ń ssii@@@@@@NSMutableString˘[28c]Helvetica ƒ!O8@@@@ȥ$$LucidaGrande  εK@NSProgressIndicatorܜb NSPSMatrix[12f] ccddc"ՙՒ -Box ƒ$W66ȥ$7Poser un fichier Torrent sans ce fichier ou ce dossier.ąڵH