Fix up some commands

This commit is contained in:
Andrew Resch 2009-04-21 02:43:20 +00:00
parent 9da95552f3
commit c17af41940
13 changed files with 222 additions and 160 deletions

View File

@ -50,6 +50,15 @@ schemes = {
"event": ("magenta", "black", "bold")
}
# Colors for various torrent states
state_color = {
"Seeding": "{{blue,black,bold}}",
"Downloading": "{{green,black,bold}}",
"Paused": "{{white,black}}",
"Checking": "{{green,black}}",
"Queued": "{{yellow,black}}",
"Error": "{{red,black,bold}}"
}
def init_colors():
# Create the color_pairs dict
@ -65,6 +74,19 @@ def init_colors():
class BadColorString(Exception):
pass
def get_line_length(line):
"""
Returns the string length without the color formatting.
"""
if line.count("{{") != line.count ("}}"):
raise BadColorString("Number of {{ does not equal number of }}")
while line.find("{{") != -1:
line = line[:line.find("{{")] + line[line.find("}}") + 2:]
return len(line)
def parse_color_string(s):
"""
Parses a string and returns a list of 2-tuples (color, string).
@ -72,6 +94,9 @@ def parse_color_string(s):
:param s:, string to parse
"""
if s.count("{{") != s.count ("}}"):
raise BadColorString("Number of {{ does not equal number of }}")
ret = []
# Keep track of where the strings
col_index = 0

View File

@ -22,10 +22,11 @@
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping
from deluge.ui.console.main import BaseCommand
import deluge.ui.console.colors as colors
from deluge.ui.client import client
import deluge.component as component
from optparse import make_option
import os
import base64
@ -40,18 +41,20 @@ class Command(BaseCommand):
usage = "Usage: add [-p <save-location>] <torrent-file> [<torrent-file> ...]"
def handle(self, *args, **options):
self.console = component.get("ConsoleUI")
t_options = {}
if options["path"]:
t_options["download_location"] = options["path"]
for arg in args:
self.write("{{info}}Attempting to add torrent: %s" % arg)
self.console.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!")
self.console.write("{{success}}Torrent added!")
def on_fail(result):
self.write("{{error}}Torrent was not added! %s" % result)
self.console.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

