[UI] Replace optparse with argparse for cmd arguments handling

optparse is deprecation and succeeded by argparse. See
https://www.python.org/dev/peps/pep-0389
This commit is contained in:
bendikro 2016-01-11 18:12:14 +01:00
commit 7b54a2a1ee
36 changed files with 783 additions and 789 deletions

View file

@ -162,6 +162,28 @@ def osx_check():
return platform.system() == "Darwin" return platform.system() == "Darwin"
def linux_check():
"""
Checks if the current platform is Linux
:returns: True or False
:rtype: bool
"""
return platform.system() == "Linux"
def get_os_version():
if windows_check():
return platform.win32_ver()
elif osx_check():
return platform.mac_ver()
elif linux_check():
return platform.linux_distribution()
else:
return (platform.release(), )
def get_pixmap(fname): def get_pixmap(fname):
""" """
Provides easy access to files in the deluge/ui/data/pixmaps folder within the Deluge egg Provides easy access to files in the deluge/ui/data/pixmaps folder within the Deluge egg

View file

@ -1,79 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 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 logging
import optparse
import os
import platform
import sys
import deluge.common
import deluge.configmanager
from deluge.log import setup_logger
def version_callback(option, opt_str, value, parser):
print(os.path.basename(sys.argv[0]) + ": " + deluge.common.get_version())
try:
from deluge._libtorrent import lt
print("libtorrent: %s" % lt.version)
except ImportError:
pass
print("Python: %s" % platform.python_version())
for version in (platform.linux_distribution(), platform.win32_ver(), platform.mac_ver(), ("n/a",)):
if filter(None, version): # pylint: disable=bad-builtin
print("OS: %s %s" % (platform.system(), " ".join(version)))
break
raise SystemExit
class CommonOptionParser(optparse.OptionParser):
def __init__(self, *args, **kwargs):
optparse.OptionParser.__init__(self, *args, **kwargs)
self.common_group = optparse.OptionGroup(self, _("Common Options"))
self.common_group.add_option("-v", "--version", action="callback", callback=version_callback,
help="Show program's version number and exit")
self.common_group.add_option("-c", "--config", dest="config", action="store", type="str",
help="Set the config folder location")
self.common_group.add_option("-l", "--logfile", dest="logfile", action="store", type="str",
help="Output to designated logfile instead of stdout")
self.common_group.add_option("-L", "--loglevel", dest="loglevel", action="store", type="str",
help="Set the log level: none, info, warning, error, critical, debug")
self.common_group.add_option("-q", "--quiet", dest="quiet", action="store_true", default=False,
help="Sets the log level to 'none', this is the same as `-L none`")
self.common_group.add_option("-r", "--rotate-logs", action="store_true", default=False,
help="Rotate logfiles.")
self.add_option_group(self.common_group)
def parse_args(self, *args):
options, args = optparse.OptionParser.parse_args(self, *args)
# Setup the logger
if options.quiet:
options.loglevel = "none"
if options.loglevel:
options.loglevel = options.loglevel.lower()
logfile_mode = 'w'
if options.rotate_logs:
logfile_mode = 'a'
# Setup the logger
setup_logger(level=options.loglevel, filename=options.logfile, filemode=logfile_mode)
if options.config:
if not deluge.configmanager.set_config_dir(options.config):
log = logging.getLogger(__name__)
log.error("There was an error setting the config dir! Exiting..")
sys.exit(1)
else:
if not os.path.exists(deluge.common.get_default_config_dir()):
os.makedirs(deluge.common.get_default_config_dir())
return options, args

View file

