Start work on new Console UI .. Don't expect anything to work, cause most of it doesn't.

This commit is contained in:
Andrew Resch 2009-04-18 05:35:03 +00:00
parent a513f9c12a
commit 067f0c3713
14 changed files with 299 additions and 273 deletions

View File

@ -1,8 +1,7 @@
#!/usr/bin/env python
# #
# colors.py # colors.py
# #
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -22,87 +21,108 @@
# 51 Franklin Street, Fifth Floor # 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
import re, sys
def color(string, fg=None, attrs=[], bg=None, keep_open=False, input=False): import curses
if isinstance(attrs, basestring):
attrs = [attrs]
attrs = map(str.lower, attrs)
ansi_reset = "\x1b[0m"
if input:
ansi_reset = '\001'+ansi_reset+'\002'
if len(attrs) == 1 and 'reset' in attrs:
return ansi_reset
colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']
attributes = ['reset', 'bright', 'dim', None, 'underscore', 'blink', 'reverse', 'hidden']
_fg = 30 + colors.index(fg.lower()) if fg and fg.lower() in colors else None
_bg = 40 + colors.index(bg.lower()) if bg and bg.lower() in colors else None
_attrs = [ str(attributes.index(a)) for a in attrs if a in attributes]
color_vals = map(str, filter(lambda x: x is not None, [_fg, _bg]))
color_vals.extend(_attrs)
reset_cmd = ansi_reset if not keep_open else ''
color_code = '\x1b['+';'.join(color_vals)+'m'
if input:
color_code = '\001'+color_code+'\002'
return color_code+string+reset_cmd
def make_style(*args, **kwargs): colors = [
return lambda text: color(text, *args, **kwargs) 'COLOR_BLACK',
'COLOR_BLUE',
'COLOR_CYAN',
'COLOR_GREEN',
'COLOR_MAGENTA',
'COLOR_RED',
'COLOR_WHITE',
'COLOR_YELLOW'
]
default_style = { # {(fg, bg): pair_number, ...}
'black' : make_style(fg='black'), color_pairs = {
'red' : make_style(fg='red'), ("white", "black"): 0 # Special case, can't be changed
'green' : make_style(fg='green'),
'yellow' : make_style(fg='yellow'),
'blue' : make_style(fg='blue'),
'magenta' : make_style(fg='magenta'),
'cyan' : make_style(fg='cyan'),
'white' : make_style(fg='white'),
'bold_black' : make_style(fg='black', attrs='bright'),
'bold_red' : make_style(fg='red', attrs='bright'),
'bold_green' : make_style(fg='green', attrs='bright'),
'bold_yellow' : make_style(fg='yellow', attrs='bright'),
'bold_blue' : make_style(fg='blue', attrs='bright'),
'bold_magenta' : make_style(fg='magenta', attrs='bright'),
'bold_cyan' : make_style(fg='cyan', attrs='bright'),
'bold_white' : make_style(fg='white', attrs='bright'),
} }
class Template(str): # Some default color schemes
regex = re.compile(r'{{\s*(?P<style>.*?)\((?P<arg>.*?)\)\s*}}') schemes = {
style = default_style "input": ("white", "black"),
def __new__(self, text): "status": ("yellow", "blue", "bold"),
return str.__new__(self, Template.regex.sub(lambda mo: Template.style[mo.group('style')](mo.group('arg')), text)) "info": ("white", "black", "bold"),
"error": ("red", "black", "bold"),
"success": ("green", "black", "bold")
}
def __call__(self, *args, **kwargs):
if kwargs:
return str(self) % kwargs
else:
return str(self) % args
class InputTemplate(Template): def init_colors():
"""This class is similar to Template, but the escapes are wrapped in \001 # Create the color_pairs dict
and \002 so that readline can properly know the length of each line and counter = 1
can wrap lines accordingly. Use this class for any colored text which for fg in colors:
needs to be used in input prompts, such as in calls to raw_input().""" for bg in colors:
input_codes = re.compile('(\x1b\[.*?m)') if fg == "COLOR_WHITE" and bg == "COLOR_BLACK":
def __new__(self, text): continue
regular_string = InputTemplate.regex.sub(lambda mo: InputTemplate.style[mo.group('style')](mo.group('arg')) , text) color_pairs[(fg[6:].lower(), bg[6:].lower())] = counter
return str.__new__(self, InputTemplate.input_codes.sub(r'\001\1\002', regular_string)) curses.init_pair(counter, getattr(curses, fg), getattr(curses, bg))
counter += 1
class struct(object): class BadColorString(Exception):
pass pass
templates = struct() def parse_color_string(s):
templates.prompt = InputTemplate('{{bold_white(%s)}}') """
templates.ERROR = Template('{{bold_red( * %s)}}') Parses a string and returns a list of 2-tuples (color, string).
templates.SUCCESS = Template('{{bold_green( * %s)}}')
templates.help = Template(' * {{bold_blue(%-*s)}} %s') :param s:, string to parse
templates.info_general = Template('{{bold_blue(*** %s:)}} %s')
templates.info_transfers = Template('{{bold_green(*** %s:)}} %s') """
templates.info_network = Template('{{bold_white(*** %s:)}} %s') ret = []
templates.info_files_header = Template('{{bold_cyan(*** %s:)}}') # Keep track of where the strings
templates.info_peers_header = Template('{{bold_magenta(*** %s:)}}') col_index = 0
templates.info_peers = Template('\t * {{bold_blue(%-22s)}} {{bold_green(%-25s)}} {{bold_cyan(Up: %-12s)}} {{bold_magenta(Down: %-12s)}}') while s.find("{{") != -1:
templates.config_display = Template(' * {{bold_blue(%s)}}: %s') begin = s.find("{{")
end = s.find("}}")
if end == -1:
raise BadColorString("Missing closing '}}'")
# Get a list of attributes in the bracketed section
attrs = s[begin+2:end].split(",")
if len(attrs) == 1 and not attrs:
raise BadColorString("No description in {{ }}")
def apply_attrs(cp, a):
# This function applies any additional attributes as necessary
if len(a) > 2:
for attr in a[2:]:
cp |= getattr(curses, "A_" + attr.upper())
return cp
# Check for a builtin type first
if attrs[0] in schemes:
# Get the color pair number
color_pair = curses.color_pair(color_pairs[(schemes[attrs[0]][0], schemes[attrs[0]][1])])
color_pair = apply_attrs(color_pair, schemes[attrs[0]])
else:
# This is a custom color scheme
fg = attrs[0]
if len(attrs) > 1:
bg = attrs[1]
else:
# Default to 'black' if no bg is chosen
bg = "black"
color_pair = curses.color_pair(color_pairs[(fg, bg)])
# Check for additional attributes and OR them to the color_pair
color_pair = apply_attrs(color_pair, attrs)
# We need to find the text now, so lets try to find another {{ and if
# there isn't one, then it's the rest of the string
next_begin = s.find("{{", end)
if next_begin == -1:
ret.append((color_pair, s[end+2:]))
break
else:
ret.append((color_pair, s[end+2:next_begin]))
s = s[next_begin:]
if not ret:
# There was no color scheme so we add it with a 0 for white on black
ret = [(0, s)]
return ret

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
# #
# add.py # add.py
# #
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -24,10 +24,11 @@
# #
from deluge.ui.console.main import BaseCommand, match_torrents from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping from deluge.ui.console import mapping
from deluge.ui.console.colors import templates import deluge.ui.console.colors as colors
from deluge.ui.client import aclient as client from deluge.ui.client import client
from optparse import make_option from optparse import make_option
import os import os
import base64
class Command(BaseCommand): class Command(BaseCommand):
"""Add a torrent""" """Add a torrent"""
@ -39,19 +40,18 @@ class Command(BaseCommand):
usage = "Usage: add [-p <save-location>] <torrent-file> [<torrent-file> ...]" usage = "Usage: add [-p <save-location>] <torrent-file> [<torrent-file> ...]"
def handle(self, *args, **options): def handle(self, *args, **options):
if options['path'] is None: t_options = {}
def _got_config(configs): if options["path"]:
global save_path t_options["download_location"] = options["path"]
save_path = configs['download_location']
client.get_config(_got_config) for arg in args:
client.force_call() self.write("{{info}}Attempting to add torrent: %s" % arg)
options['path'] = save_path filename = os.path.split(arg)[-1]
else: filedump = base64.encodestring(open(arg).read())
client.set_config({'download_location': options['path']})
if not options['path']: def on_success(result):
print templates.ERROR("There's no save-path specified. You must specify a path to save the downloaded files.") self.write("{{success}}Torrent added!")
return def on_fail(result):
try: self.write("{{error}}Torrent was not added! %s" % result)
client.add_torrent_file(args)
except Exception, msg: client.core.add_torrent_file(filename, filedump, t_options).addCallback(on_success).addErrback(on_fail)
print templates.ERROR("Error: %s" % str(msg))

