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