diff --git a/deluge/main.py b/deluge/main.py index 146dcdb14..565afa581 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -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): diff --git a/deluge/ui/console/__init__.py b/deluge/ui/console/__init__.py index f5e7afd79..79b91e487 100644 --- a/deluge/ui/console/__init__.py +++ b/deluge/ui/console/__init__.py @@ -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() diff --git a/deluge/ui/console/console.py b/deluge/ui/console/console.py new file mode 100644 index 000000000..096b46454 --- /dev/null +++ b/deluge/ui/console/console.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2008-2009 Ido Abramovich +# Copyright (C) 2009 Andrew Resch +# +# 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)) diff --git a/deluge/ui/console/main.py b/deluge/ui/console/main.py index 2aa981ebe..e75731a9d 100644 --- a/deluge/ui/console/main.py +++ b/deluge/ui/console/main.py @@ -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) diff --git a/deluge/ui/gtkui/__init__.py b/deluge/ui/gtkui/__init__.py index b7eb0288f..709fb8e6a 100644 --- a/deluge/ui/gtkui/__init__.py +++ b/deluge/ui/gtkui/__init__.py @@ -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() diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 53698b31c..a1b6f4040 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -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="", 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 diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py index 828d763f4..2e53aefe3 100644 --- a/deluge/ui/ui.py +++ b/deluge/ui/ui.py @@ -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 diff --git a/deluge/ui/web/__init__.py b/deluge/ui/web/__init__.py index 81070096c..99d896ba3 100644 --- a/deluge/ui/web/__init__.py +++ b/deluge/ui/web/__init__.py @@ -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() diff --git a/deluge/ui/web/web.py b/deluge/ui/web/web.py index 633c104bc..be335c468 100644 --- a/deluge/ui/web/web.py +++ b/deluge/ui/web/web.py @@ -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) diff --git a/setup.py b/setup.py index fa7408b4a..a5c986b4c 100755 --- a/setup.py +++ b/setup.py @@ -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():