View File

@ -24,8 +24,8 @@
# #
from deluge.ui.console.main import BaseCommand, match_torrents from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console.colors import templates, default_style as style import deluge.ui.console.colors as colors
from deluge.ui.client import aclient as client from deluge.ui.client import client
from optparse import make_option from optparse import make_option
import re import re
@ -136,4 +136,3 @@ class Command(BaseCommand):
def split(self, text): def split(self, text):
return str.split(text) return str.split(text)

View File

@ -23,14 +23,19 @@
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
from deluge.ui.console.colors import templates, default_style as style import deluge.ui.console.colors as colors
from deluge.ui.client import aclient as client from deluge.ui.client import client
class Command(BaseCommand): class Command(BaseCommand):
"""Connect to a new deluge server.""" """Connect to a new deluge server."""
def handle(self, host='localhost', port='58846', **options): def handle(self, host='localhost', port='58846', username="", password="", **options):
port = int(port) port = int(port)
if host[:7] != "http://": d = client.connect(host, port, username, password)
host = "http://" + host def on_connect(result):
client.set_core_uri("%s:%d" % (host, port)) print templates.SUCCESS('Connected to %s:%d!' % (host, port))
print templates.SUCCESS('connected to %s:%d' % (host, port))
def on_connect_fail(result):
print templates.ERROR("Failed to connect to %s:%d!" % (host, port))
d.addCallback(on_connect)
d.addErrback(on_connect_fail)

