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