murder 0.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/LICENSE +17 -0
- data/README +224 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/dist/BitTornado/BT1/Choker.py +128 -0
- data/dist/BitTornado/BT1/Connecter.py +288 -0
- data/dist/BitTornado/BT1/Downloader.py +594 -0
- data/dist/BitTornado/BT1/DownloaderFeedback.py +155 -0
- data/dist/BitTornado/BT1/Encrypter.py +333 -0
- data/dist/BitTornado/BT1/FileSelector.py +245 -0
- data/dist/BitTornado/BT1/Filter.py +12 -0
- data/dist/BitTornado/BT1/HTTPDownloader.py +251 -0
- data/dist/BitTornado/BT1/NatCheck.py +95 -0
- data/dist/BitTornado/BT1/PiecePicker.py +320 -0
- data/dist/BitTornado/BT1/Rerequester.py +426 -0
- data/dist/BitTornado/BT1/Statistics.py +177 -0
- data/dist/BitTornado/BT1/Storage.py +584 -0
- data/dist/BitTornado/BT1/StorageWrapper.py +1045 -0
- data/dist/BitTornado/BT1/StreamCheck.py +135 -0
- data/dist/BitTornado/BT1/T2T.py +193 -0
- data/dist/BitTornado/BT1/Uploader.py +145 -0
- data/dist/BitTornado/BT1/__init__.py +1 -0
- data/dist/BitTornado/BT1/btformats.py +100 -0
- data/dist/BitTornado/BT1/fakeopen.py +89 -0
- data/dist/BitTornado/BT1/makemetafile.py +263 -0
- data/dist/BitTornado/BT1/track.py +1067 -0
- data/dist/BitTornado/ConfigDir.py +401 -0
- data/dist/BitTornado/ConfigReader.py +1068 -0
- data/dist/BitTornado/ConnChoice.py +31 -0
- data/dist/BitTornado/CreateIcons.py +105 -0
- data/dist/BitTornado/CurrentRateMeasure.py +37 -0
- data/dist/BitTornado/HTTPHandler.py +167 -0
- data/dist/BitTornado/PSYCO.py +5 -0
- data/dist/BitTornado/RateLimiter.py +153 -0
- data/dist/BitTornado/RateMeasure.py +75 -0
- data/dist/BitTornado/RawServer.py +195 -0
- data/dist/BitTornado/ServerPortHandler.py +188 -0
- data/dist/BitTornado/SocketHandler.py +375 -0
- data/dist/BitTornado/__init__.py +63 -0
- data/dist/BitTornado/bencode.py +319 -0
- data/dist/BitTornado/bitfield.py +162 -0
- data/dist/BitTornado/clock.py +27 -0
- data/dist/BitTornado/download_bt1.py +882 -0
- data/dist/BitTornado/inifile.py +169 -0
- data/dist/BitTornado/iprangeparse.py +194 -0
- data/dist/BitTornado/launchmanycore.py +381 -0
- data/dist/BitTornado/natpunch.py +254 -0
- data/dist/BitTornado/parseargs.py +137 -0
- data/dist/BitTornado/parsedir.py +150 -0
- data/dist/BitTornado/piecebuffer.py +86 -0
- data/dist/BitTornado/selectpoll.py +109 -0
- data/dist/BitTornado/subnetparse.py +218 -0
- data/dist/BitTornado/torrentlistparse.py +38 -0
- data/dist/BitTornado/zurllib.py +100 -0
- data/dist/murder_client.py +291 -0
- data/dist/murder_make_torrent.py +46 -0
- data/dist/murder_tracker.py +28 -0
- data/doc/examples/Capfile +28 -0
- data/lib/capistrano/recipes/deploy/strategy/murder.rb +52 -0
- data/lib/murder.rb +43 -0
- data/lib/murder/admin.rb +47 -0
- data/lib/murder/murder.rb +121 -0
- data/murder.gemspec +101 -0
- metadata +129 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
# Written by Bram Cohen
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
from cStringIO import StringIO
|
5
|
+
from binascii import b2a_hex
|
6
|
+
from socket import error as socketerror
|
7
|
+
from urllib import quote
|
8
|
+
from traceback import print_exc
|
9
|
+
import Connecter
|
10
|
+
try:
|
11
|
+
True
|
12
|
+
except:
|
13
|
+
True = 1
|
14
|
+
False = 0
|
15
|
+
|
16
|
+
DEBUG = False
|
17
|
+
|
18
|
+
|
19
|
+
protocol_name = 'BitTorrent protocol'
|
20
|
+
option_pattern = chr(0)*8
|
21
|
+
|
22
|
+
def toint(s):
|
23
|
+
return long(b2a_hex(s), 16)
|
24
|
+
|
25
|
+
def tobinary(i):
|
26
|
+
return (chr(i >> 24) + chr((i >> 16) & 0xFF) +
|
27
|
+
chr((i >> 8) & 0xFF) + chr(i & 0xFF))
|
28
|
+
|
29
|
+
hexchars = '0123456789ABCDEF'
|
30
|
+
hexmap = []
|
31
|
+
for i in xrange(256):
|
32
|
+
hexmap.append(hexchars[(i&0xF0)/16]+hexchars[i&0x0F])
|
33
|
+
|
34
|
+
def tohex(s):
|
35
|
+
r = []
|
36
|
+
for c in s:
|
37
|
+
r.append(hexmap[ord(c)])
|
38
|
+
return ''.join(r)
|
39
|
+
|
40
|
+
def make_readable(s):
|
41
|
+
if not s:
|
42
|
+
return ''
|
43
|
+
if quote(s).find('%') >= 0:
|
44
|
+
return tohex(s)
|
45
|
+
return '"'+s+'"'
|
46
|
+
|
47
|
+
def toint(s):
|
48
|
+
return long(b2a_hex(s), 16)
|
49
|
+
|
50
|
+
# header, reserved, download id, my id, [length, message]
|
51
|
+
|
52
|
+
streamno = 0
|
53
|
+
|
54
|
+
|
55
|
+
class StreamCheck:
|
56
|
+
def __init__(self):
|
57
|
+
global streamno
|
58
|
+
self.no = streamno
|
59
|
+
streamno += 1
|
60
|
+
self.buffer = StringIO()
|
61
|
+
self.next_len, self.next_func = 1, self.read_header_len
|
62
|
+
|
63
|
+
def read_header_len(self, s):
|
64
|
+
if ord(s) != len(protocol_name):
|
65
|
+
print self.no, 'BAD HEADER LENGTH'
|
66
|
+
return len(protocol_name), self.read_header
|
67
|
+
|
68
|
+
def read_header(self, s):
|
69
|
+
if s != protocol_name:
|
70
|
+
print self.no, 'BAD HEADER'
|
71
|
+
return 8, self.read_reserved
|
72
|
+
|
73
|
+
def read_reserved(self, s):
|
74
|
+
return 20, self.read_download_id
|
75
|
+
|
76
|
+
def read_download_id(self, s):
|
77
|
+
if DEBUG:
|
78
|
+
print self.no, 'download ID ' + tohex(s)
|
79
|
+
return 20, self.read_peer_id
|
80
|
+
|
81
|
+
def read_peer_id(self, s):
|
82
|
+
if DEBUG:
|
83
|
+
print self.no, 'peer ID' + make_readable(s)
|
84
|
+
return 4, self.read_len
|
85
|
+
|
86
|
+
def read_len(self, s):
|
87
|
+
l = toint(s)
|
88
|
+
if l > 2 ** 23:
|
89
|
+
print self.no, 'BAD LENGTH: '+str(l)+' ('+s+')'
|
90
|
+
return l, self.read_message
|
91
|
+
|
92
|
+
def read_message(self, s):
|
93
|
+
if not s:
|
94
|
+
return 4, self.read_len
|
95
|
+
m = s[0]
|
96
|
+
if ord(m) > 8:
|
97
|
+
print self.no, 'BAD MESSAGE: '+str(ord(m))
|
98
|
+
if m == Connecter.REQUEST:
|
99
|
+
if len(s) != 13:
|
100
|
+
print self.no, 'BAD REQUEST SIZE: '+str(len(s))
|
101
|
+
return 4, self.read_len
|
102
|
+
index = toint(s[1:5])
|
103
|
+
begin = toint(s[5:9])
|
104
|
+
length = toint(s[9:])
|
105
|
+
print self.no, 'Request: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length)
|
106
|
+
elif m == Connecter.CANCEL:
|
107
|
+
if len(s) != 13:
|
108
|
+
print self.no, 'BAD CANCEL SIZE: '+str(len(s))
|
109
|
+
return 4, self.read_len
|
110
|
+
index = toint(s[1:5])
|
111
|
+
begin = toint(s[5:9])
|
112
|
+
length = toint(s[9:])
|
113
|
+
print self.no, 'Cancel: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length)
|
114
|
+
elif m == Connecter.PIECE:
|
115
|
+
index = toint(s[1:5])
|
116
|
+
begin = toint(s[5:9])
|
117
|
+
length = len(s)-9
|
118
|
+
print self.no, 'Piece: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length)
|
119
|
+
else:
|
120
|
+
print self.no, 'Message '+str(ord(m))+' (length '+str(len(s))+')'
|
121
|
+
return 4, self.read_len
|
122
|
+
|
123
|
+
def write(self, s):
|
124
|
+
while True:
|
125
|
+
i = self.next_len - self.buffer.tell()
|
126
|
+
if i > len(s):
|
127
|
+
self.buffer.write(s)
|
128
|
+
return
|
129
|
+
self.buffer.write(s[:i])
|
130
|
+
s = s[i:]
|
131
|
+
m = self.buffer.getvalue()
|
132
|
+
self.buffer.reset()
|
133
|
+
self.buffer.truncate()
|
134
|
+
x = self.next_func(m)
|
135
|
+
self.next_len, self.next_func = x
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# Written by John Hoffman
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
from Rerequester import Rerequester
|
5
|
+
from urllib import quote
|
6
|
+
from threading import Event
|
7
|
+
from random import randrange
|
8
|
+
from string import lower
|
9
|
+
import sys
|
10
|
+
import __init__
|
11
|
+
try:
|
12
|
+
True
|
13
|
+
except:
|
14
|
+
True = 1
|
15
|
+
False = 0
|
16
|
+
|
17
|
+
DEBUG = True
|
18
|
+
|
19
|
+
|
20
|
+
def excfunc(x):
|
21
|
+
print x
|
22
|
+
|
23
|
+
class T2TConnection:
|
24
|
+
def __init__(self, myid, tracker, hash, interval, peers, timeout,
|
25
|
+
rawserver, disallow, isdisallowed):
|
26
|
+
self.tracker = tracker
|
27
|
+
self.interval = interval
|
28
|
+
self.hash = hash
|
29
|
+
self.operatinginterval = interval
|
30
|
+
self.peers = peers
|
31
|
+
self.rawserver = rawserver
|
32
|
+
self.disallow = disallow
|
33
|
+
self.isdisallowed = isdisallowed
|
34
|
+
self.active = True
|
35
|
+
self.busy = False
|
36
|
+
self.errors = 0
|
37
|
+
self.rejected = 0
|
38
|
+
self.trackererror = False
|
39
|
+
self.peerlists = []
|
40
|
+
|
41
|
+
self.rerequester = Rerequester([[tracker]], interval,
|
42
|
+
rawserver.add_task, lambda: 0, peers, self.addtolist,
|
43
|
+
rawserver.add_task, lambda: 1, 0, 0, 0, '',
|
44
|
+
myid, hash, timeout, self.errorfunc, excfunc, peers, Event(),
|
45
|
+
lambda: 0, lambda: 0)
|
46
|
+
|
47
|
+
if self.isactive():
|
48
|
+
rawserver.add_task(self.refresh, randrange(int(self.interval/10), self.interval))
|
49
|
+
# stagger announces
|
50
|
+
|
51
|
+
def isactive(self):
|
52
|
+
if self.isdisallowed(self.tracker): # whoops!
|
53
|
+
self.deactivate()
|
54
|
+
return self.active
|
55
|
+
|
56
|
+
def deactivate(self):
|
57
|
+
self.active = False
|
58
|
+
|
59
|
+
def refresh(self):
|
60
|
+
if not self.isactive():
|
61
|
+
return
|
62
|
+
self.lastsuccessful = True
|
63
|
+
self.newpeerdata = []
|
64
|
+
if DEBUG:
|
65
|
+
print 'contacting %s for info_hash=%s' % (self.tracker, quote(self.hash))
|
66
|
+
self.rerequester.snoop(self.peers, self.callback)
|
67
|
+
|
68
|
+
def callback(self):
|
69
|
+
self.busy = False
|
70
|
+
if self.lastsuccessful:
|
71
|
+
self.errors = 0
|
72
|
+
self.rejected = 0
|
73
|
+
if self.rerequester.announce_interval > (3*self.interval):
|
74
|
+
# I think I'm stripping from a regular tracker; boost the number of peers requested
|
75
|
+
self.peers = int(self.peers * (self.rerequester.announce_interval / self.interval))
|
76
|
+
self.operatinginterval = self.rerequester.announce_interval
|
77
|
+
if DEBUG:
|
78
|
+
print ("%s with info_hash=%s returned %d peers" %
|
79
|
+
(self.tracker, quote(self.hash), len(self.newpeerdata)))
|
80
|
+
self.peerlists.append(self.newpeerdata)
|
81
|
+
self.peerlists = self.peerlists[-10:] # keep up to the last 10 announces
|
82
|
+
if self.isactive():
|
83
|
+
self.rawserver.add_task(self.refresh, self.operatinginterval)
|
84
|
+
|
85
|
+
def addtolist(self, peers):
|
86
|
+
for peer in peers:
|
87
|
+
self.newpeerdata.append((peer[1],peer[0][0],peer[0][1]))
|
88
|
+
|
89
|
+
def errorfunc(self, r):
|
90
|
+
self.lastsuccessful = False
|
91
|
+
if DEBUG:
|
92
|
+
print "%s with info_hash=%s gives error: '%s'" % (self.tracker, quote(self.hash), r)
|
93
|
+
if r == self.rerequester.rejectedmessage + 'disallowed': # whoops!
|
94
|
+
if DEBUG:
|
95
|
+
print ' -- disallowed - deactivating'
|
96
|
+
self.deactivate()
|
97
|
+
self.disallow(self.tracker) # signal other torrents on this tracker
|
98
|
+
return
|
99
|
+
if lower(r[:8]) == 'rejected': # tracker rejected this particular torrent
|
100
|
+
self.rejected += 1
|
101
|
+
if self.rejected == 3: # rejected 3 times
|
102
|
+
if DEBUG:
|
103
|
+
print ' -- rejected 3 times - deactivating'
|
104
|
+
self.deactivate()
|
105
|
+
return
|
106
|
+
self.errors += 1
|
107
|
+
if self.errors >= 3: # three or more errors in a row
|
108
|
+
self.operatinginterval += self.interval # lengthen the interval
|
109
|
+
if DEBUG:
|
110
|
+
print ' -- lengthening interval to '+str(self.operatinginterval)+' seconds'
|
111
|
+
|
112
|
+
def harvest(self):
|
113
|
+
x = []
|
114
|
+
for list in self.peerlists:
|
115
|
+
x += list
|
116
|
+
self.peerlists = []
|
117
|
+
return x
|
118
|
+
|
119
|
+
|
120
|
+
class T2TList:
|
121
|
+
def __init__(self, enabled, trackerid, interval, maxpeers, timeout, rawserver):
|
122
|
+
self.enabled = enabled
|
123
|
+
self.trackerid = trackerid
|
124
|
+
self.interval = interval
|
125
|
+
self.maxpeers = maxpeers
|
126
|
+
self.timeout = timeout
|
127
|
+
self.rawserver = rawserver
|
128
|
+
self.list = {}
|
129
|
+
self.torrents = {}
|
130
|
+
self.disallowed = {}
|
131
|
+
self.oldtorrents = []
|
132
|
+
|
133
|
+
def parse(self, allowed_list):
|
134
|
+
if not self.enabled:
|
135
|
+
return
|
136
|
+
|
137
|
+
# step 1: Create a new list with all tracker/torrent combinations in allowed_dir
|
138
|
+
newlist = {}
|
139
|
+
for hash, data in allowed_list.items():
|
140
|
+
if data.has_key('announce-list'):
|
141
|
+
for tier in data['announce-list']:
|
142
|
+
for tracker in tier:
|
143
|
+
self.disallowed.setdefault(tracker, False)
|
144
|
+
newlist.setdefault(tracker, {})
|
145
|
+
newlist[tracker][hash] = None # placeholder
|
146
|
+
|
147
|
+
# step 2: Go through and copy old data to the new list.
|
148
|
+
# if the new list has no place for it, then it's old, so deactivate it
|
149
|
+
for tracker, hashdata in self.list.items():
|
150
|
+
for hash, t2t in hashdata.items():
|
151
|
+
if not newlist.has_key(tracker) or not newlist[tracker].has_key(hash):
|
152
|
+
t2t.deactivate() # this connection is no longer current
|
153
|
+
self.oldtorrents += [t2t]
|
154
|
+
# keep it referenced in case a thread comes along and tries to access.
|
155
|
+
else:
|
156
|
+
newlist[tracker][hash] = t2t
|
157
|
+
if not newlist.has_key(tracker):
|
158
|
+
self.disallowed[tracker] = False # reset when no torrents on it left
|
159
|
+
|
160
|
+
self.list = newlist
|
161
|
+
newtorrents = {}
|
162
|
+
|
163
|
+
# step 3: If there are any entries that haven't been initialized yet, do so.
|
164
|
+
# At the same time, copy all entries onto the by-torrent list.
|
165
|
+
for tracker, hashdata in newlist.items():
|
166
|
+
for hash, t2t in hashdata.items():
|
167
|
+
if t2t is None:
|
168
|
+
hashdata[hash] = T2TConnection(self.trackerid, tracker, hash,
|
169
|
+
self.interval, self.maxpeers, self.timeout,
|
170
|
+
self.rawserver, self._disallow, self._isdisallowed)
|
171
|
+
newtorrents.setdefault(hash,[])
|
172
|
+
newtorrents[hash] += [hashdata[hash]]
|
173
|
+
|
174
|
+
self.torrents = newtorrents
|
175
|
+
|
176
|
+
# structures:
|
177
|
+
# list = {tracker: {hash: T2TConnection, ...}, ...}
|
178
|
+
# torrents = {hash: [T2TConnection, ...]}
|
179
|
+
# disallowed = {tracker: flag, ...}
|
180
|
+
# oldtorrents = [T2TConnection, ...]
|
181
|
+
|
182
|
+
def _disallow(self,tracker):
|
183
|
+
self.disallowed[tracker] = True
|
184
|
+
|
185
|
+
def _isdisallowed(self,tracker):
|
186
|
+
return self.disallowed[tracker]
|
187
|
+
|
188
|
+
def harvest(self,hash):
|
189
|
+
harvest = []
|
190
|
+
if self.enabled:
|
191
|
+
for t2t in self.torrents[hash]:
|
192
|
+
harvest += t2t.harvest()
|
193
|
+
return harvest
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# Written by Bram Cohen
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
from BitTornado.CurrentRateMeasure import Measure
|
5
|
+
|
6
|
+
try:
|
7
|
+
True
|
8
|
+
except:
|
9
|
+
True = 1
|
10
|
+
False = 0
|
11
|
+
|
12
|
+
class Upload:
|
13
|
+
def __init__(self, connection, ratelimiter, totalup, choker, storage,
|
14
|
+
picker, config):
|
15
|
+
self.connection = connection
|
16
|
+
self.ratelimiter = ratelimiter
|
17
|
+
self.totalup = totalup
|
18
|
+
self.choker = choker
|
19
|
+
self.storage = storage
|
20
|
+
self.picker = picker
|
21
|
+
self.config = config
|
22
|
+
self.max_slice_length = config['max_slice_length']
|
23
|
+
self.choked = True
|
24
|
+
self.cleared = True
|
25
|
+
self.interested = False
|
26
|
+
self.super_seeding = False
|
27
|
+
self.buffer = []
|
28
|
+
self.measure = Measure(config['max_rate_period'], config['upload_rate_fudge'])
|
29
|
+
self.was_ever_interested = False
|
30
|
+
if storage.get_amount_left() == 0:
|
31
|
+
if choker.super_seed:
|
32
|
+
self.super_seeding = True # flag, and don't send bitfield
|
33
|
+
self.seed_have_list = [] # set from piecepicker
|
34
|
+
self.skipped_count = 0
|
35
|
+
else:
|
36
|
+
if config['breakup_seed_bitfield']:
|
37
|
+
bitfield, msgs = storage.get_have_list_cloaked()
|
38
|
+
connection.send_bitfield(bitfield)
|
39
|
+
for have in msgs:
|
40
|
+
connection.send_have(have)
|
41
|
+
else:
|
42
|
+
connection.send_bitfield(storage.get_have_list())
|
43
|
+
else:
|
44
|
+
if storage.do_I_have_anything():
|
45
|
+
connection.send_bitfield(storage.get_have_list())
|
46
|
+
self.piecedl = None
|
47
|
+
self.piecebuf = None
|
48
|
+
|
49
|
+
def got_not_interested(self):
|
50
|
+
if self.interested:
|
51
|
+
self.interested = False
|
52
|
+
del self.buffer[:]
|
53
|
+
self.piecedl = None
|
54
|
+
if self.piecebuf:
|
55
|
+
self.piecebuf.release()
|
56
|
+
self.piecebuf = None
|
57
|
+
self.choker.not_interested(self.connection)
|
58
|
+
|
59
|
+
def got_interested(self):
|
60
|
+
if not self.interested:
|
61
|
+
self.interested = True
|
62
|
+
self.was_ever_interested = True
|
63
|
+
self.choker.interested(self.connection)
|
64
|
+
|
65
|
+
def get_upload_chunk(self):
|
66
|
+
if self.choked or not self.buffer:
|
67
|
+
return None
|
68
|
+
index, begin, length = self.buffer.pop(0)
|
69
|
+
if self.config['buffer_reads']:
|
70
|
+
if index != self.piecedl:
|
71
|
+
if self.piecebuf:
|
72
|
+
self.piecebuf.release()
|
73
|
+
self.piecedl = index
|
74
|
+
self.piecebuf = self.storage.get_piece(index, 0, -1)
|
75
|
+
try:
|
76
|
+
piece = self.piecebuf[begin:begin+length]
|
77
|
+
assert len(piece) == length
|
78
|
+
except: # fails if storage.get_piece returns None or if out of range
|
79
|
+
self.connection.close()
|
80
|
+
return None
|
81
|
+
else:
|
82
|
+
if self.piecebuf:
|
83
|
+
self.piecebuf.release()
|
84
|
+
self.piecedl = None
|
85
|
+
piece = self.storage.get_piece(index, begin, length)
|
86
|
+
if piece is None:
|
87
|
+
self.connection.close()
|
88
|
+
return None
|
89
|
+
self.measure.update_rate(len(piece))
|
90
|
+
self.totalup.update_rate(len(piece))
|
91
|
+
return (index, begin, piece)
|
92
|
+
|
93
|
+
def got_request(self, index, begin, length):
|
94
|
+
if ( (self.super_seeding and not index in self.seed_have_list)
|
95
|
+
or not self.interested or length > self.max_slice_length ):
|
96
|
+
self.connection.close()
|
97
|
+
return
|
98
|
+
if not self.cleared:
|
99
|
+
self.buffer.append((index, begin, length))
|
100
|
+
if not self.choked and self.connection.next_upload is None:
|
101
|
+
self.ratelimiter.queue(self.connection)
|
102
|
+
|
103
|
+
|
104
|
+
def got_cancel(self, index, begin, length):
|
105
|
+
try:
|
106
|
+
self.buffer.remove((index, begin, length))
|
107
|
+
except ValueError:
|
108
|
+
pass
|
109
|
+
|
110
|
+
def choke(self):
|
111
|
+
if not self.choked:
|
112
|
+
self.choked = True
|
113
|
+
self.connection.send_choke()
|
114
|
+
self.piecedl = None
|
115
|
+
if self.piecebuf:
|
116
|
+
self.piecebuf.release()
|
117
|
+
self.piecebuf = None
|
118
|
+
|
119
|
+
def choke_sent(self):
|
120
|
+
del self.buffer[:]
|
121
|
+
self.cleared = True
|
122
|
+
|
123
|
+
def unchoke(self):
|
124
|
+
if self.choked:
|
125
|
+
self.choked = False
|
126
|
+
self.cleared = False
|
127
|
+
self.connection.send_unchoke()
|
128
|
+
|
129
|
+
def disconnected(self):
|
130
|
+
if self.piecebuf:
|
131
|
+
self.piecebuf.release()
|
132
|
+
self.piecebuf = None
|
133
|
+
|
134
|
+
def is_choked(self):
|
135
|
+
return self.choked
|
136
|
+
|
137
|
+
def is_interested(self):
|
138
|
+
return self.interested
|
139
|
+
|
140
|
+
def has_queries(self):
|
141
|
+
return not self.choked and len(self.buffer) > 0
|
142
|
+
|
143
|
+
def get_rate(self):
|
144
|
+
return self.measure.get_rate()
|
145
|
+
|