View File

@ -23,8 +23,8 @@
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
from deluge.ui.client import aclient as client from deluge.ui.client import client
from deluge.ui.console.colors import templates, default_style as style import deluge.ui.console.colors as colors
import logging import logging
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -24,8 +24,8 @@
# #
from deluge.ui.console.main import BaseCommand, match_torrents from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping from deluge.ui.console import mapping
from deluge.ui.console.colors import templates import deluge.ui.console.colors as colors
from deluge.ui.client import aclient as client from deluge.ui.client import client
class Command(BaseCommand): class Command(BaseCommand):
"Shutdown the deluge server." "Shutdown the deluge server."

View File

@ -24,7 +24,8 @@
# #
from deluge.ui.console import UI_PATH from deluge.ui.console import UI_PATH
from deluge.ui.console.main import BaseCommand, load_commands from deluge.ui.console.main import BaseCommand, load_commands
from deluge.ui.console.colors import templates import deluge.ui.console.colors as colors
#from deluge.ui.console.colors import templates
import os import os
class Command(BaseCommand): class Command(BaseCommand):
@ -35,30 +36,32 @@ class Command(BaseCommand):
def __init__(self): def __init__(self):
BaseCommand.__init__(self) BaseCommand.__init__(self)
# get a list of commands, exclude 'help' so we won't run into a recursive loop. # get a list of commands, exclude 'help' so we won't run into a recursive loop.
self._commands = load_commands(os.path.join(UI_PATH,'commands'), exclude=['help']) self._commands = load_commands(os.path.join(UI_PATH,'commands'), None, exclude=['help'])
self._commands['help'] = self self._commands['help'] = self
def handle(self, *args, **options): def handle(self, *args, **options):
if args: if args:
if len(args) > 1: if len(args) > 1:
print usage #print usage
self.write(usage)
return return
try: try:
cmd = self._commands[args[0]] cmd = self._commands[args[0]]
except KeyError: except KeyError:
print templates.ERROR('unknown command %r' % args[0]) #print templates.ERROR('unknown command %r' % args[0])
self.write("{{error}}Unknown command %r" % args[0])
return return
try: try:
parser = cmd.create_parser() parser = cmd.create_parser()
print parser.format_help() self.write(parser.format_help())
except AttributeError, e: except AttributeError, e:
print cmd.__doc__ or 'No help for this command' self.write(cmd.__doc__ or 'No help for this command')
else: else:
max_length = max( len(k) for k in self._commands) max_length = max( len(k) for k in self._commands)
for cmd in sorted(self._commands): for cmd in sorted(self._commands):
print templates.help(max_length, cmd, self._commands[cmd].__doc__ or '') self.write("{{info}}" + cmd + "{{input}} - " + self._commands[cmd].__doc__ or '')
print self.write("")
print 'for help on a specific command, use "<command> --help"' self.write('For help on a specific command, use "<command> --help"')
def complete(self, text, *args): def complete(self, text, *args):
return [ x for x in self._commands.keys() if x.startswith(text) ] return [ x for x in self._commands.keys() if x.startswith(text) ]

