diff --git a/plugins/WebUi/__init__.py b/plugins/WebUi/__init__.py index b6346c535..497fe5f0b 100644 --- a/plugins/WebUi/__init__.py +++ b/plugins/WebUi/__init__.py @@ -29,7 +29,7 @@ # this exception statement from your version. If you delete this exception plugin_name = _("Web User Interface") -plugin_author = _("Martijn Voncken") +plugin_author = "Martijn Voncken" plugin_version = "rev." plugin_description = _("""A Web based User Interface @@ -41,7 +41,7 @@ There is support for multiple templates, but just one is included. Other contributors: *somedude : template enhancements. - +*markybob : stability : synced with his changes in deluge-svn. """) import deluge.common @@ -80,9 +80,10 @@ class plugin_WebUi(object): self.web_server = None if not deluge.common.windows_check(): import commands - status = commands.getstatusoutput('ps x |grep -v grep |grep run_webserver') + status = commands.getstatusoutput( + 'ps x |grep -v grep |grep run_webserver') if status[0] == 0: - os.kill(status[1].split()[0], 9) + os.kill(int(status[1].split()[0]), 9) time.sleep(1) #safe time to wait for kill to finish. self.config_file = deluge.common.CONFIG_DIR + "/webui.conf" self.config = deluge.pref.Preferences(self.config_file, False) @@ -95,9 +96,6 @@ class plugin_WebUi(object): if not self.config.get('port'): #ugly way to detect new config file. #set default values: self.config.set("port", 8112) - #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) @@ -112,11 +110,9 @@ class plugin_WebUi(object): self.config.set("cache_templates", True) if deluge.common.windows_check(): - if self.config.get("run_in_thread") == None: - self.config.set("run_in_thread", True) + self.config.set("run_in_thread", True) else: - if self.config.get("run_in_thread") == None: - self.config.set("run_in_thread", False) + self.config.set("run_in_thread", False) self.dbus_manager = get_dbus_manager(deluge_core, deluge_interface, self.config, self.config_file) @@ -141,8 +137,8 @@ class plugin_WebUi(object): self.kill_server() if self.config.get("run_in_thread"): - print 'start Webui(inside gtk)..' - webserver_common.init() #reload changed config. + print 'Start Webui(inside gtk)..' + webserver_common.init_gtk_05() #reload changed config. from deluge_webserver import WebServer #only import in threaded mode @@ -150,11 +146,9 @@ class plugin_WebUi(object): self.web_server.start_gtk() else: - print 'start Webui(in process)..' - path = os.path.dirname(__file__) - server_bin = path + '/run_webserver' - port = str(self.config.get('port')) - self.proc = Popen((server_bin, port),cwd=path) + print 'Start Webui(in process)..' + server_bin = os.path.dirname(__file__) + '/run_webserver' + self.proc = Popen((server_bin,'env=0.5')) def kill_server(self): if self.web_server: @@ -187,7 +181,8 @@ class ConfigDialog(gtk.Dialog): template_path = os.path.join(os.path.dirname(__file__), 'templates') self.templates = [dirname for dirname in os.listdir(template_path) - if os.path.isdir(os.path.join(template_path, dirname))] + if os.path.isdir(os.path.join(template_path, dirname)) + and not dirname.startswith('.')] self.port = self.add_widget(_('Port Number'), gtk.SpinButton()) self.pwd1 = self.add_widget(_('New Password'), gtk.Entry()) @@ -195,16 +190,11 @@ class ConfigDialog(gtk.Dialog): 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'), - gtk.FileChooserButton(_('Torrent Directory'))) self.cache_templates = self.add_widget(_('Cache Templates'), gtk.CheckButton()) - self.run_in_thread = self.add_widget(_('Run inside GTK'), gtk.CheckButton()) + #self.share_downloads = self.add_widget(_('Share Download Directory'), + # gtk.CheckButton()) - self.download_dir.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - 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) @@ -218,23 +208,16 @@ class ConfigDialog(gtk.Dialog): for item in [_('Text and image'), _('Image Only'), _('Text Only')]: self.button_style.append_text(item) - if not self.config.get("button_style"): + if self.config.get("button_style") == None: 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.share_downloads.set_active( + # bool(self.config.get("share_downloads"))) - self.torrent_dir.set_filename(self.config.get("torrent_dir")) - self.download_dir.set_filename(self.config.get("download_dir")) - - if deluge.common.windows_check(): - self.run_in_thread.set_active(True) - self.run_in_thread.set_sensitive(False) - else: - self.run_in_thread.set_active(False) - self.run_in_thread.set_sensitive(False) self.cache_templates.set_active(self.config.get("cache_templates")) self.vbox.pack_start(self.vb, True, True, 0) @@ -270,9 +253,7 @@ class ConfigDialog(gtk.Dialog): 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.set("cache_templates", self.cache_templates.get_active()) - self.config.set("run_in_thread", self.run_in_thread.get_active()) + #self.config.set("share_downloads", self.share_downloads.get_active()) self.config.save(self.plugin.config_file) self.plugin.start_server() #restarts server diff --git a/plugins/WebUi/dbus_interface.py b/plugins/WebUi/dbus_interface.py index 67fe2fa68..b253b821e 100644 --- a/plugins/WebUi/dbus_interface.py +++ b/plugins/WebUi/dbus_interface.py @@ -68,7 +68,7 @@ class DbusManager(dbus.service.Object): @dbus.service.method(dbus_interface=dbus_interface, in_signature="",out_signature="as") - def get_torrent_state(self): + def get_session_state(self): """Returns a list of torrent_ids in the session. same as 0.6, but returns type "as" instead of a pickle """ @@ -129,18 +129,20 @@ class DbusManager(dbus.service.Object): return status_subset @dbus.service.method(dbus_interface=dbus_interface, - in_signature="s",out_signature="") - def pause_torrent(self, torrent_id): + in_signature="as",out_signature="") + def pause_torrent(self, torrents): """same as 0.6 interface""" - torrent_id = int(torrent_id) - self.core.set_user_pause(torrent_id,True) + for torrent_id in torrents: + torrent_id = int(torrent_id) + self.core.set_user_pause(torrent_id,True) @dbus.service.method(dbus_interface=dbus_interface, - in_signature="s", out_signature="") - def resume_torrent(self, torrent_id): + in_signature="as", out_signature="") + def resume_torrent(self, torrents): """same as 0.6 interface""" - torrent_id = int(torrent_id) - self.core.set_user_pause(torrent_id,False) + for torrent_id in torrents: + torrent_id = int(torrent_id) + self.core.set_user_pause(torrent_id,False) @dbus.service.method(dbus_interface=dbus_interface, in_signature="sbb", out_signature="") @@ -157,7 +159,6 @@ class DbusManager(dbus.service.Object): @dbus.service.method(dbus_interface=dbus_interface, in_signature="s", out_signature="b") def add_torrent_url(self, url): - """not available in deluge 0.6 interface""" filename = fetch_url(url) self._add_torrent(filename) return True @@ -182,8 +183,8 @@ class DbusManager(dbus.service.Object): #name = fillename without directory name = name.replace('\\','/') name = 'deluge_' + str(random.random()) + '_' + name.split('/')[-1] + filename = os.path.join(self.core.config.get("default_download_path"), name) - filename = os.path.join(self.config.get("torrent_dir"),name) filecontent = base64.b64decode(filecontent_b64) f = open(filename,"wb") #no with statement, that's py 2.5+ f.write(filecontent) @@ -192,11 +193,42 @@ class DbusManager(dbus.service.Object): self._add_torrent(filename) return True + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="", out_signature="a{sv}") + def get_config(self): + return self.core.config.mapping + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="s", out_signature="v") + def get_config_value(self,key): + return self.core.config.mapping[pythonize(key)] #ugly! + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="a{sv}", out_signature="") + def set_config(self, config): + """Set the config with values from dictionary""" + config = deluge.common.pythonize(config) + # Load all the values into the configuration + for key in self.core.config.keys(): + self.core.config[key] = config[key] + self.core.apply_prefs() + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="", out_signature="v") + def get_download_rate(self): + return self.core.get_state()['download_rate'] + + @dbus.service.method(dbus_interface=dbus_interface, + in_signature="", out_signature="v") + def get_upload_rate(self): + return self.core.get_state()['upload_rate'] + + #internal def _add_torrent(self, filename): - #dbus types break pickle, again..... filename = unicode(filename) - target = self.config.get("download_dir") + target = self.core.config.get("default_download_path") torrent_id = self.core.add_torrent(filename, target, self.interface.config.get("use_compact_storage")) diff --git a/plugins/WebUi/deluge_webserver.py b/plugins/WebUi/deluge_webserver.py index a39200683..4c989dccb 100644 --- a/plugins/WebUi/deluge_webserver.py +++ b/plugins/WebUi/deluge_webserver.py @@ -31,67 +31,15 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. - -from webserver_common import TORRENT_KEYS, STATE_MESSAGES import webserver_common as ws from webserver_framework import * - import webpy022 as web from webpy022.http import seeother, url -from webpy022.utils import Storage -from md5 import md5 import base64 -from deluge.common import fsize from operator import attrgetter - -#utils: -def check_pwd(pwd): - m = md5() - m.update(ws.config.get('pwd_salt')) - m.update(pwd) - return (m.digest() == ws.config.get('pwd_md5')) - -def get_torrent_status(torrent_id): - """ - helper method. - enhance ws.proxy.get_torrent_status with some extra data - """ - status = ws.proxy.get_torrent_status(torrent_id,TORRENT_KEYS) - status["id"] = torrent_id - - #for naming the status-images - status["calc_state_str"] = "downloading" - if status["paused"]: - status["calc_state_str"] = "inactive" - elif status["is_seed"]: - status["calc_state_str"] = "seeding" - - #action for torrent_pause - if status["calc_state_str"] == "inactive": - status["action"] = "start" - else: - status["action"] = "stop" - - if status["paused"]: - status["message"] = _("Paused %s%%") % status['progress'] - else: - status["message"] = "%s %i%%" % (STATE_MESSAGES[status["state"]] - , status['progress']) - - #add some pre-calculated values - status.update({ - "calc_total_downloaded" : (fsize(status["total_done"]) - + " (" + fsize(status["total_download"]) + ")"), - "calc_total_uploaded": (fsize(status['uploaded_memory'] - + status["total_payload_upload"]) + " (" - + fsize(status["total_upload"]) + ")"), - }) - - return Storage(status) #Storage for easy templating. - -#/utils +import os #routing: urls = ( @@ -107,15 +55,21 @@ urls = ( "/resume_all(.*)", "resume_all", "/refresh/set(.*)", "refresh_set", "/refresh/(.*)", "refresh", + "/config(.*)","config", "/home(.*)", "home", "/about(.*)", "about", + "/logout(.*)", "logout", #default-pages - "/", "login", - "", "login", + "/", "home", + "", "home", #remote-api: - "/remote/torrent/add(.*)", "remote_torrent_add" -) + "/remote/torrent/add(.*)", "remote_torrent_add", + #static: + "/static/(.*)","static", + "/template/static/(.*)","template_static", + #"/downloads/(.*)","downloads" disabled until it can handle large downloads. +) #/routing #pages: @@ -133,15 +87,10 @@ class login: start_session() do_redirect() elif vars.redir: - seeother(url('/login',error=1,redir=vars.redir)) + seeother(url('/login',error=1, redir=vars.redir)) else: seeother('/login?error=1') -class home: - @check_session - def GET(self, name): - do_redirect() - class index: "page containing the torrent list." @auto_refreshed @@ -150,7 +99,7 @@ class index: vars = web.input(sort=None, order=None) status_rows = [get_torrent_status(torrent_id) - for torrent_id in ws.proxy.get_torrent_state()] + for torrent_id in ws.proxy.get_session_state()] #sorting: if vars.sort: @@ -164,21 +113,19 @@ class index: return ws.render.index(status_rows) class torrent_info: - "torrent details" @auto_refreshed @deluge_page def GET(self, torrent_id): return ws.render.torrent_info(get_torrent_status(torrent_id)) class torrent_pause: - "start/stop a torrent" @check_session def POST(self, name): vars = web.input(stop = None, start = None, redir = None) if vars.stop: - ws.proxy.pause_torrent(vars.stop) + ws.proxy.pause_torrent([vars.stop]) elif vars.start: - ws.proxy.resume_torrent(vars.start) + ws.proxy.resume_torrent([vars.start]) do_redirect() @@ -194,7 +141,7 @@ class torrent_add: if vars.url and vars.torrent.filename: error_page(_("Choose an url or a torrent, not both.")) if vars.url: - ws.proxy.add_torrent_url(vars.url) + ws.proxy.add_torrent_url(vars.url ) do_redirect() elif vars.torrent.filename: data = vars.torrent.file.read() @@ -227,8 +174,7 @@ class torrent_delete: return ws.render.torrent_delete(get_torrent_status(torrent_id)) @check_session - def POST(self, name): - torrent_id = name + def POST(self, torrent_id): vars = web.input(data_also = None, torrent_also = None) data_also = bool(vars.data_also) torrent_also = bool(vars.torrent_also) @@ -238,30 +184,26 @@ class torrent_delete: class torrent_queue_up: @check_session - def POST(self, name): - torrent_id = name + def POST(self, torrent_id): ws.proxy.queue_up(torrent_id) do_redirect() class torrent_queue_down: @check_session - def POST(self, name): - torrent_id = name + def POST(self, torrent_id): ws.proxy.queue_down(torrent_id) do_redirect() class pause_all: @check_session def POST(self, name): - for torrent_id in ws.proxy.get_torrent_state(): - ws.proxy.pause_torrent(torrent_id) + ws.proxy.pause_torrent(ws.proxy.get_session_state()) do_redirect() class resume_all: @check_session def POST(self, name): - for torrent_id in ws.proxy.get_torrent_state(): - ws.proxy.resume_torrent(torrent_id) + ws.proxy.resume_torrent(ws.proxy.get_session_state()) do_redirect() class refresh: @@ -287,13 +229,61 @@ class refresh_set: else: error_page(_('refresh must be > 0')) +class config: + """core config + TODO:good validation. + """ + cfg_form = web.form.Form( + web.form.Dropdown('max_download', ws.SPEED_VALUES, + description=_('Download Speed Limit'), + post='%s Kib/sec' % ws.proxy.get_config_value('max_download_speed') + ) + ,web.form.Dropdown('max_upload', ws.SPEED_VALUES, + description=_('Upload Speed Limit'), + post='%s Kib/sec' % ws.proxy.get_config_value('max_upload_speed') + ) + ) + + @deluge_page + def GET(self, name): + return ws.render.config(self.cfg_form()) + + def POST(self, name): + vars = web.input(max_download=None, max_upload=None) + + #self.config.set("max_download_speed", float(str_bwdown)) + raise NotImplementedError('todo') + +class home: + @check_session + def GET(self, name): + do_redirect() + class about: @deluge_page_noauth def GET(self, name): return ws.render.about() -#/pages +class logout: + def POST(self, name): + end_session() + seeother('/login') +class static(static_handler): + base_dir = os.path.join(os.path.dirname(__file__),'static') + +class template_static(static_handler): + def get_base_dir(self): + return os.path.join(os.path.dirname(__file__), + 'templates/%s/static' % ws.config.get('template')) + +class downloads(static_handler): + def GET(self, name): + self.base_dir = ws.proxy.get_config_value('default_download_path') + if not ws.config.get('share_downloads'): + raise Exception('Access to downloads is forbidden.') + return static_handler.GET(self, name) +#/pages def WebServer(): return create_webserver(urls, globals()) @@ -307,4 +297,3 @@ def run(): if __name__ == "__main__": run() - diff --git a/plugins/WebUi/revno b/plugins/WebUi/revno index 84df3526d..194b81caa 100644 --- a/plugins/WebUi/revno +++ b/plugins/WebUi/revno @@ -1 +1 @@ -87 +112 diff --git a/plugins/WebUi/run_webserver b/plugins/WebUi/run_webserver index 9ac32c4ff..b8e9b47f5 100755 --- a/plugins/WebUi/run_webserver +++ b/plugins/WebUi/run_webserver @@ -1,3 +1,3 @@ #!/usr/bin/env python -from deluge_webserver import * -web.run(urls, globals()) \ No newline at end of file +import deluge_webserver +deluge_webserver.run() diff --git a/plugins/WebUi/scripts/add_torrent_to_deluge_webui b/plugins/WebUi/scripts/add_torrent_to_deluge_webui new file mode 100755 index 000000000..740857ccb --- /dev/null +++ b/plugins/WebUi/scripts/add_torrent_to_deluge_webui @@ -0,0 +1,10 @@ +#!/bin/bash + +pwd=deluge +url=http://localhost:8112 + +for arg in "$@" +do + curl -F torrent=@"$arg" -F pwd=$pwd $url/remote/torrent/add +done + diff --git a/plugins/WebUi/static/images/tango/system-log-out.png b/plugins/WebUi/static/images/tango/system-log-out.png new file mode 100644 index 000000000..0010931e2 Binary files /dev/null and b/plugins/WebUi/static/images/tango/system-log-out.png differ diff --git a/plugins/WebUi/static_handler.py b/plugins/WebUi/static_handler.py new file mode 100644 index 000000000..d5b706cca --- /dev/null +++ b/plugins/WebUi/static_handler.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +#(c) Martijn Voncken, mvoncken@gmail.com +#Same Licence as web.py 0.22 ->Public Domain +# +""" +static fileserving for web.py +without the need for wsgi wrapper magic. +""" +import webpy022 as web +from webpy022.http import seeother, url + +import posixpath +import urlparse +import urllib +import mimetypes +import os +import datetime +import cgi +from StringIO import StringIO +mimetypes.init() # try to read system mime.types + +class static_handler: + """ + mostly c&p from SimpleHttpServer + serves relative from start location + """ + base_dir = './' + extensions_map = mimetypes.types_map + + def get_base_dir(self): + #override this if you have a config that changes the base dir at runtime + #deluge on windows :( + return self.base_dir + + def GET(self, path): + path = self.translate_path(path) + if os.path.isdir(path): + if not path.endswith('/'): + path += "/" + return self.list_directory(path) + + ctype = self.guess_type(path) + + try: + f = open(path, 'rb') + except IOError: + raise Exception('file not found:%s' % path) + #web.header("404", "File not found") + #return + web.header("Content-type", ctype) + fs = os.fstat(f.fileno()) + web.header("Content-Length", str(fs[6])) + web.lastmodified(datetime.datetime.fromtimestamp(fs.st_mtime)) + print f.read() + + def translate_path(self, path): + """Translate a /-separated PATH to the local filename syntax. + + Components that mean special things to the local file system + (e.g. drive or directory names) are ignored. (XXX They should + probably be diagnosed.) + + """ + # abandon query parameters + path = urlparse.urlparse(path)[2] + path = posixpath.normpath(urllib.unquote(path)) + words = path.split('/') + words = filter(None, words) + path = self.get_base_dir() + for word in words: + drive, word = os.path.splitdrive(word) + head, word = os.path.split(word) + if word in (os.curdir, os.pardir): continue + path = os.path.join(path, word) + return path + + def guess_type(self, path): + base, ext = posixpath.splitext(path) + if ext in self.extensions_map: + return self.extensions_map[ext] + ext = ext.lower() + if ext in self.extensions_map: + return self.extensions_map[ext] + else: + return 'application/octet-stream' + + + def list_directory(self, path): + """Helper to produce a directory listing (absent index.html). + + Return value is either a file object, or None (indicating an + error). In either case, the headers are sent, making the + interface the same as for send_head(). + #TODO ->use web.py +template! + """ + try: + list = os.listdir(path) + except os.error: + web.header('404', "No permission to list directory") + return None + list.sort(key=lambda a: a.lower()) + f = StringIO() + displaypath = cgi.escape(urllib.unquote(path)) + f.write("