support command line options again

This commit is contained in:
Nick 2011-02-17 16:26:58 +01:00
parent 9a3316f950
commit e1a3a431f0
2 changed files with 307 additions and 4 deletions

View File

@ -0,0 +1,145 @@
#
# commander.py
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
#
from twisted.internet import defer, reactor
import deluge.component as component
from deluge.ui.client import client
from deluge.ui.console import UI_PATH
from colors import strip_colors
import logging
log = logging.getLogger(__name__)
class Commander:
def __init__(self, cmds):
self._commands = cmds
self.console = component.get("ConsoleUI")
def write(self,line):
print(strip_colors(line))
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._commands[cmd].create_parser()
except KeyError:
self.write("{!error!}Unknown command: %s" % cmd)
return
args = self._commands[cmd].split(line)
# Do a little hack here to print 'command --help' properly
parser._print_help = parser.print_help
def print_help(f=None):
if self.interactive:
self.write(parser.format_help())
else:
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._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 Exception, e:
self.write("{!error!}Error parsing options: %s" % e)
return
if not getattr(options, '_exit', False):
try:
ret = self._commands[cmd].handle(*args, **options.__dict__)
except Exception, e:
self.write("{!error!}" + str(e))
log.exception(e)
import traceback
self.write("%s" % traceback.format_exc())
return defer.succeed(True)
else:
return ret
def exec_args(self,args,host,port,username,password):
def on_connect(result):
def on_started(result):
def on_started(result):
deferreds = []
# If we have args, lets process them and quit
# allow multiple commands split by ";"
for arg in args.split(";"):
deferreds.append(defer.maybeDeferred(self.do_command, arg.strip()))
def on_complete(result):
self.do_command("quit")
dl = defer.DeferredList(deferreds).addCallback(on_complete)
# 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(result):
from deluge.ui.client import DelugeRPCError
if isinstance(result.value,DelugeRPCError):
rm = result.value.exception_msg
else:
rm = result.getErrorMessage()
print "Could not connect to: %s:%d\n %s"%(host,port,rm)
self.do_command("quit")
if host:
d = client.connect(host,port,username,password)
else:
d = client.connect()
d.addCallback(on_connect)
d.addErrback(on_connect_fail)

View File