View File

@ -25,8 +25,9 @@
from deluge.ui.console.main import BaseCommand, match_torrents from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping from deluge.ui.console import mapping
from deluge.ui.console.colors import templates import deluge.ui.console.colors as colors
from deluge.ui.client import aclient as client #from deluge.ui.console.colors import templates
from deluge.ui.client import client
import deluge.common as common import deluge.common as common
from optparse import make_option from optparse import make_option
@ -72,15 +73,18 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
args = mapping.to_ids(args) def on_to_ids(result):
self.torrents = match_torrents(args) def on_match_torrents(torrents):
for tor in self.torrents: for torrent in torrents:
self.show_info(tor, options.get('verbose')) self.show_info(torrent, options.get("verbose"))
def complete(self, text, *args): match_torrents(result).addCallback(on_match_torrents)
torrents = match_torrents() mapping.to_ids(args).addCallback(on_to_ids)
names = mapping.get_names(torrents)
return [ x[1] for x in names if x[1].startswith(text) ] # def complete(self, text, *args):
# torrents = match_torrents()
# names = mapping.get_names(torrents)
# return [ x[1] for x in names if x[1].startswith(text) ]
def show_info(self, torrent, verbose): def show_info(self, torrent, verbose):
def _got_torrent_status(state): def _got_torrent_status(state):
@ -124,4 +128,4 @@ class Command(BaseCommand):
print templates.info_peers(str(peer['ip']), unicode(client_str), print templates.info_peers(str(peer['ip']), unicode(client_str),
str(common.fspeed(peer['up_speed'])), str(common.fspeed(peer['down_speed']))) str(common.fspeed(peer['up_speed'])), str(common.fspeed(peer['down_speed'])))
print "" print ""
client.get_torrent_status(_got_torrent_status, torrent, status_keys) client.core.get_torrent_status(torrent, status_keys).addCallback(_got_torrent_status)

View File

