]> git.ipfire.org Git - ipfire.org.git/blob - webapp/backend/tracker.py
d5642c4ae332d9e6e83fce37f037492e4550c663
[ipfire.org.git] / webapp / backend / tracker.py
1 #!/usr/bin/python
2
3 from __future__ import division
4
5 import random
6
7 from misc import Object
8
9 def encode_hex(s):
10 return s.encode("hex")
11
12 def decode_hex(s):
13 return s.decode("hex")
14
15
16 class Tracker(Object):
17 @property
18 def tracker_id(self):
19 return self.settings.get("tracker_id", "TheIPFireTorrentTracker")
20
21 def _fuzzy_interval(self, interval, fuzz=120):
22 return interval - random.randint(0, fuzz)
23
24 @property
25 def _interval(self):
26 return self.settings.get_int("tracker_interval", 3600)
27
28 @property
29 def interval(self):
30 return self._fuzzy_interval(self._interval)
31
32 @property
33 def min_interval(self):
34 return self.settings.get_int("tracker_min_interval", self._interval // 2)
35
36 @property
37 def numwant(self):
38 return self.settings.get_int("tracker_numwant", 50)
39
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'"
42 args = [self._interval,]
43
44 if info_hash:
45 query += " AND hash = %s"
46 args.append(info_hash)
47
48 if random:
49 query += " ORDER BY RANDOM()"
50
51 if limit:
52 query += " LIMIT %s"
53 args.append(limit)
54
55 peers = []
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 }
71
72 if not no_peer_id:
73 if peer6:
74 peer6["peer id"] = decode_hex(row.id)
75
76 if peer4:
77 peer4["peer id"] = decode_hex(row.id)
78
79 if peer6:
80 peers.append(peer6)
81
82 if peer4:
83 peers.append(peer4)
84
85 return peers
86
87 def cleanup_peers(self):
88 """
89 Remove all peers that have timed out.
90 """
91 self.db.execute("DELETE FROM tracker \
92 WHERE last_update < NOW() - INTERVAL '%s s'", int(self._interval * 1.2))
93
94 def update_peer(self, peer_id, info_hash, **kwargs):
95 # Translate the location IP address
96 address4 = kwargs.get("address4", None)
97 if address4 and address4.startswith("172.28.1."):
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
113
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
135 def __insert_peer(self, peer_id, info_hash, address6=None, port6=None, address4=None, port4=None, **kwargs):
136 self.db.execute("INSERT INTO tracker(id, hash, address6, port6, address4, port4) \
137 VALUES(%s, %s, %s, %s, %s, %s)", peer_id, info_hash, address6, port6, address4, port4)
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):
141 query = "UPDATE tracker SET last_update = NOW()"
142 args = []
143
144 if address6:
145 query += ", address6 = %s"
146 args.append(address6)
147
148 if port6:
149 query += ", port6 = %s"
150 args.append(port6)
151
152 if address4:
153 query += ", address4 = %s"
154 args.append(address4)
155
156 if port4:
157 query += ", port4 = %s"
158 args.append(port4)
159
160 if downloaded:
161 query += ", downloaded = %s"
162 args.append(downloaded)
163
164 if uploaded:
165 query += ", uploaded = %s"
166 args.append(uploaded)
167
168 if left_data:
169 query += ", left_data = %s"
170 args.append(left_data)
171
172 query += " WHERE id = %s AND hash = %s"
173 args += [peer_id, info_hash]
174
175 self.db.execute(query, *args)
176
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)
180
181 def scrape(self, info_hashes):
182 ret = {
183 "files" : {},
184 "flags" : {
185 "min_request_interval" : self.interval,
186 }
187 }
188
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 }
196
197 return ret
198
199
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)