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
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -22,87 +21,108 @@
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
import re, sys
def color(string, fg=None, attrs=[], bg=None, keep_open=False, input=False):
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
import curses
def make_style(*args, **kwargs):
return lambda text: color(text, *args, **kwargs)
colors = [
'COLOR_BLACK',
'COLOR_BLUE',
'COLOR_CYAN',
'COLOR_GREEN',
'COLOR_MAGENTA',
'COLOR_RED',
'COLOR_WHITE',
'COLOR_YELLOW'
]
default_style = {
'black' : make_style(fg='black'),
'red' : make_style(fg='red'),
'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'),
# {(fg, bg): pair_number, ...}
color_pairs = {
("white", "black"): 0 # Special case, can't be changed
}
class Template(str):
regex = re.compile(r'{{\s*(?P<style>.*?)\((?P<arg>.*?)\)\s*}}')
style = default_style
def __new__(self, text):
return str.__new__(self, Template.regex.sub(lambda mo: Template.style[mo.group('style')](mo.group('arg')), text))
# Some default color schemes
schemes = {
"input": ("white", "black"),
"status": ("yellow", "blue", "bold"),
"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):
"""This class is similar to Template, but the escapes are wrapped in \001
and \002 so that readline can properly know the length of each line and
can wrap lines accordingly. Use this class for any colored text which
needs to be used in input prompts, such as in calls to raw_input()."""
input_codes = re.compile('(\x1b\[.*?m)')
def __new__(self, text):
regular_string = InputTemplate.regex.sub(lambda mo: InputTemplate.style[mo.group('style')](mo.group('arg')) , text)
return str.__new__(self, InputTemplate.input_codes.sub(r'\001\1\002', regular_string))
def init_colors():
# Create the color_pairs dict
counter = 1
for fg in colors:
for bg in colors:
if fg == "COLOR_WHITE" and bg == "COLOR_BLACK":
continue
color_pairs[(fg[6:].lower(), bg[6:].lower())] = counter
curses.init_pair(counter, getattr(curses, fg), getattr(curses, bg))
counter += 1
class struct(object):
class BadColorString(Exception):
pass
templates = struct()
templates.prompt = InputTemplate('{{bold_white(%s)}}')
templates.ERROR = Template('{{bold_red( * %s)}}')
templates.SUCCESS = Template('{{bold_green( * %s)}}')
templates.help = Template(' * {{bold_blue(%-*s)}} %s')
templates.info_general = Template('{{bold_blue(*** %s:)}} %s')
templates.info_transfers = Template('{{bold_green(*** %s:)}} %s')
templates.info_network = Template('{{bold_white(*** %s:)}} %s')
templates.info_files_header = Template('{{bold_cyan(*** %s:)}}')
templates.info_peers_header = Template('{{bold_magenta(*** %s:)}}')
templates.info_peers = Template('\t * {{bold_blue(%-22s)}} {{bold_green(%-25s)}} {{bold_cyan(Up: %-12s)}} {{bold_magenta(Down: %-12s)}}')
templates.config_display = Template(' * {{bold_blue(%s)}}: %s')
def parse_color_string(s):
"""
Parses a string and returns a list of 2-tuples (color, string).
:param s:, string to parse
"""
ret = []
# Keep track of where the strings
col_index = 0
while s.find("{{") != -1:
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
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -24,10 +24,11 @@
#
from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping
from deluge.ui.console.colors import templates
from deluge.ui.client import aclient as client
import deluge.ui.console.colors as colors
from deluge.ui.client import client
from optparse import make_option
import os
import base64
class Command(BaseCommand):
"""Add a torrent"""
@ -39,19 +40,18 @@ class Command(BaseCommand):
usage = "Usage: add [-p <save-location>] <torrent-file> [<torrent-file> ...]"
def handle(self, *args, **options):
if options['path'] is None:
def _got_config(configs):
global save_path
save_path = configs['download_location']
client.get_config(_got_config)
client.force_call()
options['path'] = save_path
else:
client.set_config({'download_location': options['path']})
if not options['path']:
print templates.ERROR("There's no save-path specified. You must specify a path to save the downloaded files.")
return
try:
client.add_torrent_file(args)
except Exception, msg:
print templates.ERROR("Error: %s" % str(msg))
t_options = {}
if options["path"]:
t_options["download_location"] = options["path"]
for arg in args:
self.write("{{info}}Attempting to add torrent: %s" % arg)
filename = os.path.split(arg)[-1]
filedump = base64.encodestring(open(arg).read())
def on_success(result):
self.write("{{success}}Torrent added!")
def on_fail(result):
self.write("{{error}}Torrent was not added! %s" % result)
client.core.add_torrent_file(filename, filedump, t_options).addCallback(on_success).addErrback(on_fail)

View File

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

View File

@ -23,14 +23,19 @@
# Boston, MA 02110-1301, USA.
#
from deluge.ui.console.main import BaseCommand
from deluge.ui.console.colors import templates, default_style as style
from deluge.ui.client import aclient as client
import deluge.ui.console.colors as colors
from deluge.ui.client import client
class Command(BaseCommand):
"""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)
if host[:7] != "http://":
host = "http://" + host
client.set_core_uri("%s:%d" % (host, port))
print templates.SUCCESS('connected to %s:%d' % (host, port))
d = client.connect(host, port, username, password)
def on_connect(result):
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.
#
from deluge.ui.console.main import BaseCommand
from deluge.ui.client import aclient as client
from deluge.ui.console.colors import templates, default_style as style
from deluge.ui.client import client
import deluge.ui.console.colors as colors
import logging
class Command(BaseCommand):

View File

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

View File

@ -24,7 +24,8 @@
#
from deluge.ui.console import UI_PATH
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
class Command(BaseCommand):
@ -35,30 +36,32 @@ class Command(BaseCommand):
def __init__(self):
BaseCommand.__init__(self)
# 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
def handle(self, *args, **options):
if args:
if len(args) > 1:
print usage
#print usage
self.write(usage)
return
try:
cmd = self._commands[args[0]]
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
try:
parser = cmd.create_parser()
print parser.format_help()
self.write(parser.format_help())
except AttributeError, e:
print cmd.__doc__ or 'No help for this command'
self.write(cmd.__doc__ or 'No help for this command')
else:
max_length = max( len(k) for k in self._commands)
for cmd in sorted(self._commands):
print templates.help(max_length, cmd, self._commands[cmd].__doc__ or '')
print
print 'for help on a specific command, use "<command> --help"'
self.write("{{info}}" + cmd + "{{input}} - " + self._commands[cmd].__doc__ or '')
self.write("")
self.write('For help on a specific command, use "<command> --help"')
def complete(self, text, *args):
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 import mapping
from deluge.ui.console.colors import templates
from deluge.ui.client import aclient as client
import deluge.ui.console.colors as colors
#from deluge.ui.console.colors import templates
from deluge.ui.client import client
import deluge.common as common
from optparse import make_option
@ -72,15 +73,18 @@ class Command(BaseCommand):
def handle(self, *args, **options):
args = mapping.to_ids(args)
self.torrents = match_torrents(args)
for tor in self.torrents:
self.show_info(tor, options.get('verbose'))
def on_to_ids(result):
def on_match_torrents(torrents):
for torrent in torrents:
self.show_info(torrent, options.get("verbose"))
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) ]
match_torrents(result).addCallback(on_match_torrents)
mapping.to_ids(args).addCallback(on_to_ids)
# 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 _got_torrent_status(state):
@ -124,4 +128,4 @@ class Command(BaseCommand):
print templates.info_peers(str(peer['ip']), unicode(client_str),
str(common.fspeed(peer['up_speed'])), str(common.fspeed(peer['down_speed'])))
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 import mapping
from deluge.ui.client import aclient as client
from deluge.ui.console.colors import templates, default_style as style
from deluge.ui.client import client
import deluge.ui.console.colors as colors
class Command(BaseCommand):
"""Pause a torrent"""

View File

@ -22,7 +22,7 @@
# 51 Franklin Street, Fifth Floor
# 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
class Command(BaseCommand):
@ -31,4 +31,3 @@ class Command(BaseCommand):
def handle(self, *args, **options):
print "Thanks!"
raise StopIteration

View File

@ -24,8 +24,8 @@
#
from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping
from deluge.ui.client import aclient as client
from deluge.ui.console.colors import templates, default_style as style
from deluge.ui.client import client
import deluge.ui.console.colors as colors
class Command(BaseCommand):
"""Resume a torrent"""
@ -49,4 +49,3 @@ class Command(BaseCommand):
torrents = match_torrents()
names = mapping.get_names(torrents)
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 import mapping
from deluge.ui.console.colors import templates
from deluge.ui.client import aclient as client
import deluge.ui.console.colors as colors
from deluge.ui.client import client
from optparse import make_option
import os

View File

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

View File

@ -22,10 +22,11 @@
# 51 Franklin Street, Fifth Floor
# 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
import re
import logging
from twisted.internet import defer
_idregex = re.compile(r'^[0-9a-f]{40}$')
@ -35,40 +36,42 @@ def _arg_is_id(arg):
return bool(_idregex.match(arg))
def get_names(torrents):
global names
names = []
d = defer.Deferred()
def _got_torrents_status(states):
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:
print e
d.errback(e)
client.get_torrents_status(_got_torrents_status, {'id':torrents}, ['name'])
client.force_call()
return names
client.core.get_torrents_status({'id':torrents}, ['name']).addCallback(_got_torrents_status)
return d
def rehash():
global _mapping
torrents = match_torrents()
names = get_names(torrents)
_mapping = dict([(x[1],x[0]) for x in names])
logging.debug('rehashed torrent name->id mapping')
d = defer.Deferred()
def on_match_torrents(torrents):
def on_get_names(names):
_mapping = dict([(x[1],x[0]) for x in names])
d.callback()
get_names(torrents).addCallback(on_get_names)
match_torrents().addCallback(on_match_torrents)
return d
def to_ids(args):
res = []
rehashed = False
for i in args:
if _arg_is_id(i):
res.append(i)
else:
if i in _mapping:
res.append(_mapping[i])
elif not rehashed:
rehash()
d = defer.Deferred()
def on_rehash(result):
res = []
for i in args:
if _arg_is_id(i):
res.append(i)
else:
if i in _mapping:
res.append(_mapping[i])
rehashed = True
return res
d.callback(res)
rehash().addCallback(on_rehash)
return d
def names():
return _mapping.keys()