@ -53,6 +53,7 @@ from deluge.ui.console.eventlog import EventLog
#import screen
import colors
from deluge.ui.ui import _UI
from deluge.ui.console import UI_PATH
log = logging.getLogger(__name__)
@ -62,11 +63,62 @@ class Console(_UI):
def __init__(self):
super(Console, self).__init__("console")
group = optparse.OptionGroup(self.parser, "Console Options","These options control how "
"the console connects to the daemon. These options will be "
"used if you pass a command, or if you have autoconnect "
"enabled for the console ui.")
group.add_option("-d","--daemon",dest="daemon_addr",
action="store",type="str",default="127.0.0.1",
help="Set the address of the daemon to connect to."
" [default: %default]")
group.add_option("-p","--port",dest="daemon_port",
help="Set the port to connect to the daemon on. [default: %default]",
action="store",type="int",default=58846)
group.add_option("-u","--username",dest="daemon_user",
help="Set the username to connect to the daemon with. [default: %default]",
action="store",type="string")
group.add_option("-P","--password",dest="daemon_pass",
help="Set the password to connect to the daemon with. [default: %default]",
action="store",type="string")
self.parser.add_option_group(group)
self.cmds = load_commands(os.path.join(UI_PATH, 'commands'))
class CommandOptionGroup(optparse.OptionGroup):
def __init__(self, parser, title, description=None, cmds = None):
optparse.OptionGroup.__init__(self,parser,title,description)
self.cmds = cmds
def format_help(self, formatter):
result = formatter.format_heading(self.title)
formatter.indent()
if self.description:
result += "%s\n"%formatter.format_description(self.description)
for cname in self.cmds:
cmd = self.cmds[cname]
if cmd.interactive_only or cname in cmd.aliases: continue
allnames = [cname]
allnames.extend(cmd.aliases)
cname = "/".join(allnames)
result += formatter.format_heading(" - ".join([cname,cmd.__doc__]))
formatter.indent()
result += "%*s%s\n" % (formatter.current_indent, "", cmd.usage)
formatter.dedent()
formatter.dedent()
return result
cmd_group = CommandOptionGroup(self.parser, "Console Commands",
description="The following commands can be issued at the "
"command line. Commands should be quoted, so, for example, "
"to pause torrent with id 'abc' you would run: '%s "
"\"pause abc\"'"%os.path.basename(sys.argv[0]),
cmds=self.cmds)
self.parser.add_option_group(cmd_group)
def start(self):
super(Console, self).start()
ConsoleUI(self.args)
ConsoleUI(self.args,self.cmds,(self.options.daemon_addr,
self.options.daemon_port,self.options.daemon_user,
self.options.daemon_pass))
def start():
Console().start()
@ -88,8 +140,61 @@ class OptionParser(optparse.OptionParser):
raise Exception(msg)
class BaseCommand(object):
usage = 'usage'
interactive_only = False
option_list = tuple()
aliases = []
def complete(self, text, *args):
return []
def handle(self, *args, **options):
pass
@property
def name(self):
return 'base'
@property
def epilog(self):
return self.__doc__
def split(self, text):
if deluge.common.windows_check():
text = text.replace('\\', '\\\\')
return shlex.split(text)
def create_parser(self):
return OptionParser(prog = self.name,
usage = self.usage,
epilog = self.epilog,
option_list = self.option_list)
def load_commands(command_dir, exclude=[]):
def get_command(name):
return getattr(__import__('deluge.ui.console.commands.%s' % name, {}, {}, ['Command']), 'Command')()
try:
commands = []
for filename in os.listdir(command_dir):
if filename.split('.')[0] in exclude or filename.startswith('_'):
continue
if not (filename.endswith('.py') or filename.endswith('.pyc')):
continue
cmd = get_command(filename.split('.')[len(filename.split('.')) - 2])
aliases = [ filename.split('.')[len(filename.split('.')) - 2] ]
aliases.extend(cmd.aliases)
for a in aliases:
commands.append((a, cmd))
return dict(commands)
except OSError, e:
return {}
class ConsoleUI(component.Component):
def __init__(self, args=None):
def __init__(self, args=None, cmds = None, daemon = None):
component.Component.__init__(self, "ConsoleUI", 2)
# keep track of events for the log view
@ -114,6 +219,18 @@ class ConsoleUI(component.Component):
if args:
args = args[0]
self.interactive = False
if not cmds:
print "Sorry, couldn't find any commands"
return
else:
self._commands = cmds
from commander import Commander
cmdr = Commander(cmds)
if daemon:
cmdr.exec_args(args,*daemon)
else:
cmdr.exec_args(args,None,None,None,None)
self.coreconfig = CoreConfig()
if self.interactive and not deluge.common.windows_check():
@ -155,6 +272,44 @@ class ConsoleUI(component.Component):
reactor.run()
def start(self):
# Maintain a list of (torrent_id, name) for use in tab completion
if not self.interactive:
self.started_deferred = defer.Deferred()
self.torrents = []
def on_session_state(result):
def on_torrents_status(torrents):
for torrent_id, status in torrents.items():
self.torrents.append((torrent_id, status["name"]))
self.started_deferred.callback(True)
client.core.get_torrents_status({"id": result}, ["name"]).addCallback(on_torrents_status)
client.core.get_session_state().addCallback(on_session_state)
def match_torrent(self, string):
"""
Returns a list of torrent_id matches for the string. It will search both
torrent_ids and torrent names, but will only return torrent_ids.
:param string: str, the string to match on
:returns: list of matching torrent_ids. Will return an empty list if
no matches are found.
"""
ret = []
for tid, name in self.torrents:
if tid.startswith(string) or name.startswith(string):
ret.append(tid)
return ret
def set_batch_write(self, batch):
# only kept for legacy reasons, don't actually do anything
pass
def set_mode(self, mode):
reactor.removeReader(self.screen)
self.screen = mode
@ -165,4 +320,7 @@ class ConsoleUI(component.Component):
component.stop()
def write(self, s):
self.events.append(s)
if self.interactive:
self.events.append(s)
else:
print colors.strip_colors(s)