support command line options again
This commit is contained in:
parent
9a3316f950
commit
e1a3a431f0
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue