]>
Commit | Line | Data |
---|---|---|
43d991f6 MT |
1 | #!/usr/bin/python |
2 | ||
938d083d | 3 | import os |
479f3388 | 4 | import random |
43d991f6 MT |
5 | import time |
6 | ||
938d083d MT |
7 | import releases |
8 | ||
9 | from databases import Databases, Row | |
940227cb MT |
10 | from misc import Singleton |
11 | ||
43d991f6 MT |
12 | def 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 | ||
23 | class 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 | ||
171 | def 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 | ||
182 | def 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 | ||
190 | def 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 | ||
197 | def 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 | ||
204 | decode_func = {} | |
205 | decode_func['l'] = decode_list | |
206 | decode_func['d'] = decode_dict | |
207 | decode_func['i'] = decode_int | |
208 | decode_func['0'] = decode_string | |
209 | decode_func['1'] = decode_string | |
210 | decode_func['2'] = decode_string | |
211 | decode_func['3'] = decode_string | |
212 | decode_func['4'] = decode_string | |
213 | decode_func['5'] = decode_string | |
214 | decode_func['6'] = decode_string | |
215 | decode_func['7'] = decode_string | |
216 | decode_func['8'] = decode_string | |
217 | decode_func['9'] = decode_string | |
218 | ||
219 | def 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 | ||
228 | from types import StringType, IntType, LongType, DictType, ListType, TupleType | |
229 | ||
230 | ||
231 | class Bencached(object): | |
232 | ||
233 | __slots__ = ['bencoded'] | |
234 | ||
235 | def __init__(self, s): | |
236 | self.bencoded = s | |
237 | ||
238 | def encode_bencached(x,r): | |
239 | r.append(x.bencoded) | |
240 | ||
241 | def encode_int(x, r): | |
242 | r.extend(('i', str(x), 'e')) | |
243 | ||
244 | def encode_bool(x, r): | |
245 | if x: | |
246 | encode_int(1, r) | |
247 | else: | |
248 | encode_int(0, r) | |
249 | ||
250 | def encode_string(x, r): | |
251 | r.extend((str(len(x)), ':', x)) | |
252 | ||
253 | def 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 | ||
259 | def 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 | ||
268 | encode_func = {} | |
269 | encode_func[Bencached] = encode_bencached | |
270 | encode_func[IntType] = encode_int | |
271 | encode_func[LongType] = encode_int | |
272 | encode_func[StringType] = encode_string | |
273 | encode_func[ListType] = encode_list | |
274 | encode_func[TupleType] = encode_list | |
275 | encode_func[DictType] = encode_dict | |
276 | ||
277 | try: | |
278 | from types import BooleanType | |
279 | encode_func[BooleanType] = encode_bool | |
280 | except ImportError: | |
281 | pass | |
282 | ||
283 | def bencode(x): | |
284 | r = [] | |
285 | encode_func[type(x)](x, r) | |
286 | return ''.join(r) |