@ -24,8 +24,8 @@
# #
from deluge.ui.console.main import BaseCommand, match_torrents from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping from deluge.ui.console import mapping
from deluge.ui.client import aclient as client from deluge.ui.client import client
from deluge.ui.console.colors import templates, default_style as style import deluge.ui.console.colors as colors
class Command(BaseCommand): class Command(BaseCommand):
"""Pause a torrent""" """Pause a torrent"""

View File

@ -22,7 +22,7 @@
# 51 Franklin Street, Fifth Floor # 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
from deluge.ui.client import aclient as client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
@ -31,4 +31,3 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
print "Thanks!" print "Thanks!"
raise StopIteration raise StopIteration

View File

@ -24,8 +24,8 @@
# #
from deluge.ui.console.main import BaseCommand, match_torrents from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping from deluge.ui.console import mapping
from deluge.ui.client import aclient as client from deluge.ui.client import client
from deluge.ui.console.colors import templates, default_style as style import deluge.ui.console.colors as colors
class Command(BaseCommand): class Command(BaseCommand):
"""Resume a torrent""" """Resume a torrent"""
@ -49,4 +49,3 @@ class Command(BaseCommand):
torrents = match_torrents() torrents = match_torrents()
names = mapping.get_names(torrents) names = mapping.get_names(torrents)
return [ x[1] for x in names if x[1].startswith(text) ] return [ x[1] for x in names if x[1].startswith(text) ]

View File

