]> git.ipfire.org Git - ipfire.org.git/blame - webapp/backend/tracker.py
netboot: Serve updated images from webapp
[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
f1349ebc
MT
9def encode_hex(s):
10 return s.encode("hex")
43d991f6 11
f1349ebc
MT
12def decode_hex(s):
13 return s.decode("hex")
43d991f6 14
43d991f6 15
9068dba1
MT
16class 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)
08f85138
MT
97
98 if self.peer_exists(peer_id, info_hash):
99 self.__update_peer(peer_id, info_hash, **kwargs)
100 else:
101 self.__insert_peer(peer_id, info_hash, **kwargs)
102
103 def complete(self, info_hash):
104 ret = self.db.get("SELECT COUNT(*) AS c FROM tracker \
105 WHERE hash = %s AND left_data = 0", info_hash)
106
107 if ret:
108 return ret.c
43d991f6 109
08f85138
MT
110 def incomplete(self, info_hash):
111 ret = self.db.get("SELECT COUNT(*) AS c FROM tracker \
112 WHERE hash = %s AND left_data > 0", info_hash)
113
114 if ret:
115 return ret.c
116
117 def handle_event(self, event, peer_id, info_hash, **kwargs):
118 # stopped
119 if event == "stopped":
120 self.remove_peer(peer_id, info_hash)
121
122 def peer_exists(self, peer_id, info_hash):
123 ret = self.db.get("SELECT COUNT(*) AS c FROM tracker \
124 WHERE id = %s AND hash = %s", peer_id, info_hash)
125
126 if ret and ret.c > 0:
127 return True
128
129 return False
130
307f7396
MT
131 def __insert_peer(self, peer_id, info_hash, address6=None, port6=None, address4=None, port4=None, left_data=0, **kwargs):
132 self.db.execute("INSERT INTO tracker(id, hash, address6, port6, address4, port4, left_data) \
133 VALUES(%s, %s, %s, %s, %s, %s, %s)", peer_id, info_hash, address6, port6, address4, port4, left_data)
08f85138
MT
134
135 def __update_peer(self, peer_id, info_hash, address6=None, port6=None,
136 address4=None, port4=None, downloaded=None, uploaded=None, left_data=None):
9068dba1
MT
137 query = "UPDATE tracker SET last_update = NOW()"
138 args = []
43d991f6 139
9068dba1
MT
140 if address6:
141 query += ", address6 = %s"
142 args.append(address6)
43d991f6 143
9068dba1
MT
144 if port6:
145 query += ", port6 = %s"
146 args.append(port6)
43d991f6 147
9068dba1
MT
148 if address4:
149 query += ", address4 = %s"
150 args.append(address4)
43d991f6 151
9068dba1
MT
152 if port4:
153 query += ", port4 = %s"
154 args.append(port4)
e2afbd6a 155
9068dba1
MT
156 if downloaded:
157 query += ", downloaded = %s"
158 args.append(downloaded)
e2afbd6a 159
9068dba1
MT
160 if uploaded:
161 query += ", uploaded = %s"
162 args.append(uploaded)
e2afbd6a 163
9068dba1
MT
164 if left_data:
165 query += ", left_data = %s"
166 args.append(left_data)
e2afbd6a 167
9068dba1
MT
168 query += " WHERE id = %s AND hash = %s"
169 args += [peer_id, info_hash]
43d991f6 170
9068dba1 171 self.db.execute(query, *args)
d2a80c6e 172
9068dba1
MT
173 def remove_peer(self, peer_id, info_hash):
174 self.db.execute("DELETE FROM tracker \
175 WHERE id = %s AND hash = %s", peer_id, info_hash)
43d991f6 176
9068dba1
MT
177 def scrape(self, info_hashes):
178 ret = {
179 "files" : {},
180 "flags" : {
181 "min_request_interval" : self.interval,
182 }
183 }
479f3388 184
ea324f48
MT
185 if info_hashes:
186 for info_hash in info_hashes:
187 ret["files"][info_hash] = {
188 "complete" : self.complete(info_hash),
189 "incomplete" : self.incomplete(info_hash),
190 "downloaded" : 0,
191 }
9068dba1
MT
192
193 return ret
479f3388 194
43d991f6 195
43d991f6
MT
196##### This is borrowed from the bittorrent client libary #####
197
198def decode_int(x, f):
199 f += 1
200 newf = x.index('e', f)
201 n = int(x[f:newf])
202 if x[f] == '-':
203 if x[f + 1] == '0':
204 raise ValueError
205 elif x[f] == '0' and newf != f+1:
206 raise ValueError
207 return (n, newf+1)
208
209def decode_string(x, f):
210 colon = x.index(':', f)
211 n = int(x[f:colon])
212 if x[f] == '0' and colon != f+1:
213 raise ValueError
214 colon += 1
215 return (x[colon:colon+n], colon+n)
216
217def decode_list(x, f):
218 r, f = [], f+1
219 while x[f] != 'e':
220 v, f = decode_func[x[f]](x, f)
221 r.append(v)
222 return (r, f + 1)
223
224def decode_dict(x, f):
225 r, f = {}, f+1
226 while x[f] != 'e':
227 k, f = decode_string(x, f)
228 r[k], f = decode_func[x[f]](x, f)
229 return (r, f + 1)
230
231decode_func = {}
232decode_func['l'] = decode_list
233decode_func['d'] = decode_dict
234decode_func['i'] = decode_int
235decode_func['0'] = decode_string
236decode_func['1'] = decode_string
237decode_func['2'] = decode_string
238decode_func['3'] = decode_string
239decode_func['4'] = decode_string
240decode_func['5'] = decode_string
241decode_func['6'] = decode_string
242decode_func['7'] = decode_string
243decode_func['8'] = decode_string
244decode_func['9'] = decode_string
245
246def bdecode(x):
247 try:
248 r, l = decode_func[x[0]](x, 0)
249 except (IndexError, KeyError, ValueError):
250 raise Exception("not a valid bencoded string")
251 if l != len(x):
252 raise Exception("invalid bencoded value (data after valid prefix)")
253 return r
254
255from types import StringType, IntType, LongType, DictType, ListType, TupleType
256
257
258class Bencached(object):
259
260 __slots__ = ['bencoded']
261
262 def __init__(self, s):
263 self.bencoded = s
264
265def encode_bencached(x,r):
266 r.append(x.bencoded)
267
268def encode_int(x, r):
269 r.extend(('i', str(x), 'e'))
270
271def encode_bool(x, r):
272 if x:
273 encode_int(1, r)
274 else:
275 encode_int(0, r)
276
277def encode_string(x, r):
278 r.extend((str(len(x)), ':', x))
279
280def encode_list(x, r):
281 r.append('l')
282 for i in x:
283 encode_func[type(i)](i, r)
284 r.append('e')
285
286def encode_dict(x,r):
287 r.append('d')
288 ilist = x.items()
289 ilist.sort()
290 for k, v in ilist:
291 r.extend((str(len(k)), ':', k))
292 encode_func[type(v)](v, r)
293 r.append('e')
294
295encode_func = {}
296encode_func[Bencached] = encode_bencached
297encode_func[IntType] = encode_int
298encode_func[LongType] = encode_int
299encode_func[StringType] = encode_string
300encode_func[ListType] = encode_list
301encode_func[TupleType] = encode_list
302encode_func[DictType] = encode_dict
303
304try:
305 from types import BooleanType
306 encode_func[BooleanType] = encode_bool
307except ImportError:
308 pass
309
310def bencode(x):
311 r = []
312 encode_func[type(x)](x, r)
313 return ''.join(r)