]> git.ipfire.org Git - ipfire.org.git/blob - www/webapp/handlers.py
tracker: Add scrape functionality.
[ipfire.org.git] / www / webapp / handlers.py
1 #!/usr/bin/python
2
3 import datetime
4 import httplib
5 import mimetypes
6 import operator
7 import os
8 import simplejson
9 import stat
10 import sqlite3
11 import time
12 import urlparse
13
14 import tornado.httpclient
15 import tornado.locale
16 import tornado.web
17
18 from banners import banners
19 from helpers import size, Item
20 from info import info
21 from mirrors import mirrors
22 from news import news
23 from releases import releases
24 from torrent import tracker, bencode, bdecode, decode_hex
25
26 import builds
27 import cluster
28 import menu
29 import translations
30 #import uriel
31
32 class BaseHandler(tornado.web.RequestHandler):
33 def get_user_locale(self):
34 uri = self.request.uri.split("/")
35 if len(uri) > 1:
36 for lang in tornado.locale.get_supported_locales(None):
37 if lang[:2] == uri[1]:
38 return tornado.locale.get(lang)
39
40 @property
41 def render_args(self):
42 return {
43 "banner" : banners.get(),
44 "lang" : self.locale.code[:2],
45 "langs" : [l[:2] for l in tornado.locale.get_supported_locales(None)],
46 "lang_link" : self.lang_link,
47 "link" : self.link,
48 "title" : "no title given",
49 "server" : self.request.host.replace("ipfire", "<span>ipfire</span>"),
50 "uri" : self.request.uri,
51 "year" : time.strftime("%Y"),
52 }
53
54 def render(self, *args, **kwargs):
55 nargs = self.render_args
56 nargs.update(kwargs)
57 nargs["title"] = "%s - %s" % (self.request.host, nargs["title"])
58 tornado.web.RequestHandler.render(self, *args, **nargs)
59
60 def link(self, s):
61 return "/%s/%s" % (self.locale.code[:2], s)
62
63 def lang_link(self, lang):
64 return "/%s/%s" % (lang, self.request.uri[4:])
65
66 def get_error_html(self, status_code, **kwargs):
67 if status_code in (404, 500):
68 render_args = self.render_args
69 render_args.update({
70 "code" : status_code,
71 "exception" : kwargs.get("exception", None),
72 "message" : httplib.responses[status_code],
73 })
74 return self.render_string("error-%s.html" % status_code, **render_args)
75 else:
76 return tornado.web.RequestHandler.get_error_html(self, status_code, **kwargs)
77
78 @property
79 def hash_db(self):
80 return self.application.hash_db
81
82 class MainHandler(BaseHandler):
83 def get(self):
84 lang = self.locale.code[:2]
85 self.redirect("/%s/index" % (lang))
86
87
88 class DownloadHandler(BaseHandler):
89 def get(self):
90 self.render("downloads.html", release=releases.latest)
91
92
93 class DownloadAllHandler(BaseHandler):
94 def get(self):
95 self.render("downloads-all.html", releases=releases)
96
97
98 class DownloadDevelopmentHandler(BaseHandler):
99 def get(self):
100 self.render("downloads-development.html", releases=releases)
101
102
103 class DownloadTorrentHandler(BaseHandler):
104 tracker_url = "http://tracker.ipfire.org:6969/stats?format=txt&mode=tpbs"
105
106 @tornado.web.asynchronous
107 def get(self):
108 http = tornado.httpclient.AsyncHTTPClient()
109 http.fetch(self.tracker_url, callback=self.async_callback(self.on_response))
110
111 def on_response(self, response):
112 torrents = releases.torrents
113 hashes = {}
114 if response.code == 200:
115 for line in response.body.split("\n"):
116 if not line: continue
117 hash, seeds, peers = line.split(":")
118 hash.lower()
119 hashes[hash] = {
120 "peers" : peers,
121 "seeds" : seeds,
122 }
123
124 self.render("downloads-torrents.html",
125 hashes=hashes,
126 releases=torrents,
127 request_time=response.request_time,
128 tracker=urlparse.urlparse(response.request.url).netloc)
129
130
131 class DownloadMirrorHandler(BaseHandler):
132 def get(self):
133 self.render("downloads-mirrors.html", mirrors=mirrors)
134
135
136 class StaticHandler(BaseHandler):
137 @property
138 def static_path(self):
139 return os.path.join(self.application.settings["template_path"], "static")
140
141 @property
142 def static_files(self):
143 ret = []
144 for filename in os.listdir(self.static_path):
145 if filename.endswith(".html"):
146 ret.append(filename)
147 return ret
148
149 def get(self, name=None):
150 name = "%s.html" % name
151
152 if not name in self.static_files:
153 raise tornado.web.HTTPError(404)
154
155 self.render("static/%s" % name)
156
157
158 class IndexHandler(BaseHandler):
159 def get(self):
160 self.render("index.html", news=news)
161
162
163 class NewsHandler(BaseHandler):
164 def get(self):
165 self.render("news.html", news=news)
166
167
168 class BuildHandler(BaseHandler):
169 def prepare(self):
170 self.builds = {
171 "<12h" : [],
172 ">12h" : [],
173 ">24h" : [],
174 }
175
176 for build in builds.find():
177 if (time.time() - float(build.get("date"))) < 12*60*60:
178 self.builds["<12h"].append(build)
179 elif (time.time() - float(build.get("date"))) < 24*60*60:
180 self.builds[">12h"].append(build)
181 else:
182 self.builds[">24h"].append(build)
183
184 for l in self.builds.values():
185 l.sort()
186
187 def get(self):
188 self.render("builds.html", builds=self.builds)
189
190
191 class UrielBaseHandler(BaseHandler):
192 #db = uriel.Database()
193 pass
194
195 class UrielHandler(UrielBaseHandler):
196 def get(self):
197 pass
198
199
200 class ApiClusterInfoHandler(BaseHandler):
201 def get(self):
202 id = self.get_argument("id", "null")
203
204 c = cluster.Cluster(info["cluster"]["hostname"])
205
206 self.write(simplejson.dumps({
207 "version": "1.1",
208 "id": id,
209 "result" : c.json,
210 "error" : "null", }))
211 self.finish()
212
213
214 class TranslationHandler(BaseHandler):
215 def get(self):
216 self.render("translations.html", projects=translations.projects)
217
218
219 class SourceHandler(BaseHandler):
220 def get(self):
221 source_path = "/srv/sources"
222 fileobjects = []
223
224 for dir, subdirs, files in os.walk(source_path):
225 if not files:
226 continue
227 for file in files:
228 if file in [f["name"] for f in fileobjects]:
229 continue
230
231 hash = self.hash_db.get_hash(os.path.join(dir, file))
232
233 if not hash:
234 hash = "0000000000000000000000000000000000000000"
235
236 fileobjects.append({
237 "dir" : dir[len(source_path)+1:],
238 "name" : file,
239 "hash" : hash,
240 "size" : size(os.path.getsize(os.path.join(source_path, dir, file))),
241 })
242
243 fileobjects.sort(key=operator.itemgetter("name"))
244
245 self.render("sources.html", files=fileobjects)
246
247
248 class SourceDownloadHandler(BaseHandler):
249 def head(self, path):
250 self.get(path, include_body=False)
251
252 def get(self, path, include_body=True):
253 source_path = "/srv/sources"
254
255 path = os.path.abspath(os.path.join(source_path, path[1:]))
256
257 if not path.startswith(source_path):
258 raise tornado.web.HTTPError(403)
259 if not os.path.exists(path):
260 raise tornado.web.HTTPError(404)
261
262 stat_result = os.stat(path)
263 modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME])
264
265 self.set_header("Last-Modified", modified)
266 self.set_header("Content-Length", stat_result[stat.ST_SIZE])
267
268 mime_type, encoding = mimetypes.guess_type(path)
269 if mime_type:
270 self.set_header("Content-Type", mime_type)
271
272 hash = self.hash_db.get_hash(path)
273 if hash:
274 self.set_header("X-Hash-Sha1", "%s" % hash)
275
276 if not include_body:
277 return
278 file = open(path, "r")
279 try:
280 self.write(file.read())
281 finally:
282 file.close()
283
284
285 class DownloadFileHandler(BaseHandler):
286 def get(self, path):
287 for mirror in mirrors.with_file(path):
288 if not mirror.reachable:
289 continue
290
291 self.redirect(mirror.url + path)
292 return
293
294 raise tornado.web.HTTPError(404)
295
296 def get_error_html(self, status_code, **kwargs):
297 return tornado.web.RequestHandler.get_error_html(self, status_code, **kwargs)
298
299
300 class RSSHandler(BaseHandler):
301 def get(self, lang):
302 items = []
303 for item in news.get(15):
304 item = Item(**item.args.copy())
305 for attr in ("subject", "content"):
306 if type(item[attr]) == type({}):
307 item[attr] = item[attr][lang]
308 items.append(item)
309
310 self.set_header("Content-Type", "application/rss+xml")
311 self.render("rss.xml", items=items, lang=lang)
312
313
314 class TrackerBaseHandler(tornado.web.RequestHandler):
315 def get_hexencoded_argument(self, name, all=False):
316 try:
317 arguments = self.request.arguments[name]
318 except KeyError:
319 return None
320
321 arguments_new = []
322 for argument in arguments:
323 arguments_new.append(decode_hex(argument))
324
325 arguments = arguments_new
326
327 if all:
328 return arguments
329
330 return arguments[0]
331
332 def send_tracker_error(self, error_message):
333 self.write(bencode({"failure reason" : error_message }))
334 self.finish()
335
336 class TrackerAnnounceHandler(TrackerBaseHandler):
337 def get(self):
338 self.set_header("Content-Type", "text/plain")
339
340 info_hash = self.get_hexencoded_argument("info_hash")
341 if not info_hash:
342 self.send_tracker_error("Your client forgot to send your torrent's info_hash.")
343 return
344
345 peer = {
346 "id" : self.get_hexencoded_argument("peer_id"),
347 "ip" : self.get_argument("ip", None),
348 "port" : self.get_argument("port", None),
349 "downloaded" : self.get_argument("downloaded", 0),
350 "uploaded" : self.get_argument("uploaded", 0),
351 "left" : self.get_argument("left", 0),
352 }
353
354 event = self.get_argument("event", "")
355 if not event in ("started", "stopped", "completed", ""):
356 self.send_tracker_error("Got unknown event")
357 return
358
359 if peer["ip"]:
360 if peer["ip"].startswith("10.") or \
361 peer["ip"].startswith("172.") or \
362 peer["ip"].startswith("192.168."):
363 peer["ip"] = self.request.remote_ip
364
365 if peer["port"]:
366 peer["port"] = int(peer["port"])
367
368 if peer["port"] < 0 or peer["port"] > 65535:
369 self.send_tracker_error("Port number is not in valid range")
370 return
371
372 eventhandlers = {
373 "started" : tracker.event_started,
374 "stopped" : tracker.event_stopped,
375 "completed" : tracker.event_completed,
376 }
377
378 if event:
379 eventhandlers[event](info_hash, peer["id"])
380
381 tracker.update(hash=info_hash, **peer)
382
383 no_peer_id = self.get_argument("no_peer_id", False)
384 numwant = self.get_argument("numwant", tracker.numwant)
385
386 self.write(bencode({
387 "tracker id" : tracker.id,
388 "interval" : tracker.interval,
389 "min interval" : tracker.min_interval,
390 "peers" : tracker.get_peers(info_hash, limit=numwant,
391 random=True, no_peer_id=no_peer_id),
392 "complete" : tracker.complete(info_hash),
393 "incomplete" : tracker.incomplete(info_hash),
394 }))
395 self.finish()
396
397
398 class TrackerScrapeHandler(TrackerBaseHandler):
399 def get(self):
400 info_hashes = self.get_hexencoded_argument("info_hash", all=True)
401
402 self.write(bencode(tracker.scrape(hashes=info_hashes)))
403 self.finish()