@ -15,7 +15,6 @@
"""Main starting point for Deluge. Contains the main() entry point.""" """Main starting point for Deluge. Contains the main() entry point."""
from __future__ import print_function from __future__ import print_function
import optparse
import os import os
import sys import sys
from logging import FileHandler, getLogger from logging import FileHandler, getLogger
@ -38,25 +37,27 @@ def start_ui():
# Setup the argument parser # Setup the argument parser
parser = CommonOptionParser() parser = CommonOptionParser()
group = optparse.OptionGroup(parser, _("Default Options")) group = parser.add_argument_group(_("Default Options"))
ui_entrypoints = dict([(entrypoint.name, entrypoint.load()) ui_entrypoints = dict([(entrypoint.name, entrypoint.load())
for entrypoint in pkg_resources.iter_entry_points('deluge.ui')]) 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 = ['The UI that you wish to launch. The UI choices are:']
max_len = 0
for k, v in ui_entrypoints.iteritems():
cmdline = getattr(v, 'cmdline', "")
max_len = max(max_len, len(cmdline))
cmd_help.extend(["%s -- %s" % (k, getattr(v, 'cmdline', "")) for k, v in ui_entrypoints.iteritems()]) cmd_help.extend(["%s -- %s" % (k, getattr(v, 'cmdline', "")) for k, v in ui_entrypoints.iteritems()])
parser.add_option("-u", "--ui", dest="ui", parser.add_argument("-u", "--ui", action="store",
choices=ui_entrypoints.keys(), help="\n\t ".join(cmd_help), action="store") choices=ui_entrypoints.keys(), help="\n* ".join(cmd_help))
group.add_option("-a", "--args", dest="args", group.add_argument("-a", "--args", action="store",
help="Arguments to pass to UI, -a '--option args'", action="store", type="str") help='Arguments to pass to the UI. Multiple args must be quoted, e.g. -a "--option args"')
group.add_option("-s", "--set-default-ui", dest="default_ui", group.add_argument("-s", "--set-default-ui", dest="default_ui", choices=ui_entrypoints.keys(),
help="Sets the default UI to be run when no UI is specified", action="store", type="str") help="Sets the default UI to be run when no UI is specified", action="store")
parser.add_option_group(group) options = parser.parse_args(deluge.common.unicode_argv()[1:])
# Get the options and args from the OptionParser
(options, args) = parser.parse_args(deluge.common.unicode_argv()[1:])
config = deluge.configmanager.ConfigManager("ui.conf", DEFAULT_PREFS) config = deluge.configmanager.ConfigManager("ui.conf", DEFAULT_PREFS)
log = getLogger(__name__) log = getLogger(__name__)
@ -69,7 +70,6 @@ def start_ui():
log.info("Deluge ui %s", deluge.common.get_version()) log.info("Deluge ui %s", deluge.common.get_version())
log.debug("options: %s", options) log.debug("options: %s", options)
log.debug("args: %s", args)
selected_ui = options.ui if options.ui else config["default_ui"] selected_ui = options.ui if options.ui else config["default_ui"]
@ -81,15 +81,13 @@ def start_ui():
if options.args: if options.args:
import shlex import shlex
client_args.extend(shlex.split(options.args)) client_args.extend(shlex.split(options.args))
client_args.extend(args)
try: try:
ui = ui_entrypoints[selected_ui](skip_common=True) ui = ui_entrypoints[selected_ui](parser=parser)
except KeyError as ex: except KeyError as ex:
log.error("Unable to find the requested UI: '%s'. Please select a different UI with the '-u' option" 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) " or alternatively use the '-s' option to select a different default UI.", selected_ui)
except ImportError as ex: except ImportError as ex:
import sys
import traceback import traceback
error_type, error_value, tb = sys.exc_info() error_type, error_value, tb = sys.exc_info()
stack = traceback.extract_tb(tb) stack = traceback.extract_tb(tb)
@ -106,6 +104,31 @@ def start_ui():
ui.start(client_args) ui.start(client_args)
def add_daemon_options(parser):
group = parser.add_argument_group('Daemon Options')
group.add_argument("-p", "--port", metavar="<port>", action="store", type=int,
help="The port the daemon will listen on")
group.add_argument("-i", "--interface", metavar="<iface>", dest="listen_interface",
help="Interface daemon will listen for bittorrent connections on, "
"this should be an IP address", action="store")
group.add_argument("-u", "--ui-interface", metavar="<iface>", action="store",
help="Interface daemon will listen for UI connections on, "
"this should be an IP address")
if not deluge.common.windows_check():
group.add_argument("-d", "--do-not-daemonize", dest="donot",
help="Do not daemonize", action="store_true", default=False)
group.add_argument("-P", "--pidfile", metavar="<pidfile>",
help="Use pidfile to store process id", action="store")
if not deluge.common.windows_check():
group.add_argument("-U", "--user", metavar="<user>", action="store",
help="User to switch to. Only use it when starting as root")
group.add_argument("-g", "--group", metavar="<group>", action="store",
help="Group to switch to. Only use it when starting as root")
group.add_argument("--read-only-config-keys",
help="List of comma-separated config keys that will not be modified by set_config RPC.",
action="store", type=str, default="")
def start_daemon(skip_start=False): def start_daemon(skip_start=False):
""" """
Entry point for daemon script Entry point for daemon script
@ -124,38 +147,10 @@ def start_daemon(skip_start=False):
warnings.filterwarnings('ignore', category=DeprecationWarning, module='twisted') warnings.filterwarnings('ignore', category=DeprecationWarning, module='twisted')
# Setup the argument parser # Setup the argument parser
parser = CommonOptionParser(usage="%prog [options] [actions]") parser = CommonOptionParser()
add_daemon_options(parser)
group = optparse.OptionGroup(parser, _("Daemon Options")) options = parser.parse_args()
group.add_option("-p", "--port", dest="port",
help="Port daemon will listen on", action="store", type="int")
group.add_option("-i", "--interface", dest="listen_interface",
help="Interface daemon will listen for bittorrent connections on, "
"this should be an IP address", metavar="IFACE",
action="store", type="str")
group.add_option("-u", "--ui-interface", dest="ui_interface",
help="Interface daemon will listen for UI connections on, this should be "
"an IP address", metavar="IFACE", action="store", type="str")
if not (deluge.common.windows_check() or deluge.common.osx_check()):
group.add_option("-d", "--do-not-daemonize", dest="donot",
help="Do not daemonize", action="store_true", default=False)
group.add_option("-P", "--pidfile", dest="pidfile",
help="Use pidfile to store process id", action="store", type="str")
if not deluge.common.windows_check():
group.add_option("-U", "--user", dest="user",
help="User to switch to. Only use it when starting as root", action="store", type="str")
group.add_option("-g", "--group", dest="group",
help="Group to switch to. Only use it when starting as root", action="store", type="str")
group.add_option("--read-only-config-keys",
help="List of comma-separated config keys that will not be modified by set_config RPC.",
action="store", type="str", default="")
group.add_option("--profile", dest="profile", action="store_true", default=False,
help="Profiles the daemon")
parser.add_option_group(group)
# Get the options and args from the OptionParser
(options, args) = parser.parse_args()
# Check for any daemons running with this same config # Check for any daemons running with this same config
from deluge.core.daemon import check_running_daemon from deluge.core.daemon import check_running_daemon

View file

@ -10,42 +10,31 @@ from __future__ import print_function
import os import os
import sys import sys
from argparse import ArgumentParser
from datetime import datetime from datetime import datetime
from optparse import OptionParser
import deluge.common import deluge.common
parser = OptionParser() parser = ArgumentParser()
parser.add_option("-n", "--name", dest="name", help="plugin name") parser.add_argument("-n", "--name", metavar="<plugin name>", required=True, help="Plugin name")
parser.add_option("-m", "--module-name", dest="module", help="plugin name") parser.add_argument("-m", "--module-name", metavar="<module name>", help="Module name")
parser.add_option("-p", "--basepath", dest="path", help="base path") parser.add_argument("-p", "--basepath", metavar="<path>", required=True, help="Base path")
parser.add_option("-a", "--author-name", dest="author_name", help="author name,for the GPL header") parser.add_argument("-a", "--author-name", metavar="<author name>", required=True,
parser.add_option("-e", "--author-email", dest="author_email", help="author email,for the GPL header") help="Author name,for the GPL header")
parser.add_option("-u", "--url", dest="url", help="Homepage URL") parser.add_argument("-e", "--author-email", metavar="<author email>", required=True,
parser.add_option("-c", "--config", dest="configdir", help="location of deluge configuration") help="Author email,for the GPL header")
parser.add_argument("-u", "--url", metavar="<URL>", help="Homepage URL")
parser.add_argument("-c", "--config", metavar="<Config dir>", dest="configdir", help="Location of deluge configuration")
options = parser.parse_args()
(options, args) = parser.parse_args()
def create_plugin(): def create_plugin():
if not options.name:
print("--name is mandatory , use -h for more info")
return
if not options.path:
print("--basepath is mandatory , use -h for more info")
return
if not options.author_email:
print("--author-email is mandatory , use -h for more info")
return
if not options.author_email:
print("--author-name is mandatory , use -h for more info")
return
if not options.url: if not options.url:
options.url = "" options.url = ""
if not os.path.exists(options.path): if not os.path.exists(options.basepath):
print("basepath does not exist") print("basepath does not exist")
return return
@ -57,9 +46,9 @@ def create_plugin():
real_name = options.name real_name = options.name
name = real_name.replace(" ", "_") name = real_name.replace(" ", "_")
safe_name = name.lower() safe_name = name.lower()
if options.module: if options.module_name:
safe_name = options.module.lower() safe_name = options.module_name.lower()
plugin_base = os.path.realpath(os.path.join(options.path, name)) plugin_base = os.path.realpath(os.path.join(options.basepath, name))
deluge_namespace = os.path.join(plugin_base, "deluge") deluge_namespace = os.path.join(plugin_base, "deluge")
plugins_namespace = os.path.join(deluge_namespace, "plugins") plugins_namespace = os.path.join(deluge_namespace, "plugins")
src = os.path.join(plugins_namespace, safe_name) src = os.path.join(plugins_namespace, safe_name)
@ -121,16 +110,16 @@ def create_plugin():
CORE = """ CORE = """
import logging import logging
from deluge.plugins.pluginbase import CorePluginBase from deluge.plugins.pluginbase import CorePluginBase
import deluge.component as component
import deluge.configmanager import deluge.configmanager
from deluge.core.rpcserver import export from deluge.core.rpcserver import export
DEFAULT_PREFS = { DEFAULT_PREFS = {
"test":"NiNiNi" "test": "NiNiNi"
} }
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Core(CorePluginBase): class Core(CorePluginBase):
def enable(self): def enable(self):
self.config = deluge.configmanager.ConfigManager("%(safe_name)s.conf", DEFAULT_PREFS) self.config = deluge.configmanager.ConfigManager("%(safe_name)s.conf", DEFAULT_PREFS)
@ -157,22 +146,25 @@ class Core(CorePluginBase):
INIT = """ INIT = """
from deluge.plugins.init import PluginInitBase from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase): class CorePlugin(PluginInitBase):
def __init__(self, plugin_name): def __init__(self, plugin_name):
from core import Core as _plugin_cls from core import Core as PluginClass
self._plugin_cls = _plugin_cls self._plugin_cls = PluginClass
super(CorePlugin, self).__init__(plugin_name) super(CorePlugin, self).__init__(plugin_name)
class GtkUIPlugin(PluginInitBase): class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name): def __init__(self, plugin_name):
from gtkui import GtkUI as _plugin_cls from gtkui import GtkUI as PluginClass
self._plugin_cls = _plugin_cls self._plugin_cls = PluginClass
super(GtkUIPlugin, self).__init__(plugin_name) super(GtkUIPlugin, self).__init__(plugin_name)
class WebUIPlugin(PluginInitBase): class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name): def __init__(self, plugin_name):
from webui import WebUI as _plugin_cls from webui import WebUI as PluginClass
self._plugin_cls = _plugin_cls self._plugin_cls = PluginClass
super(WebUIPlugin, self).__init__(plugin_name) super(WebUIPlugin, self).__init__(plugin_name)
""" """
@ -201,8 +193,8 @@ setup(
long_description=__long_description__ if __long_description__ else __description__, long_description=__long_description__ if __long_description__ else __description__,
packages=find_packages(), packages=find_packages(),
namespace_packages = ["deluge", "deluge.plugins"], namespace_packages=["deluge", "deluge.plugins"],
package_data = __pkg_data__, package_data=__pkg_data__,
entry_points=\"\"\" entry_points=\"\"\"
[deluge.plugin.core] [deluge.plugin.core]
@ -218,7 +210,8 @@ setup(
COMMON = """ COMMON = """
def get_resource(filename): def get_resource(filename):
import pkg_resources, os import pkg_resources
import os
return pkg_resources.resource_filename("deluge.plugins.%(safe_name)s", return pkg_resources.resource_filename("deluge.plugins.%(safe_name)s",
os.path.join("data", filename)) os.path.join("data", filename))
""" """
@ -230,12 +223,12 @@ import logging
from deluge.ui.client import client from deluge.ui.client import client
from deluge.plugins.pluginbase import GtkPluginBase from deluge.plugins.pluginbase import GtkPluginBase
import deluge.component as component import deluge.component as component
import deluge.common
from common import get_resource from common import get_resource
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class GtkUI(GtkPluginBase): class GtkUI(GtkPluginBase):
def enable(self): def enable(self):
self.glade = gtk.glade.XML(get_resource("config.glade")) self.glade = gtk.glade.XML(get_resource("config.glade"))
@ -252,7 +245,7 @@ class GtkUI(GtkPluginBase):
def on_apply_prefs(self): def on_apply_prefs(self):
log.debug("applying prefs for %(name)s") log.debug("applying prefs for %(name)s")
config = { config = {
"test":self.glade.get_widget("txt_test").get_text() "test": self.glade.get_widget("txt_test").get_text()
} }
client.%(safe_name)s.set_config(config) client.%(safe_name)s.set_config(config)
@ -296,13 +289,13 @@ GLADE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
WEBUI = """ WEBUI = """
import logging import logging
from deluge.ui.client import client from deluge.ui.client import client
from deluge import component
from deluge.plugins.pluginbase import WebPluginBase from deluge.plugins.pluginbase import WebPluginBase
from common import get_resource from common import get_resource
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class WebUI(WebPluginBase): class WebUI(WebPluginBase):
scripts = [get_resource("%(safe_name)s.js")] scripts = [get_resource("%(safe_name)s.js")]

137
deluge/ui/baseargparser.py Normal file
View file

@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 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 argparse
import logging
import os
import platform
import sys
import textwrap
import deluge.common
import deluge.configmanager
import deluge.log
from deluge.log import setup_logger
def get_version():
version_str = "%s\n" % (deluge.common.get_version())
try:
from deluge._libtorrent import lt
version_str += "libtorrent: %s\n" % lt.version
except ImportError:
pass
version_str += "Python: %s\n" % platform.python_version()
version_str += "OS: %s %s\n" % (platform.system(), " ".join(deluge.common.get_os_version()))
return version_str
class DelugeTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
"""Help message formatter which retains formatting of all help text.
"""
def _split_lines(self, text, width):
"""
Do not remove whitespaces in string but still wrap text to max width.
Instead of passing the entire text to textwrap.wrap, split and pass each
line instead. This way list formatting is not mangled by textwrap.wrap.
"""
wrapped_lines = []
for l in text.splitlines():
wrapped_lines.extend(textwrap.wrap(l, width))
return wrapped_lines
def _format_action_invocation(self, action):
"""
Combines the options with comma and displays the argument
value only once instead of after both options.
Instead of: -s <arg>, --long-opt <arg>
Show : -s, --long-opt <arg>
"""
if not action.option_strings:
metavar, = self._metavar_formatter(action, action.dest)(1)
return metavar
else:
parts = []
# if the Optional doesn't take a value, format is:
# -s, --long
if action.nargs == 0:
parts.extend(action.option_strings)
# if the Optional takes a value, format is:
# -s, --long ARGS
else:
default = action.dest.upper()
args_string = self._format_args(action, default)
opt = ", ".join(action.option_strings)
parts.append('%s %s' % (opt, args_string))
return ', '.join(parts)
class HelpAction(argparse._HelpAction):
def __call__(self, parser, namespace, values, option_string=None):
if hasattr(parser, "subparser"):
subparser = getattr(parser, "subparser")
# If -h on a subparser is given, the subparser will exit after help message
subparser.parse_args()
parser.print_help()
parser.exit()
class BaseArgParser(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
if "formatter_class" not in kwargs:
kwargs["formatter_class"] = lambda prog: DelugeTextHelpFormatter(prog, max_help_position=33, width=90)
super(BaseArgParser, self).__init__(*args, add_help=False, **kwargs)
self.group = self.add_argument_group('Common Options')
self.group.add_argument('--version', action='version', version='%(prog)s ' + get_version(),
help="Show program's version number and exit")
self.group.add_argument("-c", "--config", action="store", metavar='<config>',
help="Set the config directory path")
self.group.add_argument("-l", "--logfile", action="store", metavar='<logfile>',
help="Output to designated logfile instead of stdout")
self.group.add_argument("-L", "--loglevel", action="store", choices=deluge.log.levels, metavar='<level>',
help="Set the log level: %s" % ", ".join(deluge.log.levels))
self.group.add_argument("-q", "--quiet", action="store_true", default=False,
help="Sets the log level to 'none', this is the same as `-L none`")
self.group.add_argument("-r", "--rotate-logs", action="store_true", default=False,
help="Rotate logfiles.")
self.group.add_argument("-h", "--help", action=HelpAction, help='Show this help message and exit')
def parse_args(self, *args):
options, remaining = super(BaseArgParser, self).parse_known_args(*args)
options.remaining = remaining
# Setup the logger
if options.quiet:
options.loglevel = "none"
if options.loglevel:
options.loglevel = options.loglevel.lower()
logfile_mode = 'w'
if options.rotate_logs:
logfile_mode = 'a'
# Setup the logger
setup_logger(level=options.loglevel, filename=options.logfile, filemode=logfile_mode)
if options.config:
if not deluge.configmanager.set_config_dir(options.config):
log = logging.getLogger(__name__)
log.error("There was an error setting the config dir! Exiting..")
sys.exit(1)
else:
if not os.path.exists(deluge.common.get_default_config_dir()):
os.makedirs(deluge.common.get_default_config_dir())
return options

View file

@ -379,14 +379,14 @@ class DaemonSSLProxy(DaemonProxy):
def authenticate(self, username, password): def authenticate(self, username, password):
log.debug("%s.authenticate: %s", self.__class__.__name__, username) log.debug("%s.authenticate: %s", self.__class__.__name__, username)
self.login_deferred = defer.Deferred() login_deferred = defer.Deferred()
d = self.call("daemon.login", username, password, d = self.call("daemon.login", username, password,
client_version=deluge.common.get_version()) client_version=deluge.common.get_version())
d.addCallback(self.__on_login, username) d.addCallbacks(self.__on_login, self.__on_login_fail, callbackArgs=[username, login_deferred],
d.addErrback(self.__on_login_fail) errbackArgs=[login_deferred])
return self.login_deferred return login_deferred
def __on_login(self, result, username): def __on_login(self, result, username, login_deferred):
log.debug("__on_login called: %s %s", username, result) log.debug("__on_login called: %s %s", username, result)
self.username = username self.username = username
self.authentication_level = result self.authentication_level = result
@ -399,11 +399,10 @@ class DaemonSSLProxy(DaemonProxy):
self.__on_auth_levels_mappings self.__on_auth_levels_mappings
) )
self.login_deferred.callback(result) login_deferred.callback(result)
def __on_login_fail(self, result): def __on_login_fail(self, result, login_deferred):
log.debug("_on_login_fail(): %s", result.value) login_deferred.errback(result)
self.login_deferred.errback(result)
def __on_auth_levels_mappings(self, result): def __on_auth_levels_mappings(self, result):
auth_levels_mapping, auth_levels_mapping_reverse = result auth_levels_mapping, auth_levels_mapping_reverse = result
@ -549,7 +548,12 @@ class Client(object):
d = self._daemon_proxy.connect(host, port) d = self._daemon_proxy.connect(host, port)
def on_connected(daemon_version):
log.debug("on_connected. Daemon version: %s", daemon_version)
return daemon_version
def on_connect_fail(reason): def on_connect_fail(reason):
log.debug("on_connect_fail: %s", reason)
self.disconnect() self.disconnect()
return reason return reason
@ -561,11 +565,6 @@ class Client(object):
log.debug("Failed to authenticate: %s", reason.value) log.debug("Failed to authenticate: %s", reason.value)
return reason return reason
def on_connected(daemon_version):
log.debug("Client.connect.on_connected. Daemon version: %s",
daemon_version)
return daemon_version
def authenticate(daemon_version, username, password): def authenticate(daemon_version, username, password):
if not username and host in ("127.0.0.1", "localhost"): if not username and host in ("127.0.0.1", "localhost"):
# No username provided and it's localhost, so attempt to get credentials from auth file. # No username provided and it's localhost, so attempt to get credentials from auth file.

View file

@ -7,7 +7,7 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
UI_PATH = __path__[0] UI_PATH = __path__[0] # NOQA Ignore 'E402 module level import not at top of file'
from deluge.ui.console.console import Console from deluge.ui.console.console import Console

View file

@ -7,6 +7,8 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
import re
from deluge.ui.console.modes import format_utils from deluge.ui.console.modes import format_utils
try: try:
@ -228,3 +230,39 @@ def parse_color_string(s, encoding="UTF-8"):
# There was no color scheme so we add it with a 0 for white on black # There was no color scheme so we add it with a 0 for white on black
ret = [(0, s)] ret = [(0, s)]
return ret return ret
class ConsoleColorFormatter(object):
"""
Format help in a way suited to deluge Legacy mode - colors, format, indentation...
"""
replace_dict = {
"<torrent-id>": "{!green!}%s{!input!}",
"<torrent>": "{!green!}%s{!input!}",
"<command>": "{!green!}%s{!input!}",
"<state>": "{!yellow!}%s{!input!}",
"\\.\\.\\.": "{!yellow!}%s{!input!}",
"\\s\\*\\s": "{!blue!}%s{!input!}",
"(?<![\\-a-z])(-[a-zA-Z0-9])": "{!red!}%s{!input!}",
# "(\-[a-zA-Z0-9])": "{!red!}%s{!input!}",
"--[_\\-a-zA-Z0-9]+": "{!green!}%s{!input!}",
"(\\[|\\])": "{!info!}%s{!input!}",
"<tab>": "{!white!}%s{!input!}",
"[_A-Z]{3,}": "{!cyan!}%s{!input!}",
"<key>": "{!cyan!}%s{!input!}",
"<value>": "{!cyan!}%s{!input!}",
"usage:": "{!info!}%s{!input!}",
"<download-folder>": "{!yellow!}%s{!input!}",
"<torrent-file>": "{!green!}%s{!input!}"
}
def format_colors(self, string):
def r(repl):
return lambda s: repl % s.group()
for key, replacement in self.replace_dict.items():
string = re.sub(key, r(replacement), string)
return string

View file

@ -13,43 +13,45 @@
from __future__ import print_function from __future__ import print_function
import logging import logging
import sys
from twisted.internet import defer from twisted.internet import defer
import deluge.component as component
from deluge.error import DelugeError
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.colors import strip_colors from deluge.ui.console.colors import strip_colors
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Commander: class Commander(object):
def __init__(self, cmds, interactive=False): def __init__(self, cmds, interactive=False):
self._commands = cmds self._commands = cmds
self.console = component.get("ConsoleUI")
self.interactive = interactive self.interactive = interactive
def write(self, line): def write(self, line):
print(strip_colors(line)) print(strip_colors(line))
def do_command(self, cmd): def do_command(self, cmd_line):
""" """
Processes a command. Processes a command.
:param cmd: str, the command string :param cmd: str, the command string
""" """
if not cmd: if not cmd_line:
return return
cmd, _, line = cmd.partition(" ") cmd, _, line = cmd_line.partition(" ")
try: try:
parser = self._commands[cmd].create_parser() parser = self._commands[cmd].create_parser()
except KeyError: except KeyError:
self.write("{!error!}Unknown command: %s" % cmd) self.write("{!error!}Unknown command: %s" % cmd)
return return
args = self._commands[cmd].split(line)
try:
args = [cmd] + self._commands[cmd].split(line)
except ValueError as ex:
self.write("{!error!}Error parsing command: %s" % ex)
return
# Do a little hack here to print 'command --help' properly # Do a little hack here to print 'command --help' properly
parser._print_help = parser.print_help parser._print_help = parser.print_help
@ -73,14 +75,20 @@ class Commander:
return return
try: try:
options, args = parser.parse_args(args) options = parser.parse_args(args=args)
except TypeError as ex: except TypeError as ex:
self.write("{!error!}Error parsing options: %s" % ex) self.write("{!error!}Error parsing options: %s" % ex)
import traceback
self.write("%s" % traceback.format_exc())
return
except Exception as ex:
self.write("{!error!} %s" % ex)
parser.print_help()
return return
if not getattr(options, "_exit", False): if not getattr(parser, "_exit", False):
try: try:
ret = self._commands[cmd].handle(*args, **options.__dict__) ret = self._commands[cmd].handle(options)
except Exception as ex: except Exception as ex:
self.write("{!error!} %s" % ex) self.write("{!error!} %s" % ex)
log.exception(ex) log.exception(ex)
@ -89,50 +97,3 @@ class Commander:
return defer.succeed(True) return defer.succeed(True)
else: else:
return ret return ret
def exec_args(self, args, host, port, username, password):
commands = []
if args:
# Multiple commands split by ";"
commands = [arg.strip() for arg in args.split(";")]
def on_connect(result):
def on_started(result):
def on_started(result):
def do_command(result, cmd):
return self.do_command(cmd)
d = defer.succeed(None)
for command in commands:
if command in ("quit", "exit"):
break
d.addCallback(do_command, command)
d.addCallback(do_command, "quit")
# We need to wait for the rpcs in start() to finish before processing
# any of the commands.
self.console.started_deferred.addCallback(on_started)
component.start().addCallback(on_started)
def on_connect_fail(reason):
if reason.check(DelugeError):
rm = reason.value.message
else:
rm = reason.getErrorMessage()
if host:
print("Could not connect to daemon: %s:%s\n %s" % (host, port, rm))
else:
print("Could not connect to localhost daemon\n %s" % rm)
self.do_command("quit")
if host:
d = client.connect(host, port, username, password)
else:
d = client.connect()
if not self.interactive:
if commands[0].startswith("connect"):
d = self.do_command(commands.pop(0))
elif "help" in commands:
self.do_command("help")
sys.exit(0)
d.addCallback(on_connect)
d.addErrback(on_connect_fail)

View file

@ -10,7 +10,6 @@
import base64 import base64
import os import os
from optparse import make_option
from urllib import url2pathname from urllib import url2pathname
from urlparse import urlparse from urlparse import urlparse
@ -23,20 +22,19 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Add a torrent""" """Add torrents"""
option_list = BaseCommand.option_list + (
make_option('-p', '--path', dest='path', help='download folder for torrent'),
)
usage = "Usage: add [-p <download-folder>] <torrent-file> [<torrent-file> ...]\n"\ def add_arguments(self, parser):
" <torrent-file> arguments can be file paths, URLs or magnet uris" parser.add_argument("-p", "--path", dest="path", help="download folder for torrent")
parser.add_argument("torrents", metavar="<torrent>", nargs="+",
help="One or more torrent files, URLs or magnet URIs")
def handle(self, *args, **options): def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
t_options = {} t_options = {}
if options["path"]: if options.path:
t_options["download_location"] = os.path.expanduser(options["path"]) t_options["download_location"] = os.path.expanduser(options.path)
def on_success(result): def on_success(result):
if not result: if not result:
@ -49,22 +47,22 @@ class Command(BaseCommand):
# Keep a list of deferreds to make a DeferredList # Keep a list of deferreds to make a DeferredList
deferreds = [] deferreds = []
for arg in args: for torrent in options.torrents:
if not arg.strip(): if not torrent.strip():
continue continue
if deluge.common.is_url(arg): if deluge.common.is_url(torrent):
self.console.write("{!info!}Attempting to add torrent from url: %s" % arg) self.console.write("{!info!}Attempting to add torrent from url: %s" % torrent)
deferreds.append(client.core.add_torrent_url(arg, t_options).addCallback(on_success).addErrback( deferreds.append(client.core.add_torrent_url(torrent, t_options).addCallback(on_success).addErrback(
on_fail)) on_fail))
elif deluge.common.is_magnet(arg): elif deluge.common.is_magnet(torrent):
self.console.write("{!info!}Attempting to add torrent from magnet uri: %s" % arg) self.console.write("{!info!}Attempting to add torrent from magnet uri: %s" % torrent)
deferreds.append(client.core.add_torrent_magnet(arg, t_options).addCallback(on_success).addErrback( deferreds.append(client.core.add_torrent_magnet(torrent, t_options).addCallback(on_success).addErrback(
on_fail)) on_fail))
else: else:
# Just a file # Just a file
if urlparse(arg).scheme == "file": if urlparse(torrent).scheme == "file":
arg = url2pathname(urlparse(arg).path) torrent = url2pathname(urlparse(torrent).path)
path = os.path.abspath(os.path.expanduser(arg)) path = os.path.abspath(os.path.expanduser(torrent))
if not os.path.exists(path): if not os.path.exists(path):
self.console.write("{!error!}%s doesn't exist!" % path) self.console.write("{!error!}%s doesn't exist!" % path)
continue continue

View file

@ -14,9 +14,8 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Show information about the disk cache""" """Show information about the disk cache"""
usage = "Usage: cache"
def handle(self, *args, **options): def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
def on_cache_status(status): def on_cache_status(status):

View file

@ -11,7 +11,6 @@
import cStringIO import cStringIO
import logging import logging
import tokenize import tokenize
from optparse import make_option
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors import deluge.ui.console.colors as colors
@ -66,25 +65,28 @@ def simple_eval(source):
class Command(BaseCommand): class Command(BaseCommand):
"""Show and set configuration values""" """Show and set configuration values"""
option_list = BaseCommand.option_list + ( usage = "config [--set <key> <value>] [<key> [<key>...] ]"
make_option("-s", "--set", action="store", nargs=2, dest="set", help="set value for key"),
)
usage = """Usage: config [key1 [key2 ...]]"
config --set key value"""
def handle(self, *args, **options): def add_arguments(self, parser):
set_group = parser.add_argument_group("setting a value")
set_group.add_argument("-s", "--set", action="store", metavar="<key>", help="set value for this key")
set_group.add_argument("values", metavar="<value>", nargs="+", help="Value to set")
get_group = parser.add_argument_group("getting values")
get_group.add_argument("keys", metavar="<keys>", nargs="*", help="one or more keys separated by space")
def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if options["set"]: if options.set:
return self._set_config(*args, **options) return self._set_config(options)
else: else:
return self._get_config(*args, **options) return self._get_config(options)
def _get_config(self, *args, **options): def _get_config(self, options):
def _on_get_config(config): def _on_get_config(config):
keys = sorted(config.keys()) keys = sorted(config.keys())
s = "" s = ""
for key in keys: for key in keys:
if args and key not in args: if key not in options.values:
continue continue
color = "{!white,black,bold!}" color = "{!white,black,bold!}"
value = config[key] value = config[key]
@ -107,10 +109,16 @@ class Command(BaseCommand):
return client.core.get_config().addCallback(_on_get_config) return client.core.get_config().addCallback(_on_get_config)
def _set_config(self, *args, **options): def _set_config(self, options):
config = component.get("CoreConfig") config = component.get("CoreConfig")
key = options["set"][0] key = options.set
val = simple_eval(options["set"][1] + " " .join(args)) val = " ".join(options.values)
try:
val = simple_eval(val)
except SyntaxError as ex:
self.console.write("{!error!}%s" % ex)
return
if key not in config.keys(): if key not in config.keys():
self.console.write("{!error!}The key '%s' is invalid!" % key) self.console.write("{!error!}The key '%s' is invalid!" % key)
@ -126,7 +134,7 @@ class Command(BaseCommand):
def on_set_config(result): def on_set_config(result):
self.console.write("{!success!}Configuration value successfully updated.") self.console.write("{!success!}Configuration value successfully updated.")
self.console.write("Setting %s to %s.." % (key, val)) self.console.write("Setting '%s' to '%s'" % (key, val))
return client.core.set_config({key: val}).addCallback(on_set_config) return client.core.set_config({key: val}).addCallback(on_set_config)
def complete(self, text): def complete(self, text):

View file

@ -8,27 +8,41 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
import logging
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.main import BaseCommand from deluge.ui.console.main import BaseCommand
log = logging.getLogger(__name__)
class Command(BaseCommand): class Command(BaseCommand):
"""Connect to a new deluge server.""" """Connect to a new deluge server."""
usage = "Usage: connect <host[:port]> <username> <password>" usage = "Usage: connect <host[:port]> [<username>] [<password>]"
def handle(self, host="127.0.0.1:58846", username="", password="", **options): def add_arguments(self, parser):
parser.add_argument("host", help="host and port", metavar="<host[:port]>")
parser.add_argument("username", help="Username", metavar="<username>", nargs="?", default="")
parser.add_argument("password", help="Password", metavar="<password>", nargs="?", default="")
def add_parser(self, subparsers):
parser = subparsers.add_parser(self.name, help=self.__doc__, description=self.__doc__, prog="connect")
self.add_arguments(parser)
def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
host = options.host
try: try:
host, port = host.split(":") host, port = host.split(":")
port = int(port)
except ValueError: except ValueError:
port = 58846 port = 58846
else:
port = int(port)
def do_connect(): def do_connect():
d = client.connect(host, port, username, password) d = client.connect(host, port, options.username, options.password)
def on_connect(result): def on_connect(result):
if self.console.interactive: if self.console.interactive:
@ -39,17 +53,18 @@ class Command(BaseCommand):
try: try:
msg = result.value.exception_msg msg = result.value.exception_msg
except AttributeError: except AttributeError:
msg = result.value.args[0] msg = result.value.message
self.console.write("{!error!}Failed to connect to %s:%s with reason: %s" % (host, port, msg)) self.console.write("{!error!}Failed to connect to %s:%s with reason: %s" % (host, port, msg))
return result return result
d.addCallback(on_connect) d.addCallbacks(on_connect, on_connect_fail)
d.addErrback(on_connect_fail)
return d return d
if client.connected(): if client.connected():
def on_disconnect(result): def on_disconnect(result):
self.console.statusbars.update_statusbars() if self.console.statusbars:
self.console.statusbars.update_statusbars()
return do_connect() return do_connect()
return client.disconnect().addCallback(on_disconnect) return client.disconnect().addCallback(on_disconnect)
else: else:

View file

@ -17,12 +17,14 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Enable and disable debugging""" """Enable and disable debugging"""
usage = "Usage: debug [on|off]"
def handle(self, state="", **options): def add_arguments(self, parser):
if state == "on": parser.add_argument("state", metavar="<on|off>", choices=["on", "off"], help="The new state")
def handle(self, options):
if options.state == "on":
deluge.log.set_logger_level("debug") deluge.log.set_logger_level("debug")
elif state == "off": elif options.state == "off":
deluge.log.set_logger_level("error") deluge.log.set_logger_level("error")
else: else:
component.get("ConsoleUI").write("{!error!}%s" % self.usage) component.get("ConsoleUI").write("{!error!}%s" % self.usage)

View file

@ -7,22 +7,26 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
import logging
import deluge.component as component import deluge.component as component
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
from deluge.ui.console.modes.alltorrents import AllTorrents from deluge.ui.console.modes.alltorrents import AllTorrents
log = logging.getLogger(__name__)
class Command(BaseCommand): class Command(BaseCommand):
"""Exit this mode and go into the more 'gui' like mode""" """Enable interactive mode"""
usage = "Usage: gui"
interactive_only = True interactive_only = True
def handle(self, *args, **options): def handle(self, options):
console = component.get("ConsoleUI") console = component.get("ConsoleUI")
try: try:
at = component.get("AllTorrents") at = component.get("AllTorrents")
except KeyError: except KeyError:
at = AllTorrents(console.stdscr, console.encoding) at = AllTorrents(console.stdscr, console.encoding)
console.set_mode(at) console.set_mode(at)
at._go_top = True at._go_top = True
at.resume() at.resume()

View file

@ -15,9 +15,8 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"Shutdown the deluge server" "Shutdown the deluge server"
usage = "Usage: halt"
def handle(self, *args, **options): def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
def on_shutdown(result): def on_shutdown(result):

View file

@ -8,27 +8,32 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
import logging
from twisted.internet import defer from twisted.internet import defer
import deluge.component as component import deluge.component as component
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
log = logging.getLogger(__name__)
class Command(BaseCommand): class Command(BaseCommand):
"""displays help on other commands""" """displays help on other commands"""
usage = "Usage: help [command]" def add_arguments(self, parser):
parser.add_argument("commands", metavar="<command>", nargs="*", help="One or more commands")
def handle(self, *args, **options): def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
self._commands = self.console._commands self._commands = self.console._commands
deferred = defer.succeed(True) deferred = defer.succeed(True)
if args: if options.commands:
for arg in args: for arg in options.commands:
try: try:
cmd = self._commands[arg] cmd = self._commands[arg]
except KeyError: except KeyError:
self.console.write("{!error!}Unknown command %r" % args[0]) self.console.write("{!error!}Unknown command %s" % arg)
continue continue
try: try:
parser = cmd.create_parser() parser = cmd.create_parser()
@ -38,8 +43,15 @@ class Command(BaseCommand):
self.console.write(" ") self.console.write(" ")
else: else:
self.console.set_batch_write(True) self.console.set_batch_write(True)
cmds_doc = ""
for cmd in sorted(self._commands): for cmd in sorted(self._commands):
self.console.write("{!info!}" + cmd + "{!input!} - " + self._commands[cmd].__doc__ or '') if cmd in self._commands[cmd].aliases:
continue
parser = self._commands[cmd].create_parser()
cmd_doc = "{!info!}" + "%-9s" % self._commands[cmd].name_with_alias + "{!input!} - "\
+ self._commands[cmd].__doc__ + "\n " + parser.format_usage() or ''
cmds_doc += parser.formatter.format_colors(cmd_doc)
self.console.write(cmds_doc)
self.console.write(" ") self.console.write(" ")
self.console.write("For help on a specific command, use '<command> --help'") self.console.write("For help on a specific command, use '<command> --help'")
self.console.set_batch_write(False) self.console.set_batch_write(False)

View file

@ -8,7 +8,6 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
from optparse import make_option
from os.path import sep as dirsep from os.path import sep as dirsep
import deluge.common as common import deluge.common as common
@ -90,44 +89,51 @@ class Command(BaseCommand):
sort_help = "sort items. Possible keys: " + ", ".join(STATUS_KEYS) sort_help = "sort items. Possible keys: " + ", ".join(STATUS_KEYS)
option_list = BaseCommand.option_list + ( epilog = """
make_option("-v", "--verbose", action="store_true", default=False, dest="verbose", You can give the first few characters of a torrent-id to identify the torrent.
help="Show more information per torrent."),
make_option("-d", "--detailed", action="store_true", default=False, dest="detailed",
help="Show more detailed information including files and peers."),
make_option("-s", "--state", action="store", dest="state",
help="Show torrents with state STATE: %s." % (", ".join(STATES))),
make_option("--sort", action="store", type="string", default="", dest="sort", help=sort_help),
make_option("--sort-reverse", action="store", type="string", default="", dest="sort_rev",
help="Same as --sort but items are in reverse order.")
)
usage = """Usage: info [-v | -d | -s <state>] [<torrent-id> [<torrent-id> ...]] Tab Completion (info *pattern*<tab>):\n
You can give the first few characters of a torrent-id to identify the torrent. | First press of <tab> will output up to 15 matches;
info * will list all torrents. | hitting <tab> a second time, will print 15 more matches;
| and a third press will print all remaining matches.
| (To modify behaviour of third <tab>, set `third_tab_lists_all` to False)
"""
Tab Completion (info *pattern*<tab>): def add_arguments(self, parser):
| First press of <tab> will output up to 15 matches; parser.add_argument("-v", "--verbose", action="store_true", default=False, dest="verbose",
| hitting <tab> a second time, will print 15 more matches; help="Show more information per torrent.")
| and a third press will print all remaining matches. parser.add_argument("-d", "--detailed", action="store_true", default=False, dest="detailed",
| (To modify behaviour of third <tab>, set `third_tab_lists_all` to False)""" help="Show more detailed information including files and peers.")
parser.add_argument("-s", "--state", action="store", dest="state",
help="Show torrents with state STATE: %s." % (", ".join(STATES)))
parser.add_argument("--sort", action="store", type=str, default="", dest="sort", help=self.sort_help)
parser.add_argument("--sort-reverse", action="store", type=str, default="", dest="sort_rev",
help="Same as --sort but items are in reverse order.")
parser.add_argument("torrent_ids", metavar="<torrent-id>", nargs="*",
help="One or more torrent ids. If none is given, list all")
def handle(self, *args, **options): def add_subparser(self, subparsers):
parser = subparsers.add_parser(self.name, prog=self.name, help=self.__doc__,
description=self.__doc__, epilog=self.epilog)
self.add_arguments(parser)
def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
# Compile a list of torrent_ids to request the status of # Compile a list of torrent_ids to request the status of
torrent_ids = [] torrent_ids = []
for arg in args:
torrent_ids.extend(self.console.match_torrent(arg))
if not args: if options.torrent_ids:
for t_id in options.torrent_ids:
torrent_ids.extend(self.console.match_torrent(t_id))
else:
torrent_ids.extend(self.console.match_torrent("")) torrent_ids.extend(self.console.match_torrent(""))
def on_torrents_status(status): def on_torrents_status(status):
# Print out the information for each torrent # Print out the information for each torrent
sort_key = options["sort"] sort_key = options.sort
sort_reverse = False sort_reverse = False
if not sort_key: if not sort_key:
sort_key = options["sort_rev"] sort_key = options.sort_rev
sort_reverse = True sort_reverse = True
if not sort_key: if not sort_key:
sort_key = "name" sort_key = "name"
@ -138,19 +144,19 @@ class Command(BaseCommand):
sort_key = "name" sort_key = "name"
sort_reverse = False sort_reverse = False
for key, value in sorted(status.items(), key=lambda x: x[1].get(sort_key), reverse=sort_reverse): for key, value in sorted(status.items(), key=lambda x: x[1].get(sort_key), reverse=sort_reverse):
self.show_info(key, status[key], options["verbose"], options["detailed"]) self.show_info(key, status[key], options.verbose, options.detailed)
def on_torrents_status_fail(reason): def on_torrents_status_fail(reason):
self.console.write("{!error!}Error getting torrent info: %s" % reason) self.console.write("{!error!}Error getting torrent info: %s" % reason)
status_dict = {"id": torrent_ids} status_dict = {"id": torrent_ids}
if options["state"]: if options.state:
options["state"] = options["state"].capitalize() options.state = options.state.capitalize()
if options["state"] in STATES: if options.state in STATES:
status_dict["state"] = options["state"] status_dict.state = options.state
else: else:
self.console.write("Invalid state: %s" % options["state"]) self.console.write("Invalid state: %s" % options.state)
self.console.write("Possible values are: %s." % (", ".join(STATES))) self.console.write("Possible values are: %s." % (", ".join(STATES)))
return return

View file

@ -8,8 +8,6 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
from optparse import make_option
from twisted.internet import defer from twisted.internet import defer
import deluge.component as component import deluge.component as component
@ -35,20 +33,25 @@ torrent_options = {
class Command(BaseCommand): class Command(BaseCommand):
"""Show and manage per-torrent options""" """Show and manage per-torrent options"""
option_list = BaseCommand.option_list + ( usage = "manage <torrent-id> [--set <key> <value>] [<key> [<key>...] ]"
make_option("-s", "--set", action="store", nargs=2, dest="set", help="set value for key"),
)
usage = "Usage: manage <torrent-id> [<key1> [<key2> ...]]\n"\
" manage <torrent-id> --set <key> <value>"
def handle(self, *args, **options): def add_arguments(self, parser):
parser.add_argument("torrent", metavar="<torrent>",
help="an expression matched against torrent ids and torrent names")
set_group = parser.add_argument_group("setting a value")
set_group.add_argument("-s", "--set", action="store", metavar="<key>", help="set value for this key")
set_group.add_argument("values", metavar="<value>", nargs="+", help="Value to set")
get_group = parser.add_argument_group("getting values")
get_group.add_argument("keys", metavar="<keys>", nargs="*", help="one or more keys separated by space")
def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if options['set']: if options.set:
return self._set_option(*args, **options) return self._set_option(options)
else: else:
return self._get_option(*args, **options) return self._get_option(options)
def _get_option(self, *args, **options): def _get_option(self, options):
def on_torrents_status(status): def on_torrents_status(status):
for torrentid, data in status.items(): for torrentid, data in status.items():
@ -63,11 +66,10 @@ class Command(BaseCommand):
def on_torrents_status_fail(reason): def on_torrents_status_fail(reason):
self.console.write('{!error!}Failed to get torrent data.') self.console.write('{!error!}Failed to get torrent data.')
torrent_ids = [] torrent_ids = self.console.match_torrent(options.torrent)
torrent_ids.extend(self.console.match_torrent(args[0]))
request_options = [] request_options = []
for opt in args[1:]: for opt in options.values:
if opt not in torrent_options: if opt not in torrent_options:
self.console.write('{!error!}Unknown torrent option: %s' % opt) self.console.write('{!error!}Unknown torrent option: %s' % opt)
return return
@ -77,16 +79,14 @@ class Command(BaseCommand):
request_options.append('name') request_options.append('name')
d = client.core.get_torrents_status({"id": torrent_ids}, request_options) d = client.core.get_torrents_status({"id": torrent_ids}, request_options)
d.addCallback(on_torrents_status) d.addCallbacks(on_torrents_status, on_torrents_status_fail)
d.addErrback(on_torrents_status_fail)
return d return d
def _set_option(self, *args, **options): def _set_option(self, options):
deferred = defer.Deferred() deferred = defer.Deferred()
torrent_ids = [] key = options.set
torrent_ids.extend(self.console.match_torrent(args[0])) val = " " .join(options.values)
key = options["set"][0] torrent_ids = self.console.match_torrent(options.torrent)
val = options["set"][1] + " " .join(args[1:])
if key not in torrent_options: if key not in torrent_options:
self.console.write("{!error!}The key '%s' is invalid!" % key) self.console.write("{!error!}The key '%s' is invalid!" % key)

View file

@ -7,43 +7,43 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
import logging
import os.path import os.path
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.main import BaseCommand from deluge.ui.console.main import BaseCommand
log = logging.getLogger(__name__)
class Command(BaseCommand): class Command(BaseCommand):
"""Move torrents' storage location""" """Move torrents' storage location"""
usage = "Usage: move <torrent-id> [<torrent-id> ...] <path>"
def handle(self, *args, **options): def add_arguments(self, parser):
parser.add_argument("torrent_ids", metavar="<torrent-id>", nargs="+", help="One or more torrent ids")
parser.add_argument("path", metavar="<path>", help="The path to move the torrents to")
def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if len(args) < 2: if os.path.exists(options.path) and not os.path.isdir(options.path):
self.console.write(self.usage) self.console.write("{!error!}Cannot Move Download Folder: %s exists and is not a directory" % options.path)
return
path = args[-1]
if os.path.exists(path) and not os.path.isdir(path):
self.console.write("{!error!}Cannot Move Download Folder: %s exists and is not a directory" % path)
return return
ids = [] ids = []
for i in args[:-1]:
ids.extend(self.console.match_torrent(i))
names = [] names = []
for i in ids: for t_id in options.torrent_ids:
names.append(self.console.get_torrent_name(i)) tid = self.console.match_torrent(t_id)
namestr = ", ".join(names) ids.extend(tid)
names.append(self.console.get_torrent_name(tid))
def on_move(res): def on_move(res):
self.console.write("Moved \"%s\" to %s" % (namestr, path)) msg = "Moved \"%s\" to %s" % (", ".join(names), options.path)
self.console.write(msg)
log.info(msg)
d = client.core.move_storage(ids, path) d = client.core.move_storage(ids, options.path)
d.addCallback(on_move) d.addCallback(on_move)
return d return d
@ -81,5 +81,4 @@ class Command(BaseCommand):
if os.path.isdir(p): if os.path.isdir(p):
p += "/" p += "/"
ret.append(p) ret.append(p)
return ret return ret

View file

@ -14,21 +14,22 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Pause a torrent""" """Pause torrents"""
usage = "Usage: pause [ * | <torrent-id> [<torrent-id> ...] ]" usage = "pause [ * | <torrent-id> [<torrent-id> ...] ]"
def handle(self, *args, **options): def add_arguments(self, parser):
parser.add_argument("torrent_ids", metavar="<torrent-id>", nargs="+",
help="One or more torrent ids. '*' pauses all torrents")
def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if len(args) == 0: if options.torrent_ids[0] == "*":
self.console.write(self.usage)
return
if len(args) > 0 and args[0].lower() == '*':
client.core.pause_session() client.core.pause_session()
return return
torrent_ids = [] torrent_ids = []
for arg in args: for arg in options.torrent_ids:
torrent_ids.extend(self.console.match_torrent(arg)) torrent_ids.extend(self.console.match_torrent(arg))
if torrent_ids: if torrent_ids:

View file

@ -7,8 +7,6 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
from optparse import make_option
import deluge.component as component import deluge.component as component
import deluge.configmanager import deluge.configmanager
from deluge.ui.client import client from deluge.ui.client import client
@ -16,41 +14,28 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Manage plugins with this command""" """Manage plugins"""
option_list = BaseCommand.option_list + (
make_option("-l", "--list", action="store_true", default=False, dest="list",
help="Lists available plugins"),
make_option("-s", "--show", action="store_true", default=False, dest="show",
help="Shows enabled plugins"),
make_option("-e", "--enable", dest="enable",
help="Enables a plugin"),
make_option("-d", "--disable", dest="disable",
help="Disables a plugin"),
make_option("-r", "--reload", action="store_true", default=False, dest="reload",
help="Reload list of available plugins"),
make_option("-i", "--install", dest="plugin_file",
help="Install a plugin from an .egg file"),
)
usage = """Usage: plugin [ -l | --list ]
plugin [ -s | --show ]
plugin [ -e | --enable ] <plugin-name>
plugin [ -d | --disable ] <plugin-name>
plugin [ -i | --install ] <plugin-file>
plugin [ -r | --reload]"""
def handle(self, *args, **options): def add_arguments(self, parser):
parser.add_argument("-l", "--list", action="store_true", default=False, dest="list",
help="Lists available plugins")
parser.add_argument("-s", "--show", action="store_true", default=False, dest="show",
help="Shows enabled plugins")
parser.add_argument("-e", "--enable", dest="enable", nargs="+", help="Enables a plugin")
parser.add_argument("-d", "--disable", dest="disable", nargs="+", help="Disables a plugin")
parser.add_argument("-r", "--reload", action="store_true", default=False, dest="reload",
help="Reload list of available plugins")
parser.add_argument("-i", "--install", help="Install a plugin from an .egg file")
def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if len(args) == 0 and not any(options.values()): if options.reload:
self.console.write(self.usage)
return
if options["reload"]:
client.core.pluginmanager.rescan_plugins() client.core.pluginmanager.rescan_plugins()
self.console.write("{!green!}Plugin list successfully reloaded") self.console.write("{!green!}Plugin list successfully reloaded")
return return
elif options["list"]: elif options.list:
def on_available_plugins(result): def on_available_plugins(result):
self.console.write("{!info!}Available Plugins:") self.console.write("{!info!}Available Plugins:")
for p in result: for p in result:
@ -58,7 +43,7 @@ class Command(BaseCommand):
return client.core.get_available_plugins().addCallback(on_available_plugins) return client.core.get_available_plugins().addCallback(on_available_plugins)
elif options["show"]: elif options.show:
def on_enabled_plugins(result): def on_enabled_plugins(result):
self.console.write("{!info!}Enabled Plugins:") self.console.write("{!info!}Enabled Plugins:")
for p in result: for p in result:
@ -66,50 +51,42 @@ class Command(BaseCommand):
return client.core.get_enabled_plugins().addCallback(on_enabled_plugins) return client.core.get_enabled_plugins().addCallback(on_enabled_plugins)
elif options["enable"]: elif options.enable:
def on_available_plugins(result): def on_available_plugins(result):
plugins = {} plugins = {}
for p in result: for p in result:
plugins[p.lower()] = p plugins[p.lower()] = p
p_args = [options["enable"]] + list(args) for arg in options.enable:
for arg in p_args:
if arg.lower() in plugins: if arg.lower() in plugins:
client.core.enable_plugin(plugins[arg.lower()]) client.core.enable_plugin(plugins[arg.lower()])
return client.core.get_available_plugins().addCallback(on_available_plugins) return client.core.get_available_plugins().addCallback(on_available_plugins)
elif options["disable"]: elif options.disable:
def on_enabled_plugins(result): def on_enabled_plugins(result):
plugins = {} plugins = {}
for p in result: for p in result:
plugins[p.lower()] = p plugins[p.lower()] = p
p_args = [options["disable"]] + list(args) for arg in options.disable:
for arg in p_args:
if arg.lower() in plugins: if arg.lower() in plugins:
client.core.disable_plugin(plugins[arg.lower()]) client.core.disable_plugin(plugins[arg.lower()])
return client.core.get_enabled_plugins().addCallback(on_enabled_plugins) return client.core.get_enabled_plugins().addCallback(on_enabled_plugins)
elif options["plugin_file"]: elif options.install:
filepath = options["plugin_file"]
import os.path import os.path
import base64 import base64
import shutil import shutil
filepath = options.install
if not os.path.exists(filepath): if not os.path.exists(filepath):
self.console.write("{!error!}Invalid path: %s" % filepath) self.console.write("{!error!}Invalid path: %s" % filepath)
return return
config_dir = deluge.configmanager.get_config_dir() config_dir = deluge.configmanager.get_config_dir()
filename = os.path.split(filepath)[1] filename = os.path.split(filepath)[1]
shutil.copyfile(filepath, os.path.join(config_dir, "plugins", filename))
shutil.copyfile(
filepath,
os.path.join(config_dir, "plugins", filename))
client.core.rescan_plugins() client.core.rescan_plugins()

View file

@ -15,11 +15,11 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Exit from the client.""" """Exit the client."""
aliases = ["exit"] aliases = ["exit"]
interactive_only = True interactive_only = True
def handle(self, *args, **options): def handle(self, options):
if client.connected(): if client.connected():
def on_disconnect(result): def on_disconnect(result):
reactor.stop() reactor.stop()

View file

@ -14,20 +14,20 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Forces a recheck of the torrent data""" """Forces a recheck of the torrent data"""
usage = "Usage: recheck [ * | <torrent-id> [<torrent-id> ...] ]" usage = "recheck [ * | <torrent-id> [<torrent-id> ...] ]"
def handle(self, *args, **options): def add_arguments(self, parser):
parser.add_argument("torrent_ids", metavar="<torrent-id>", nargs="+", help="One or more torrent ids")
def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if len(args) == 0: if options.torrent_ids[0] == "*":
self.console.write(self.usage)
return
if len(args) > 0 and args[0].lower() == "*":
client.core.force_recheck(self.console.match_torrent("")) client.core.force_recheck(self.console.match_torrent(""))
return return
torrent_ids = [] torrent_ids = []
for arg in args: for arg in options.torrent_ids:
torrent_ids.extend(self.console.match_torrent(arg)) torrent_ids.extend(self.console.match_torrent(arg))
if torrent_ids: if torrent_ids:

View file

@ -14,22 +14,23 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Resume a torrent""" """Resume torrents"""
usage = "Usage: resume [ * | <torrent-id> [<torrent-id> ...] ]" usage = "resume [ * | <torrent-id> [<torrent-id> ...] ]"
def handle(self, *args, **options): def add_arguments(self, parser):
parser.add_argument("torrent_ids", metavar="<torrent-id>", nargs="+",
help="One or more torrent ids. '*' resumes all torrents")
def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if len(args) == 0: if options.torrent_ids[0] == "*":
self.console.write(self.usage)
return
if len(args) > 0 and args[0] == "*":
client.core.resume_session() client.core.resume_session()
return return
torrent_ids = [] torrent_ids = []
for arg in args: for t_id in options.torrent_ids:
torrent_ids.extend(self.console.match_torrent(arg)) torrent_ids.extend(self.console.match_torrent(t_id))
if torrent_ids: if torrent_ids:
return client.core.resume_torrent(torrent_ids) return client.core.resume_torrent(torrent_ids)

View file

@ -8,8 +8,6 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
from optparse import make_option
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.main import BaseCommand from deluge.ui.console.main import BaseCommand
@ -17,21 +15,16 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Remove a torrent""" """Remove a torrent"""
usage = "Usage: rm <torrent-id>"
aliases = ["del"] aliases = ["del"]
option_list = BaseCommand.option_list + ( def add_arguments(self, parser):
make_option("--remove_data", action="store_true", default=False, parser.add_argument("--remove_data", action="store_true", default=False, help="remove the torrent's data")
help="remove the torrent's data"), parser.add_argument("torrent_ids", metavar="<torrent-id>", nargs="+", help="One or more torrent ids")
)
def handle(self, *args, **options): def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if len(args) == 0:
self.console.write(self.usage)
torrent_ids = [] torrent_ids = []
for arg in args: for arg in options.torrent_ids:
torrent_ids.extend(self.console.match_torrent(arg)) torrent_ids.extend(self.console.match_torrent(arg))
def on_removed_finished(errors): def on_removed_finished(errors):
@ -40,7 +33,7 @@ class Command(BaseCommand):
for t_id, e_msg in errors: for t_id, e_msg in errors:
self.console.write("Error removing torrent %s : %s" % (t_id, e_msg)) self.console.write("Error removing torrent %s : %s" % (t_id, e_msg))
d = client.core.remove_torrents(torrent_ids, options["remove_data"]) d = client.core.remove_torrents(torrent_ids, options.remove_data)
d.addCallback(on_removed_finished) d.addCallback(on_removed_finished)
def complete(self, line): def complete(self, line):

View file

@ -7,7 +7,7 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
from optparse import make_option import logging
from twisted.internet import defer from twisted.internet import defer
@ -16,44 +16,34 @@ from deluge.common import TORRENT_STATE, fspeed
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
log = logging.getLogger(__name__)
class Command(BaseCommand): class Command(BaseCommand):
"""Shows a various status information from the daemon.""" """Shows a various status information from the daemon."""
option_list = BaseCommand.option_list + (
make_option("-r", "--raw", action="store_true", default=False, dest="raw",
help="Don't format upload/download rates in KiB/s \
(useful for scripts that want to do their own parsing)"),
make_option("-n", "--no-torrents", action="store_false", default=True, dest="show_torrents",
help="Don't show torrent status (this will make the command a bit faster)"),
)
usage = "Usage: status [-r] [-n]" def add_arguments(self, parser):
parser.add_argument("-r", "--raw", action="store_true", default=False, dest="raw",
help="Don't format upload/download rates in KiB/s \
(useful for scripts that want to do their own parsing)")
parser.add_argument("-n", "--no-torrents", action="store_false", default=True, dest="show_torrents",
help="Don't show torrent status (this will make the command a bit faster)")
def handle(self, *args, **options): def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
self.status = None self.status = None
self.connections = None self.torrents = 1 if options.show_torrents else 0
if options["show_torrents"]: self.raw = options.raw
self.torrents = None
else:
self.torrents = -2
self.raw = options["raw"]
def on_session_status(status): def on_session_status(status):
self.status = status self.status = status
if self.status is not None and self.connections is not None and self.torrents is not None:
self.print_status()
def on_torrents_status(status): def on_torrents_status(status):
self.torrents = status self.torrents = status
if self.status is not None and self.connections is not None and self.torrents is not None:
self.print_status()
def on_torrents_status_fail(reason): def on_torrents_status_fail(reason):
self.torrents = -1 log.warn("Failed to retrieve session status: %s", reason)
if self.status is not None and self.connections is not None and self.torrents is not None: self.torrents = -2
self.print_status()
deferreds = [] deferreds = []
@ -61,15 +51,15 @@ class Command(BaseCommand):
ds.addCallback(on_session_status) ds.addCallback(on_session_status)
deferreds.append(ds) deferreds.append(ds)
if options["show_torrents"]: if options.show_torrents:
dt = client.core.get_torrents_status({}, ["state"]) dt = client.core.get_torrents_status({}, ["state"])
dt.addCallback(on_torrents_status) dt.addCallback(on_torrents_status)
dt.addErrback(on_torrents_status_fail) dt.addErrback(on_torrents_status_fail)
deferreds.append(dt) deferreds.append(dt)
return defer.DeferredList(deferreds) return defer.DeferredList(deferreds).addCallback(self.print_status)
def print_status(self): def print_status(self, *args):
self.console.set_batch_write(True) self.console.set_batch_write(True)
if self.raw: if self.raw:
self.console.write("{!info!}Total upload: %f" % self.status["payload_upload_rate"]) self.console.write("{!info!}Total upload: %f" % self.status["payload_upload_rate"])
@ -78,12 +68,12 @@ class Command(BaseCommand):
self.console.write("{!info!}Total upload: %s" % fspeed(self.status["payload_upload_rate"])) self.console.write("{!info!}Total upload: %s" % fspeed(self.status["payload_upload_rate"]))
self.console.write("{!info!}Total download: %s" % fspeed(self.status["payload_download_rate"])) self.console.write("{!info!}Total download: %s" % fspeed(self.status["payload_download_rate"]))
self.console.write("{!info!}DHT Nodes: %i" % self.status["dht_nodes"]) self.console.write("{!info!}DHT Nodes: %i" % self.status["dht_nodes"])
self.console.write("{!info!}Total connections: %i" % self.connections)
if self.torrents == -1:
self.console.write("{!error!}Error getting torrent info")
elif self.torrents != -2:
self.console.write("{!info!}Total torrents: %i" % len(self.torrents))
if isinstance(self.torrents, int):
if self.torrents == -2:
self.console.write("{!error!}Error getting torrent info")
else:
self.console.write("{!info!}Total torrents: %i" % len(self.torrents))
state_counts = {} state_counts = {}
for state in TORRENT_STATE: for state in TORRENT_STATE:
state_counts[state] = 0 state_counts[state] = 0

View file

@ -15,15 +15,17 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Update tracker for torrent(s)""" """Update tracker for torrent(s)"""
usage = "Usage: update_tracker [ * | <torrent-id> [<torrent-id> ...] ]" usage = "update_tracker [ * | <torrent-id> [<torrent-id> ...] ]"
aliases = ['reannounce'] aliases = ['reannounce']
def handle(self, *args, **options): def add_arguments(self, parser):
parser.add_argument('torrent_ids', metavar="<torrent-id>", nargs='+',
help='One or more torrent ids. "*" updates all torrents')
def handle(self, options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if len(args) == 0: args = options.torrent_ids
self.console.write(self.usage) if options.torrent_ids[0] == "*":
return
if len(args) > 0 and args[0].lower() == '*':
args = [""] args = [""]
torrent_ids = [] torrent_ids = []

View file

@ -8,29 +8,36 @@
# See LICENSE for more details. # See LICENSE for more details.
# #
import optparse
import os import os
import sys
from deluge.ui.baseargparser import DelugeTextHelpFormatter
from deluge.ui.console import UI_PATH from deluge.ui.console import UI_PATH
from deluge.ui.ui import UI from deluge.ui.ui import UI
def load_commands(command_dir, exclude=[]): #
# Note: Cannot import from console.main here because it imports the twisted reactor.
# Console is imported from console/__init__.py loaded by the script entry points
# defined in setup.py
#
def load_commands(command_dir):
def get_command(name): def get_command(name):
return getattr(__import__('deluge.ui.console.commands.%s' % name, {}, {}, ['Command']), 'Command')() return getattr(__import__('deluge.ui.console.commands.%s' % name, {}, {}, ['Command']), 'Command')()
try: try:
commands = [] commands = []
for filename in os.listdir(command_dir): for filename in os.listdir(command_dir):
if filename.split('.')[0] in exclude or filename.startswith('_'): if filename.startswith('_'):
continue continue
if not (filename.endswith('.py') or filename.endswith('.pyc')): if not (filename.endswith('.py') or filename.endswith('.pyc')):
continue continue
cmd = get_command(filename.split('.')[len(filename.split('.')) - 2]) cmd = get_command(filename.split('.')[len(filename.split('.')) - 2])
aliases = [filename.split('.')[len(filename.split('.')) - 2]] cmd._name = filename.split('.')[len(filename.split('.')) - 2]
aliases.extend(cmd.aliases) names = [cmd._name]
for a in aliases: names.extend(cmd.aliases)
for a in names:
commands.append((a, cmd)) commands.append((a, cmd))
return dict(commands) return dict(commands)
except OSError: except OSError:
@ -44,61 +51,29 @@ class Console(UI):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Console, self).__init__("console", *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", group = self.parser.add_argument_group(_("Console Options"), "These daemon connect options will be "
action="store", type="str", default="127.0.0.1", "used for commands, or if console ui autoconnect is enabled.")
help="Set the address of the daemon to connect to. [default: %default]") group.add_argument("-d", "--daemon", dest="daemon_addr", required=False, default="127.0.0.1")
group.add_option("-p", "--port", dest="daemon_port", group.add_argument("-p", "--port", dest="daemon_port", type=int, required=False, default="58846")
help="Set the port to connect to the daemon on. [default: %default]", group.add_argument("-U", "--username", dest="daemon_user", required=False)
action="store", type="int", default=58846) group.add_argument("-P", "--password", dest="daemon_pass", required=False)
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')) # To properly print help message for the console commands ( e.g. deluge-console info -h),
# we add a subparser for each command which will trigger the help/usage when given
class CommandOptionGroup(optparse.OptionGroup): from deluge.ui.console.main import ConsoleCommandParser # import here because (see top)
def __init__(self, parser, title, description=None, cmds=None): self.console_parser = ConsoleCommandParser(parents=[self.parser], add_help=False,
optparse.OptionGroup.__init__(self, parser, title, description) formatter_class=lambda prog:
self.cmds = cmds DelugeTextHelpFormatter(prog, max_help_position=33, width=90))
self.parser.subparser = self.console_parser
def format_help(self, formatter): subparsers = self.console_parser.add_subparsers(title="Console commands", help="Description", dest="commands",
result = formatter.format_heading(self.title) description="The following console commands are available:",
formatter.indent() metavar="command")
if self.description: self.console_cmds = load_commands(os.path.join(UI_PATH, "commands"))
result += "%s\n" % formatter.format_description(self.description) for c in sorted(self.console_cmds):
for cname in self.cmds: self.console_cmds[c].add_subparser(subparsers)
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): def start(self, args=None):
from main import ConsoleUI
super(Console, self).start(args) super(Console, self).start(args)
ConsoleUI(self.args, self.cmds, (self.options.daemon_addr, from deluge.ui.console.main import ConsoleUI # import here because (see top)
self.options.daemon_port, self.options.daemon_user, ConsoleUI(self.options, self.console_cmds)
self.options.daemon_pass))

