adding null2 ui alongside the current null ui
This commit is contained in:
parent
71bc34be0d
commit
868612413d
|
@ -0,0 +1 @@
|
||||||
|
UI_PATH = __path__[0]
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import re, sys
|
||||||
|
|
||||||
|
def color(string, fg=None, attrs=[], bg=None, keep_open=False):
|
||||||
|
if isinstance(attrs, basestring):
|
||||||
|
attrs = [attrs]
|
||||||
|
attrs = map(str.lower, attrs)
|
||||||
|
ansi_reset = "\x1b[0m"
|
||||||
|
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 ''
|
||||||
|
return "\x1b["+";".join(color_vals)+"m"+string+reset_cmd
|
||||||
|
|
||||||
|
def make_style(*args, **kwargs):
|
||||||
|
return lambda text: color(text, *args, **kwargs)
|
||||||
|
|
||||||
|
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'),
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
if kwargs:
|
||||||
|
return str(self) % kwargs
|
||||||
|
else:
|
||||||
|
return str(self) % args
|
||||||
|
|
||||||
|
class struct(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
templates = struct()
|
||||||
|
templates.prompt = Template('{{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')
|
|
@ -0,0 +1,34 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from deluge.ui.null2.main import BaseCommand, match_torrents
|
||||||
|
from deluge.ui.null2 import mapping
|
||||||
|
from deluge.ui.null2.colors import templates
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
from optparse import make_option
|
||||||
|
import os
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Add a torrent"""
|
||||||
|
option_list = BaseCommand.option_list + (
|
||||||
|
make_option('-p', '--path', dest='path',
|
||||||
|
help='save path for torrent'),
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
|
@ -0,0 +1,116 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from deluge.ui.null2.main import BaseCommand, match_torrents
|
||||||
|
from deluge.ui.null2.colors import templates, default_style as style
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
from optparse import make_option
|
||||||
|
import re
|
||||||
|
|
||||||
|
import cStringIO, tokenize
|
||||||
|
|
||||||
|
def atom(next, token):
|
||||||
|
"""taken with slight modifications from http://effbot.org/zone/simple-iterator-parser.htm"""
|
||||||
|
if token[1] == "(":
|
||||||
|
out = []
|
||||||
|
token = next()
|
||||||
|
while token[1] != ")":
|
||||||
|
out.append(atom(next, token))
|
||||||
|
token = next()
|
||||||
|
if token[1] == ",":
|
||||||
|
token = next()
|
||||||
|
return tuple(out)
|
||||||
|
elif token[0] is tokenize.STRING:
|
||||||
|
return token[1][1:-1].decode("string-escape")
|
||||||
|
elif token[0] is tokenize.NUMBER:
|
||||||
|
try:
|
||||||
|
return int(token[1], 0)
|
||||||
|
except ValueError:
|
||||||
|
return float(token[1])
|
||||||
|
elif token[1].lower() == 'true':
|
||||||
|
return True
|
||||||
|
elif token[1].lower() == 'false':
|
||||||
|
return False
|
||||||
|
raise SyntaxError("malformed expression (%s)" % token[1])
|
||||||
|
|
||||||
|
def simple_eval(source):
|
||||||
|
""" evaluates the 'source' string into a combination of primitive python objects
|
||||||
|
taken from http://effbot.org/zone/simple-iterator-parser.htm"""
|
||||||
|
src = cStringIO.StringIO(source).readline
|
||||||
|
src = tokenize.generate_tokens(src)
|
||||||
|
src = (token for token in src if token[0] is not tokenize.NL)
|
||||||
|
res = atom(src.next, src.next())
|
||||||
|
if src.next()[0] is not tokenize.ENDMARKER:
|
||||||
|
raise SyntaxError("bogus data after expression")
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Show configuration values"""
|
||||||
|
|
||||||
|
option_list = BaseCommand.option_list + (
|
||||||
|
make_option('-s', '--set', action='store_true', default=False, dest='set',
|
||||||
|
help='set value for key'),
|
||||||
|
)
|
||||||
|
usage = "Usage: config [key1 [key2 ...]]\n"\
|
||||||
|
" config --set key value"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
if options['set']:
|
||||||
|
self._set_config(*args, **options)
|
||||||
|
else:
|
||||||
|
self._get_config(*args, **options)
|
||||||
|
|
||||||
|
def _get_config(self, *args, **options):
|
||||||
|
def _on_get_config(config):
|
||||||
|
keys = config.keys()
|
||||||
|
keys.sort()
|
||||||
|
for key in keys:
|
||||||
|
if args and key not in args:
|
||||||
|
continue
|
||||||
|
color = 'white'
|
||||||
|
value = config[key]
|
||||||
|
if isinstance(value, bool):
|
||||||
|
color = 'yellow'
|
||||||
|
elif isinstance(value, int) or isinstance(value, float):
|
||||||
|
color = 'green'
|
||||||
|
elif isinstance(value, str):
|
||||||
|
color = 'cyan'
|
||||||
|
elif isinstance(value, list):
|
||||||
|
color = 'magenta'
|
||||||
|
|
||||||
|
print templates.config_display(key, style[color](str(value)))
|
||||||
|
client.get_config(_on_get_config)
|
||||||
|
|
||||||
|
def _set_config(self, *args, **options):
|
||||||
|
def _got_config_value(config_val):
|
||||||
|
global c_val
|
||||||
|
c_val = config_val
|
||||||
|
key = args[0]
|
||||||
|
try:
|
||||||
|
val = simple_eval(' '.join(args[1:]))
|
||||||
|
except SyntaxError,e:
|
||||||
|
print templates.ERROR(str(e))
|
||||||
|
return
|
||||||
|
client.get_config_value(_got_config_value, key)
|
||||||
|
client.force_call()
|
||||||
|
if c_val is None:
|
||||||
|
print templates.ERROR("Invalid configuration name '%s'" % key)
|
||||||
|
return
|
||||||
|
if type(c_val) != type(val):
|
||||||
|
print templates.ERROR("Configuration value provided has incorrect type.")
|
||||||
|
return
|
||||||
|
client.set_config({key: val})
|
||||||
|
client.force_call()
|
||||||
|
print templates.SUCCESS("Configuration value successfully updated.")
|
||||||
|
|
||||||
|
def complete(self, text, *args):
|
||||||
|
keys = []
|
||||||
|
def _on_get_config(config):
|
||||||
|
keys.extend(config.keys())
|
||||||
|
client.get_config(_on_get_config)
|
||||||
|
client.force_call()
|
||||||
|
return [ k for k in keys if k.startswith(text) ]
|
||||||
|
|
||||||
|
def split(self, text):
|
||||||
|
return str.split(text)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from deluge.ui.null2.main import BaseCommand
|
||||||
|
from deluge.ui.null2.colors import templates, default_style as style
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Connect to a new deluge server."""
|
||||||
|
def handle(self, host='localhost', port='58846', **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))
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from deluge.ui.null2.main import BaseCommand
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
from deluge.ui.null2.colors import templates, default_style as style
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Enable and disable debugging"""
|
||||||
|
usage = 'debug [on|off]'
|
||||||
|
def handle(self, state='', **options):
|
||||||
|
if state == 'on':
|
||||||
|
logging.disable(logging.DEBUG)
|
||||||
|
elif state == 'off':
|
||||||
|
logging.disable(logging.ERROR)
|
||||||
|
else:
|
||||||
|
print templates.ERROR(self.usage)
|
||||||
|
|
||||||
|
def complete(self, text, *args):
|
||||||
|
return [ x for x in ['on', 'off'] if x.startswith(text) ]
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from deluge.ui.null2.main import BaseCommand, match_torrents
|
||||||
|
from deluge.ui.null2 import mapping
|
||||||
|
from deluge.ui.null2.colors import templates
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"Shutdown the deluge server."
|
||||||
|
def handle(self, **options):
|
||||||
|
client.shutdown()
|
|
@ -0,0 +1,104 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from deluge.ui.null2.main import BaseCommand, match_torrents
|
||||||
|
from deluge.ui.null2 import mapping
|
||||||
|
from deluge.ui.null2.colors import templates
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
import deluge.common as common
|
||||||
|
from optparse import make_option
|
||||||
|
|
||||||
|
status_keys = ["state",
|
||||||
|
"save_path",
|
||||||
|
"tracker",
|
||||||
|
"next_announce",
|
||||||
|
"name",
|
||||||
|
"total_size",
|
||||||
|
"progress",
|
||||||
|
"num_seeds",
|
||||||
|
"total_seeds",
|
||||||
|
"num_peers",
|
||||||
|
"total_peers",
|
||||||
|
"eta",
|
||||||
|
"download_payload_rate",
|
||||||
|
"upload_payload_rate",
|
||||||
|
"ratio",
|
||||||
|
"distributed_copies",
|
||||||
|
"num_pieces",
|
||||||
|
"piece_length",
|
||||||
|
"total_done",
|
||||||
|
"files",
|
||||||
|
"file_priorities",
|
||||||
|
"file_progress",
|
||||||
|
"peers",
|
||||||
|
"is_seed",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Show information about the torrents"""
|
||||||
|
|
||||||
|
option_list = BaseCommand.option_list + (
|
||||||
|
make_option('-v', '--verbose', action='store_true', default=False, dest='verbose',
|
||||||
|
help='shows more information per torrent'),
|
||||||
|
make_option('-i', '--id', action='store_true', default=False, dest='tid',
|
||||||
|
help='use internal id instead of torrent name'),
|
||||||
|
)
|
||||||
|
|
||||||
|
usage = "Usage: info [<torrent-id> [<torrent-id> ...]]\n"\
|
||||||
|
" You can give the first few characters of a torrent-id to identify the torrent."
|
||||||
|
|
||||||
|
|
||||||
|
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 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):
|
||||||
|
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 not state['is_seed']:
|
||||||
|
print templates.info_transfers("Completed", common.fsize(state['total_done']) + "/" + common.fsize(state['total_size']))
|
||||||
|
print templates.info_transfers("Status", state['state'])
|
||||||
|
|
||||||
|
if state['state'] == 'Downloading':
|
||||||
|
print templates.info_transfers("Download Speed", common.fspeed(state['download_payload_rate']))
|
||||||
|
if state['state'] in ('Downloading', 'Seeding'):
|
||||||
|
print templates.info_transfers("Upload Speed", common.fspeed(state['upload_payload_rate']))
|
||||||
|
if state['state'] == ('Downloading'):
|
||||||
|
print templates.info_transfers("ETA", common.ftime(state['eta']))
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print templates.info_network("Seeders", "%s (%s)" % (state['num_seeds'], state['total_seeds']))
|
||||||
|
print templates.info_network("Peers", "%s (%s)" % (state['num_peers'], state['total_peers']))
|
||||||
|
print templates.info_network("Share Ratio", "%.1f" % state['ratio'])
|
||||||
|
print templates.info_network("Availability", "%.1f" % state['distributed_copies'])
|
||||||
|
print templates.info_files_header("Files")
|
||||||
|
for i, file in enumerate(state['files']):
|
||||||
|
status = ""
|
||||||
|
if not state['is_seed']:
|
||||||
|
if state['file_priorities'][i] == 0:
|
||||||
|
status = " - Do not download"
|
||||||
|
else:
|
||||||
|
status = " - %1.f%% completed" % (state['file_progress'][i] * 100)
|
||||||
|
print "\t* %s (%s)%s" % (file['path'], common.fsize(file['size']), status)
|
||||||
|
|
||||||
|
print templates.info_peers_header("Peers")
|
||||||
|
if len(state['peers']) == 0:
|
||||||
|
print "\t* None"
|
||||||
|
for peer in state['peers']:
|
||||||
|
client_str = unicode(peer['client'])
|
||||||
|
client_str += unicode(peer['seed']) if peer['seed'] else ''
|
||||||
|
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)
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from deluge.ui.null2.main import BaseCommand, match_torrents
|
||||||
|
from deluge.ui.null2 import mapping
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
from deluge.ui.null2.colors import templates, default_style as style
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Pause a torrent"""
|
||||||
|
usage = "Usage: pause <torrent-id> [<torrent-id> ...]"
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
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 ''))
|
||||||
|
|
||||||
|
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) ]
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
from deluge.ui.null2.main import BaseCommand
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Exit from the client."""
|
||||||
|
aliases = ['exit']
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
print "Thanks!"
|
||||||
|
raise StopIteration
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from deluge.ui.null2.main import BaseCommand, match_torrents
|
||||||
|
from deluge.ui.null2 import mapping
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
from deluge.ui.null2.colors import templates, default_style as style
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Resume a torrent"""
|
||||||
|
usage = "Usage: resume <torrent-id> [<torrent-id> ...]"
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
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 ''))
|
||||||
|
|
||||||
|
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) ]
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from deluge.ui.null2.main import BaseCommand, match_torrents
|
||||||
|
from deluge.ui.null2 import mapping
|
||||||
|
from deluge.ui.null2.colors import templates
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
from optparse import make_option
|
||||||
|
import os
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Remove a torrent"""
|
||||||
|
usage = "Usage: rm <torrent-id>"
|
||||||
|
aliases = ['del']
|
||||||
|
|
||||||
|
option_list = BaseCommand.option_list + (
|
||||||
|
make_option('--remove_torrent', action='store_true', default=False,
|
||||||
|
help="remove the torrent's file"),
|
||||||
|
make_option('--remove_data', action='store_true', default=False,
|
||||||
|
help="remove the torrent's data"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
try:
|
||||||
|
args = mapping.to_ids(args)
|
||||||
|
torrents = match_torrents(args)
|
||||||
|
client.remove_torrent(torrents, options['remove_torrent'], options['remove_data'])
|
||||||
|
except Exception, msg:
|
||||||
|
print template.ERROR(str(msg))
|
||||||
|
|
||||||
|
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) ]
|
|
@ -0,0 +1,242 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import os, sys
|
||||||
|
import optparse
|
||||||
|
from deluge.ui.null2 import UI_PATH
|
||||||
|
from deluge.ui.null2.colors import Template, make_style, templates, default_style as style
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
import shlex
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.disable(logging.ERROR)
|
||||||
|
|
||||||
|
class OptionParser(optparse.OptionParser):
|
||||||
|
"""subclass from optparse.OptionParser so exit() won't exit."""
|
||||||
|
def exit(self, status=0, msg=None):
|
||||||
|
self.values._exit = True
|
||||||
|
if msg:
|
||||||
|
print msg
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
"""error(msg : string)
|
||||||
|
|
||||||
|
Print a usage message incorporating 'msg' to stderr and exit.
|
||||||
|
If you override this in a subclass, it should not return -- it
|
||||||
|
should either exit or raise an exception.
|
||||||
|
"""
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCommand(object):
|
||||||
|
|
||||||
|
usage = 'usage'
|
||||||
|
option_list = tuple()
|
||||||
|
aliases = []
|
||||||
|
|
||||||
|
|
||||||
|
def complete(self, text, *args):
|
||||||
|
return []
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return 'base'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def epilog(self):
|
||||||
|
return self.__doc__
|
||||||
|
|
||||||
|
def split(self, text):
|
||||||
|
return shlex.split(text)
|
||||||
|
|
||||||
|
def _create_parser(self):
|
||||||
|
return OptionParser(prog = self.name,
|
||||||
|
usage = self.usage,
|
||||||
|
epilog = self.epilog,
|
||||||
|
option_list = self.option_list)
|
||||||
|
|
||||||
|
def match_torrents(array=None):
|
||||||
|
global torrents
|
||||||
|
if array is None:
|
||||||
|
array = list()
|
||||||
|
torrents = []
|
||||||
|
array = set(array)
|
||||||
|
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
|
||||||
|
|
||||||
|
class NullUI(object):
|
||||||
|
prompt = '>>> '
|
||||||
|
|
||||||
|
def __init__(self, args=None):
|
||||||
|
client.set_core_uri("http://localhost:58846")
|
||||||
|
self._commands = self._load_commands()
|
||||||
|
self._builtins = { 'help': self.help }
|
||||||
|
self._all_commands = dict(self._commands)
|
||||||
|
self._all_commands.update(self._builtins)
|
||||||
|
|
||||||
|
def _load_commands(self):
|
||||||
|
def get_command(name):
|
||||||
|
return getattr(__import__('deluge.ui.null2.commands.%s' % name, {}, {}, ['Command']), 'Command')()
|
||||||
|
|
||||||
|
command_dir = os.path.join(UI_PATH, 'commands')
|
||||||
|
try:
|
||||||
|
commands = []
|
||||||
|
for filename in os.listdir(command_dir):
|
||||||
|
if filename.startswith('_') or not filename.endswith('.py'):
|
||||||
|
continue
|
||||||
|
cmd = get_command(filename[:-3])
|
||||||
|
aliases = [ filename[:-3] ]
|
||||||
|
aliases.extend(cmd.aliases)
|
||||||
|
for a in aliases:
|
||||||
|
commands.append((a, cmd))
|
||||||
|
return dict(commands)
|
||||||
|
#return dict([ (f[:-3], get_command(f[:-3])) for f in os.listdir(command_dir) if not f.startswith('_') and f.endswith('.py') ])
|
||||||
|
except OSError, e:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def completedefault(self, *ignored):
|
||||||
|
"""Method called to complete an input line when no command-specific
|
||||||
|
method is available.
|
||||||
|
|
||||||
|
By default, it returns an empty list.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
def completenames(self, text, *ignored):
|
||||||
|
return [n for n in self._commands.keys() if n.startswith(text)]
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
def preloop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def postloop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def precmd(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onecmd(self, line):
|
||||||
|
if not line:
|
||||||
|
return
|
||||||
|
#v_line = line.split()
|
||||||
|
cmd, _, line = line.partition(' ')
|
||||||
|
if cmd in self._builtins:
|
||||||
|
args = shlex.split(line)
|
||||||
|
self._builtins[cmd](*args)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
parser = self._commands[cmd]._create_parser()
|
||||||
|
except KeyError:
|
||||||
|
print templates.ERROR('Error! unknown command: %s' % cmd)
|
||||||
|
return
|
||||||
|
args = self._commands[cmd].split(line)
|
||||||
|
options, args = parser.parse_args(args)
|
||||||
|
if not getattr(options, '_exit', False):
|
||||||
|
try:
|
||||||
|
self._commands[cmd].handle(*args, **options.__dict__)
|
||||||
|
except StopIteration, e:
|
||||||
|
raise
|
||||||
|
except Exception, e:
|
||||||
|
print templates.ERROR(str(e))
|
||||||
|
|
||||||
|
def postcmd(self):
|
||||||
|
client.force_call()
|
||||||
|
|
||||||
|
def _all_commands_keys_generator(self):
|
||||||
|
return [ (self._commands, key) for key in self._commands] +\
|
||||||
|
[ (self._builtins, key) for key in self._builtins]
|
||||||
|
|
||||||
|
def help(self, *args):
|
||||||
|
"""displays this text"""
|
||||||
|
usage = 'usage: help [command]'
|
||||||
|
if args:
|
||||||
|
if len(args) > 1:
|
||||||
|
print usage
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
cmd = self._all_commands[args[0]]
|
||||||
|
except KeyError:
|
||||||
|
print templates.ERROR('unknown command %r' % args[0])
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
parser = cmd.create_parser()
|
||||||
|
print parser.format_help()
|
||||||
|
except AttributeError, e:
|
||||||
|
print cmd.__doc__ or 'No help for this command'
|
||||||
|
else:
|
||||||
|
max_length = max( len(k) for k in self._all_commands)
|
||||||
|
for cmd in sorted(self._all_commands):
|
||||||
|
print templates.help(max_length, cmd, self._all_commands[cmd].__doc__ or '')
|
||||||
|
print
|
||||||
|
print 'for help on a specific command, use "<command> --help"'
|
||||||
|
|
||||||
|
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 = NullUI()
|
||||||
|
ui.precmd()
|
||||||
|
ui.onecmd(' '.join(sys.argv[1:]))
|
||||||
|
ui.postcmd()
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from deluge.ui.client import aclient as client
|
||||||
|
from deluge.ui.null2.main import match_torrents
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_idregex = re.compile(r'^[0-9a-f]{40}$')
|
||||||
|
|
||||||
|
_mapping = {}
|
||||||
|
|
||||||
|
def _arg_is_id(arg):
|
||||||
|
return bool(_idregex.match(arg))
|
||||||
|
|
||||||
|
def get_names(torrents):
|
||||||
|
global names
|
||||||
|
names = []
|
||||||
|
def _got_torrents_status(states):
|
||||||
|
try:
|
||||||
|
names.extend(list([ (tid, state['name']) for (tid, state) in states.items() ]))
|
||||||
|
except Exception, e:
|
||||||
|
print e
|
||||||
|
|
||||||
|
client.get_torrents_status(_got_torrents_status, torrents, ['name'])
|
||||||
|
client.force_call()
|
||||||
|
return names
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
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()
|
||||||
|
if i in _mapping:
|
||||||
|
res.append(_mapping[i])
|
||||||
|
rehashed = True
|
||||||
|
return res
|
||||||
|
|
||||||
|
def names():
|
||||||
|
return _mapping.keys()
|
|
@ -68,4 +68,9 @@ class UI:
|
||||||
log.info("Starting NullUI..")
|
log.info("Starting NullUI..")
|
||||||
from deluge.ui.null.deluge_shell import NullUI
|
from deluge.ui.null.deluge_shell import NullUI
|
||||||
ui = NullUI(args)
|
ui = NullUI(args)
|
||||||
|
elif selected_ui == "null2":
|
||||||
|
log.info("Starting NullUI2..")
|
||||||
|
from deluge.ui.null2.main import NullUI
|
||||||
|
ui = NullUI(args)
|
||||||
|
ui.run()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue