]> git.ipfire.org Git - people/shoehn/ipfire.org.git/blame - webapp/backend/tracker.py
Major update of the webapp.
[people/shoehn/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
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
201def 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
212def 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
220def 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
227def 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
234decode_func = {}
235decode_func['l'] = decode_list
236decode_func['d'] = decode_dict
237decode_func['i'] = decode_int
238decode_func['0'] = decode_string
239decode_func['1'] = decode_string
240decode_func['2'] = decode_string
241decode_func['3'] = decode_string
242decode_func['4'] = decode_string
243decode_func['5'] = decode_string
244decode_func['6'] = decode_string
245decode_func['7'] = decode_string
246decode_func['8'] = decode_string
247decode_func['9'] = decode_string
248
249def 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
258from types import StringType, IntType, LongType, DictType, ListType, TupleType
259
260
261class Bencached(object):
262
263 __slots__ = ['bencoded']
264
265 def __init__(self, s):
266 self.bencoded = s
267
268def encode_bencached(x,r):
269 r.append(x.bencoded)
270
271def encode_int(x, r):
272 r.extend(('i', str(x), 'e'))
273
274def encode_bool(x, r):
275 if x:
276 encode_int(1, r)
277 else:
278 encode_int(0, r)
279
280def encode_string(x, r):
281 r.extend((str(len(x)), ':', x))
282
283def 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
289def 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
298encode_func = {}
299encode_func[Bencached] = encode_bencached
300encode_func[IntType] = encode_int
301encode_func[LongType] = encode_int
302encode_func[StringType] = encode_string
303encode_func[ListType] = encode_list
304encode_func[TupleType] = encode_list
305encode_func[DictType] = encode_dict
306
307try:
308 from types import BooleanType
309 encode_func[BooleanType] = encode_bool
310except ImportError:
311 pass
312
313def bencode(x):
314 r = []
315 encode_func[type(x)](x, r)
316 return ''.join(r)