diff --git a/plugins/WebUi/__init__.py b/plugins/WebUi/__init__.py index 25db3ca70..0b4c5d2f4 100644 --- a/plugins/WebUi/__init__.py +++ b/plugins/WebUi/__init__.py @@ -33,18 +33,24 @@ plugin_name = "Web User interface" plugin_author = "Martijn Voncken" plugin_version = "rev." -plugin_description = """A Web based User Interface (and dbus-ipc) -beta test version, disclaimer, etc.. -""" +plugin_description = "A Web based User Interface\n" -import deluge.common, deluge.pref +import deluge.common +import deluge.pref +from deluge.dialogs import show_popup_warning from dbus_interface import DbusManager + import gtk import os - from subprocess import Popen +from md5 import md5 +import random +random.seed() plugin_version += open(os.path.join(os.path.dirname(__file__),'revno')).read() +plugin_description += ( + open(os.path.join(os.path.dirname(__file__),'version')).read()) + def deluge_init(deluge_path): global path @@ -72,19 +78,24 @@ class plugin_WebUi: if not self.config.get('port'): #ugly way to detect new config file. #set default values: self.config.set("port", 8112) - self.config.set("user", "deluge") - self.config.set("pwd", "deluge") #future->use deluge-core setting for download_dir (if it is set) self.config.set("download_dir", os.path.expanduser("~/")) self.config.set("torrent_dir", os.path.expanduser("~/")) + self.config.set("button_style", 2) self.config.set("auto_refresh", False) self.config.set("auto_refresh_secs", 4) self.config.set("template", "deluge") self.config.save(self.config_file) + if not self.config.get("pwd_salt"): + self.config.set("pwd_salt", "invalid") + self.config.set("pwd_md5", "invalid") + self.dbusManager = DbusManager(deluge_core, deluge_interface , self.config, self.config_file) + + print dir(self.dbusManager) self.start_server() def unload(self): @@ -135,9 +146,11 @@ class ConfigDialog(gtk.Dialog): if os.path.isdir(os.path.join(template_path, dirname))] self.port = self.add_widget(_('Port Number'), gtk.SpinButton()) - self.user = self.add_widget(_('User'), gtk.Entry()) - self.pwd = self.add_widget(_('Password'), gtk.Entry()) + self.pwd1 = self.add_widget(_('New Password'), gtk.Entry()) + self.pwd2 = self.add_widget(_('New Password(confirm)'), gtk.Entry()) self.template = self.add_widget(_('Template'), gtk.combo_box_new_text()) + self.button_style = self.add_widget(_('Button Style'), + gtk.combo_box_new_text()) self.download_dir = self.add_widget(_('Download Directory'), gtk.FileChooserButton(_('Download Directory'))) self.torrent_dir = self.add_widget(_('Torrent Directory'), @@ -147,16 +160,24 @@ class ConfigDialog(gtk.Dialog): self.torrent_dir.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) self.port.set_range(80, 65536) self.port.set_increments(1, 10) + self.pwd1.set_visibility(False) + self.pwd2.set_visibility(False) + for item in self.templates: self.template.append_text(item) + self.button_style if not self.config.get("template") in self.templates: self.config.set("template","deluge") - self.user.set_text(self.config.get("user")) - self.pwd.set_text(self.config.get("pwd")) + for item in [_('Text and image'), _('Image Only'), _('Text Only')]: + self.button_style.append_text(item) + if not self.config.get("button_style"): + self.config.set("button_style", 2) + self.port.set_value(int(self.config.get("port"))) self.template.set_active( self.templates.index(self.config.get("template"))) + self.button_style.set_active(self.config.get("button_style")) self.torrent_dir.set_filename(self.config.get("torrent_dir")) self.download_dir.set_filename(self.config.get("download_dir")) self.vbox.pack_start(self.vb, True, True, 0) @@ -177,11 +198,21 @@ class ConfigDialog(gtk.Dialog): self.add_buttons(dgtk.STOCK_CLOSE, dgtk.RESPONSE_CLOSE) def save_config(self): - print 'save config' - self.config.set("user", self.user.get_text()) - self.config.set("pwd", self.pwd.get_text()) + if self.pwd1.get_text() > '': + if self.pwd1.get_text() <> self.pwd2.get_text(): + show_popup_warning(self,_("Confirmed Password <> New Password\n" + + "Password was not changed")) + else: + salt = str(random.getrandbits(500)) + m = md5() + m.update(salt) + m.update(unicode(self.pwd1.get_text())) + self.config.set("pwd_salt", salt) + self.config.set("pwd_md5", m.digest()) + self.config.set("port", int(self.port.get_value())) self.config.set("template", self.template.get_active_text()) + self.config.set("button_style", self.button_style.get_active()) self.config.set("torrent_dir", self.torrent_dir.get_filename()) self.config.set("download_dir",self.download_dir.get_filename()) self.config.save(self.plugin.config_file) diff --git a/plugins/WebUi/curl-example b/plugins/WebUi/curl-example new file mode 100644 index 000000000..f6fb0821a --- /dev/null +++ b/plugins/WebUi/curl-example @@ -0,0 +1 @@ +curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add diff --git a/plugins/WebUi/dbus_interface.py b/plugins/WebUi/dbus_interface.py index 4f646da1d..d44666da6 100644 --- a/plugins/WebUi/dbus_interface.py +++ b/plugins/WebUi/dbus_interface.py @@ -39,6 +39,7 @@ import dbus import deluge.common as common from dbus_pythonize import pythonize import base64 +from md5 import md5 import random random.seed() @@ -182,7 +183,7 @@ class DbusManager(dbus.service.Object): not in 0.6 """ retval = self.config.get(str(key)) - print 'get webui config:', str(key), retval + #print 'get webui config:', str(key), retval if retval == None: retval = False #dbus does not accept None :( @@ -195,10 +196,18 @@ class DbusManager(dbus.service.Object): return data from wevbui config. not in 0.6 """ - print 'set webui config:', str(key), pythonize(value) + #print 'set webui config:', str(key), pythonize(value) self.config.set(str(key), pythonize(value)) self.config.save(self.config_file) + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="s",out_signature="b") + def check_pwd(self, pwd): + m = md5() + m.update(self.config.get('pwd_salt')) + m.update(pwd) + return (m.digest() == self.config.get('pwd_md5')) + #internal def _add_torrent(self, filename): #dbus types break pickle, again..... diff --git a/plugins/WebUi/deluge_webserver.py b/plugins/WebUi/deluge_webserver.py index 864579fff..bc62f556b 100644 --- a/plugins/WebUi/deluge_webserver.py +++ b/plugins/WebUi/deluge_webserver.py @@ -50,7 +50,7 @@ from webpy022 import template import dbus -import gettext, os, platform, locale +import gettext, os, platform, locale, traceback import random import base64 from operator import attrgetter @@ -105,7 +105,7 @@ def deluge_page_noauth(func): web.header("Content-Type", "text/html; charset=utf-8") web.header("Cache-Control", "no-cache, must-revalidate") res = func(self, name) - print res + print unicode(res) return deco def check_session(func): @@ -139,6 +139,17 @@ def error_page(error): web.header("Cache-Control", "no-cache, must-revalidate") print render.error(error) +def remote(func): + "decorator for remote api's" + def deco(self, name): + try: + print func(self, name) + except Exception, e: + print 'error:' + e.message + print '-'*20 + print traceback.format_exc() + return deco + #/framework #utils: @@ -191,7 +202,7 @@ def template_crop(text, end): return text[0:end - 3] + '...' return text -def sort_head(id,name): +def template_sort_head(id,name): #got tired of doing these complex things inside templetor.. vars = web.input(sort=None, order=None) active_up = False @@ -210,18 +221,23 @@ def sort_head(id,name): render = template.render('templates/%s/' % proxy.get_webui_config('template')) -template.Template.globals['crop'] = template_crop -template.Template.globals['fspeed'] = common.fspeed -template.Template.globals['fsize'] = common.fsize -template.Template.globals['sorted'] = sorted -template.Template.globals['_'] = _ #gettext/translations -template.Template.globals['deluge_web_version'] = ('rev.' - + open(os.path.join(os.path.dirname(__file__),'revno')).read()) -template.Template.globals['render'] = render #for easy resuse of templates -template.Template.globals['sort_head'] = sort_head -template.Template.globals['get_config'] = proxy.get_webui_config -template.Template.globals['self_url'] = web.changequery - +template.Template.globals.update({ + 'sort_head': template_sort_head, + 'crop': template_crop, + '_': _ , #gettext/translations + 'str': str, #because % in templetor is broken. + 'sorted': sorted, + 'get_config': proxy.get_webui_config, + 'self_url': web.changequery, + 'fspeed': common.fspeed, + 'fsize': common.fsize, + 'render': render, #for easy resuse of templates + 'button_style': (proxy.get_webui_config('button_style')), + 'rev': ('rev.' + + open(os.path.join(os.path.dirname(__file__),'revno')).read()), + 'version': ( + open(os.path.join(os.path.dirname(__file__),'version')).read()) +}) #/template-defs #routing: @@ -237,9 +253,12 @@ urls = ( "/refresh/set(.*)", "refresh_set", "/refresh/(.*)", "refresh", "/home(.*)", "home", + "/about(.*)", "about", #default-pages "/", "login", - "", "login" + "", "login", + #remote-api: + "/remote/torrent/add(.*)", "remote_torrent_add" ) #/routing @@ -248,20 +267,20 @@ urls = ( class login: @deluge_page_noauth def GET(self, name): - return render.login() + vars = web.input(error = None) + return render.login(vars.error) def POST(self, name): - vars = web.input(var = None, pwd = None) + vars = web.input(pwd = None) - if (vars.user == proxy.get_webui_config('user') - and vars.pwd == proxy.get_webui_config('pwd')): + if proxy.check_pwd(vars.pwd): #start new session session_id = str(random.random()) - SESSIONS[session_id] = {"user":vars.user} + SESSIONS[session_id] = {"not":"used"} setcookie("session_id", session_id) do_redirect() else: - error_page(_("Username or Password is invalid.")) + seeother('/login?error=1') class home: @check_session @@ -332,6 +351,23 @@ class torrent_add: else: error_page(_("no data.")) +class remote_torrent_add: + """ + For use in remote scripts etc. + POST user and file + Example : curl -F torrent=@./test1.torrent -F pwd=deluge http://localhost:8112/remote/torrent/add" + """ + @remote + def POST(self, name): + vars = web.input(pwd = None, torrent = {}) + + if not proxy.check_pwd(vars.pwd): + return 'error:wrong password' + + data_b64 = base64.b64encode(vars.torrent.file.read()) + proxy.add_torrent_filecontent(vars.torrent.filename,data_b64) + return 'ok' + class torrent_delete: @deluge_page def GET(self, torrent_id): @@ -383,6 +419,12 @@ class refresh_set: else: error_page(_('refresh must be > 0')) +class about: + @deluge_page_noauth + def GET(self, name): + return render.about() + + #/pages if __name__ == "__main__": diff --git a/plugins/WebUi/revno b/plugins/WebUi/revno index 425151f3a..21e72e8ac 100644 --- a/plugins/WebUi/revno +++ b/plugins/WebUi/revno @@ -1 +1 @@ -40 +48 diff --git a/plugins/WebUi/static/simple_site_style.css b/plugins/WebUi/static/simple_site_style.css index b5f51d018..16d65480e 100755 --- a/plugins/WebUi/static/simple_site_style.css +++ b/plugins/WebUi/static/simple_site_style.css @@ -31,6 +31,15 @@ button.deluge_button { color: #000; } +div.error { + background-color:#FFFFFF; + color:#AA0000; + font-weight:bold; + -moz-border-radius:10px; + width:200px; + margin-bottom:20px; + padding:10px; +} /* Hides from IE-mac \*/ * html .clearfix {height: 1%;} .clearfix {display: block;} /* End hide from IE-mac */ \ No newline at end of file diff --git a/plugins/WebUi/templates/deluge/about.html b/plugins/WebUi/templates/deluge/about.html new file mode 100644 index 000000000..66430a8b8 --- /dev/null +++ b/plugins/WebUi/templates/deluge/about.html @@ -0,0 +1,39 @@ +$:render.header(_('About')) +
+

