mirror of
https://git.deluge-torrent.org/deluge
synced 2025-08-08 09:28:41 +00:00
support command line options again
This commit is contained in:
parent
9a3316f950
commit
e1a3a431f0
2 changed files with 307 additions and 4 deletions
145
deluge/ui/console/commander.py
Normal file
145
deluge/ui/console/commander.py
Normal 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)
|
||||||
|
|
|
@ -53,6 +53,7 @@ from deluge.ui.console.eventlog import EventLog
|
||||||
#import screen
|
#import screen
|
||||||
import colors
|
import colors
|
||||||
from deluge.ui.ui import _UI
|
from deluge.ui.ui import _UI
|
||||||
|
from deluge.ui.console import UI_PATH
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -62,11 +63,62 @@ class Console(_UI):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Console, self).__init__("console")
|
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):
|
def start(self):
|
||||||
super(Console, self).start()
|
super(Console, self).start()
|
||||||
|
ConsoleUI(self.args,self.cmds,(self.options.daemon_addr,
|
||||||
ConsoleUI(self.args)
|
self.options.daemon_port,self.options.daemon_user,
|
||||||
|
self.options.daemon_pass))
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
Console().start()
|
Console().start()
|
||||||
|
@ -88,8 +140,61 @@ class OptionParser(optparse.OptionParser):
|
||||||
raise Exception(msg)
|
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):
|
class ConsoleUI(component.Component):
|
||||||
def __init__(self, args=None):
|
def __init__(self, args=None, cmds = None, daemon = None):
|
||||||
component.Component.__init__(self, "ConsoleUI", 2)
|
component.Component.__init__(self, "ConsoleUI", 2)
|
||||||
|
|
||||||
# keep track of events for the log view
|
# keep track of events for the log view
|
||||||
|
@ -114,6 +219,18 @@ class ConsoleUI(component.Component):
|
||||||
if args:
|
if args:
|
||||||
args = args[0]
|
args = args[0]
|
||||||
self.interactive = False
|
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()
|
self.coreconfig = CoreConfig()
|
||||||
if self.interactive and not deluge.common.windows_check():
|
if self.interactive and not deluge.common.windows_check():
|
||||||
|
@ -155,6 +272,44 @@ class ConsoleUI(component.Component):
|
||||||
reactor.run()
|
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):
|
def set_mode(self, mode):
|
||||||
reactor.removeReader(self.screen)
|
reactor.removeReader(self.screen)
|
||||||
self.screen = mode
|
self.screen = mode
|
||||||
|
@ -165,4 +320,7 @@ class ConsoleUI(component.Component):
|
||||||
component.stop()
|
component.stop()
|
||||||
|
|
||||||
def write(self, s):
|
def write(self, s):
|
||||||
|
if self.interactive:
|
||||||
self.events.append(s)
|
self.events.append(s)
|
||||||
|
else:
|
||||||
|
print colors.strip_colors(s)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue