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