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