]>
Commit | Line | Data |
---|---|---|
43d991f6 MT |
1 | #!/usr/bin/python |
2 | ||
3 | import time | |
4 | ||
5 | import tornado.database | |
6 | ||
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("%0x" % i) | |
16 | ||
17 | return "".join(ret) | |
18 | ||
19 | class 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 | ||
129 | tracker = Tracker() | |
130 | ||
131 | ||
132 | ##### This is borrowed from the bittorrent client libary ##### | |
133 | ||
134 | def 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 | ||
145 | def 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 | ||
153 | def 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 | ||
160 | def 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 | ||
167 | decode_func = {} | |
168 | decode_func['l'] = decode_list | |
169 | decode_func['d'] = decode_dict | |
170 | decode_func['i'] = decode_int | |
171 | decode_func['0'] = decode_string | |
172 | decode_func['1'] = decode_string | |
173 | decode_func['2'] = decode_string | |
174 | decode_func['3'] = decode_string | |
175 | decode_func['4'] = decode_string | |
176 | decode_func['5'] = decode_string | |
177 | decode_func['6'] = decode_string | |
178 | decode_func['7'] = decode_string | |
179 | decode_func['8'] = decode_string | |
180 | decode_func['9'] = decode_string | |
181 | ||
182 | def 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 | ||
191 | from types import StringType, IntType, LongType, DictType, ListType, TupleType | |
192 | ||
193 | ||
194 | class Bencached(object): | |
195 | ||
196 | __slots__ = ['bencoded'] | |
197 | ||
198 | def __init__(self, s): | |
199 | self.bencoded = s | |
200 | ||
201 | def encode_bencached(x,r): | |
202 | r.append(x.bencoded) | |
203 | ||
204 | def encode_int(x, r): | |
205 | r.extend(('i', str(x), 'e')) | |
206 | ||
207 | def encode_bool(x, r): | |
208 | if x: | |
209 | encode_int(1, r) | |
210 | else: | |
211 | encode_int(0, r) | |
212 | ||
213 | def encode_string(x, r): | |
214 | r.extend((str(len(x)), ':', x)) | |
215 | ||
216 | def 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 | ||
222 | def 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 | ||
231 | encode_func = {} | |
232 | encode_func[Bencached] = encode_bencached | |
233 | encode_func[IntType] = encode_int | |
234 | encode_func[LongType] = encode_int | |
235 | encode_func[StringType] = encode_string | |
236 | encode_func[ListType] = encode_list | |
237 | encode_func[TupleType] = encode_list | |
238 | encode_func[DictType] = encode_dict | |
239 | ||
240 | try: | |
241 | from types import BooleanType | |
242 | encode_func[BooleanType] = encode_bool | |
243 | except ImportError: | |
244 | pass | |
245 | ||
246 | def bencode(x): | |
247 | r = [] | |
248 | encode_func[type(x)](x, r) | |
249 | return ''.join(r) |