diff options
author | RaNaN <Mast3rRaNaN@hotmail.de> | 2009-12-02 20:36:43 +0100 |
---|---|---|
committer | RaNaN <Mast3rRaNaN@hotmail.de> | 2009-12-02 20:36:43 +0100 |
commit | 106e79456886563e4ee4ed43027bc69984f65928 (patch) | |
tree | bd1d1ea1aabe2bac3faf77117145466382e5f3b7 | |
parent | New Update Function, pycurl able to just load headers, little fixes (diff) | |
download | pyload-106e79456886563e4ee4ed43027bc69984f65928.tar.xz |
new bottle.py, re implemented webserver(not ready yet)
-rw-r--r-- | config | 4 | ||||
-rw-r--r-- | module/web/WebServer.py | 428 | ||||
-rw-r--r-- | module/web/bottle.py | 727 | ||||
-rwxr-xr-x | pyLoadCore.py | 57 |
4 files changed, 929 insertions, 287 deletions
@@ -5,12 +5,12 @@ username = admin password = pwhere [ssl] -activated = True +activated = False cert = ssl.crt key = ssl.key [webinterface] -activated = False +activated = True listenaddr = 0.0.0.0 port = 8080 username = User diff --git a/module/web/WebServer.py b/module/web/WebServer.py index 0712f1dce..29b0aafe8 100644 --- a/module/web/WebServer.py +++ b/module/web/WebServer.py @@ -1,72 +1,364 @@ -import sys -from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler -from xmlrpclib import ServerProxy -from time import time -import re +#import sys +#from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler +#from xmlrpclib import ServerProxy +#from time import time +#import re +# +#class Handler(BaseHTTPRequestHandler): +# +# def do_GET(self): +# global coreserver +# stdout = sys.stdout +# sys.stdout = self.wfile +# if self.path == "/": +# print "Server Runs" +# elif self.path == "/downloads": +# print self.get_downloads() +# elif re.search("/add=.?", self.path): +# if re.match(is_url, self.path.split("/add=")[1]): +# coreserver.add_urls([self.path.split("/add=")[1]]) +# print "Link Added" +# else: +# try: +# print open(self.path[1:], 'r').read() +# except IOError: +# self.send_error(404) +# +# def format_size(self, size): +# return str(size / 1024) + " MiB" +# +# def format_time(self,seconds): +# seconds = int(seconds) +# hours, seconds = divmod(seconds, 3600) +# minutes, seconds = divmod(seconds, 60) +# return "%.2i:%.2i:%.2i" % (hours, minutes, seconds) +# +# def get_downloads(self): +# data = coreserver.status_downloads() +# for download in data: +# print "<h3>%s</h3>" % download["name"] +# if download["status"] == "downloading": +# percent = download["percent"] +# z = percent / 4 +# print "<h3>%s</h3>" % dl_name +# print "<font face='font-family:Fixedsys,Courier,monospace;'>[" + z * "#" + (25-z) * " " + "]</font>" + str(percent) + "%<br />" +# print "Speed: " + str(int(download['speed'])) + " kb/s" +# print "Size: " + self.format_size(download['size']) +# print "Finished in: " + self.format_time(download['eta']) +# print "ID: " + str(download['id']) +# dl_status = "[" + z * "#" + (25-z) * " " + "] " + str(percent) + "%" + " Speed: " + str(int(download['speed'])) + " kb/s" + " Size: " + self.format_size(download['size']) + " Finished in: " + self.format_time(download['eta']) + " ID: " + str(download['id']) +# if download["status"] == "waiting": +# print "waiting: " + self.format_time(download["wait_until"]- time()) +# +#is_url = re.compile("^(((https?|ftp)\:\/\/)?([\w\.\-]+(\:[\w\.\&%\$\-]+)*@)?((([^\s\(\)\<\>\\\"\.\[\]\,@;:]+)(\.[^\s\(\)\<\>\\\"\.\[\]\,@;:]+)*(\.[a-zA-Z]{2,4}))|((([01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}([01]?\d{1,2}|2[0-4]\d|25[0-5])))(\b\:(6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)\b)?((\/[^\/][\w\.\,\?\'\\\/\+&%\$#\=~_\-@]*)*[^\.\,\?\"\'\(\)\[\]!;<>{}\s\x7F-\xFF])?)$",re.IGNORECASE) +# +#coreserver = None +# +#class WebServer(): +# +# def start(self): +# try: +# global coreserver +# coreserver = ServerProxy("https://testuser:testpw@localhost:1337", allow_none=True) +# webserver = HTTPServer(('',8080),Handler) +# print 'server started at port 8080' +# webserver.serve_forever() +# except KeyboardInterrupt: +# webserver.socket.close() +# +#if __name__ == "__main__": +# web = WebServer() +# web.start() -class Handler(BaseHTTPRequestHandler): + +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#Copyright (C) 2009 RaNaN +# +#This program is free software; you can redistribute it and/or modify +#it under the terms of the GNU General Public License as published by +#the Free Software Foundation; either version 3 of the License, +#or (at your option) any later version. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#See the GNU General Public License for more details. +# +#You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# +### +import random +import threading +import time + +import bottle +from bottle import abort +from bottle import redirect +from bottle import request +from bottle import response +from bottle import route +from bottle import run +from bottle import send_file +from bottle import template +from bottle import validate + + +core = None +core_methods = None + +PATH = "./module/web/" +TIME = time.strftime("%a, %d %b %Y 00:00:00 +0000", time.localtime()) #set time to current day +USERS = {} +# TODO: Implement new server methods +@route('/login', method='POST') +def do_login(): + #print request.GET + + + username = core.config['webinterface']['username'] + pw = core.config['webinterface']['password'] + + if request.POST['u'] == username and request.POST['p'] == pw: + + id = int(random.getrandbits(16)) + ua = request.HEADER("HTTP_USER_AGENT") + ip = request.HEADER("REMOTE_ADDR") + + auth = {} + + auth['ua'] = ua + auth['ip'] = ip + auth['user'] = username + + USERS[id] = auth + + response.COOKIES['user'] = username + response.COOKIES['id'] = id + + return template('default', page='loggedin', user=username) + else: + return template('default', page='login') + +@route('/login') +def login(): + + if check_auth(request): + redirect("/") + + return template('default', page='login') + +@route('/logout') +def logout(): + try: + del USERS[int(request.COOKIES.get('id'))] + except: + pass - def do_GET(self): - global coreserver - stdout = sys.stdout - sys.stdout = self.wfile - if self.path == "/": - print "Server Runs" - elif self.path == "/downloads": - print self.get_downloads() - elif re.search("/add=.?", self.path): - if re.match(is_url, self.path.split("/add=")[1]): - coreserver.add_urls([self.path.split("/add=")[1]]) - print "Link Added" + redirect("/login") + +@route('/') +def home(): + + if not check_auth(request): + redirect("/login") + + username = request.COOKIES.get('user') + + dls = core_methods.status_downloads() + + for dl in dls: + dl['eta'] = str(core.format_time(dl['eta'])) + dl['wait_until'] = str(core.format_time(dl['wait_until'] - time.time())) + + + return template('default', page='home', links=dls, user=username, status=core_methods.status_server()) + +@route('/queue') +def queue(): + + if not check_auth(request): + redirect("/login") + + username = request.COOKIES.get('user') + + return template('default', page='queue', links=core.get_links(), user=username, status=core_methods.status_server()) + +@route('/downloads') +def downloads(): + + if not check_auth(request): + redirect("/login") + + username = request.COOKIES.get('user') + + return template('default', page='downloads', links=core_methods.status_downloads(), user=username, status=core_methods.status_server()) + + +@route('/logs') +def logs(): + + if not check_auth(request): + redirect("/login") + + username = request.COOKIES.get('user') + + return template('default', page='logs', links=core_methods.status_downloads(), user=username, status=core_methods.status_server()) + +@route('/json/links') +def get_links(): + response.header['Cache-Control'] = 'no-cache, must-revalidate' + response.content_type = 'application/json' + + if not check_auth(request): + abort(404, "No Access") + + json = '{ "downloads": [' + + downloads = core_methods.status_downloads() + ids = [] + + for dl in downloads: + ids.append(dl['id']) + json += '{' + json += '"id": %s, "name": "%s", "speed": %s, "eta": "%s", "kbleft": %s, "size": %s, "percent": %s, "wait": "%s", "status": "%s"'\ + % (str(dl['id']), str(dl['name']), str(int(dl['speed'])), str(core.format_time(dl['eta'])), dl['kbleft'], dl['size'], dl['percent'], str(core.format_time(dl['wait_until'] - time.time())), dl['status']) + + json += "}," + + if json.endswith(","): json = json[:-1] + + json += '], "ids": %s }' % str(ids) + + return json + +@route('/json/status') +def get_status(): + response.header['Cache-Control'] = 'no-cache, must-revalidate' + response.content_type = 'application/json' + + if not check_auth(request): + abort(404, "No Access") + + data = core_methods.status_server() + + if data['pause']: + status = "paused" + else: + status = "running" + + json = '{ "status": "%s", "speed": "%s", "queue": "%s" }' % (status, str(int(data['speed'])), str(data['queue'])) + + return json + +@route('/json/addlinks', method='POST') +def add_links(): + response.header['Cache-Control'] = 'no-cache, must-revalidate' + response.content_type = 'application/json' + + if not check_auth(request): + abort(404, "No Access") + + links = request.POST['links'].split('\n') + + core.add_links(links) + + return "{}" + +@route('/json/pause') +def pause(): + response.header['Cache-Control'] = 'no-cache, must-revalidate' + response.content_type = 'application/json' + + if not check_auth(request): + abort(404, "No Access") + + core.thread_list.pause = True + + return "{}" + + +@route('/json/play') +def play(): + response.header['Cache-Control'] = 'no-cache, must-revalidate' + response.content_type = 'application/json' + + if not check_auth(request): + abort(404, "No Access") + + core.thread_list.pause = False + + return "{}" + +@route('/favicon.ico') +def favicon(): + + if request.HEADER("HTTP_IF_MODIFIED_SINCE") == TIME: abort(304, "Not Modified") + + response.header['Last-Modified'] = TIME + + send_file('favicon.ico', root=(PATH + 'static/')) + +@route('static/:section/:filename') +def static_folder(section, filename): + + if request.HEADER("HTTP_IF_MODIFIED_SINCE") == TIME: abort(304, "Not Modified") + + response.header['Last-Modified'] = TIME + send_file(filename, root=(PATH + 'static/' + section)) + +@route('/static/:filename') +def static_file(filename): + + if request.HEADER("HTTP_IF_MODIFIED_SINCE") == TIME: abort(304, "Not Modified") + + response.header['Last-Modified'] = TIME + send_file(filename, root=(PATH + 'static/')) + + +def check_auth(req): + + try: + user = req.COOKIES.get('user') + id = int(req.COOKIES.get('id')) + ua = req.HEADER("HTTP_USER_AGENT") + ip = req.HEADER("REMOTE_ADDR") + + if USERS[id]['user'] == user and USERS[id]['ua'] == ua and USERS[id]['ip'] == ip: + return True + except: + return False + + return False + + +class WebServer(threading.Thread): + def __init__(self, pycore): + threading.Thread.__init__(self) + + global core, core_methods, TIME + core = pycore + core_methods = pycore.server_methods + self.core = pycore + self.setDaemon(True) + + if pycore.config['general']['debug_mode']: + bottle.debug(True) + TIME = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime()) else: - try: - print open(self.path[1:], 'r').read() - except IOError: - self.send_error(404) - - def format_size(self, size): - return str(size / 1024) + " MiB" - - def format_time(self,seconds): - seconds = int(seconds) - hours, seconds = divmod(seconds, 3600) - minutes, seconds = divmod(seconds, 60) - return "%.2i:%.2i:%.2i" % (hours, minutes, seconds) - - def get_downloads(self): - data = coreserver.status_downloads() - for download in data: - print "<h3>%s</h3>" % download["name"] - if download["status"] == "downloading": - percent = download["percent"] - z = percent / 4 - print "<h3>%s</h3>" % dl_name - print "<font face='font-family:Fixedsys,Courier,monospace;'>[" + z * "#" + (25-z) * " " + "]</font>" + str(percent) + "%<br />" - print "Speed: " + str(int(download['speed'])) + " kb/s" - print "Size: " + self.format_size(download['size']) - print "Finished in: " + self.format_time(download['eta']) - print "ID: " + str(download['id']) - dl_status = "[" + z * "#" + (25-z) * " " + "] " + str(percent) + "%" + " Speed: " + str(int(download['speed'])) + " kb/s" + " Size: " + self.format_size(download['size']) + " Finished in: " + self.format_time(download['eta']) + " ID: " + str(download['id']) - if download["status"] == "waiting": - print "waiting: " + self.format_time(download["wait_until"]- time()) - -is_url = re.compile("^(((https?|ftp)\:\/\/)?([\w\.\-]+(\:[\w\.\&%\$\-]+)*@)?((([^\s\(\)\<\>\\\"\.\[\]\,@;:]+)(\.[^\s\(\)\<\>\\\"\.\[\]\,@;:]+)*(\.[a-zA-Z]{2,4}))|((([01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}([01]?\d{1,2}|2[0-4]\d|25[0-5])))(\b\:(6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)\b)?((\/[^\/][\w\.\,\?\'\\\/\+&%\$#\=~_\-@]*)*[^\.\,\?\"\'\(\)\[\]!;<>{}\s\x7F-\xFF])?)$",re.IGNORECASE) - -coreserver = None - -class WebServer(): - - def start(self): - try: - global coreserver - coreserver = ServerProxy("https://testuser:testpw@localhost:1337", allow_none=True) - webserver = HTTPServer(('',8080),Handler) - print 'server started at port 8080' - webserver.serve_forever() - except KeyboardInterrupt: - webserver.socket.close() - -if __name__ == "__main__": - web = WebServer() - web.start() + bottle.debug(False) + + #@TODO remove + #TIME = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime()) + bottle.TEMPLATE_PATH.append('./module/web/templates/') + + def run(self): + self.core.logger.info("Starting Webinterface on %s port %s" % (self.core.config['webinterface']['listenaddr'],self.core.config['webinterface']['port'])) + try: + run(host=self.core.config['webinterface']['listenaddr'], port=int(self.core.config['webinterface']['port']), quiet=True) + except: + self.core.logger.error("Failed starting webserver, no webinterface available: Can't create socket") + exit()
\ No newline at end of file diff --git a/module/web/bottle.py b/module/web/bottle.py index 66ceb527f..41a8c8fc0 100644 --- a/module/web/bottle.py +++ b/module/web/bottle.py @@ -1,15 +1,15 @@ # -*- coding: utf-8 -*- """ -Bottle is a fast and simple mirco-framework for small web-applications. It -offers request dispatching (Routes) with url parameter support, Templates, -key/value Databases, a build-in HTTP Server and adapters for many third party -WSGI/HTTP-server and template engines. All in a single file and with no +Bottle is a fast and simple micro-framework for small web applications. It +offers request dispatching (Routes) with url parameter support, templates, +key/value databases, a built-in HTTP Server and adapters for many third party +WSGI/HTTP-server and template engines - all in a single file and with no dependencies other than the Python Standard Library. Homepage and documentation: http://wiki.github.com/defnull/bottle Special thanks to Stefan Matthias Aust [http://github.com/sma] - for his contribution to SimpelTemplate + for his contribution to SimpleTemplate Licence (MIT) ------------- @@ -62,9 +62,10 @@ Example """ __author__ = 'Marcel Hellkamp' -__version__ = '0.5.7' +__version__ = '0.6.4' __license__ = 'MIT' +import types import sys import cgi import mimetypes @@ -75,26 +76,31 @@ import re import random import threading import time +import warnings +import email.utils from wsgiref.headers import Headers as HeaderWrapper from Cookie import SimpleCookie import anydbm as dbm +import subprocess +import thread + try: from urlparse import parse_qs -except ImportError: +except ImportError: # pragma: no cover from cgi import parse_qs try: import cPickle as pickle -except ImportError: +except ImportError: # pragma: no cover import pickle as pickle try: try: from json import dumps as json_dumps - except ImportError: + except ImportError: # pragma: no cover from simplejson import dumps as json_dumps -except ImportError: +except ImportError: # pragma: no cover json_dumps = None @@ -105,38 +111,37 @@ except ImportError: # Exceptions and Events class BottleException(Exception): - """ A base class for exceptions used by bottle.""" + """ A base class for exceptions used by bottle. """ pass class HTTPError(BottleException): - """ A way to break the execution and instantly jump to an error handler. """ + """ + A way to break the execution and instantly jump to an error handler. + """ def __init__(self, status, text): self.output = text self.http_status = int(status) + BottleException.__init__(self, status, text) def __repr__(self): - return "HTTPError(%d,%s)" % (self.http_status, repr(self.output)) + return 'HTTPError(%d,%s)' % (self.http_status, repr(self.output)) def __str__(self): - out = [] - status = self.http_status - name = HTTP_CODES.get(status,'Unknown').title() - url = request.path - out.append('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">') - out.append('<html><head><title>Error %d: %s</title>' % (status, name)) - out.append('</head><body><h1>Error %d: %s</h1>' % (status, name)) - out.append('<p>Sorry, the requested URL "%s" caused an error.</p>' % url) - out.append(''.join(list(self.output))) - out.append('</body></html>') - return "\n".join(out) + return HTTP_ERROR_TEMPLATE % { + 'status' : self.http_status, + 'url' : request.path, + 'error_name' : HTTP_CODES.get(self.http_status, 'Unknown').title(), + 'error_message' : ''.join(self.output) + } class BreakTheBottle(BottleException): - """ Not an exception, but a straight jump out of the controller code. - + """ + Not an exception, but a straight jump out of the controller code. Causes the Bottle to instantly call start_response() and return the - content of output """ + content of output + """ def __init__(self, output): self.output = output @@ -149,8 +154,10 @@ class BreakTheBottle(BottleException): _default_app = None def default_app(newapp = None): - ''' Returns the current default app or sets a new one. - Defaults to an instance of Bottle ''' + """ + Returns the current default app or sets a new one. + Defaults to an instance of Bottle + """ global _default_app if newapp: _default_app = newapp @@ -161,17 +168,20 @@ def default_app(newapp = None): class Bottle(object): - def __init__(self, catchall=True, debug=False, optimize=False, autojson=True): + def __init__(self, catchall=True, optimize=False, autojson=True): self.simple_routes = {} self.regexp_routes = {} + self.default_route = None self.error_handler = {} self.optimize = optimize - self.debug = debug self.autojson = autojson self.catchall = catchall + self.serve = True def match_url(self, url, method='GET'): - """Returns the first matching handler and a parameter dict or (None, None) """ + """ + Returns the first matching handler and a parameter dict or (None, None) + """ url = url.strip().lstrip("/ ") # Search for static routes first route = self.simple_routes.get(method,{}).get(url,None) @@ -186,47 +196,116 @@ class Bottle(object): if i > 0 and self.optimize and random.random() <= 0.001: routes[i-1], routes[i] = routes[i], routes[i-1] return (handler, match.groupdict()) - return (None, None) - - def add_route(self, route, handler, method='GET', simple=False): + if self.default_route: + return (self.default_route, {}) + if method == 'HEAD': # Fall back to GET + return self.match_url(url) + else: + return (None, None) + + def add_controller(self, route, controller, **kargs): + """ Adds a controller class or object """ + if '{action}' not in route and 'action' not in kargs: + raise BottleException("Routes to controller classes or object MUST" + " contain an {action} placeholder or use the action-parameter") + for action in (m for m in dir(controller) if not m.startswith('_')): + handler = getattr(controller, action) + if callable(handler) and action == kargs.get('action', action): + self.add_route(route.replace('{action}', action), handler, **kargs) + + def add_route(self, route, handler, method='GET', simple=False, **kargs): """ Adds a new route to the route mappings. """ + if isinstance(handler, type) and issubclass(handler, BaseController): + handler = handler() + if isinstance(handler, BaseController): + self.add_controller(route, handler, method=method, simple=simple, **kargs) + return method = method.strip().upper() route = route.strip().lstrip('$^/ ').rstrip('$^ ') if re.match(r'^(\w+/)*\w*$', route) or simple: self.simple_routes.setdefault(method, {})[route] = handler else: - route = re.sub(r':([a-zA-Z_]+)(?P<uniq>[^\w/])(?P<re>.+?)(?P=uniq)',r'(?P<\1>\g<re>)',route) - route = re.sub(r':([a-zA-Z_]+)',r'(?P<\1>[^/]+)', route) + route = re.sub(r':([a-zA-Z_]+)(?P<uniq>[^\w/])(?P<re>.+?)(?P=uniq)', + r'(?P<\1>\g<re>)',route) + route = re.sub(r':([a-zA-Z_]+)', r'(?P<\1>[^/]+)', route) route = re.compile('^%s$' % route) self.regexp_routes.setdefault(method, []).append([route, handler]) def route(self, url, **kargs): - """ Decorator for request handler. Same as add_route(url, handler, **kargs).""" + """ + Decorator for request handler. + Same as add_route(url, handler, **kargs). + """ def wrapper(handler): self.add_route(url, handler, **kargs) return handler return wrapper + def set_default(self, handler): + self.default_route = handler + + def default(self): + """ Decorator for request handler. Same as add_defroute( handler ).""" + def wrapper(handler): + self.set_default(handler) + return handler + return wrapper + def set_error_handler(self, code, handler): """ Adds a new error handler. """ - code = int(code) - self.error_handler[code] = handler + self.error_handler[int(code)] = handler def error(self, code=500): - """ Decorator for error handler. Same as set_error_handler(code, handler).""" + """ + Decorator for error handler. + Same as set_error_handler(code, handler). + """ def wrapper(handler): self.set_error_handler(code, handler) return handler return wrapper + def cast(self, out): + """ + Cast the output to an iterable of strings or something WSGI can handle. + Set Content-Type and Content-Length when possible. Then clear output + on HEAD requests. + Supports: False, str, unicode, list(unicode), dict(), open() + """ + if not out: + out = [] + response.header['Content-Length'] = '0' + elif isinstance(out, types.StringType): + out = [out] + elif isinstance(out, unicode): + out = [out.encode(response.charset)] + elif isinstance(out, list) and isinstance(out[0], unicode): + out = map(lambda x: x.encode(response.charset), out) + elif self.autojson and json_dumps and isinstance(out, dict): + out = [json_dumps(out)] + response.content_type = 'application/json' + elif hasattr(out, 'read'): + out = request.environ.get('wsgi.file_wrapper', + lambda x: iter(lambda: x.read(8192), ''))(out) + if isinstance(out, list) and len(out) == 1: + response.header['Content-Length'] = str(len(out[0])) + if not hasattr(out, '__iter__'): + raise TypeError('Request handler for route "%s" returned [%s] ' + 'which is not iterable.' % (request.path, type(out).__name__)) + return out + + def __call__(self, environ, start_response): - """ The bottle WSGI-interface .""" + """ The bottle WSGI-interface. """ request.bind(environ) response.bind() try: # Unhandled Exceptions try: # Bottle Error Handling + if not self.serve: + abort(503, "Server stopped") handler, args = self.match_url(request.path, request.method) - if not handler: raise HTTPError(404, "Not found") + if not handler: + raise HTTPError(404, "Not found") output = handler(**args) db.close() except BreakTheBottle, e: @@ -234,26 +313,18 @@ class Bottle(object): except HTTPError, e: response.status = e.http_status output = self.error_handler.get(response.status, str)(e) - # output casting - if hasattr(output, 'read'): - output = environ.get('wsgi.file_wrapper', lambda x: iter(lambda: x.read(8192), ''))(output) - elif self.autojson and json_dumps and isinstance(output, dict): - output = json_dumps(output) - response.content_type = 'application/json' - if isinstance(output, str): - response.header['Content-Length'] = str(len(output)) - output = [output] + output = self.cast(output) + if response.status in (100, 101, 204, 304) or request.method == 'HEAD': + output = [] # rfc2616 section 4.3 except (KeyboardInterrupt, SystemExit, MemoryError): raise except Exception, e: response.status = 500 if self.catchall: err = "Unhandled Exception: %s\n" % (repr(e)) - if self.debug: - err += "<h2>Traceback:</h2>\n<pre>\n" - err += traceback.format_exc(10) - err += "\n</pre>" - output = str(HTTPError(500, err)) + if DEBUG: + err += TRACEBACK_TEMPLATE % traceback.format_exc(10) + output = [str(HTTPError(500, err))] request._environ['wsgi.errors'].write(err) else: raise @@ -267,8 +338,11 @@ class Request(threading.local): """ Represents a single request using thread-local namespace. """ def bind(self, environ): - """ Binds the enviroment of the current request to this request handler """ + """ + Binds the enviroment of the current request to this request handler + """ self._environ = environ + self.environ = self._environ self._GET = None self._POST = None self._GETPOST = None @@ -279,25 +353,25 @@ class Request(threading.local): @property def method(self): - ''' Returns the request method (GET,POST,PUT,DELETE,...) ''' + """ Get the request method (GET,POST,PUT,DELETE,...) """ return self._environ.get('REQUEST_METHOD', 'GET').upper() @property def query_string(self): - ''' Content of QUERY_STRING ''' + """ Get content of QUERY_STRING """ return self._environ.get('QUERY_STRING', '') @property def input_length(self): - ''' Content of CONTENT_LENGTH ''' + """ Get content of CONTENT_LENGTH """ try: - return int(self._environ.get('CONTENT_LENGTH', '0')) + return max(0,int(self._environ.get('CONTENT_LENGTH', '0'))) except ValueError: return 0 @property def GET(self): - """Returns a dict with GET parameters.""" + """ Get a dict with GET parameters. """ if self._GET is None: data = parse_qs(self.query_string, keep_blank_values=True) self._GET = {} @@ -310,9 +384,10 @@ class Request(threading.local): @property def POST(self): - """Returns a dict with parsed POST or PUT data.""" + """ Get a dict with parsed POST or PUT data. """ if self._POST is None: - data = cgi.FieldStorage(fp=self._environ['wsgi.input'], environ=self._environ, keep_blank_values=True) + data = cgi.FieldStorage(fp=self._environ['wsgi.input'], + environ=self._environ, keep_blank_values=True) self._POST = {} for item in data.list: name = item.name @@ -326,7 +401,7 @@ class Request(threading.local): @property def params(self): - ''' Returns a mix of GET and POST data. POST overwrites GET ''' + """ Returns a mix of GET and POST data. POST overwrites GET """ if self._GETPOST is None: self._GETPOST = dict(self.GET) self._GETPOST.update(dict(self.POST)) @@ -334,7 +409,7 @@ class Request(threading.local): @property def COOKIES(self): - """Returns a dict with COOKIES.""" + """ Returns a dict with COOKIES. """ if self._COOKIES is None: raw_dict = SimpleCookie(self._environ.get('HTTP_COOKIE','')) self._COOKIES = {} @@ -357,6 +432,7 @@ class Response(threading.local): self.header = HeaderWrapper(self.header_list) self.content_type = 'text/html' self.error = None + self.charset = 'utf8' def wsgiheaders(self): ''' Returns a wsgi conform list of header/value pairs ''' @@ -371,19 +447,33 @@ class Response(threading.local): return self._COOKIES def set_cookie(self, key, value, **kargs): - """ Sets a Cookie. Optional settings: expires, path, comment, domain, max-age, secure, version, httponly """ + """ + Sets a Cookie. Optional settings: + expires, path, comment, domain, max-age, secure, version, httponly + """ self.COOKIES[key] = value - for k in kargs: - self.COOKIES[key][k] = kargs[k] + for k, v in kargs.iteritems(): + self.COOKIES[key][k] = v def get_content_type(self): - '''Gives access to the 'Content-Type' header and defaults to 'text/html'.''' + """ Get the current 'Content-Type' header. """ return self.header['Content-Type'] def set_content_type(self, value): + if 'charset=' in value: + self.charset = value.split('charset=')[-1].split(';')[0].strip() self.header['Content-Type'] = value - - content_type = property(get_content_type, set_content_type, None, get_content_type.__doc__) + + content_type = property(get_content_type, set_content_type, None, + get_content_type.__doc__) + + +class BaseController(object): + _singleton = None + def __new__(cls, *a, **k): + if not cls._singleton: + cls._singleton = object.__new__(cls, *a, **k) + return cls._singleton def abort(code=500, text='Unknown Error: Appliction stopped.'): @@ -398,12 +488,11 @@ def redirect(url, code=307): raise BreakTheBottle("") -def send_file(filename, root, guessmime = True, mimetype = 'text/plain'): +def send_file(filename, root, guessmime = True, mimetype = None): """ Aborts execution and sends a static files as response. """ - root = os.path.abspath(root) + '/' - filename = os.path.normpath(filename).strip('/') - filename = os.path.join(root, filename) - + root = os.path.abspath(root) + os.sep + filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) + if not filename.startswith(root): abort(401, "Access denied.") if not os.path.exists(filename) or not os.path.isfile(filename): @@ -411,25 +500,41 @@ def send_file(filename, root, guessmime = True, mimetype = 'text/plain'): if not os.access(filename, os.R_OK): abort(401, "You do not have permission to access this file.") - if guessmime: - guess = mimetypes.guess_type(filename)[0] - if guess: - response.content_type = guess - elif mimetype: - response.content_type = mimetype - elif mimetype: - response.content_type = mimetype + if guessmime and not mimetype: + mimetype = mimetypes.guess_type(filename)[0] + if not mimetype: mimetype = 'text/plain' + response.content_type = mimetype stats = os.stat(filename) - # TODO: HTTP_IF_MODIFIED_SINCE -> 304 (Thu, 02 Jul 2009 23:16:31 CEST) - if 'Content-Length' not in response.header: - response.header['Content-Length'] = stats.st_size if 'Last-Modified' not in response.header: - ts = time.gmtime(stats.st_mtime) - ts = time.strftime("%a, %d %b %Y %H:%M:%S +0000", ts) - response.header['Last-Modified'] = ts + lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) + response.header['Last-Modified'] = lm + if 'HTTP_IF_MODIFIED_SINCE' in request.environ: + ims = request.environ['HTTP_IF_MODIFIED_SINCE'] + # IE sends "<date>; length=146" + ims = ims.split(";")[0].strip() + ims = parse_date(ims) + if ims is not None and ims >= stats.st_mtime: + abort(304, "Not modified") + if 'Content-Length' not in response.header: + response.header['Content-Length'] = str(stats.st_size) + raise BreakTheBottle(open(filename, 'rb')) + - raise BreakTheBottle(open(filename, 'r')) +def parse_date(ims): + """ + Parses date strings usually found in HTTP header and returns UTC epoch. + Understands rfc1123, rfc850 and asctime. + """ + try: + ts = email.utils.parsedate_tz(ims) + if ts is not None: + if ts[9] is None: + return time.mktime(ts[:8] + (0,)) - time.timezone + else: + return time.mktime(ts[:8] + (0,)) - ts[9] - time.timezone + except (ValueError, IndexError): + return None @@ -439,15 +544,17 @@ def send_file(filename, root, guessmime = True, mimetype = 'text/plain'): # Decorators def validate(**vkargs): - ''' Validates and manipulates keyword arguments by user defined callables - and handles ValueError and missing arguments by raising HTTPError(403) ''' + """ + Validates and manipulates keyword arguments by user defined callables. + Handles ValueError and missing arguments by raising HTTPError(403). + """ def decorator(func): def wrapper(**kargs): - for key in vkargs: + for key, value in vkargs.iteritems(): if key not in kargs: abort(403, 'Missing parameter: %s' % key) try: - kargs[key] = vkargs[key](kargs[key]) + kargs[key] = value(kargs[key]) except ValueError, e: abort(403, 'Wrong parameter format for: %s' % key) return func(**kargs) @@ -456,12 +563,21 @@ def validate(**vkargs): def route(url, **kargs): - """ Decorator for request handler. Same as add_route(url, handler, **kargs).""" + """ + Decorator for request handler. Same as add_route(url, handler, **kargs). + """ return default_app().route(url, **kargs) +def default(): + """ + Decorator for request handler. Same as set_default(handler). + """ + return default_app().default() def error(code=500): - """ Decorator for error handler. Same as set_error_handler(code, handler).""" + """ + Decorator for error handler. Same as set_error_handler(code, handler). + """ return default_app().error(code) @@ -471,8 +587,23 @@ def error(code=500): # Server adapter -class ServerAdapter(object): +class WSGIAdapter(object): + def run(self, handler): # pragma: no cover + pass + + def __repr__(self): + return "%s()" % (self.__class__.__name__) + + +class CGIServer(WSGIAdapter): + def run(self, handler): + from wsgiref.handlers import CGIHandler + CGIHandler().run(handler) + + +class ServerAdapter(WSGIAdapter): def __init__(self, host='127.0.0.1', port=8080, **kargs): + WSGIAdapter.__init__(self) self.host = host self.port = int(port) self.options = kargs @@ -480,9 +611,6 @@ class ServerAdapter(object): def __repr__(self): return "%s (%s:%d)" % (self.__class__.__name__, self.host, self.port) - def run(self, handler): - pass - class WSGIRefServer(ServerAdapter): def run(self, handler): @@ -513,12 +641,14 @@ class PasteServer(ServerAdapter): class FapwsServer(ServerAdapter): - """ Extreamly fast Webserver using libev (see http://william-os4y.livejournal.com/) - Experimental ... """ + """ + Extremly fast webserver using libev. + See http://william-os4y.livejournal.com/ + Experimental ... + """ def run(self, handler): import fapws._evwsgi as evwsgi from fapws import base - import sys evwsgi.start(self.host, self.port) evwsgi.set_base_module(base) def app(environ, start_response): @@ -528,35 +658,75 @@ class FapwsServer(ServerAdapter): evwsgi.run() -def run(app=None, server=WSGIRefServer, host='127.0.0.1', port=8080, **kargs): - """ Runs bottle as a web server, using Python's built-in wsgiref implementation by default. - - You may choose between WSGIRefServer, CherryPyServer, FlupServer and - PasteServer or write your own server adapter. - """ +def run(app=None, server=WSGIRefServer, host='127.0.0.1', port=8080, + interval=1, reloader=False, **kargs): + """ Runs bottle as a web server. """ if not app: app = default_app() - quiet = bool('quiet' in kargs and kargs['quiet']) - - # Instanciate server, if it is a class instead of an instance - if isinstance(server, type) and issubclass(server, ServerAdapter): - server = server(host=host, port=port, **kargs) + quiet = bool(kargs.get('quiet', False)) - if not isinstance(server, ServerAdapter): - raise RuntimeError("Server must be a subclass of ServerAdapter") + # Instantiate server, if it is a class instead of an instance + if isinstance(server, type): + if issubclass(server, CGIServer): + server = server() + elif issubclass(server, ServerAdapter): + server = server(host=host, port=port, **kargs) - if not quiet: - print 'Bottle server starting up (using %s)...' % repr(server) - print 'Listening on http://%s:%d/' % (server.host, server.port) - print 'Use Ctrl-C to quit.' - print + if not isinstance(server, WSGIAdapter): + raise RuntimeError("Server must be a subclass of WSGIAdapter") + + if not quiet and isinstance(server, ServerAdapter): # pragma: no cover + if not reloader or os.environ.get('BOTTLE_CHILD') == 'true': + print "Bottle server starting up (using %s)..." % repr(server) + print "Listening on http://%s:%d/" % (server.host, server.port) + print "Use Ctrl-C to quit." + print + else: + print "Bottle auto reloader starting up..." try: - server.run(app) + if reloader and interval: + reloader_run(server, app, interval) + else: + server.run(app) except KeyboardInterrupt: - print "Shuting down..." - + if not quiet: # pragma: no cover + print "Shutting Down..." + + +#TODO: If the parent process is killed (with SIGTERM) the childs survive... +def reloader_run(server, app, interval): + if os.environ.get('BOTTLE_CHILD') == 'true': + # We are a child process + files = dict() + for module in sys.modules.values(): + file_path = getattr(module, '__file__', None) + if file_path and os.path.isfile(file_path): + file_split = os.path.splitext(file_path) + if file_split[1] in ('.py', '.pyc', '.pyo'): + file_path = file_split[0] + '.py' + files[file_path] = os.stat(file_path).st_mtime + thread.start_new_thread(server.run, (app,)) + while True: + time.sleep(interval) + for file_path, file_mtime in files.iteritems(): + if not os.path.exists(file_path): + print "File changed: %s (deleted)" % file_path + elif os.stat(file_path).st_mtime > file_mtime: + print "File changed: %s (modified)" % file_path + else: continue + print "Restarting..." + app.serve = False + time.sleep(interval) # be nice and wait for running requests + sys.exit(3) + while True: + args = [sys.executable] + sys.argv + environ = os.environ.copy() + environ['BOTTLE_CHILD'] = 'true' + exit_status = subprocess.call(args, env=environ) + if exit_status != 3: + sys.exit(exit_status) @@ -564,89 +734,183 @@ def run(app=None, server=WSGIRefServer, host='127.0.0.1', port=8080, **kargs): # Templates +class TemplateError(HTTPError): + def __init__(self, message): + HTTPError.__init__(self, 500, message) + class BaseTemplate(object): - def __init__(self, template='', filename=None): - self.source = filename - if self.source: - fp = open(filename) - template = fp.read() - fp.close() - self.parse(template) - - def parse(self, template): raise NotImplementedError - def render(self, **args): raise NotImplementedError - - @classmethod - def find(cls, name): - for path in TEMPLATE_PATH: - if os.path.isfile(path % name): - return cls(filename = path % name) - return None + def __init__(self, template='', name=None, filename=None, lookup=[]): + """ + Create a new template. + If a name is provided, but no filename and no template string, the + filename is guessed using the lookup path list. + Subclasses can assume that either self.template or self.filename is set. + If both are present, self.template should be used. + """ + self.name = name + self.filename = filename + self.template = template + self.lookup = lookup + if self.name and not self.filename: + for path in self.lookup: + fpath = os.path.join(path, self.name+'.tpl') + if os.path.isfile(fpath): + self.filename = fpath + if not self.template and not self.filename: + raise TemplateError('Template (%s) not found.' % self.name) + self.prepare() + + def prepare(self): + """ + Run preparatios (parsing, caching, ...). + It should be possible to call this multible times to refresh a template. + """ + raise NotImplementedError + + def render(self, **args): + """ + Render the template with the specified local variables and return an + iterator of strings (bytes). This must be thread save! + """ + raise NotImplementedError class MakoTemplate(BaseTemplate): - def parse(self, template): + output_encoding=None + input_encoding=None + default_filters=None + global_variables={} + + def prepare(self): from mako.template import Template - self.tpl = Template(template) + from mako.lookup import TemplateLookup + #TODO: This is a hack... http://github.com/defnull/bottle/issues#issue/8 + mylookup = TemplateLookup(directories=map(os.path.abspath, self.lookup)+['./']) + if self.template: + self.tpl = Template(self.template, + lookup=mylookup, + output_encoding=MakoTemplate.output_encoding, + input_encoding=MakoTemplate.input_encoding, + default_filters=MakoTemplate.default_filters + ) + else: + self.tpl = Template(filename=self.filename, + lookup=mylookup, + output_encoding=MakoTemplate.output_encoding, + input_encoding=MakoTemplate.input_encoding, + default_filters=MakoTemplate.default_filters + ) def render(self, **args): - return self.tpl.render(**args) + _defaults = MakoTemplate.global_variables.copy() + _defaults.update(args) + return self.tpl.render(**_defaults) class CheetahTemplate(BaseTemplate): - def parse(self, template): + def prepare(self): from Cheetah.Template import Template self.context = threading.local() self.context.vars = {} - self.tpl = Template(source = template, searchList=[self.context.vars]) + if self.template: + self.tpl = Template(source=self.template, searchList=[self.context.vars]) + else: + self.tpl = Template(file=self.filename, searchList=[self.context.vars]) def render(self, **args): self.context.vars.update(args) out = str(self.tpl) self.context.vars.clear() - return out + return [out] + + +class Jinja2Template(BaseTemplate): + env = None # hopefully, a Jinja environment is actually thread-safe + + def prepare(self): + if not self.env: + from jinja2 import Environment, FunctionLoader + self.env = Environment(line_statement_prefix="#", loader=FunctionLoader(self.loader)) + if self.template: + self.tpl = self.env.from_string(self.template) + else: + self.tpl = self.env.get_template(self.filename) + + def render(self, **args): + return self.tpl.render(**args).encode("utf-8") + + def loader(self, name): + if not name.endswith(".tpl"): + for path in self.lookup: + fpath = os.path.join(path, name+'.tpl') + if os.path.isfile(fpath): + name = fpath + break + f = open(name) + try: return f.read() + finally: f.close() class SimpleTemplate(BaseTemplate): - re_python = re.compile(r'^\s*%\s*(?:(if|elif|else|try|except|finally|for|while|with|def|class)|(include)|(end)|(.*))') + re_python = re.compile(r'^\s*%\s*(?:(if|elif|else|try|except|finally|for|' + 'while|with|def|class)|(include|rebase)|(end)|(.*))') re_inline = re.compile(r'\{\{(.*?)\}\}') dedent_keywords = ('elif', 'else', 'except', 'finally') + def prepare(self): + if self.template: + code = self.translate(self.template) + self.co = compile(code, '<string>', 'exec') + else: + code = self.translate(open(self.filename).read()) + self.co = compile(code, self.filename, 'exec') + def translate(self, template): indent = 0 strbuffer = [] code = [] - self.subtemplates = {} + self.includes = dict() class PyStmt(str): def __repr__(self): return 'str(' + self + ')' def flush(allow_nobreak=False): if len(strbuffer): if allow_nobreak and strbuffer[-1].endswith("\\\\\n"): strbuffer[-1]=strbuffer[-1][:-3] - code.append(" " * indent + "stdout.append(%s)" % repr(''.join(strbuffer))) - code.append((" " * indent + "\n") * len(strbuffer)) # to preserve line numbers + code.append(' ' * indent + "_stdout.append(%s)" % repr(''.join(strbuffer))) + code.append((' ' * indent + '\n') * len(strbuffer)) # to preserve line numbers del strbuffer[:] for line in template.splitlines(True): m = self.re_python.match(line) if m: flush(allow_nobreak=True) - keyword, include, end, statement = m.groups() + keyword, subtpl, end, statement = m.groups() if keyword: if keyword in self.dedent_keywords: indent -= 1 code.append(" " * indent + line[m.start(1):]) indent += 1 - elif include: + elif subtpl: tmp = line[m.end(2):].strip().split(None, 1) - name = tmp[0] - args = tmp[1:] and tmp[1] or '' - self.subtemplates[name] = SimpleTemplate.find(name) - code.append(" " * indent + "stdout.append(_subtemplates[%s].render(%s))\n" % (repr(name), args)) + if not tmp: + code.append(' ' * indent + "_stdout.extend(_base)\n") + else: + name = tmp[0] + args = tmp[1:] and tmp[1] or '' + if name not in self.includes: + self.includes[name] = SimpleTemplate(name=name, lookup=self.lookup) + if subtpl == 'include': + code.append(' ' * indent + + "_ = _includes[%s].execute(_stdout, %s)\n" + % (repr(name), args)) + else: + code.append(' ' * indent + + "_tpl['_rebase'] = (_includes[%s], dict(%s))\n" + % (repr(name), args)) elif end: indent -= 1 - code.append(" " * indent + '#' + line[m.start(3):]) + code.append(' ' * indent + '#' + line[m.start(3):]) elif statement: - code.append(" " * indent + line[m.start(4):]) + code.append(' ' * indent + line[m.start(4):]) else: splits = self.re_inline.split(line) # text, (expr, text)* if len(splits) == 1: @@ -656,40 +920,86 @@ class SimpleTemplate(BaseTemplate): for i in range(1, len(splits), 2): splits[i] = PyStmt(splits[i]) splits = [x for x in splits if bool(x)] - code.append(" " * indent + "stdout.extend(%s)\n" % repr(splits)) + code.append(' ' * indent + "_stdout.extend(%s)\n" % repr(splits)) flush() return ''.join(code) - def parse(self, template): - code = self.translate(template) - self.co = compile(code, self.source or '<template>', 'exec') - - def render(self, **args): - ''' Returns the rendered template using keyword arguments as local variables. ''' - args['stdout'] = [] - args['_subtemplates'] = self.subtemplates + def execute(self, stdout, **args): + args['_stdout'] = stdout + args['_includes'] = self.includes + args['_tpl'] = args eval(self.co, args) - return ''.join(args['stdout']) + if '_rebase' in args: + subtpl, args = args['_rebase'] + args['_base'] = stdout[:] #copy stdout + del stdout[:] # clear stdout + return subtpl.execute(stdout, **args) + return args + def render(self, **args): + """ Render the template using keyword arguments as local variables. """ + stdout = [] + self.execute(stdout, **args) + return stdout + -def template(template, template_adapter=SimpleTemplate, **args): - ''' Returns a string from a template ''' - if template not in TEMPLATES: - if template.find("\n") == template.find("{") == template.find("%") == -1: - TEMPLATES[template] = template_adapter.find(template) +def template(tpl, template_adapter=SimpleTemplate, **args): + ''' + Get a rendered template as a string iterator. + You can use a name, a filename or a template string as first parameter. + ''' + lookup = args.get('template_lookup', TEMPLATE_PATH) + if tpl not in TEMPLATES or DEBUG: + if "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl: + TEMPLATES[tpl] = template_adapter(template=tpl, lookup=lookup) + elif '.' in tpl: + TEMPLATES[tpl] = template_adapter(filename=tpl, lookup=lookup) else: - TEMPLATES[template] = template_adapter(template) - if not TEMPLATES[template]: - abort(500, 'Template not found') + TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup) + if not TEMPLATES[tpl]: + abort(500, 'Template (%s) not found' % tpl) args['abort'] = abort args['request'] = request args['response'] = response - return TEMPLATES[template].render(**args) + return TEMPLATES[tpl].render(**args) + + +def mako_template(tpl_name, **kargs): + kargs['template_adapter'] = MakoTemplate + return template(tpl_name, **kargs) + +def cheetah_template(tpl_name, **kargs): + kargs['template_adapter'] = CheetahTemplate + return template(tpl_name, **kargs) + +def jinja2_template(tpl_name, **kargs): + kargs['template_adapter'] = Jinja2Template + return template(tpl_name, **kargs) +def view(tpl_name, **defaults): + ''' Decorator: Rendes a template for a handler. + Return a dict of template vars to fill out the template. + ''' + def decorator(func): + def wrapper(**kargs): + out = func(**kargs) + defaults.update(out) + return template(tpl_name, **defaults) + return wrapper + return decorator + +def mako_view(tpl_name, **kargs): + kargs['template_adapter'] = MakoTemplate + return view(tpl_name, **kargs) + +def cheetah_view(tpl_name, **kargs): + kargs['template_adapter'] = CheetahTemplate + return view(tpl_name, **kargs) -def mako_template(template_name, **args): return template(template_name, template_adapter=MakoTemplate, **args) +def jinja2_view(tpl_name, **kargs): + kargs['template_adapter'] = Jinja2Template + return view(tpl_name, **kargs) -def cheetah_template(template_name, **args): return template(template_name, template_adapter=CheetahTemplate, **args) @@ -698,8 +1008,8 @@ def cheetah_template(template_name, **args): return template(template_name, temp # Database -class BottleBucket(object): - '''Memory-caching wrapper around anydbm''' +class BottleBucket(object): # pragma: no cover + """ Memory-caching wrapper around anydbm """ def __init__(self, name): self.__dict__['name'] = name self.__dict__['db'] = dbm.open(DB_PATH + '/%s.db' % name, 'c') @@ -711,6 +1021,7 @@ class BottleBucket(object): return self.mmap[key] def __setitem__(self, key, value): + if not isinstance(key, str): raise TypeError("Bottle keys must be strings") self.mmap[key] = value def __delitem__(self, key): @@ -754,7 +1065,10 @@ class BottleBucket(object): if key not in self.db or pvalue != self.db[key]: self.db[key] = pvalue self.mmap.clear() - self.db.close() + if hasattr(self.db, 'sync'): + self.db.sync() + if hasattr(self.db, 'close'): + self.db.close() def clear(self): for key in self.db: @@ -773,12 +1087,13 @@ class BottleBucket(object): raise -class BottleDB(threading.local): - '''Holds multible BottleBucket instances in a thread-local way.''' +class BottleDB(threading.local): # pragma: no cover + """ Holds multible BottleBucket instances in a thread-local way. """ def __init__(self): self.__dict__['open'] = {} def __getitem__(self, key): + warnings.warn("Please do not use bottle.db anymore. This feature is deprecated. You may use anydb directly.", DeprecationWarning) if key not in self.open and not key.startswith('_'): self.open[key] = BottleBucket(key) return self.open[key] @@ -829,8 +1144,9 @@ class BottleDB(threading.local): # Modul initialization and configuration DB_PATH = './' -TEMPLATE_PATH = ['./%s.tpl', './views/%s.tpl'] +TEMPLATE_PATH = ['./', './views/'] TEMPLATES = {} +DEBUG = False HTTP_CODES = { 100: 'CONTINUE', 101: 'SWITCHING PROTOCOLS', @@ -875,12 +1191,41 @@ HTTP_CODES = { 505: 'HTTP VERSION NOT SUPPORTED', } +HTTP_ERROR_TEMPLATE = """ +<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> +<html> + <head> + <title>Error %(status)d: %(error_name)s</title> + </head> + <body> + <h1>Error %(status)d: %(error_name)s</h1> + <p>Sorry, the requested URL <tt>%(url)s</tt> caused an error:</p> + <pre> + %(error_message)s + </pre> + </body> +</html> +""" + +TRACEBACK_TEMPLATE = """ +<h2>Traceback:</h2> +<pre> +%s +</pre> +""" + request = Request() response = Response() db = BottleDB() local = threading.local() -def debug(mode=True): default_app().debug = bool(mode) -def optimize(mode=True): default_app().optimize = bool(mode) +#TODO: Global and app local configuration (debug, defaults, ...) is a mess + +def debug(mode=True): + global DEBUG + DEBUG = bool(mode) + +def optimize(mode=True): + default_app().optimize = bool(mode) diff --git a/pyLoadCore.py b/pyLoadCore.py index b78cf5b5c..fc050a309 100755 --- a/pyLoadCore.py +++ b/pyLoadCore.py @@ -46,10 +46,15 @@ from time import sleep import urllib2 from imp import find_module from re import sub +try: + find_module("Crypto") +except ImportError: + print "Install pycrypto to use pyLoad" + exit() from module.file_list import File_List from module.thread_list import Thread_List from module.network.Request import Request -import module.remote.SecureXMLRPCServer as Server +from module.web import WebServer import thread class Core(object): @@ -112,7 +117,6 @@ class Core(object): translation = gettext.translation("pyLoad", "locale", languages=[self.config['general']['language']]) translation.install(unicode=True) - self.check_install("Crypto", "pycrypto to decode container files") self.check_install("pycurl", "pycurl for lower memory footprint while downloading") self.check_install("tesseract", "tesseract for captcha reading", False) self.check_install("gocr", "gocr for captcha reading", False) @@ -140,6 +144,8 @@ class Core(object): self.init_server() + self.init_webserver() + self.logger.info(_("Downloadtime: %s") % self.server_methods.is_time_download()) # debug only self.read_url_list(self.config['general']['link_file']) @@ -174,6 +180,7 @@ class Core(object): try: server_addr = (self.config['remote']['listenaddr'], int(self.config['remote']['port'])) usermap = { self.config['remote']['username']: self.config['remote']['password']} + Server = __import__("module.remote.SecureXMLRPCServer", globals(), locals(), "SecureXMLRPCServer", -1) if self.config['ssl']['activated']: self.server = Server.SecureXMLRPCServer(server_addr, self.config['ssl']['cert'], self.config['ssl']['key'], usermap) self.logger.info("Secure XMLRPC Server Started") @@ -240,31 +247,19 @@ class Core(object): def check_update(self): """checks newst version""" - if self.config['updates']['search_updates']: - version_check = Request().load("http://update.pyload.org/index.php?do=dev%s&download=%s" %(CURRENT_VERSION, self.config['updates']['install_updates'])) - if version_check == "": - self.logger.info("No Updates for pyLoad") - return False + if not self.config['updates']['search_updates']: + return False + + newst_version = Request().load("http://update.pyload.org/s/" + CURRENT_VERSION) + if newst_version == "True": + if not self.config['updates']['install_updates']: + self.logger.info("New Version of pyLoad available") else: - if self.config['updates']['install_updates']: - try: - tmp_zip_name = __import__("tempfile").NamedTemporaryFile(suffix=".zip").name - tmp_zip = open(tmp_zip_name, 'w') - tmp_zip.write(version_check) - tmp_zip.close() - __import__("module.Unzip", globals(), locals(), "Unzip", -1).Unzip().extract(tmp_zip_name,"Test/") - return True - - except: - self.logger.info("Auto install Faild") - return False - - else: - self.logger.info("New pyLoad Version %s available" % version_check) - return True + updater = __import__("pyLoadUpdater") + updater.main() else: - return False - + self.logger.info("No Updates for pyLoad") + def create_plugin_index(self): for file_handler in glob(self.plugin_folder + sep + '*.py') + glob(self.plugin_folder + sep + 'DLC.pyc'): plugin_pattern = "" @@ -289,7 +284,15 @@ class Core(object): elif start > end and (now > start or now < end): return True elif start < now and end < now and start > end: return True else: return False - + + + def init_webserver(self): + self.webserver = WebServer.WebServer(self) + if self.config['webinterface']['activated']: + self.webserver.start() + + + #################################### ########## XMLRPC Methods ########## #################################### @@ -436,6 +439,8 @@ class ServerMethods(): end = self.core.config['reconnectTime']['end'].split(":") return self.compare_time(start, end) + + if __name__ == "__main__": pyload_core = Core() try: |