@ -1,4 +1,3 @@
#!/usr/bin/env python
#
# connect.py
#
@ -25,17 +24,20 @@
from deluge.ui.console.main import BaseCommand
import deluge.ui.console.colors as colors
from deluge.ui.client import client
import deluge.component as component
class Command(BaseCommand):
"""Connect to a new deluge server."""
def handle(self, host='localhost', port='58846', username="", password="", **options):
def handle(self, host="", port="58846", username="", password="", **options):
self.console = component.get("ConsoleUI")
port = int(port)
d = client.connect(host, port, username, password)
def on_connect(result):
print templates.SUCCESS('Connected to %s:%d!' % (host, port))
self.console.write("{{success}}Connected to %s:%s!" % (host, port))
def on_connect_fail(result):
print templates.ERROR("Failed to connect to %s:%d!" % (host, port))
self.console.write("{{error}}Failed to connect to %s:%s!" % (host, port))
d.addCallback(on_connect)
d.addErrback(on_connect_fail)

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
#
# halt.py
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -22,12 +22,20 @@
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping
from deluge.ui.console.main import BaseCommand
import deluge.ui.console.colors as colors
from deluge.ui.client import client
import deluge.component as component
class Command(BaseCommand):
"Shutdown the deluge server."
def handle(self, **options):
client.daemon.shutdown(None)
self.console = component.get("ConsoleUI")
def on_shutdown(result):
self.write("{{success}}Daemon was shutdown")
def on_shutdown_fail(reason):
self.write("{{error}}Unable to shutdown daemon: %s" % reason)
client.daemon.shutdown().addCallback(on_shutdown).addErrback(on_shutdown_fail)

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
#
# help.py
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -22,46 +22,44 @@
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
from deluge.ui.console import UI_PATH
from deluge.ui.console.main import BaseCommand, load_commands
from deluge.ui.console.main import BaseCommand
import deluge.ui.console.colors as colors
#from deluge.ui.console.colors import templates
import os
import deluge.component as component
class Command(BaseCommand):
"""displays help on other commands"""
usage = "Usage: help [command]"
def __init__(self):
BaseCommand.__init__(self)
# 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'), None, exclude=['help'])
self._commands['help'] = self
#self._commands = load_commands(os.path.join(UI_PATH,'commands'), None, exclude=['help'])
# self._commands['help'] = self
def handle(self, *args, **options):
self.console = component.get("ConsoleUI")
self._commands = self.console._commands
if args:
if len(args) > 1:
#print usage
self.write(usage)
self.console.write(usage)
return
try:
cmd = self._commands[args[0]]
except KeyError:
#print templates.ERROR('unknown command %r' % args[0])
self.write("{{error}}Unknown command %r" % args[0])
self.console.write("{{error}}Unknown command %r" % args[0])
return
try:
parser = cmd.create_parser()
self.write(parser.format_help())
self.console.write(parser.format_help())
except AttributeError, e:
self.write(cmd.__doc__ or 'No help for this command')
self.console.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):
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) ]
self.console.write("{{info}}" + cmd + "{{input}} - " + self._commands[cmd].__doc__ or '')
self.console.write("")
self.console.write('For help on a specific command, use "<command> --help"')

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
#
# info.py
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -23,13 +23,13 @@
# Boston, MA 02110-1301, USA.
#
from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping
from optparse import make_option
from deluge.ui.console.main import BaseCommand
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
import deluge.component as component
status_keys = ["state",
"save_path",
@ -73,25 +73,58 @@ class Command(BaseCommand):
def handle(self, *args, **options):
def on_to_ids(result):
def on_match_torrents(torrents):
for torrent in torrents:
self.show_info(torrent, options.get("verbose"))
self.console = component.get("ConsoleUI")
# Compile a list of torrent_ids to request the status of
torrent_ids = []
for arg in args:
torrent_ids.extend(self.console.match_torrent(arg))
match_torrents(result).addCallback(on_match_torrents)
mapping.to_ids(args).addCallback(on_to_ids)
def on_torrents_status(status):
# Print out the information for each torrent
for key, value in status.items():
self.show_info(key, value, options["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) ]
def on_torrents_status_fail(reason):
self.console.write("{{error}}Error getting torrent info: %s" % reason)
def show_info(self, torrent, verbose):
d = client.core.get_torrents_status({"id": torrent_ids}, status_keys)
d.addCallback(on_torrents_status)
d.addErrback(on_torrents_status_fail)
def show_info(self, torrent_id, status, verbose=False):
"""
Writes out the torrents information to the screen.
:param torrent_id: str, the torrent_id
:param status: dict, the torrents status, this should have the same keys
as status_keys
:param verbose: bool, if true, we print out more information about the
the torrent
"""
self.console.write(" ")
self.console.write("{{info}}Name: {{input}}%s" % (status["name"]))
self.console.write("{{info}}ID: {{input}}%s" % (torrent_id))
s = "{{info}}State: %s%s" % (colors.state_color[status["state"]], status["state"])
# Only show speed if active
if status["state"] in ("Seeding", "Downloading"):
if status["state"] != "Seeding":
s += " {{info}}Down Speed: {{input}}%s" % common.fspeed(status["download_payload_rate"])
s += " {{info}}Up Speed: {{input}}%s" % common.fspeed(status["upload_payload_rate"])
if common.ftime(status["eta"]):
s += " {{info}}ETA: {{input}}%s" % common.ftime(status["eta"])
self.console.write(s)
s = "{{info}}Seeds: {{input}}%s (%s)" % (status["num_seeds"], status["total_seeds"])
s += " {{info}}Peers: {{input}}%s (%s)" % (status["num_peers"], status["total_peers"])
s += " {{info}}Ratio: {{input}}%.3f" % status["ratio"]
s += " {{info}}Availibility: {{input}}%.2f" % status["distributed_copies"]
self.console.write(s)
""" def __show_info(self, torrent, verbose):
def _got_torrent_status(state):
print templates.info_general('ID', torrent)
print templates.info_general('Name', state['name'])
#self._mapping[state['name']] = torrent # update mapping
print templates.info_general('Path', state['save_path'])
if verbose or not state['is_seed']:
print templates.info_transfers("Completed", common.fsize(state['total_done']) + "/" + common.fsize(state['total_size']))
@ -129,3 +162,4 @@ class Command(BaseCommand):
str(common.fspeed(peer['up_speed'])), str(common.fspeed(peer['down_speed'])))
print ""
client.core.get_torrent_status(torrent, status_keys).addCallback(_got_torrent_status)
"""

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
#
# pause.py
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -22,30 +22,27 @@
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping
from deluge.ui.console.main import BaseCommand
from deluge.ui.client import client
import deluge.ui.console.colors as colors
import deluge.component as component
class Command(BaseCommand):
"""Pause a torrent"""
usage = "Usage: pause [ all | <torrent-id> [<torrent-id> ...] ]"
usage = "Usage: pause [ * | <torrent-id> [<torrent-id> ...] ]"
def handle(self, *args, **options):
if len(args) == 0:
print self.usage
return
if len(args) == 1 and args[0] == 'all':
args = tuple() # empty tuple means everything
try:
args = mapping.to_ids(args)
torrents = match_torrents(args)
client.pause_torrent(torrents)
except Exception, msg:
print templates.ERROR(str(msg))
else:
print templates.SUCCESS('torrent%s successfully paused' % ('s' if len(args) > 1 else ''))
self.console = component.get("ConsoleUI")
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) ]
if len(args) == 0:
self.console.write(self.usage)
return
if len(args) > 0 and args[0].lower() == '*':
client.core.pause_all_torrents()
return
torrent_ids = []
for arg in args:
torrent_ids.extend(self.console.match_torrent(arg))
if torrent_ids:
client.core.pause_torrent(torrent_ids)

View File

@ -34,3 +34,5 @@ class Command(BaseCommand):
def on_disconnect(result):
reactor.stop()
client.disconnect().addCallback(on_disconnect)
else:
reactor.stop()

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
#
# resume.py
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -22,30 +22,27 @@
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping
from deluge.ui.console.main import BaseCommand
from deluge.ui.client import client
import deluge.ui.console.colors as colors
import deluge.component as component
class Command(BaseCommand):
"""Resume a torrent"""
usage = "Usage: resume [ all | <torrent-id> [<torrent-id> ...] ]"
usage = "Usage: resume [ * | <torrent-id> [<torrent-id> ...] ]"
def handle(self, *args, **options):
if len(args) == 0:
print self.usage
return
if len(args) == 1 and args[0] == 'all':
args = tuple() # empty tuple means everything
try:
args = mapping.to_ids(args)
torrents = match_torrents(args)
client.resume_torrent(torrents)
except Exception, msg:
print templates.ERROR(str(msg))
else:
print templates.SUCCESS('torrent%s successfully resumed' % ('s' if len(args) > 1 else ''))
self.console = component.get("ConsoleUI")
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) ]
if len(args) == 0:
self.console.write(self.usage)
return
if len(args) > 0 and args[0] == '*':
client.core.resume_all_torrents()
return
torrent_ids = []
for arg in args:
torrent_ids.extend(self.console.match_torrent(arg))
if torrent_ids:
client.core.resume_torrent(torrent_ids)

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
#
# rm.py
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
@ -22,12 +22,13 @@
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
from deluge.ui.console.main import BaseCommand, match_torrents
from deluge.ui.console import mapping
from deluge.ui.console.main import BaseCommand
import deluge.ui.console.colors as colors
from deluge.ui.client import client
import deluge.component as component
from optparse import make_option
import os
class Command(BaseCommand):
"""Remove a torrent"""
@ -40,14 +41,12 @@ class Command(BaseCommand):
)
def handle(self, *args, **options):
try:
args = mapping.to_ids(args)
torrents = match_torrents(args)
client.remove_torrent(torrents, options['remove_data'])
except Exception, msg:
print template.ERROR(str(msg))
self.console = component.get("ConsoleUI")
if len(args) == 0:
self.console.write(usage)
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) ]
torrent_ids = []
for arg in args:
torrent_ids.extend(self.console.match_torrent(arg))
client.core.remove_torrent(torrent_ids, options['remove_data'])