@ -24,8 +24,8 @@
# #
from deluge.ui.console.main import BaseCommand, match_torrents from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping from deluge.ui.console import mapping
from deluge.ui.console.colors import templates import deluge.ui.console.colors as colors
from deluge.ui.client import aclient as client from deluge.ui.client import client
from optparse import make_option from optparse import make_option
import os import os

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
# #
# main.py # main.py
# #
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -22,16 +22,23 @@
# 51 Franklin Street, Fifth Floor # 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
import logging
logging.disable(logging.ERROR)
import os, sys import os, sys
import optparse import optparse
from deluge.ui.console import UI_PATH from deluge.ui.console import UI_PATH
from deluge.ui.console.colors import Template, make_style, templates, default_style as style #from deluge.ui.console.colors import Template, make_style, templates, default_style as style
from deluge.ui.client import aclient as client import deluge.component as component
from deluge.ui.common import get_localhost_auth_uri from deluge.ui.client import client
import shlex import deluge.common
from deluge.ui.coreconfig import CoreConfig
from deluge.ui.console.statusbars import StatusBars
from twisted.internet import defer, reactor
import shlex
import screen
import colors
from deluge.log import LOG as log
class OptionParser(optparse.OptionParser): class OptionParser(optparse.OptionParser):
"""subclass from optparse.OptionParser so exit() won't exit.""" """subclass from optparse.OptionParser so exit() won't exit."""
@ -49,7 +56,6 @@ class OptionParser(optparse.OptionParser):
""" """
raise raise
class BaseCommand(object): class BaseCommand(object):
usage = 'usage' usage = 'usage'
@ -79,24 +85,24 @@ class BaseCommand(object):
epilog = self.epilog, epilog = self.epilog,
option_list = self.option_list) option_list = self.option_list)
def match_torrents(array=None):
global torrents def match_torrents(array=[]):
if array is None: # Make sure we don't have any duplicates
array = list()
torrents = []
array = set(array) array = set(array)
# We return this defer and it will be fired once we received the session
# state and intersect the data.
d = defer.Deferred()
def _got_session_state(tors): def _got_session_state(tors):
if not array: if not array:
torrents.extend(tors) d.callback(tors)
return d.callback(list(tors.intersection(array)))
tors = set(tors)
torrents.extend(list(tors.intersection(array)))
return
client.get_session_state(_got_session_state)
client.force_call()
return torrents
def load_commands(command_dir, exclude=[]): client.core.get_session_state().addCallback(_got_session_state)
return d
def load_commands(command_dir, write_func, exclude=[]):
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')()
@ -106,6 +112,8 @@ def load_commands(command_dir, exclude=[]):
if filename.split('.')[0] in exclude or filename.startswith('_') or not filename.endswith('.py'): if filename.split('.')[0] in exclude or filename.startswith('_') or not filename.endswith('.py'):
continue continue
cmd = get_command(filename[:-3]) cmd = get_command(filename[:-3])
# Hack to give the commands a write function
cmd.write = write_func
aliases = [ filename[:-3] ] aliases = [ filename[:-3] ]
aliases.extend(cmd.aliases) aliases.extend(cmd.aliases)
for a in aliases: for a in aliases:
@ -114,80 +122,103 @@ def load_commands(command_dir, exclude=[]):
except OSError, e: except OSError, e:
return {} return {}
class ConsoleUI(object): class ConsoleUI(component.Component):
prompt = '>>> '
def __init__(self, args=None): def __init__(self, args=None):
client.set_core_uri(get_localhost_auth_uri("http://localhost:58846")) component.Component.__init__(self, "ConsoleUI", 2)
self._commands = load_commands(os.path.join(UI_PATH, 'commands')) # Load all the commands
self._commands = load_commands(os.path.join(UI_PATH, 'commands'), self.write)
# Try to connect to the localhost daemon
def on_connect(result):
component.start()
client.connect().addCallback(on_connect)
# Set the interactive flag to indicate where we should print the output
self.interactive = True
if args: if args:
self.precmd() self.interactive = False
# If we have args, lets process them and quit
#allow multiple commands split by ";" #allow multiple commands split by ";"
for arg in args.split(";"): for arg in args.split(";"):
self.onecmd(arg) self.do_command(arg)
self.postcmd()
sys.exit(0) sys.exit(0)
def completedefault(self, *ignored): self.coreconfig = CoreConfig()
"""Method called to complete an input line when no command-specific
method is available.
By default, it returns an empty list. # We use the curses.wrapper function to prevent the console from getting
# messed up if an uncaught exception is experienced.
import curses.wrapper
curses.wrapper(self.run)
def run(self, stdscr):
"""
This method is called by the curses.wrapper to start the mainloop and
screen.
:param stdscr: curses screen passed in from curses.wrapper
""" """
return [] # We want to do an interactive session, so start up the curses screen and
# pass it the function that handles commands
colors.init_colors()
self.screen = screen.Screen(stdscr, self.do_command)
self.statusbars = StatusBars()
def completenames(self, text, *ignored): self.screen.topbar = "{{status}}Deluge " + deluge.common.get_version() + " Console"
return [n for n in self._commands.keys() if n.startswith(text)] self.screen.bottombar = "{{status}}"
self.screen.refresh()
def complete(self, text, state): # The Screen object is designed to run as a twisted reader so that it
"""Return the next possible completion for 'text'. # can use twisted's select poll for non-blocking user input.
If a command has not been entered, then complete against command list. reactor.addReader(self.screen)
Otherwise try to call complete_<command> to get list of completions.
# Start the twisted mainloop
reactor.run()
def start(self):
pass
def update(self):
pass
def write(self, line):
""" """
if state == 0: Writes a line out depending on if we're in interactive mode or not.
import readline
origline = readline.get_line_buffer() :param line: str, the line to print
line = origline.lstrip()
stripped = len(origline) - len(line) """
begidx = readline.get_begidx() - stripped if self.interactive:
endidx = readline.get_endidx() - stripped self.screen.add_line(line)
if begidx>0:
cmd = line.split()[0]
if cmd == '':
compfunc = self.completedefault
else: else:
try: print(line)
compfunc = getattr(self._commands[cmd], 'complete')
except AttributeError:
compfunc = self.completedefault
else:
compfunc = self.completenames
self.completion_matches = compfunc(text, line, begidx, endidx)
try:
return self.completion_matches[state]
except IndexError:
return None
def preloop(self): def do_command(self, cmd):
pass """
Processes a command.
def postloop(self): :param cmd: str, the command string
pass
def precmd(self): """
pass if not cmd:
def onecmd(self, line):
if not line:
return return
cmd, _, line = line.partition(' ') cmd, _, line = cmd.partition(' ')
try: try:
parser = self._commands[cmd].create_parser() parser = self._commands[cmd].create_parser()
except KeyError: except KeyError:
print templates.ERROR('unknown command: %s' % cmd) self.write("{{error}}Unknown command: %s" % cmd)
return return
args = self._commands[cmd].split(line) 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
options, args = parser.parse_args(args) options, args = parser.parse_args(args)
if not getattr(options, '_exit', False): if not getattr(options, '_exit', False):
try: try:
@ -195,41 +226,4 @@ class ConsoleUI(object):
except StopIteration, e: except StopIteration, e:
raise raise
except Exception, e: except Exception, e:
print templates.ERROR(str(e)) self.write("{{error}}" + str(e))
def postcmd(self):
client.force_call()
def cmdloop(self):
self.preloop()
try:
import readline
self.old_completer = readline.get_completer()
readline.set_completer(self.complete)
readline.parse_and_bind("tab: complete")
except ImportError:
pass
while True:
try:
line = raw_input(templates.prompt(self.prompt)).strip()
except EOFError:
break
except Exception, e:
print e
continue
try:
self.precmd()
self.onecmd(line)
self.postcmd()
except StopIteration:
break
self.postloop()
print
run = cmdloop
if __name__ == '__main__':
ui = ConsoleUI()
ui.precmd()
ui.onecmd(' '.join(sys.argv[1:]))
ui.postcmd()