Version

+
$version 
+

Links

+ + +

Authors

+ +*and all other authors/helpers/contributors I forgot to mention. +
+ +$:render.footer() \ No newline at end of file diff --git a/plugins/WebUi/templates/deluge/error.html b/plugins/WebUi/templates/deluge/error.html index c177a88c6..002cf3fc2 100644 --- a/plugins/WebUi/templates/deluge/error.html +++ b/plugins/WebUi/templates/deluge/error.html @@ -1,6 +1,6 @@ $def with (error_msg) -$:header(_('Error')) +$:render.header(_('Error'))
     $error_msg
 
-$:footer() \ No newline at end of file +$:render.footer() diff --git a/plugins/WebUi/templates/deluge/header.html b/plugins/WebUi/templates/deluge/header.html index 076ef9ef6..42cc62eed 100644 --- a/plugins/WebUi/templates/deluge/header.html +++ b/plugins/WebUi/templates/deluge/header.html @@ -1,15 +1,13 @@ $def with (title) - - - Deluge:$title +
diff --git a/plugins/WebUi/templates/deluge/index.html b/plugins/WebUi/templates/deluge/index.html index 35fa0df1d..e925fcdca 100644 --- a/plugins/WebUi/templates/deluge/index.html +++ b/plugins/WebUi/templates/deluge/index.html @@ -54,5 +54,4 @@ $:render.part_button('POST', '/resume_all', _('Resume all'), 'tango/media-playba $:render.part_refresh() - $:render.footer() diff --git a/plugins/WebUi/templates/deluge/login.html b/plugins/WebUi/templates/deluge/login.html index f21ecabeb..361a2a4e0 100644 --- a/plugins/WebUi/templates/deluge/login.html +++ b/plugins/WebUi/templates/deluge/login.html @@ -1,13 +1,13 @@ +$def with (error) $:render.header(_('Login'))
+$if error > 0: +
$_("Password is invalid,try again")
+
- $_('User') - -
-
- $_('Pass') + $_('Password')
@@ -15,6 +15,9 @@ $:render.header(_('Login'))
+ +
+ $_('About')
diff --git a/plugins/WebUi/templates/deluge/part_button.html b/plugins/WebUi/templates/deluge/part_button.html index f65936629..3dd22d1a3 100644 --- a/plugins/WebUi/templates/deluge/part_button.html +++ b/plugins/WebUi/templates/deluge/part_button.html @@ -3,11 +3,20 @@ $def with (method, url, title, image='')
\ No newline at end of file diff --git a/plugins/WebUi/templates/deluge/part_refresh.html b/plugins/WebUi/templates/deluge/part_refresh.html index b3f065a62..f48571ae2 100644 --- a/plugins/WebUi/templates/deluge/part_refresh.html +++ b/plugins/WebUi/templates/deluge/part_refresh.html @@ -1,5 +1,5 @@ -
-
+
+
$_('Auto refresh:') $if get_config('auto_refresh'): ($(get_config('refresh')) $_('seconds'))   diff --git a/plugins/WebUi/templates/deluge/refresh_form.html b/plugins/WebUi/templates/deluge/refresh_form.html index a285eb111..34e582bba 100644 --- a/plugins/WebUi/templates/deluge/refresh_form.html +++ b/plugins/WebUi/templates/deluge/refresh_form.html @@ -3,7 +3,7 @@ $:render.header(_('Set Timeout'))
$_('Refresh page every:') - $_('Seconds') + $_('seconds')
diff --git a/plugins/WebUi/templates/deluge/torrent_info.html b/plugins/WebUi/templates/deluge/torrent_info.html index 0dae3337b..9402d1c9f 100644 --- a/plugins/WebUi/templates/deluge/torrent_info.html +++ b/plugins/WebUi/templates/deluge/torrent_info.html @@ -80,17 +80,7 @@ class="deluge_button"> name="$torrent.action" value="$torrent.id"> -
-
- - -
-
- - - +$:render.part_button('GET', '/torrent/delete/' + str(torrent.id), _('Remove'), 'tango/user-trash.png')
[$_('Debug:Data Dump')] diff --git a/plugins/WebUi/templates/hacking-templates.txt b/plugins/WebUi/templates/hacking-templates.txt index ac1d86e04..f69e47c73 100644 --- a/plugins/WebUi/templates/hacking-templates.txt +++ b/plugins/WebUi/templates/hacking-templates.txt @@ -10,16 +10,21 @@ Or use scite and my config: http://mvoncken.sohosted.com/deluge/SciTEUser.proper template language: http://webpy.org/templetor Exposed methods and variables (c&p from deluge_webserver): - -template.Template.globals['crop'] = template_crop -template.Template.globals['fspeed'] = common.fspeed -template.Template.globals['fsize'] = common.fsize -template.Template.globals['sorted'] = sorted -template.Template.globals['_'] = _ #gettext/translations -template.Template.globals['deluge_web_version'] = '0.3alfa' -template.Template.globals['render'] = render #for easy resuse of templates -template.Template.globals['sort_head'] = sort_head -template.Template.globals['get_config'] = proxy.get_webui_config + 'sort_head': template_sort_head, + 'crop': template_crop, + '_': _ , #gettext/translations + 'str': str, #because % in templetor is broken. + 'sorted': sorted, + 'get_config': proxy.get_webui_config, + 'self_url': web.changequery, + 'fspeed': common.fspeed, + 'fsize': common.fsize, + 'render': render, #for easy resuse of templates + 'button_style': (proxy.get_webui_config('button_style')), + 'rev': ('rev.' + + open(os.path.join(os.path.dirname(__file__),'revno')).read()), + 'version': ( + open(os.path.join(os.path.dirname(__file__),'version')).read()) I will update this file if there is interest in making templates. diff --git a/plugins/WebUi/version b/plugins/WebUi/version index eb31649bc..21e72e8ac 100644 --- a/plugins/WebUi/version +++ b/plugins/WebUi/version @@ -1,5 +1 @@ -revision-id: mvoncken@gmail.com-20070923193628-omqar5yaxatdmfu0 -date: 2007-09-23 21:36:28 +0200 -build-date: 2007-09-23 21:54:09 +0200 -revno: 30 -branch-nick: WebUi +48 diff --git a/plugins/WebUi/webpy022/changes.txt b/plugins/WebUi/webpy022/changes.txt index 3a9fd9477..d15c94bab 100644 --- a/plugins/WebUi/webpy022/changes.txt +++ b/plugins/WebUi/webpy022/changes.txt @@ -1,6 +1,10 @@ -Commented out some code to enable a relative redirect. +1:Commented out some code to enable a relative redirect. This is not according to HTTP/1.1 Spec But many deluge users will want to route the webui through firewalls/routers or use apache redirects. +2:Disabled logging in the builtin http-server. + + + diff --git a/plugins/WebUi/webpy022/httpserver.py b/plugins/WebUi/webpy022/httpserver.py index d4610cfad..6df60b1d8 100644 --- a/plugins/WebUi/webpy022/httpserver.py +++ b/plugins/WebUi/webpy022/httpserver.py @@ -6,11 +6,11 @@ import net def runbasic(func, server_address=("0.0.0.0", 8080)): """ - Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` + Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` is hosted statically. Based on [WsgiServer][ws] from [Colin Stewart][cs]. - + [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html [cs]: http://www.owlfish.com/ """ @@ -62,17 +62,17 @@ def runbasic(func, server_address=("0.0.0.0", 8080)): try: try: for data in result: - if data: + if data: self.wsgi_write_data(data) finally: - if hasattr(result, 'close'): + if hasattr(result, 'close'): result.close() except socket.error, socket_err: # Catch common network errors and suppress them if (socket_err.args[0] in \ - (errno.ECONNABORTED, errno.EPIPE)): + (errno.ECONNABORTED, errno.EPIPE)): return - except socket.timeout, socket_timeout: + except socket.timeout, socket_timeout: return except: print >> web.debug, traceback.format_exc(), @@ -92,7 +92,7 @@ def runbasic(func, server_address=("0.0.0.0", 8080)): else: self.run_wsgi_app() - def wsgi_start_response(self, response_status, response_headers, + def wsgi_start_response(self, response_status, response_headers, exc_info=None): if (self.wsgi_sent_headers): raise Exception \ @@ -117,8 +117,8 @@ def runbasic(func, server_address=("0.0.0.0", 8080)): class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): def __init__(self, func, server_address): - BaseHTTPServer.HTTPServer.__init__(self, - server_address, + BaseHTTPServer.HTTPServer.__init__(self, + server_address, WSGIHandler) self.app = func self.serverShuttingDown = 0 @@ -128,7 +128,7 @@ def runbasic(func, server_address=("0.0.0.0", 8080)): def runsimple(func, server_address=("0.0.0.0", 8080)): """ - Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. + Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. The directory `static/` is hosted statically. [cp]: http://www.cherrypy.org @@ -180,7 +180,7 @@ def runsimple(func, server_address=("0.0.0.0", 8080)): else: value = self.wfile.getvalue() yield value - + class WSGIWrapper(BaseHTTPRequestHandler): """WSGI wrapper for logging the status and serving static files.""" def __init__(self, app): @@ -200,20 +200,23 @@ def runsimple(func, server_address=("0.0.0.0", 8080)): return self.app(environ, xstart_response) def log(self, status, environ): + #mvoncken,no logging.. + return + outfile = environ.get('wsgi.errors', web.debug) req = environ.get('PATH_INFO', '_') protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-') method = environ.get('REQUEST_METHOD', '-') - host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), + host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), environ.get('REMOTE_PORT','-')) - #@@ It is really bad to extend from + #@@ It is really bad to extend from #@@ BaseHTTPRequestHandler just for this method time = self.log_date_time_string() - print >> outfile, self.format % (host, time, protocol, + print >> outfile, self.format % (host, time, protocol, method, req, status) - + func = WSGIWrapper(func) server = CherryPyWSGIServer(server_address, func, server_name="localhost")