murder 0.0.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|