mirror of
https://github.com/codex-storage/deluge.git
synced 2025-02-17 13:56:47 +00:00
Start work on new Console UI .. Don't expect anything to work, cause most of it doesn't.
This commit is contained in:
parent
a513f9c12a
commit
067f0c3713
@ -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
|
||||||
|
@ -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))
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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."
|
||||||
|
@ -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) ]
|
||||||
|
@ -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)
|
||||||
|
@ -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"""
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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) ]
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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()
|
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user