View File

@ -22,10 +22,11 @@
# 51 Franklin Street, Fifth Floor # 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
from deluge.ui.client import aclient as client from deluge.ui.client import client
from deluge.ui.console.main import match_torrents from deluge.ui.console.main import match_torrents
import re import re
import logging import logging
from twisted.internet import defer
_idregex = re.compile(r'^[0-9a-f]{40}$') _idregex = re.compile(r'^[0-9a-f]{40}$')
@ -35,40 +36,42 @@ def _arg_is_id(arg):
return bool(_idregex.match(arg)) return bool(_idregex.match(arg))
def get_names(torrents): def get_names(torrents):
global names d = defer.Deferred()
names = []
def _got_torrents_status(states): def _got_torrents_status(states):
try: try:
names.extend(list([ (tid, state['name']) for (tid, state) in states.items() ])) d.callback(list([ (tid, state['name']) for (tid, state) in states.items() ]))
except Exception, e: except Exception, e:
print e print e
d.errback(e)
client.get_torrents_status(_got_torrents_status, {'id':torrents}, ['name']) client.core.get_torrents_status({'id':torrents}, ['name']).addCallback(_got_torrents_status)
client.force_call() return d
return names
def rehash(): def rehash():
global _mapping global _mapping
torrents = match_torrents() d = defer.Deferred()
names = get_names(torrents) def on_match_torrents(torrents):
def on_get_names(names):
_mapping = dict([(x[1],x[0]) for x in names]) _mapping = dict([(x[1],x[0]) for x in names])
logging.debug('rehashed torrent name->id mapping') d.callback()
get_names(torrents).addCallback(on_get_names)
match_torrents().addCallback(on_match_torrents)
return d
def to_ids(args): def to_ids(args):
d = defer.Deferred()
def on_rehash(result):
res = [] res = []
rehashed = False
for i in args: for i in args:
if _arg_is_id(i): if _arg_is_id(i):
res.append(i) res.append(i)
else: else:
if i in _mapping: if i in _mapping:
res.append(_mapping[i]) res.append(_mapping[i])
elif not rehashed: d.callback(res)
rehash() rehash().addCallback(on_rehash)
if i in _mapping:
res.append(_mapping[i]) return d
rehashed = True
return res
def names(): def names():
return _mapping.keys() return _mapping.keys()