]> git.ipfire.org Git - ipfire.org.git/blame - webapp/backend/tracker.py
mirrors: Fix resolving hostnames.
[ipfire.org.git] / webapp / backend / tracker.py
CommitLineData
43d991f6
MT
1#!/usr/bin/python
2
9068dba1 3from __future__ import division
43d991f6 4
9068dba1 5import random
938d083d 6
9068dba1 7from misc import Object
940227cb 8
43d991f6
MT
9def 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
20class 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
204def 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
215def 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
223def 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
230def 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
237decode_func = {}
238decode_func['l'] = decode_list
239decode_func['d'] = decode_dict
240decode_func['i'] = decode_int
241decode_func['0'] = decode_string
242decode_func['1'] = decode_string
243decode_func['2'] = decode_string
244decode_func['3'] = decode_string
245decode_func['4'] = decode_string
246decode_func['5'] = decode_string
247decode_func['6'] = decode_string
248decode_func['7'] = decode_string
249decode_func['8'] = decode_string
250decode_func['9'] = decode_string
251
252def 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
261from types import StringType, IntType, LongType, DictType, ListType, TupleType
262
263
264class Bencached(object):
265
266 __slots__ = ['bencoded']
267
268 def __init__(self, s):
269 self.bencoded = s
270
271def encode_bencached(x,r):
272 r.append(x.bencoded)
273
274def encode_int(x, r):
275 r.extend(('i', str(x), 'e'))
276
277def encode_bool(x, r):
278 if x:
279 encode_int(1, r)
280 else:
281 encode_int(0, r)
282
283def encode_string(x, r):
284 r.extend((str(len(x)), ':', x))
285
286def 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
292def 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
301encode_func = {}
302encode_func[Bencached] = encode_bencached
303encode_func[IntType] = encode_int
304encode_func[LongType] = encode_int
305encode_func[StringType] = encode_string
306encode_func[ListType] = encode_list
307encode_func[TupleType] = encode_list
308encode_func[DictType] = encode_dict
309
310try:
311 from types import BooleanType
312 encode_func[BooleanType] = encode_bool
313except ImportError:
314 pass
315
316def bencode(x):
317 r = []
318 encode_func[type(x)](x, r)
319 return ''.join(r)