View File

@ -25,6 +25,7 @@
import deluge.component as component
import deluge.common
import colors
from deluge.ui.client import client
from deluge.log import LOG as log
@ -58,16 +59,8 @@ class EventLog(component.Component):
def on_torrent_state_changed_event(self, torrent_id, state):
log.debug("on_torrent_state_changed_event!")
# Modify the state string color
state_color = {
"Seeding": "{{blue,black,bold}}",
"Downloading": "{{green,black,bold}}",
"Paused": "{{white,black}}",
"Checking": "{{green,black}}",
"Queued": "{{yellow,black}}",
"Error": "{{red,black,bold}}"
}
if state in state_color:
state = state_color[state] + state
if state in colors.state_color:
state = colors.state_color[state] + state
self.console.write("{{event}}* TorrentStateChanged: %s {{info}}%s (%s)" %
(state, self.console.get_torrent_name(torrent_id), torrent_id))

View File

@ -25,6 +25,10 @@
import os, sys
import optparse
import shlex
from twisted.internet import defer, reactor
from deluge.ui.console import UI_PATH
import deluge.component as component
from deluge.ui.client import client
@ -32,14 +36,14 @@ import deluge.common
from deluge.ui.coreconfig import CoreConfig
from deluge.ui.console.statusbars import StatusBars
from deluge.ui.console.eventlog import EventLog
from twisted.internet import defer, reactor
import shlex
import screen
import colors
import deluge.ui.console.screen as screen
import deluge.ui.console.colors as colors
from deluge.log import LOG as log
# XXX: Remove when the commands are all fixed up
def match_torrents(a=[]):
pass
class OptionParser(optparse.OptionParser):
"""subclass from optparse.OptionParser so exit() won't exit."""
def exit(self, status=0, msg=None):
@ -62,7 +66,6 @@ class BaseCommand(object):
option_list = tuple()
aliases = []
def complete(self, text, *args):
return []
def handle(self, *args, **options):
@ -85,24 +88,7 @@ class BaseCommand(object):
epilog = self.epilog,
option_list = self.option_list)
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:
d.callback(tors)
d.callback(list(tors.intersection(array)))
client.core.get_session_state().addCallback(_got_session_state)
return d
def load_commands(command_dir, write_func, exclude=[]):
def load_commands(command_dir, exclude=[]):
def get_command(name):
return getattr(__import__('deluge.ui.console.commands.%s' % name, {}, {}, ['Command']), 'Command')()
@ -112,8 +98,6 @@ def load_commands(command_dir, write_func, 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:
@ -126,7 +110,7 @@ class ConsoleUI(component.Component):
def __init__(self, args=None):
component.Component.__init__(self, "ConsoleUI", 2)
# Load all the commands
self._commands = load_commands(os.path.join(UI_PATH, 'commands'), self.write)
self._commands = load_commands(os.path.join(UI_PATH, 'commands'))
# Try to connect to the localhost daemon
def on_connect(result):
@ -236,10 +220,11 @@ class ConsoleUI(component.Component):
if not getattr(options, '_exit', False):
try:
self._commands[cmd].handle(*args, **options.__dict__)
except StopIteration, e:
raise
except Exception, e:
self.write("{{error}}" + str(e))
log.exception(e)
import traceback
self.write("%s" % traceback.format_exc())
def tab_completer(self, line, cursor, second_hit):
"""
@ -330,9 +315,27 @@ class ConsoleUI(component.Component):
return None
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 on_torrent_added_event(self, torrent_id):
def on_torrent_status(status):
self.torrents.append(torrent_id, status["name"])
self.torrents.append((torrent_id, status["name"]))
client.get_torrent_status(torrent_id, ["name"]).addCallback(on_torrent_status)
def on_torrent_removed_event(self, torrent_id):

View File

@ -116,15 +116,6 @@ class Screen(CursesStdIO):
"""
log.debug("adding line: %s", text)
def get_line_length(line):
"""
Returns the string length without the color formatting.
"""
while line.find("{{") != -1:
line = line[:line.find("{{")] + line[line.find("}}") + 2:]
return len(line)
def get_line_chunks(line):
"""
@ -155,7 +146,12 @@ class Screen(CursesStdIO):
for line in text.splitlines():
# We need to check for line lengths here and split as necessary
line_length = get_line_length(line)
try:
line_length = colors.get_line_length(line)
except colors.BadColorString:
log.error("Passed a bad colored string..")
line_length = len(line)
if line_length >= (self.cols - 1):
s = ""
# The length of the text without the color tags
@ -198,7 +194,12 @@ class Screen(CursesStdIO):
"""
col = 0
try:
parsed = colors.parse_color_string(string)
except colors.BadColorString:
log.error("Cannot add bad color string: %s", string)
return
for index, (color, s) in enumerate(parsed):
if index + 1 == len(parsed):
# This is the last string so lets append some " " to it