[#1974] [UI] Decouple UI selection from core.

Add entry points into setup for each of the UIs and then use this
information to determine which client UI to run.

This ensures that custom UIs may be written and run without
the need to modifify deluge source code.
This commit is contained in:
Jamie Lennox 2011-11-19 20:20:43 +11:00 committed by bendikro
commit aa82efd4f1
10 changed files with 166 additions and 134 deletions

View file

@ -20,6 +20,8 @@ import os
import sys import sys
from logging import FileHandler, getLogger from logging import FileHandler, getLogger
import pkg_resources
import deluge.common import deluge.common
import deluge.configmanager import deluge.configmanager
import deluge.error import deluge.error
@ -38,11 +40,14 @@ def start_ui():
parser = CommonOptionParser() parser = CommonOptionParser()
group = optparse.OptionGroup(parser, _("Default Options")) group = optparse.OptionGroup(parser, _("Default Options"))
group.add_option("-u", "--ui", dest="ui", ui_entrypoints = dict([(entrypoint.name, entrypoint.load())
help="""The UI that you wish to launch. The UI choices are:\n for entrypoint in pkg_resources.iter_entry_points('deluge.ui')])
\t gtk -- A GTK-based graphical user interface (default)\n
\t web -- A web-based interface (http://localhost:8112)\n cmd_help = ['The UI that you wish to launch. The UI choices are:']
\t console -- A console or command-line interface""", action="store", type="str") cmd_help.extend(["%s -- %s" % (k, getattr(v, 'cmdline', "")) for k, v in ui_entrypoints.iteritems()])
parser.add_option("-u", "--ui", dest="ui",
choices=ui_entrypoints.keys(), help="\n\t ".join(cmd_help), action="store")
group.add_option("-a", "--args", dest="args", group.add_option("-a", "--args", dest="args",
help="Arguments to pass to UI, -a '--option args'", action="store", type="str") help="Arguments to pass to UI, -a '--option args'", action="store", type="str")
group.add_option("-s", "--set-default-ui", dest="default_ui", group.add_option("-s", "--set-default-ui", dest="default_ui",
@ -79,22 +84,11 @@ def start_ui():
client_args.extend(args) client_args.extend(args)
try: try:
if selected_ui == "gtk": ui = ui_entrypoints[selected_ui](skip_common=True)
log.info("Starting GtkUI..") except KeyError as ex:
from deluge.ui.gtkui.gtkui import Gtk log.error("Unable to find the requested UI: '%s'. Please select a different UI with the '-u' option"
ui = Gtk(skip_common=True) " or alternatively use the '-s' option to select a different default UI.", selected_ui)
ui.start(client_args) except ImportError as ex:
elif selected_ui == "web":
log.info("Starting WebUI..")
from deluge.ui.web.web import Web
ui = Web(skip_common=True)
ui.start(client_args)
elif selected_ui == "console":
log.info("Starting ConsoleUI..")
from deluge.ui.console.main import Console
ui = Console(skip_common=True)
ui.start(client_args)
except ImportError, e:
import sys import sys
import traceback import traceback
error_type, error_value, tb = sys.exc_info() error_type, error_value, tb = sys.exc_info()
@ -104,10 +98,12 @@ def start_ui():
log.error("Unable to find the requested UI: %s. Please select a different UI with the '-u' " log.error("Unable to find the requested UI: %s. Please select a different UI with the '-u' "
"option or alternatively use the '-s' option to select a different default UI.", selected_ui) "option or alternatively use the '-s' option to select a different default UI.", selected_ui)
else: else:
log.exception(e) log.exception(ex)
log.error("There was an error whilst launching the request UI: %s", selected_ui) log.error("There was an error whilst launching the request UI: %s", selected_ui)
log.error("Look at the traceback above for more information.") log.error("Look at the traceback above for more information.")
sys.exit(1) sys.exit(1)
else:
ui.start(client_args)
def start_daemon(skip_start=False): def start_daemon(skip_start=False):

View file

@ -8,6 +8,9 @@
# #
UI_PATH = __path__[0] UI_PATH = __path__[0]
from deluge.ui.console.main import start # NOQA
assert start # silence pyflakes from deluge.ui.console.console import Console
def start():
Console().start()

View file

@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
import optparse
import os
import sys
from deluge.ui.console import UI_PATH
from deluge.ui.ui import UI
def load_commands(command_dir, exclude=[]):
def get_command(name):
return getattr(__import__('deluge.ui.console.commands.%s' % name, {}, {}, ['Command']), 'Command')()
try:
commands = []
for filename in os.listdir(command_dir):
if filename.split('.')[0] in exclude or filename.startswith('_'):
continue
if not (filename.endswith('.py') or filename.endswith('.pyc')):
continue
cmd = get_command(filename.split('.')[len(filename.split('.')) - 2])
aliases = [filename.split('.')[len(filename.split('.')) - 2]]
aliases.extend(cmd.aliases)
for a in aliases:
commands.append((a, cmd))
return dict(commands)
except OSError:
return {}
class Console(UI):
help = """Starts the Deluge console interface"""
cmdline = """A console or command-line interface"""
def __init__(self, *args, **kwargs):
super(Console, self).__init__("console", *args, **kwargs)
group = optparse.OptionGroup(self.parser, "Console Options", "These options control how "
"the console connects to the daemon. These options will be "
"used if you pass a command, or if you have autoconnect "
"enabled for the console ui.")
group.add_option("-d", "--daemon", dest="daemon_addr",
action="store", type="str", default="127.0.0.1",
help="Set the address of the daemon to connect to. [default: %default]")
group.add_option("-p", "--port", dest="daemon_port",
help="Set the port to connect to the daemon on. [default: %default]",
action="store", type="int", default=58846)
group.add_option("-u", "--username", dest="daemon_user",
help="Set the username to connect to the daemon with. [default: %default]",
action="store", type="string")
group.add_option("-P", "--password", dest="daemon_pass",
help="Set the password to connect to the daemon with. [default: %default]",
action="store", type="string")
self.parser.add_option_group(group)
self.cmds = load_commands(os.path.join(UI_PATH, 'commands'))
class CommandOptionGroup(optparse.OptionGroup):
def __init__(self, parser, title, description=None, cmds=None):
optparse.OptionGroup.__init__(self, parser, title, description)
self.cmds = cmds
def format_help(self, formatter):
result = formatter.format_heading(self.title)
formatter.indent()
if self.description:
result += "%s\n" % formatter.format_description(self.description)
for cname in self.cmds:
cmd = self.cmds[cname]
if cmd.interactive_only or cname in cmd.aliases:
continue
allnames = [cname]
allnames.extend(cmd.aliases)
cname = "/".join(allnames)
result += formatter.format_heading(" - ".join([cname, cmd.__doc__]))
formatter.indent()
result += "%*s%s\n" % (formatter.current_indent, "", cmd.usage)
formatter.dedent()
formatter.dedent()
return result
cmd_group = CommandOptionGroup(self.parser, "Console Commands",
description="The following commands can be issued at the "
"command line. Commands should be quoted, so, for example, "
"to pause torrent with id 'abc' you would run: '%s "
"\"pause abc\"'" % os.path.basename(sys.argv[0]),
cmds=self.cmds)
self.parser.add_option_group(cmd_group)
def start(self, args=None):
from main import ConsoleUI
super(Console, self).start(args)
ConsoleUI(self.args, self.cmds, (self.options.daemon_addr,
self.options.daemon_port, self.options.daemon_user,
self.options.daemon_pass))

View file

@ -13,7 +13,6 @@ from __future__ import print_function
import locale import locale
import logging import logging
import optparse import optparse
import os
import re import re
import shlex import shlex
import sys import sys
@ -23,75 +22,15 @@ from twisted.internet import defer, reactor
import deluge.common import deluge.common
import deluge.component as component import deluge.component as component
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console import UI_PATH, colors from deluge.ui.console import colors
from deluge.ui.console.eventlog import EventLog from deluge.ui.console.eventlog import EventLog
from deluge.ui.console.statusbars import StatusBars from deluge.ui.console.statusbars import StatusBars
from deluge.ui.coreconfig import CoreConfig from deluge.ui.coreconfig import CoreConfig
from deluge.ui.sessionproxy import SessionProxy from deluge.ui.sessionproxy import SessionProxy
from deluge.ui.ui import _UI
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Console(_UI):
help = """Starts the Deluge console interface"""
def __init__(self, *args, **kwargs):
super(Console, self).__init__("console", *args, **kwargs)
group = optparse.OptionGroup(self.parser, "Console Options", "These daemon connect options will be "
"used for commands, or if console ui autoconnect is enabled.")
group.add_option("-d", "--daemon", dest="daemon_addr")
group.add_option("-p", "--port", dest="daemon_port", type="int")
group.add_option("-u", "--username", dest="daemon_user")
group.add_option("-P", "--password", dest="daemon_pass")
self.parser.add_option_group(group)
self.parser.disable_interspersed_args()
self.console_cmds = load_commands(os.path.join(UI_PATH, "commands"))
class CommandOptionGroup(optparse.OptionGroup):
def __init__(self, parser, title, description=None, cmds=None):
optparse.OptionGroup.__init__(self, parser, title, description)
self.cmds = cmds
def format_help(self, formatter):
result = formatter.format_heading(self.title)
formatter.indent()
if self.description:
result += "%s\n" % formatter.format_description(self.description)
for cname in self.cmds:
cmd = self.cmds[cname]
if cmd.interactive_only or cname in cmd.aliases:
continue
allnames = [cname]
allnames.extend(cmd.aliases)
cname = "/".join(allnames)
result += formatter.format_heading(" - ".join([cname, cmd.__doc__]))
formatter.indent()
result += "%*s%s\n" % (formatter.current_indent, "", cmd.usage.split("\n")[0])
formatter.dedent()
formatter.dedent()
return result
cmd_group = CommandOptionGroup(self.parser, "Console Commands",
description="""These commands can be issued from the command line.
They require quoting and multiple commands separated by ';'
e.g. Pause torrent with id 'abcd' and get information for id 'efgh':
`%s \"pause abcd; info efgh\"`"""
% os.path.basename(sys.argv[0]), cmds=self.console_cmds)
self.parser.add_option_group(cmd_group)
def start(self, args=None):
super(Console, self).start(args)
ConsoleUI(self.args, self.console_cmds, (self.options.daemon_addr, self.options.daemon_port,
self.options.daemon_user, self.options.daemon_pass))
def start():
Console().start()
class DelugeHelpFormatter(optparse.IndentedHelpFormatter): class DelugeHelpFormatter(optparse.IndentedHelpFormatter):
""" """
Format help in a way suited to deluge Legacy mode - colors, format, indentation... Format help in a way suited to deluge Legacy mode - colors, format, indentation...
@ -247,30 +186,6 @@ class BaseCommand(object):
return OptionParser(prog=self.name, usage=self.usage, epilog=self.epilog, option_list=self.option_list) return OptionParser(prog=self.name, usage=self.usage, epilog=self.epilog, option_list=self.option_list)
def load_commands(command_dir, exclude=None):
if not exclude:
exclude = []
def get_command(name):
return getattr(__import__("deluge.ui.console.commands.%s" % name, {}, {}, ["Command"]), "Command")()
try:
commands = []
for filename in os.listdir(command_dir):
if filename.split(".")[0] in exclude or filename.startswith("_"):
continue
if not (filename.endswith(".py") or filename.endswith(".pyc")):
continue
cmd = get_command(filename.split(".")[len(filename.split(".")) - 2])
aliases = [filename.split(".")[len(filename.split(".")) - 2]]
aliases.extend(cmd.aliases)
for a in aliases:
commands.append((a, cmd))
return dict(commands)
except OSError:
return {}
class ConsoleUI(component.Component): class ConsoleUI(component.Component):
def __init__(self, args=None, cmds=None, daemon=None): def __init__(self, args=None, cmds=None, daemon=None):
component.Component.__init__(self, "ConsoleUI", 2) component.Component.__init__(self, "ConsoleUI", 2)

View file

@ -1,3 +1,5 @@
from deluge.ui.gtkui.gtkui import start from deluge.ui.gtkui.gtkui import Gtk
assert start # silence pyflakes
def start():
Gtk().start()

View file

@ -52,7 +52,7 @@ from deluge.ui.gtkui.torrentdetails import TorrentDetails
from deluge.ui.gtkui.torrentview import TorrentView from deluge.ui.gtkui.torrentview import TorrentView
from deluge.ui.sessionproxy import SessionProxy from deluge.ui.sessionproxy import SessionProxy
from deluge.ui.tracker_icons import TrackerIcons from deluge.ui.tracker_icons import TrackerIcons
from deluge.ui.ui import _UI from deluge.ui.ui import UI
gobject.set_prgname("deluge") gobject.set_prgname("deluge")
@ -69,21 +69,6 @@ except ImportError:
return return
class Gtk(_UI):
help = """Starts the Deluge GTK+ interface"""
def __init__(self, *args, **kwargs):
super(Gtk, self).__init__("gtk", *args, **kwargs)
def start(self, args=None):
super(Gtk, self).start(args)
GtkUI(self.args)
def start():
Gtk().start()
DEFAULT_PREFS = { DEFAULT_PREFS = {
"classic_mode": True, "classic_mode": True,
"interactive_add": True, "interactive_add": True,
@ -146,6 +131,25 @@ DEFAULT_PREFS = {
} }
class Gtk(UI):
help = """Starts the Deluge GTK+ interface"""
cmdline = """A GTK-based graphical user interface"""
def __init__(self, *args, **kwargs):
super(Gtk, self).__init__("gtk", *args, **kwargs)
group = self.parser.add_argument_group(_('GTK Options'))
group.add_argument("torrents", metavar="<torrent>", nargs="*", default=None,
help="Add one or more torrent files, torrent URLs or magnet URIs"
" to a currently running Deluge GTK instance")
def start(self, args=None):
from gtkui import GtkUI
super(Gtk, self).start(args)
GtkUI(self.options)
class GtkUI(object): class GtkUI(object):
def __init__(self, args): def __init__(self, args):
# Setup gtkbuilder/glade translation # Setup gtkbuilder/glade translation

View file

@ -30,7 +30,7 @@ if 'dev' not in deluge.common.get_version():
warnings.filterwarnings('ignore', category=DeprecationWarning, module='twisted') warnings.filterwarnings('ignore', category=DeprecationWarning, module='twisted')
class _UI(object): class UI(object):
def __init__(self, name="gtk", skip_common=False): def __init__(self, name="gtk", skip_common=False):
self.__name = name self.__name = name

View file

@ -1,3 +1,5 @@
from deluge.ui.web.web import start from deluge.ui.web.web import Web
assert start # silence pyflakes
def start():
Web().start()

View file

@ -14,7 +14,7 @@ from optparse import OptionGroup
from deluge.common import osx_check, windows_check from deluge.common import osx_check, windows_check
from deluge.configmanager import get_config_dir from deluge.configmanager import get_config_dir
from deluge.ui.ui import _UI from deluge.ui.ui import UI
class WebUI(object): class WebUI(object):
@ -24,9 +24,10 @@ class WebUI(object):
deluge_web.start() deluge_web.start()
class Web(_UI): class Web(UI):
help = """Starts the Deluge web interface""" help = """Starts the Deluge web interface"""
cmdline = """A web-based interface (http://localhost:8112)"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Web, self).__init__("web", *args, **kwargs) super(Web, self).__init__("web", *args, **kwargs)

View file

@ -310,7 +310,12 @@ entry_points = {
'gui_scripts': [ 'gui_scripts': [
'deluge = deluge.main:start_ui', 'deluge = deluge.main:start_ui',
'deluge-gtk = deluge.ui.gtkui:start' 'deluge-gtk = deluge.ui.gtkui:start'
] ],
'deluge.ui': [
'console = deluge.ui.console:Console',
'web = deluge.ui.web:Web',
'gtk = deluge.ui.gtkui:Gtk',
],
} }
if windows_check(): if windows_check():