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