]> git.ipfire.org Git - people/shoehn/ipfire.org.git/blame - www/webapp/backend/tracker.py
Add torrent and magnet links for all downloads.
[people/shoehn/ipfire.org.git] / www / webapp / backend / tracker.py
CommitLineData
43d991f6
MT
1#!/usr/bin/python
2
479f3388 3import random
43d991f6
MT
4import time
5
940227cb
MT
6from databases import Databases
7from misc import Singleton
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
20class Tracker(object):
940227cb 21 id = "TheIPFireTorrentTracker"
43d991f6 22
940227cb 23 # Intervals # XXX needs to be in Settings
479f3388
MT
24 _interval = 60*60
25 _min_interval = 30*60
26
27 random_interval = -60, 60
43d991f6
MT
28
29 numwant = 50
30
feb02477
MT
31 @property
32 def db(self):
940227cb 33 return Databases().tracker
43d991f6 34
e2afbd6a 35 def _fetch(self, hash, limit=None, random=False, completed=False, no_peer_id=False):
43d991f6
MT
36 query = "SELECT * FROM peers WHERE last_update >= %d" % self.since
37
38 if hash:
39 query += " AND hash = '%s'" % hash
40
41 if completed:
42 query += " AND left_data = 0"
940227cb
MT
43 else:
44 query += " AND left_data != 0"
43d991f6
MT
45
46 if random:
47 query += " ORDER BY RAND()"
48
49 if limit:
50 query += " LIMIT %s" % limit
51
52 peers = []
53 for peer in self.db.query(query):
54 if not peer.ip or not peer.port:
55 continue
56
e2afbd6a 57 peer_dict = {
43d991f6
MT
58 "ip" : str(peer.ip),
59 "port" : int(peer.port),
e2afbd6a
MT
60 }
61
62 if not no_peer_id:
63 peer_dict["peer id"] = str(peer.id),
64
65 peers.append(peer_dict)
43d991f6
MT
66
67 return peers
68
69 def get_peers(self, hash, **kwargs):
70 return self._fetch(hash, **kwargs)
71
72 def get_seeds(self, hash, **kwargs):
73 kwargs.update({"completed" : True})
74 return self._fetch(hash, **kwargs)
75
76 def complete(self, hash):
77 return len(self.get_seeds(hash))
78
79 def incomplete(self, hash):
80 return len(self.get_peers(hash))
81
82 def event_started(self, hash, peer_id):
83 # Damn, mysql does not support INSERT IF NOT EXISTS...
84 if not self.db.query("SELECT id FROM peers WHERE hash = '%s' AND peer_id = '%s'" % (hash, peer_id)):
85 self.db.execute("INSERT INTO peers(hash, peer_id) VALUES('%s', '%s')" % (hash, peer_id))
86
940227cb 87 if not hash in self.hashes:
43d991f6
MT
88 self.db.execute("INSERT INTO hashes(hash) VALUES('%s')" % hash)
89
90 def event_stopped(self, hash, peer_id):
91 self.db.execute("DELETE FROM peers WHERE hash = '%s' AND peer_id = '%s'" % (hash, peer_id))
92
93 def event_completed(self, hash, peer_id):
94 self.db.execute("UPDATE hashes SET completed=completed+1 WHERE hash = '%s'" % hash)
95
e2afbd6a
MT
96 def scrape(self, hashes=[]):
97 ret = {}
98 for hash in self.db.query("SELECT hash, completed FROM hashes"):
99 hash, completed = hash.hash, hash.completed
100
101 if hashes and hash not in hashes:
102 continue
103
104 ret[hash] = {
105 "complete" : self.complete(hash),
106 "downloaded" : completed or 0,
107 "incomplete" : self.incomplete(hash),
108 }
109
110 return ret
111
43d991f6
MT
112 def update(self, hash, id, ip=None, port=None, downloaded=None, uploaded=None, left=None):
113 args = [ "last_update = '%s'" % self.now ]
114
115 if ip:
d2a80c6e
MT
116 if ip.startswith("172.28.1."):
117 ip = "178.63.73.246"
118
43d991f6
MT
119 args.append("ip='%s'" % ip)
120
121 if port:
122 args.append("port='%s'" % port)
123
124 if downloaded:
125 args.append("downloaded='%s'" % downloaded)
126
127 if uploaded:
128 args.append("uploaded='%s'" % uploaded)
129
130 if left:
131 args.append("left_data='%s'" % left)
132
133 if not args:
134 return
135
136 query = "UPDATE peers SET " + ", ".join(args) + \
137 " WHERE hash = '%s' AND peer_id = '%s'" % (hash, id)
138
139 self.db.execute(query)
140
141 @property
142 def hashes(self):
940227cb
MT
143 hashes = []
144 for h in self.db.query("SELECT hash FROM hashes"):
145 hashes.append(h["hash"].lower())
146
147 return hashes
43d991f6
MT
148
149 @property
150 def now(self):
151 return int(time.time())
152
153 @property
154 def since(self):
155 return int(time.time() - self.interval)
156
479f3388
MT
157 @property
158 def interval(self):
159 return self._interval + random.randint(*self.random_interval)
160
161 @property
162 def min_interval(self):
163 return self._min_interval + random.randint(*self.random_interval)
164
43d991f6 165
43d991f6
MT
166##### This is borrowed from the bittorrent client libary #####
167
168def decode_int(x, f):
169 f += 1
170 newf = x.index('e', f)
171 n = int(x[f:newf])
172 if x[f] == '-':
173 if x[f + 1] == '0':
174 raise ValueError
175 elif x[f] == '0' and newf != f+1:
176 raise ValueError
177 return (n, newf+1)
178
179def decode_string(x, f):
180 colon = x.index(':', f)
181 n = int(x[f:colon])
182 if x[f] == '0' and colon != f+1:
183 raise ValueError
184 colon += 1
185 return (x[colon:colon+n], colon+n)
186
187def decode_list(x, f):
188 r, f = [], f+1
189 while x[f] != 'e':
190 v, f = decode_func[x[f]](x, f)
191 r.append(v)
192 return (r, f + 1)
193
194def decode_dict(x, f):
195 r, f = {}, f+1
196 while x[f] != 'e':
197 k, f = decode_string(x, f)
198 r[k], f = decode_func[x[f]](x, f)
199 return (r, f + 1)
200
201decode_func = {}
202decode_func['l'] = decode_list
203decode_func['d'] = decode_dict
204decode_func['i'] = decode_int
205decode_func['0'] = decode_string
206decode_func['1'] = decode_string
207decode_func['2'] = decode_string
208decode_func['3'] = decode_string
209decode_func['4'] = decode_string
210decode_func['5'] = decode_string
211decode_func['6'] = decode_string
212decode_func['7'] = decode_string
213decode_func['8'] = decode_string
214decode_func['9'] = decode_string
215
216def bdecode(x):
217 try:
218 r, l = decode_func[x[0]](x, 0)
219 except (IndexError, KeyError, ValueError):
220 raise Exception("not a valid bencoded string")
221 if l != len(x):
222 raise Exception("invalid bencoded value (data after valid prefix)")
223 return r
224
225from types import StringType, IntType, LongType, DictType, ListType, TupleType
226
227
228class Bencached(object):
229
230 __slots__ = ['bencoded']
231
232 def __init__(self, s):
233 self.bencoded = s
234
235def encode_bencached(x,r):
236 r.append(x.bencoded)
237
238def encode_int(x, r):
239 r.extend(('i', str(x), 'e'))
240
241def encode_bool(x, r):
242 if x:
243 encode_int(1, r)
244 else:
245 encode_int(0, r)
246
247def encode_string(x, r):
248 r.extend((str(len(x)), ':', x))
249
250def encode_list(x, r):
251 r.append('l')
252 for i in x:
253 encode_func[type(i)](i, r)
254 r.append('e')
255
256def encode_dict(x,r):
257 r.append('d')
258 ilist = x.items()
259 ilist.sort()
260 for k, v in ilist:
261 r.extend((str(len(k)), ':', k))
262 encode_func[type(v)](v, r)
263 r.append('e')
264
265encode_func = {}
266encode_func[Bencached] = encode_bencached
267encode_func[IntType] = encode_int
268encode_func[LongType] = encode_int
269encode_func[StringType] = encode_string
270encode_func[ListType] = encode_list
271encode_func[TupleType] = encode_list
272encode_func[DictType] = encode_dict
273
274try:
275 from types import BooleanType
276 encode_func[BooleanType] = encode_bool
277except ImportError:
278 pass
279
280def bencode(x):
281 r = []
282 encode_func[type(x)](x, r)
283 return ''.join(r)