View file

@ -10,10 +10,9 @@
from __future__ import print_function from __future__ import print_function
import argparse
import locale import locale
import logging import logging
import optparse
import re
import shlex import shlex
import sys import sys
@ -21,8 +20,10 @@ from twisted.internet import defer, reactor
import deluge.common import deluge.common
import deluge.component as component import deluge.component as component
from deluge.error import DelugeError
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console import colors from deluge.ui.console import colors
from deluge.ui.console.colors import ConsoleColorFormatter
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
@ -31,84 +32,32 @@ from deluge.ui.sessionproxy import SessionProxy
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class DelugeHelpFormatter(optparse.IndentedHelpFormatter): class ConsoleCommandParser(argparse.ArgumentParser):
"""
Format help in a way suited to deluge Legacy mode - colors, format, indentation...
"""
replace_dict = { def format_help(self):
"<torrent-id>": "{!green!}%s{!input!}", """
"<state>": "{!yellow!}%s{!input!}", Differs from ArgumentParser.format_help by adding the raw epilog
"\\.\\.\\.": "{!yellow!}%s{!input!}", as formatted in the string. Default bahavior mangles the formatting.
"\\s\\*\\s": "{!blue!}%s{!input!}",
"(?<![\\-a-z])(-[a-zA-Z0-9])": "{!red!}%s{!input!}",
# "(\-[a-zA-Z0-9])": "{!red!}%s{!input!}",
"--[_\\-a-zA-Z0-9]+": "{!green!}%s{!input!}",
"(\\[|\\])": "{!info!}%s{!input!}",
"<tab>": "{!white!}%s{!input!}", """
"[_A-Z]{3,}": "{!cyan!}%s{!input!}", # Handle epilog manually to keep the text formatting
epilog = self.epilog
"<download-folder>": "{!yellow!}%s{!input!}", self.epilog = ""
"<torrent-file>": "{!green!}%s{!input!}" help_str = super(ConsoleCommandParser, self).format_help()
if epilog is not None:
} help_str += epilog
self.epilog = epilog
def __init__(self, return help_str
indent_increment=2,
max_help_position=24,
width=None,
short_first=1):
optparse.IndentedHelpFormatter.__init__(
self, indent_increment, max_help_position, width, short_first)
def _format_colors(self, string):
def r(repl):
return lambda s: repl % s.group()
for key, replacement in self.replace_dict.items():
string = re.sub(key, r(replacement), string)
return string
def format_usage(self, usage):
return _("{!info!}Usage{!input!}: %s\n") % self._format_colors(usage)
def format_option(self, option):
result = []
opts = self.option_strings[option]
opt_width = self.help_position - self.current_indent - 2
if len(opts) > opt_width:
opts = "%*s%s\n" % (self.current_indent, "", opts)
opts = self._format_colors(opts)
indent_first = self.help_position
else: # start help on same line as opts
opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts)
opts = self._format_colors(opts)
indent_first = 0
result.append(opts)
if option.help:
help_text = self.expand_default(option)
help_text = self._format_colors(help_text)
help_lines = optparse.textwrap.wrap(help_text, self.help_width)
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
result.extend(["%*s%s\n" % (self.help_position, "", line)
for line in help_lines[1:]])
elif opts[-1] != "\n":
result.append("\n")
return "".join(result)
class OptionParser(optparse.OptionParser): class OptionParser(ConsoleCommandParser):
"""subclass from optparse.OptionParser so exit() won't exit."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
optparse.OptionParser.__init__(self, **kwargs) super(OptionParser, self).__init__(**kwargs)
self.formatter = ConsoleColorFormatter()
self.formatter = DelugeHelpFormatter()
def exit(self, status=0, msg=None): def exit(self, status=0, msg=None):
self.values._exit = True self._exit = True
if msg: if msg:
print(msg) print(msg)
@ -124,7 +73,7 @@ class OptionParser(optparse.OptionParser):
def print_usage(self, _file=None): def print_usage(self, _file=None):
console = component.get("ConsoleUI") console = component.get("ConsoleUI")
if self.usage: if self.usage:
for line in self.get_usage().splitlines(): for line in self.format_usage().splitlines():
console.write(line) console.write(line)
def print_help(self, _file=None): def print_help(self, _file=None):
@ -134,43 +83,36 @@ class OptionParser(optparse.OptionParser):
console.write(line) console.write(line)
console.set_batch_write(False) console.set_batch_write(False)
def format_option_help(self, formatter=None): def format_help(self):
if formatter is None: """Return help formatted with colors."""
formatter = self.formatter help_str = super(OptionParser, self).format_help()
formatter.store_option_strings(self) return self.formatter.format_colors(help_str)
result = []
result.append(formatter.format_heading(_("{!info!}Options{!input!}")))
formatter.indent()
if self.option_list:
result.append(optparse.OptionContainer.format_option_help(self, formatter))
result.append("\\n")
for group in self.option_groups:
result.append(group.format_help(formatter))
result.append("\\n")
formatter.dedent()
# Drop the last "\\n", or the header if no options or option groups:
return "".join(result[:-1])
class BaseCommand(object): class BaseCommand(object):
usage = "usage" usage = None
interactive_only = False interactive_only = False
option_list = tuple()
aliases = [] aliases = []
_name = "base"
epilog = ""
def complete(self, text, *args): def complete(self, text, *args):
return [] return []
def handle(self, *args, **options): def handle(self, options):
pass pass
@property @property
def name(self): def name(self):
return "base" return self._name
@property @property
def epilog(self): def name_with_alias(self):
return "/".join([self._name] + self.aliases)
@property
def description(self):
return self.__doc__ return self.__doc__
def split(self, text): def split(self, text):
@ -183,16 +125,32 @@ class BaseCommand(object):
return result return result
def create_parser(self): def create_parser(self):
return OptionParser(prog=self.name, usage=self.usage, epilog=self.epilog, option_list=self.option_list) opts = {"prog": self.name_with_alias, "description": self.__doc__, "epilog": self.epilog}
if self.usage:
opts["usage"] = self.usage
parser = OptionParser(**opts)
parser.add_argument(self.name, metavar="")
self.add_arguments(parser)
return parser
def add_subparser(self, subparsers):
opts = {"prog": self.name_with_alias, "help": self.__doc__, "description": self.__doc__}
if self.usage:
opts["usage"] = self.usage
parser = subparsers.add_parser(self.name, **opts)
self.add_arguments(parser)
def add_arguments(self, parser):
pass
class ConsoleUI(component.Component): class ConsoleUI(component.Component):
def __init__(self, args=None, cmds=None, daemon=None):
component.Component.__init__(self, "ConsoleUI", 2)
def __init__(self, options=None, cmds=None):
component.Component.__init__(self, "ConsoleUI", 2)
# keep track of events for the log view # keep track of events for the log view
self.events = [] self.events = []
self.statusbars = None
try: try:
locale.setlocale(locale.LC_ALL, "") locale.setlocale(locale.LC_ALL, "")
self.encoding = locale.getpreferredencoding() self.encoding = locale.getpreferredencoding()
@ -209,19 +167,14 @@ class ConsoleUI(component.Component):
# Set the interactive flag to indicate where we should print the output # Set the interactive flag to indicate where we should print the output
self.interactive = True self.interactive = True
self._commands = cmds self._commands = cmds
if args:
args = " ".join(args) if options.remaining:
self.interactive = False self.interactive = False
if not cmds: if not cmds:
print("Sorry, couldn't find any commands") print("Sorry, couldn't find any commands")
return return
else: else:
from deluge.ui.console.commander import Commander self.exec_args(options)
cmdr = Commander(cmds)
if daemon:
cmdr.exec_args(args, *daemon)
else:
cmdr.exec_args(args, None, None, None, None)
self.coreconfig = CoreConfig() self.coreconfig = CoreConfig()
if self.interactive and not deluge.common.windows_check(): if self.interactive and not deluge.common.windows_check():
@ -240,6 +193,60 @@ Please use commands from the command line, eg:\n
else: else:
reactor.run() reactor.run()
def exec_args(self, options):
args = options.remaining
commands = []
if args:
cmd = " ".join([arg for arg in args])
# Multiple commands split by ";"
commands += [arg.strip() for arg in cmd.split(";")]
from deluge.ui.console.commander import Commander
commander = Commander(self._commands)
def on_connect(result):
def on_started(result):
def on_started(result):
def do_command(result, cmd):
return commander.do_command(cmd)
d = defer.succeed(None)
for command in commands:
if command in ("quit", "exit"):
break
d.addCallback(do_command, command)
d.addCallback(do_command, "quit")
# We need to wait for the rpcs in start() to finish before processing
# any of the commands.
self.started_deferred.addCallback(on_started)
component.start().addCallback(on_started)
def on_connect_fail(reason):
if reason.check(DelugeError):
rm = reason.getErrorMessage()
else:
rm = reason.value.message
print("Could not connect to daemon: %s:%s\n %s" % (options.daemon_addr, options.daemon_port, rm))
commander.do_command("quit")
d = None
if not self.interactive:
if commands[0] is not None:
if commands[0].startswith("connect"):
d = commander.do_command(commands.pop(0))
if d is None:
# Error parsing command
sys.exit(0)
elif "help" in commands:
commander.do_command("help")
sys.exit(0)
if not d:
log.info("connect: host=%s, port=%s, username=%s, password=%s",
options.daemon_addr, options.daemon_port, options.daemon_user, options.daemon_pass)
d = client.connect(options.daemon_addr, options.daemon_port, options.daemon_user, options.daemon_pass)
d.addCallback(on_connect)
d.addErrback(on_connect_fail)
def run(self, stdscr): def run(self, stdscr):
""" """
This method is called by the curses.wrapper to start the mainloop and This method is called by the curses.wrapper to start the mainloop and
@ -332,6 +339,7 @@ Please use commands from the command line, eg:\n
self.screen = mode self.screen = mode
self.statusbars.screen = self.screen self.statusbars.screen = self.screen
reactor.addReader(self.screen) reactor.addReader(self.screen)
self.stdscr.clear()
mode.refresh() mode.refresh()
def on_client_disconnect(self): def on_client_disconnect(self):

View file

@ -812,9 +812,6 @@ class AllTorrents(BaseMode, component.Component):
self.refresh() self.refresh()
def refresh(self, lines=None): def refresh(self, lines=None):
# log.error("ref")
# import traceback
# traceback.print_stack()
# Something has requested we scroll to the top of the list # Something has requested we scroll to the top of the list
if self._go_top: if self._go_top:
self.cursel = 1 self.cursel = 1

View file

@ -38,6 +38,7 @@ DEFAULT_CONFIG = {
class ConnectionManager(BaseMode): class ConnectionManager(BaseMode):
def __init__(self, stdscr, encoding=None): def __init__(self, stdscr, encoding=None):
self.popup = None self.popup = None
self.statuses = {} self.statuses = {}
@ -87,6 +88,7 @@ class ConnectionManager(BaseMode):
port = host[2] port = host[2]
user = host[3] user = host[3]
password = host[4] password = host[4]
log.debug("connect: hadr=%s, port=%s, user=%s, password=%s", hadr, port, user, password)
d = c.connect(hadr, port, user, password) d = c.connect(hadr, port, user, password)
d.addCallback(on_connect, c, host[0]) d.addCallback(on_connect, c, host[0])
d.addErrback(on_connect_failed, host[0]) d.addErrback(on_connect_failed, host[0])

View file

@ -24,6 +24,7 @@ import deluge.component as component
import deluge.configmanager import deluge.configmanager
import deluge.ui.console.colors as colors import deluge.ui.console.colors as colors
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.commander import Commander
from deluge.ui.console.modes import format_utils from deluge.ui.console.modes import format_utils
from deluge.ui.console.modes.basemode import BaseMode from deluge.ui.console.modes.basemode import BaseMode
@ -94,11 +95,15 @@ def commonprefix(m):
return s2 return s2
class Legacy(BaseMode, component.Component): class Legacy(BaseMode, Commander, component.Component):
def __init__(self, stdscr, encoding=None): def __init__(self, stdscr, encoding=None):
component.Component.__init__(self, "LegacyUI", 1, depend=["SessionProxy"]) component.Component.__init__(self, "LegacyUI", 1, depend=["SessionProxy"])
# Get a handle to the main console
self.console = component.get("ConsoleUI")
Commander.__init__(self, self.console._commands, interactive=True)
self.batch_write = False self.batch_write = False
# A list of strings to be displayed based on the offset (scroll) # A list of strings to be displayed based on the offset (scroll)
@ -122,9 +127,6 @@ class Legacy(BaseMode, component.Component):
# Keep track of double- and multi-tabs # Keep track of double- and multi-tabs
self.tab_count = 0 self.tab_count = 0
# Get a handle to the main console
self.console = component.get("ConsoleUI")
self.console_config = component.get("AllTorrents").config self.console_config = component.get("AllTorrents").config
# To avoid having to truncate the file every time we're writing # To avoid having to truncate the file every time we're writing
@ -586,64 +588,6 @@ class Legacy(BaseMode, component.Component):
col += strwidth(s) col += strwidth(s)
def do_command(self, cmd):
"""
Processes a command.
:param cmd: str, the command string
"""
if not cmd:
return
cmd, _, line = cmd.partition(" ")
try:
parser = self.console._commands[cmd].create_parser()
except KeyError:
self.write("{!error!}Unknown command: %s" % cmd)
return
try:
args = self.console._commands[cmd].split(line)
except ValueError as ex:
self.write("{!error!}Error parsing command: %s" % ex)
return
# Do a little hack here to print 'command --help' properly
parser._print_help = parser.print_help
def print_help(f=None):
parser._print_help(f)
parser.print_help = print_help
# Only these commands can be run when not connected to a daemon
not_connected_cmds = ["help", "connect", "quit"]
aliases = []
for c in not_connected_cmds:
aliases.extend(self.console._commands[c].aliases)
not_connected_cmds.extend(aliases)
if not client.connected() and cmd not in not_connected_cmds:
self.write("{!error!}Not connected to a daemon, please use the connect command first.")
return
try:
options, args = parser.parse_args(args)
except TypeError as ex:
self.write("{!error!}Error parsing options: %s" % ex)
return
if not getattr(options, "_exit", False):
try:
ret = self.console._commands[cmd].handle(*args, **options.__dict__)
except Exception as ex:
self.write("{!error!} %s" % ex)
log.exception(ex)
import traceback
self.write("%s" % traceback.format_exc())
return defer.succeed(True)
else:
return ret
def set_batch_write(self, batch): def set_batch_write(self, batch):
""" """
When this is set the screen is not refreshed after a `:meth:write` until When this is set the screen is not refreshed after a `:meth:write` until

View file

@ -8,6 +8,7 @@
# #
# We skip isorting this file as it want to move the gtk2reactor.install() below the imports # We skip isorting this file as it want to move the gtk2reactor.install() below the imports
# isort:skip_file # isort:skip_file
from __future__ import division from __future__ import division
import logging import logging
@ -24,7 +25,7 @@ from twisted.internet.task import LoopingCall
try: try:
# Install twisted reactor, before any other modules import reactor. # Install twisted reactor, before any other modules import reactor.
reactor = gtk2reactor.install() reactor = gtk2reactor.install()
except ReactorAlreadyInstalledError: except ReactorAlreadyInstalledError as ex:
# Running unit tests so trial already installed a rector # Running unit tests so trial already installed a rector
pass pass
@ -196,7 +197,7 @@ class GtkUI(object):
# Start the IPC Interface before anything else.. Just in case we are # Start the IPC Interface before anything else.. Just in case we are
# already running. # already running.
self.queuedtorrents = QueuedTorrents() self.queuedtorrents = QueuedTorrents()
self.ipcinterface = IPCInterface(args) self.ipcinterface = IPCInterface(args.torrents)
# Initialize gdk threading # Initialize gdk threading
gtk.gdk.threads_init() gtk.gdk.threads_init()

View file

@ -8,12 +8,13 @@
# #
import logging import logging
import optparse
import deluge.common import deluge.common
import deluge.configmanager import deluge.configmanager
import deluge.log import deluge.log
from deluge.commonoptions import CommonOptionParser from deluge.ui.baseargparser import BaseArgParser
log = logging.getLogger(__name__)
try: try:
from setproctitle import setproctitle from setproctitle import setproctitle
@ -21,10 +22,6 @@ except ImportError:
def setproctitle(title): def setproctitle(title):
return return
DEFAULT_PREFS = {
"default_ui": "gtk"
}
if 'dev' not in deluge.common.get_version(): if 'dev' not in deluge.common.get_version():
import warnings import warnings
warnings.filterwarnings('ignore', category=DeprecationWarning, module='twisted') warnings.filterwarnings('ignore', category=DeprecationWarning, module='twisted')
@ -32,15 +29,16 @@ if 'dev' not in deluge.common.get_version():
class UI(object): class UI(object):
def __init__(self, name="gtk", skip_common=False): def __init__(self, name="gtk", parser=None):
self.__name = name self.__name = name
self.__parser = parser if parser else BaseArgParser()
deluge.common.setup_translations(setup_pygtk=(name == "gtk"))
if name == "gtk": def parse_args(self, args=None):
deluge.common.setup_translations(setup_pygtk=True) options = self.parser.parse_args(args)
else: if not hasattr(options, "remaining"):
deluge.common.setup_translations() options.remaining = []
return options
self.__parser = optparse.OptionParser() if skip_common else CommonOptionParser()
@property @property
def name(self): def name(self):
@ -54,22 +52,15 @@ class UI(object):
def options(self): def options(self):
return self.__options return self.__options
@property def start(self, extra_args=None):
def args(self): args = deluge.common.unicode_argv()[1:]
return self.__args if extra_args:
args.extend(extra_args)
def start(self, args=None): self.__options = self.parse_args(args)
if args is None:
# Make sure all arguments are unicode
args = deluge.common.unicode_argv()[1:]
self.__options, self.__args = self.__parser.parse_args(args)
log = logging.getLogger(__name__)
setproctitle("deluge-%s" % self.__name) setproctitle("deluge-%s" % self.__name)
log.info("Deluge ui %s", deluge.common.get_version()) log.info("Deluge ui %s", deluge.common.get_version())
log.debug("options: %s", self.__options) log.debug("options: %s", self.__options)
log.debug("args: %s", self.__args)
log.info("Starting %s ui..", self.__name) log.info("Starting %s ui..", self.__name)

View file

@ -10,13 +10,18 @@
from __future__ import print_function from __future__ import print_function
import os import os
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
#
# Note: Cannot import twisted.internet.reactor because Web is imported from
# from web/__init__.py loaded by the script entry points defined in setup.py
#
class WebUI(object): class WebUI(object):
def __init__(self, args): def __init__(self, args):
from deluge.ui.web import server from deluge.ui.web import server
@ -33,36 +38,36 @@ class Web(UI):
super(Web, self).__init__("web", *args, **kwargs) super(Web, self).__init__("web", *args, **kwargs)
self.__server = None self.__server = None
group = OptionGroup(self.parser, "Web Options") group = self.parser.add_argument_group(_('Web Options'))
group.add_option("-b", "--base", dest="base", action="store", default=None,
help="Set the base path that the ui is running on (proxying)") group.add_argument("-b", "--base", metavar="<path>", action="store", default=None,
help="Set the base path that the ui is running on (proxying)")
if not (windows_check() or osx_check()): if not (windows_check() or osx_check()):
group.add_option("-d", "--do-not-daemonize", dest="donotdaemonize", action="store_true", default=False, group.add_argument("-d", "--do-not-daemonize", dest="donotdaemonize", action="store_true", default=False,
help="Do not daemonize the web interface") help="Do not daemonize the web interface")
group.add_option("-P", "--pidfile", dest="pidfile", type="str", action="store", default=None, group.add_argument("-P", "--pidfile", metavar="<pidfile>", action="store", default=None,
help="Use pidfile to store process id") help="Use pidfile to store process id")
if not windows_check(): if not windows_check():
group.add_option("-U", "--user", dest="user", type="str", action="store", default=None, group.add_argument("-U", "--user", metavar="<user>", action="store", default=None,
help="User to switch to. Only use it when starting as root") help="User to switch to. Only use it when starting as root")
group.add_option("-g", "--group", dest="group", type="str", action="store", default=None, group.add_argument("-g", "--group", metavar="<group>", action="store", default=None,
help="Group to switch to. Only use it when starting as root") help="Group to switch to. Only use it when starting as root")
group.add_option("-i", "--interface", dest="interface", action="store", default=None, group.add_argument("-i", "--interface", metavar="<interface>", action="store", default=None,
type="str", help="Binds the webserver to a specific IP address") help="Binds the webserver to a specific IP address")
group.add_option("-p", "--port", dest="port", type="int", action="store", default=None, group.add_argument("-p", "--port", metavar="<port>", type=int, action="store", default=None,
help="Sets the port to be used for the webserver") help="Sets the port to be used for the webserver")
group.add_option("--profile", dest="profile", action="store_true", default=False, group.add_argument("--profile", action="store_true", default=False,
help="Profile the web server code") help="Profile the web server code")
try: try:
import OpenSSL import OpenSSL
assert OpenSSL.__version__ assert OpenSSL.__version__
except ImportError: except ImportError:
pass pass
else: else:
group.add_option("--no-ssl", dest="ssl", action="store_false", group.add_argument("--no-ssl", dest="ssl", action="store_false",
help="Forces the webserver to disable ssl", default=False) help="Forces the webserver to disable ssl", default=False)
group.add_option("--ssl", dest="ssl", action="store_true", group.add_argument("--ssl", dest="ssl", action="store_true",
help="Forces the webserver to use ssl", default=False) help="Forces the webserver to use ssl", default=False)
self.parser.add_option_group(group)
@property @property
def server(self): def server(self):
@ -73,7 +78,7 @@ class Web(UI):
# Steps taken from http://www.faqs.org/faqs/unix-faq/programmer/faq/ # Steps taken from http://www.faqs.org/faqs/unix-faq/programmer/faq/
# Section 1.7 # Section 1.7
if not self.options.ensure_value("donotdaemonize", True): if self.options.donotdaemonize is not True:
# fork() so the parent can exit, returns control to the command line # fork() so the parent can exit, returns control to the command line
# or shell invoking the program. # or shell invoking the program.
if os.fork(): if os.fork():
@ -93,12 +98,12 @@ class Web(UI):
if self.options.pidfile: if self.options.pidfile:
open(self.options.pidfile, "wb").write("%d\n" % os.getpid()) open(self.options.pidfile, "wb").write("%d\n" % os.getpid())
if self.options.ensure_value("group", None): if self.options.group:
if not self.options.group.isdigit(): if not self.options.group.isdigit():
import grp import grp
self.options.group = grp.getgrnam(self.options.group)[2] self.options.group = grp.getgrnam(self.options.group)[2]
os.setuid(self.options.group) os.setuid(self.options.group)
if self.options.ensure_value("user", None): if self.options.user:
if not self.options.user.isdigit(): if not self.options.user.isdigit():
import pwd import pwd
self.options.user = pwd.getpwnam(self.options.user)[2] self.options.user = pwd.getpwnam(self.options.user)[2]
@ -116,8 +121,7 @@ class Web(UI):
if self.options.port: if self.options.port:
self.server.port = self.options.port self.server.port = self.options.port
if self.options.ensure_value("ssl", None): self.server.https = self.options.ssl
self.server.https = self.options.ssl
def run_server(): def run_server():
self.server.install_signal_handlers() self.server.install_signal_handlers()
@ -133,7 +137,7 @@ class Web(UI):
profiler.dump_stats(profile_output) profiler.dump_stats(profile_output)
print("Profile stats saved to %s" % profile_output) print("Profile stats saved to %s" % profile_output)
from twisted.internet import reactor from twisted.internet import reactor # import here because (see top)
reactor.addSystemEventTrigger("before", "shutdown", save_profile_stats) reactor.addSystemEventTrigger("before", "shutdown", save_profile_stats)
print("Running with profiler...") print("Running with profiler...")
profiler.runcall(run_server) profiler.runcall(run_server)