]>
Commit | Line | Data |
---|---|---|
43d991f6 MT |
1 | #!/usr/bin/python |
2 | ||
9068dba1 | 3 | from __future__ import division |
43d991f6 | 4 | |
9068dba1 | 5 | import random |
938d083d | 6 | |
9068dba1 | 7 | from misc import Object |
940227cb | 8 | |
43d991f6 MT |
9 | def decode_hex(s): |
10 | ret = [] | |
11 | for c in s: | |
12 | for i in range(256): | |
13 | if not c == chr(i): | |
14 | continue | |
15 | ||
940227cb | 16 | ret.append("%02x" % i) |
43d991f6 MT |
17 | |
18 | return "".join(ret) | |
19 | ||
9068dba1 MT |
20 | class Tracker(Object): |
21 | @property | |
22 | def tracker_id(self): | |
23 | return self.settings.get("tracker_id", "TheIPFireTorrentTracker") | |
479f3388 | 24 | |
9068dba1 MT |
25 | def _fuzzy_interval(self, interval, fuzz=60): |
26 | return interval + random.randint(-fuzz, fuzz) | |
43d991f6 | 27 | |
9068dba1 MT |
28 | @property |
29 | def interval(self): | |
30 | return self.settings.get_int("tracker_interval", 3600) | |
43d991f6 | 31 | |
feb02477 | 32 | @property |
9068dba1 MT |
33 | def min_interval(self): |
34 | interval = self.settings.get_int("tracker_min_interval", self.interval // 2) | |
43d991f6 | 35 | |
9068dba1 MT |
36 | return self._fuzzy_interval(interval) |
37 | ||
38 | @property | |
39 | def numwant(self): | |
40 | return self.settings.get_int("tracker_numwant", 50) | |
43d991f6 | 41 | |
9068dba1 MT |
42 | def get_peers(self, info_hash, limit=None, random=True, no_peer_id=False, ipfamily=None): |
43 | query = "SELECT * FROM tracker WHERE last_update >= NOW() - INTERVAL '%ss'" | |
44 | args = [self.interval,] | |
43d991f6 | 45 | |
9068dba1 MT |
46 | if info_hash: |
47 | query += " AND hash = %s" | |
48 | args.append(info_hash) | |
43d991f6 MT |
49 | |
50 | if random: | |
9068dba1 | 51 | query += " ORDER BY RANDOM()" |
43d991f6 MT |
52 | |
53 | if limit: | |
9068dba1 MT |
54 | query += " LIMIT %s" |
55 | args.append(limit) | |
43d991f6 MT |
56 | |
57 | peers = [] | |
9068dba1 MT |
58 | for row in self.db.query(query, *args): |
59 | peer6 = None | |
60 | peer4 = None | |
61 | ||
62 | if row.address6 and row.port6: | |
63 | peer6 = { | |
64 | "ip" : row.address6, | |
65 | "port" : row.port6, | |
66 | } | |
67 | ||
68 | if row.address4 and row.port4: | |
69 | peer4 = { | |
70 | "ip" : row.address4, | |
71 | "port" : row.port4, | |
72 | } | |
e2afbd6a MT |
73 | |
74 | if not no_peer_id: | |
9068dba1 MT |
75 | if peer6: |
76 | peer6["peer id"] = row.id | |
e2afbd6a | 77 | |
9068dba1 MT |
78 | if peer4: |
79 | peer6["peer id"] = row.id | |
43d991f6 | 80 | |
9068dba1 MT |
81 | if peer6: |
82 | peers.append(peer6) | |
43d991f6 | 83 | |
9068dba1 MT |
84 | if peer4: |
85 | peers.append(peer4) | |
43d991f6 | 86 | |
9068dba1 | 87 | return peers |
43d991f6 | 88 | |
9068dba1 MT |
89 | def cleanup_peers(self): |
90 | """ | |
91 | Remove all peers that have timed out. | |
92 | """ | |
93 | self.db.execute("DELETE FROM tracker \ | |
94 | WHERE last_update < NOW() - INTERVAL '%ss'", self.interval) | |
43d991f6 | 95 | |
9068dba1 MT |
96 | def update_peer(self, peer_id, info_hash, address6=None, port6=None, |
97 | address4=None, port4=None, downloaded=None, uploaded=None, left_data=None): | |
98 | if address4 and address4.startswith("172.28.1."): | |
99 | address = "178.63.73.246" | |
43d991f6 | 100 | |
9068dba1 MT |
101 | query = "UPDATE tracker SET last_update = NOW()" |
102 | args = [] | |
43d991f6 | 103 | |
9068dba1 MT |
104 | if address6: |
105 | query += ", address6 = %s" | |
106 | args.append(address6) | |
43d991f6 | 107 | |
9068dba1 MT |
108 | if port6: |
109 | query += ", port6 = %s" | |
110 | args.append(port6) | |
43d991f6 | 111 | |
9068dba1 MT |
112 | if address4: |
113 | query += ", address4 = %s" | |
114 | args.append(address4) | |
43d991f6 | 115 | |
9068dba1 MT |
116 | if port4: |
117 | query += ", port4 = %s" | |
118 | args.append(port4) | |
e2afbd6a | 119 | |
9068dba1 MT |
120 | if downloaded: |
121 | query += ", downloaded = %s" | |
122 | args.append(downloaded) | |
e2afbd6a | 123 | |
9068dba1 MT |
124 | if uploaded: |
125 | query += ", uploaded = %s" | |
126 | args.append(uploaded) | |
e2afbd6a | 127 | |
9068dba1 MT |
128 | if left_data: |
129 | query += ", left_data = %s" | |
130 | args.append(left_data) | |
e2afbd6a | 131 | |
9068dba1 MT |
132 | query += " WHERE id = %s AND hash = %s" |
133 | args += [peer_id, info_hash] | |
43d991f6 | 134 | |
9068dba1 | 135 | self.db.execute(query, *args) |
d2a80c6e | 136 | |
9068dba1 MT |
137 | def complete(self, info_hash): |
138 | ret = self.db.get("SELECT COUNT(*) AS c FROM tracker \ | |
139 | WHERE hash = %s AND left_data = 0", info_hash) | |
43d991f6 | 140 | |
9068dba1 MT |
141 | if ret: |
142 | return ret.c | |
43d991f6 | 143 | |
9068dba1 MT |
144 | def incomplete(self, info_hash): |
145 | ret = self.db.get("SELECT COUNT(*) AS c FROM tracker \ | |
146 | WHERE hash = %s AND left_data > 0", info_hash) | |
43d991f6 | 147 | |
9068dba1 MT |
148 | if ret: |
149 | return ret.c | |
43d991f6 | 150 | |
9068dba1 MT |
151 | def handle_event(self, event, peer_id, info_hash, **kwargs): |
152 | # started | |
153 | if event == "started": | |
154 | self.insert_peer(peer_id, info_hash, **kwargs) | |
43d991f6 | 155 | |
9068dba1 MT |
156 | # stopped |
157 | elif event == "stopped": | |
158 | self.remove_peer(peer_id, info_hash) | |
43d991f6 | 159 | |
9068dba1 MT |
160 | def peer_exists(self, peer_id, info_hash): |
161 | ret = self.db.get("SELECT COUNT(*) AS c FROM tracker \ | |
162 | WHERE id = %s AND hash = %s", peer_id, info_hash) | |
43d991f6 | 163 | |
9068dba1 MT |
164 | if ret and ret.c > 0: |
165 | return True | |
43d991f6 | 166 | |
9068dba1 | 167 | return False |
940227cb | 168 | |
9068dba1 MT |
169 | def insert_peer(self, peer_id, info_hash, address6=None, port6=None, address4=None, port4=None): |
170 | exists = self.peer_exists(peer_id, info_hash) | |
171 | if exists: | |
172 | return | |
43d991f6 | 173 | |
9068dba1 MT |
174 | self.db.execute("INSERT INTO tracker(id, hash, address6, port6, address4, port4) \ |
175 | VALUES(%s, %s, %s, %s, %s, %s)", peer_id, info_hash, address6, port6, address4, port4) | |
43d991f6 | 176 | |
9068dba1 MT |
177 | def remove_peer(self, peer_id, info_hash): |
178 | self.db.execute("DELETE FROM tracker \ | |
179 | WHERE id = %s AND hash = %s", peer_id, info_hash) | |
43d991f6 | 180 | |
9068dba1 MT |
181 | def scrape(self, info_hashes): |
182 | ret = { | |
183 | "files" : {}, | |
184 | "flags" : { | |
185 | "min_request_interval" : self.interval, | |
186 | } | |
187 | } | |
479f3388 | 188 | |
9068dba1 MT |
189 | for info_hash in info_hashes: |
190 | ret["files"][info_hash] = { | |
191 | "complete" : self.complete(info_hash), | |
192 | "incomplete" : self.incomplete(info_hash), | |
193 | "downloaded" : 0, | |
194 | } | |
195 | ||
196 | return ret | |
479f3388 | 197 | |
43d991f6 | 198 | |
43d991f6 MT |
199 | ##### This is borrowed from the bittorrent client libary ##### |
200 | ||
201 | def decode_int(x, f): | |
202 | f += 1 | |
203 | newf = x.index('e', f) | |
204 | n = int(x[f:newf]) | |
205 | if x[f] == '-': | |
206 | if x[f + 1] == '0': | |
207 | raise ValueError | |
208 | elif x[f] == '0' and newf != f+1: | |
209 | raise ValueError | |
210 | return (n, newf+1) | |
211 | ||
212 | def decode_string(x, f): | |
213 | colon = x.index(':', f) | |
214 | n = int(x[f:colon]) | |
215 | if x[f] == '0' and colon != f+1: | |
216 | raise ValueError | |
217 | colon += 1 | |
218 | return (x[colon:colon+n], colon+n) | |
219 | ||
220 | def decode_list(x, f): | |
221 | r, f = [], f+1 | |
222 | while x[f] != 'e': | |
223 | v, f = decode_func[x[f]](x, f) | |
224 | r.append(v) | |
225 | return (r, f + 1) | |
226 | ||
227 | def decode_dict(x, f): | |
228 | r, f = {}, f+1 | |
229 | while x[f] != 'e': | |
230 | k, f = decode_string(x, f) | |
231 | r[k], f = decode_func[x[f]](x, f) | |
232 | return (r, f + 1) | |
233 | ||
234 | decode_func = {} | |
235 | decode_func['l'] = decode_list | |
236 | decode_func['d'] = decode_dict | |
237 | decode_func['i'] = decode_int | |
238 | decode_func['0'] = decode_string | |
239 | decode_func['1'] = decode_string | |
240 | decode_func['2'] = decode_string | |
241 | decode_func['3'] = decode_string | |
242 | decode_func['4'] = decode_string | |
243 | decode_func['5'] = decode_string | |
244 | decode_func['6'] = decode_string | |
245 | decode_func['7'] = decode_string | |
246 | decode_func['8'] = decode_string | |
247 | decode_func['9'] = decode_string | |
248 | ||
249 | def bdecode(x): | |
250 | try: | |
251 | r, l = decode_func[x[0]](x, 0) | |
252 | except (IndexError, KeyError, ValueError): | |
253 | raise Exception("not a valid bencoded string") | |
254 | if l != len(x): | |
255 | raise Exception("invalid bencoded value (data after valid prefix)") | |
256 | return r | |
257 | ||
258 | from types import StringType, IntType, LongType, DictType, ListType, TupleType | |
259 | ||
260 | ||
261 | class Bencached(object): | |
262 | ||
263 | __slots__ = ['bencoded'] | |
264 | ||
265 | def __init__(self, s): | |
266 | self.bencoded = s | |
267 | ||
268 | def encode_bencached(x,r): | |
269 | r.append(x.bencoded) | |
270 | ||
271 | def encode_int(x, r): | |
272 | r.extend(('i', str(x), 'e')) | |
273 | ||
274 | def encode_bool(x, r): | |
275 | if x: | |
276 | encode_int(1, r) | |
277 | else: | |
278 | encode_int(0, r) | |
279 | ||
280 | def encode_string(x, r): | |
281 | r.extend((str(len(x)), ':', x)) | |
282 | ||
283 | def encode_list(x, r): | |
284 | r.append('l') | |
285 | for i in x: | |
286 | encode_func[type(i)](i, r) | |
287 | r.append('e') | |
288 | ||
289 | def encode_dict(x,r): | |
290 | r.append('d') | |
291 | ilist = x.items() | |
292 | ilist.sort() | |
293 | for k, v in ilist: | |
294 | r.extend((str(len(k)), ':', k)) | |
295 | encode_func[type(v)](v, r) | |
296 | r.append('e') | |
297 | ||
298 | encode_func = {} | |
299 | encode_func[Bencached] = encode_bencached | |
300 | encode_func[IntType] = encode_int | |
301 | encode_func[LongType] = encode_int | |
302 | encode_func[StringType] = encode_string | |
303 | encode_func[ListType] = encode_list | |
304 | encode_func[TupleType] = encode_list | |
305 | encode_func[DictType] = encode_dict | |
306 | ||
307 | try: | |
308 | from types import BooleanType | |
309 | encode_func[BooleanType] = encode_bool | |
310 | except ImportError: | |
311 | pass | |
312 | ||
313 | def bencode(x): | |
314 | r = [] | |
315 | encode_func[type(x)](x, r) | |
316 | return ''.join(r) |