[#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
parent 6343f32d70
commit aa82efd4f1
10 changed files with 166 additions and 134 deletions

View File

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

View File

@ -8,6 +8,9 @@
#
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 logging
import optparse
import os
import re
import shlex
import sys
@ -23,75 +22,15 @@ from twisted.internet import defer, reactor
import deluge.common
import deluge.component as component
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.statusbars import StatusBars
from deluge.ui.coreconfig import CoreConfig
from deluge.ui.sessionproxy import SessionProxy
from deluge.ui.ui import _UI
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):
"""
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)
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):
def __init__(self, args=None, cmds=None, daemon=None):
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.sessionproxy import SessionProxy
from deluge.ui.tracker_icons import TrackerIcons
from deluge.ui.ui import _UI
from deluge.ui.ui import UI
gobject.set_prgname("deluge")
@ -69,21 +69,6 @@ except ImportError:
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 = {
"classic_mode": 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):
def __init__(self, args):
# 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')
class _UI(object):
class UI(object):
def __init__(self, name="gtk", skip_common=False):
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.configmanager import get_config_dir
from deluge.ui.ui import _UI
from deluge.ui.ui import UI
class WebUI(object):
@ -24,9 +24,10 @@ class WebUI(object):
deluge_web.start()
class Web(_UI):
class Web(UI):
help = """Starts the Deluge web interface"""
cmdline = """A web-based interface (http://localhost:8112)"""
def __init__(self, *args, **kwargs):
super(Web, self).__init__("web", *args, **kwargs)

View File

@ -310,7 +310,12 @@ entry_points = {
'gui_scripts': [
'deluge = deluge.main:start_ui',
'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():