[Console] Flake8 all files

This commit is contained in:
Calum Lind 2014-09-18 13:21:29 +01:00
parent a68c3140af
commit 45ef6ac56d
39 changed files with 1300 additions and 2184 deletions

View File

@ -34,3 +34,4 @@
# #
UI_PATH = __path__[0] UI_PATH = __path__[0]
from main import start from main import start
assert start # silence pyflakes

View File

@ -1,36 +1,10 @@
# # -*- coding: utf-8 -*-
# colors.py
# #
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
from deluge.ui.console.modes import format_utils from deluge.ui.console.modes import format_utils
@ -41,19 +15,19 @@ except ImportError:
pass pass
colors = [ colors = [
'COLOR_BLACK', "COLOR_BLACK",
'COLOR_BLUE', "COLOR_BLUE",
'COLOR_CYAN', "COLOR_CYAN",
'COLOR_GREEN', "COLOR_GREEN",
'COLOR_MAGENTA', "COLOR_MAGENTA",
'COLOR_RED', "COLOR_RED",
'COLOR_WHITE', "COLOR_WHITE",
'COLOR_YELLOW' "COLOR_YELLOW"
] ]
# {(fg, bg): pair_number, ...} # {(fg, bg): pair_number, ...}
color_pairs = { color_pairs = {
("white", "black"): 0 # Special case, can't be changed ("white", "black"): 0 # Special case, can't be changed
} }
# Some default color schemes # Some default color schemes
@ -92,6 +66,7 @@ type_color = {
dict: "{!white,black,bold!}" dict: "{!white,black,bold!}"
} }
def init_colors(): def init_colors():
# Create the color_pairs dict # Create the color_pairs dict
counter = 1 counter = 1
@ -111,9 +86,11 @@ def init_colors():
except: except:
pass pass
class BadColorString(Exception): class BadColorString(Exception):
pass pass
def replace_tabs(line): def replace_tabs(line):
""" """
Returns a string with tabs replaced with spaces. Returns a string with tabs replaced with spaces.
@ -124,6 +101,7 @@ def replace_tabs(line):
line = line.replace("\t", " " * tab_length, 1) line = line.replace("\t", " " * tab_length, 1)
return line return line
def strip_colors(line): def strip_colors(line):
""" """
Returns a string with the color formatting removed. Returns a string with the color formatting removed.
@ -135,6 +113,7 @@ def strip_colors(line):
return line return line
def get_line_length(line, encoding="UTF-8"): def get_line_length(line, encoding="UTF-8"):
""" """
Returns the string length without the color formatting. Returns the string length without the color formatting.
@ -153,6 +132,7 @@ def get_line_length(line, encoding="UTF-8"):
line = replace_tabs(line) line = replace_tabs(line)
return len(line) return len(line)
def get_line_width(line, encoding="UTF-8"): def get_line_width(line, encoding="UTF-8"):
""" """
Get width of string considering double width characters Get width of string considering double width characters
@ -171,6 +151,7 @@ def get_line_width(line, encoding="UTF-8"):
line = replace_tabs(line) line = replace_tabs(line)
return format_utils.strwidth(line) return format_utils.strwidth(line)
def parse_color_string(s, encoding="UTF-8"): def parse_color_string(s, encoding="UTF-8"):
""" """
Parses a string and returns a list of 2-tuples (color, string). Parses a string and returns a list of 2-tuples (color, string).
@ -187,7 +168,6 @@ def parse_color_string(s, encoding="UTF-8"):
ret = [] ret = []
# Keep track of where the strings # Keep track of where the strings
col_index = 0
while s.find("{!") != -1: while s.find("{!") != -1:
begin = s.find("{!") begin = s.find("{!")
if begin > 0: if begin > 0:
@ -198,9 +178,9 @@ def parse_color_string(s, encoding="UTF-8"):
raise BadColorString("Missing closing '!}'") raise BadColorString("Missing closing '!}'")
# Get a list of attributes in the bracketed section # Get a list of attributes in the bracketed section
attrs = s[begin+2:end].split(",") attrs = s[begin + 2:end].split(",")
if len(attrs) == 1 and not attrs[0].strip(' '): if len(attrs) == 1 and not attrs[0].strip(" "):
raise BadColorString("No description in {! !}") raise BadColorString("No description in {! !}")
def apply_attrs(cp, a): def apply_attrs(cp, a):
@ -238,10 +218,10 @@ def parse_color_string(s, encoding="UTF-8"):
next_begin = s.find("{!", end) next_begin = s.find("{!", end)
if next_begin == -1: if next_begin == -1:
ret.append((color_pair, replace_tabs(s[end+2:]))) ret.append((color_pair, replace_tabs(s[end + 2:])))
break break
else: else:
ret.append((color_pair, replace_tabs(s[end+2:next_begin]))) ret.append((color_pair, replace_tabs(s[end + 2:next_begin])))
s = s[next_begin:] s = s[next_begin:]
if not ret: if not ret:

View File

@ -1,40 +1,15 @@
# # -*- coding: utf-8 -*-
# commander.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
from __future__ import print_function from __future__ import print_function
import logging import logging
@ -68,7 +43,7 @@ class Commander:
""" """
if not cmd: if not cmd:
return return
cmd, _, line = cmd.partition(' ') cmd, _, line = cmd.partition(" ")
try: try:
parser = self._commands[cmd].create_parser() parser = self._commands[cmd].create_parser()
except KeyError: except KeyError:
@ -103,7 +78,7 @@ class Commander:
self.write("{!error!}Error parsing options: %s" % ex) self.write("{!error!}Error parsing options: %s" % ex)
return return
if not getattr(options, '_exit', False): if not getattr(options, "_exit", False):
try: try:
ret = self._commands[cmd].handle(*args, **options.__dict__) ret = self._commands[cmd].handle(*args, **options.__dict__)
except Exception as ex: except Exception as ex:
@ -119,7 +94,7 @@ class Commander:
commands = [] commands = []
if args: if args:
# Multiple commands split by ";" # Multiple commands split by ";"
commands = [arg.strip() for arg in args.split(';')] commands = [arg.strip() for arg in args.split(";")]
def on_connect(result): def on_connect(result):
def on_started(result): def on_started(result):
@ -156,8 +131,8 @@ class Commander:
if not self.interactive: if not self.interactive:
if commands[0].startswith("connect"): if commands[0].startswith("connect"):
d = self.do_command(commands.pop(0)) d = self.do_command(commands.pop(0))
elif 'help' in commands: elif "help" in commands:
self.do_command('help') self.do_command("help")
sys.exit(0) sys.exit(0)
d.addCallback(on_connect) d.addCallback(on_connect)
d.addErrback(on_connect_fail) d.addErrback(on_connect_fail)

View File

@ -1,39 +1,13 @@
# # -*- coding: utf-8 -*-
# cache.py
# #
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
# #
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
@ -41,6 +15,7 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Show information about the disk cache""" """Show information about the disk cache"""
usage = "Usage: cache" usage = "Usage: cache"
def handle(self, *args, **options): def handle(self, *args, **options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")

View File

@ -1,42 +1,15 @@
# # -*- coding: utf-8 -*-
# config.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import cStringIO import cStringIO
import logging import logging
import re
import tokenize import tokenize
from optparse import make_option from optparse import make_option
@ -49,6 +22,7 @@ from deluge.ui.console.main import BaseCommand
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def atom(next, token): def atom(next, token):
"""taken with slight modifications from http://effbot.org/zone/simple-iterator-parser.htm""" """taken with slight modifications from http://effbot.org/zone/simple-iterator-parser.htm"""
if token[1] == "(": if token[1] == "(":
@ -71,15 +45,16 @@ def atom(next, token):
return float(token[-1]) return float(token[-1])
except ValueError: except ValueError:
return str(token[-1]) return str(token[-1])
elif token[1].lower() == 'true': elif token[1].lower() == "true":
return True return True
elif token[1].lower() == 'false': elif token[1].lower() == "false":
return False return False
elif token[0] is tokenize.STRING or token[1] == "/": elif token[0] is tokenize.STRING or token[1] == "/":
return token[-1].decode("string-escape") return token[-1].decode("string-escape")
raise SyntaxError("malformed expression (%s)" % token[1]) raise SyntaxError("malformed expression (%s)" % token[1])
def simple_eval(source): def simple_eval(source):
""" evaluates the 'source' string into a combination of primitive python objects """ evaluates the 'source' string into a combination of primitive python objects
taken from http://effbot.org/zone/simple-iterator-parser.htm""" taken from http://effbot.org/zone/simple-iterator-parser.htm"""
@ -94,21 +69,19 @@ class Command(BaseCommand):
"""Show and set configuration values""" """Show and set configuration values"""
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('-s', '--set', action='store', nargs=2, dest='set', make_option("-s", "--set", action="store", nargs=2, dest="set", help="set value for key"),
help='set value for key'),
) )
usage = "Usage: config [key1 [key2 ...]]\n"\ usage = """Usage: config [key1 [key2 ...]]"
" config --set key value" config --set key value"""
def handle(self, *args, **options): def handle(self, *args, **options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if options['set']: if options["set"]:
return self._set_config(*args, **options) return self._set_config(*args, **options)
else: else:
return self._get_config(*args, **options) return self._get_config(*args, **options)
def _get_config(self, *args, **options): def _get_config(self, *args, **options):
deferred = defer.Deferred()
config = component.get("CoreConfig") config = component.get("CoreConfig")
keys = config.keys() keys = config.keys()
keys.sort() keys.sort()
@ -161,4 +134,4 @@ class Command(BaseCommand):
return deferred return deferred
def complete(self, text): def complete(self, text):
return [ k for k in component.get("CoreConfig").keys() if k.startswith(text) ] return [k for k in component.get("CoreConfig").keys() if k.startswith(text)]

View File

@ -1,40 +1,14 @@
# # -*- coding: utf-8 -*-
# connect.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
@ -55,6 +29,7 @@ class Command(BaseCommand):
def do_connect(): def do_connect():
d = client.connect(host, port, username, password) d = client.connect(host, port, username, password)
def on_connect(result): def on_connect(result):
if self.console.interactive: if self.console.interactive:
self.console.write("{!success!}Connected to %s:%s!" % (host, port)) self.console.write("{!success!}Connected to %s:%s!" % (host, port))

View File

@ -1,55 +1,28 @@
# # -*- coding: utf-8 -*-
# debug.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
from twisted.internet import defer from twisted.internet import defer
import deluge.component as component import deluge.component as component
import deluge.log import deluge.log
import deluge.ui.console.colors as colors
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):
"""Enable and disable debugging""" """Enable and disable debugging"""
usage = 'Usage: debug [on|off]' usage = "Usage: debug [on|off]"
def handle(self, state='', **options):
if state == 'on': def handle(self, state="", **options):
if state == "on":
deluge.log.setLoggerLevel("debug") deluge.log.setLoggerLevel("debug")
elif state == 'off': elif state == "off":
deluge.log.setLoggerLevel("error") deluge.log.setLoggerLevel("error")
else: else:
component.get("ConsoleUI").write("{!error!}%s" % self.usage) component.get("ConsoleUI").write("{!error!}%s" % self.usage)
@ -57,4 +30,4 @@ class Command(BaseCommand):
return defer.succeed(True) return defer.succeed(True)
def complete(self, text): def complete(self, text):
return [x for x in ['on', 'off'] if x.startswith(text)] return [x for x in ["on", "off"] if x.startswith(text)]

View File

@ -1,38 +1,12 @@
# # -*- coding: utf-8 -*-
# status.py
# #
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
# #
import deluge.common
import deluge.component as component import deluge.component as component
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
from deluge.ui.console.modes.alltorrents import AllTorrents from deluge.ui.console.modes.alltorrents import AllTorrents
@ -42,6 +16,7 @@ class Command(BaseCommand):
"""Exit this mode and go into the more 'gui' like mode""" """Exit this mode and go into the more 'gui' like mode"""
usage = "Usage: gui" usage = "Usage: gui"
interactive_only = True interactive_only = True
def handle(self, *args, **options): def handle(self, *args, **options):
console = component.get("ConsoleUI") console = component.get("ConsoleUI")
try: try:

View File

@ -1,40 +1,14 @@
# # -*- coding: utf-8 -*-
# halt.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
@ -42,6 +16,7 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"Shutdown the deluge server" "Shutdown the deluge server"
usage = "Usage: halt" usage = "Usage: halt"
def handle(self, *args, **options): def handle(self, *args, **options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")

View File

@ -1,49 +1,23 @@
# help.py # -*- coding: utf-8 -*-
# #
# 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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
from twisted.internet import defer from twisted.internet import defer
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""displays help on other commands""" """displays help on other commands"""
usage = "Usage: help [command]" usage = "Usage: help [command]"
def handle(self, *args, **options): def handle(self, *args, **options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
@ -60,14 +34,14 @@ class Command(BaseCommand):
parser = cmd.create_parser() parser = cmd.create_parser()
self.console.write(parser.format_help()) self.console.write(parser.format_help())
except AttributeError: except AttributeError:
self.console.write(cmd.__doc__ or 'No help for this command') self.console.write(cmd.__doc__ or "No help for this command")
self.console.write(" ") self.console.write(" ")
else: else:
self.console.set_batch_write(True) self.console.set_batch_write(True)
for cmd in sorted(self._commands): for cmd in sorted(self._commands):
self.console.write("{!info!}" + cmd + "{!input!} - " + self._commands[cmd].__doc__ or '') self.console.write("{!info!}" + cmd + "{!input!} - " + self._commands[cmd].__doc__ or '')
self.console.write(" ") self.console.write(" ")
self.console.write('For help on a specific command, use "<command> --help"') self.console.write("For help on a specific command, use '<command> --help'")
self.console.set_batch_write(False) self.console.set_batch_write(False)
return deferred return deferred

View File

@ -1,37 +1,11 @@
# # -*- coding: utf-8 -*-
# info.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
from optparse import make_option from optparse import make_option
@ -47,39 +21,41 @@ from deluge.ui.console.modes import format_utils
strwidth = format_utils.strwidth strwidth = format_utils.strwidth
STATUS_KEYS = ["state", STATUS_KEYS = [
"download_location", "state",
"tracker_host", "download_location",
"tracker_status", "tracker_host",
"next_announce", "tracker_status",
"name", "next_announce",
"total_size", "name",
"progress", "total_size",
"num_seeds", "progress",
"total_seeds", "num_seeds",
"num_peers", "total_seeds",
"total_peers", "num_peers",
"eta", "total_peers",
"download_payload_rate", "eta",
"upload_payload_rate", "download_payload_rate",
"ratio", "upload_payload_rate",
"distributed_copies", "ratio",
"num_pieces", "distributed_copies",
"piece_length", "num_pieces",
"total_done", "piece_length",
"files", "total_done",
"file_priorities", "files",
"file_progress", "file_priorities",
"peers", "file_progress",
"is_seed", "peers",
"is_finished", "is_seed",
"active_time", "is_finished",
"seeding_time" "active_time",
] "seeding_time"
]
# Add filter specific state to torrent states # Add filter specific state to torrent states
STATES = ["Active"] + common.TORRENT_STATE STATES = ["Active"] + common.TORRENT_STATE
def format_progressbar(progress, width): def format_progressbar(progress, width):
""" """
Returns a string of a progress bar. Returns a string of a progress bar.
@ -90,9 +66,9 @@ def format_progressbar(progress, width):
""" """
w = width - 2 # we use a [] for the beginning and end w = width - 2 # we use a [] for the beginning and end
s = "[" s = "["
p = int(round((progress/100) * w)) p = int(round((progress / 100) * w))
s += "#" * p s += "#" * p
s += "-" * (w - p) s += "-" * (w - p)
s += "]" s += "]"
@ -112,31 +88,29 @@ def format_time(seconds):
class Command(BaseCommand): class Command(BaseCommand):
"""Show information about the torrents""" """Show information about the torrents"""
sort_help = 'sort items. Possible keys: ' + ', '.join(STATUS_KEYS) sort_help = "sort items. Possible keys: " + ", ".join(STATUS_KEYS)
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('-v', '--verbose', action='store_true', default=False, dest='verbose', make_option("-v", "--verbose", action="store_true", default=False, dest="verbose",
help='shows more information per torrent'), help="Show more information per torrent."),
make_option('-d', '--detailed', action='store_true', default=False, dest='detailed', make_option("-d", "--detailed", action="store_true", default=False, dest="detailed",
help='shows detailed information about files and peers. ' help="Show more detailed information including files and peers."),
"Implies -v"), make_option("-s", "--state", action="store", dest="state",
make_option('-s', '--state', action='store', dest='state', help="Show torrents with state STATE: %s." % (", ".join(STATES))),
help="show torrents with state STATE. " make_option("--sort", action="store", type="string", default="", dest="sort", help=sort_help),
"Possible values are: %s"%(", ".join(STATES))), make_option("--sort-reverse", action="store", type="string", default="", dest="sort_rev",
make_option('--sort', action='store', type='string', default='', dest='sort', help="Same as --sort but items are in reverse order.")
help=sort_help),
make_option('--sort-reverse', action='store', type='string', default='', dest='sort_rev',
help='sort items in reverse order. Same keys allowed as for --sort.')
) )
usage = "Usage: info [-v | -d | -s <state>] [<torrent-id> [<torrent-id> ...]]\n"\ usage = """Usage: info [-v | -d | -s <state>] [<torrent-id> [<torrent-id> ...]]
" You can give the first few characters of a torrent-id to identify the torrent.\n"\ You can give the first few characters of a torrent-id to identify the torrent.
" info * will list all torrents.\n\n"\ info * will list all torrents.
" Tab Completion (info *pattern*<tab>):\n"\
" | First press of <tab> will output up to 15 matches;\n"\ Tab Completion (info *pattern*<tab>):
" | hitting <tab> a second time, will print 15 more matches; \n"\ | First press of <tab> will output up to 15 matches;
" | and a third press will print all remaining matches.\n"\ | hitting <tab> a second time, will print 15 more matches;
" | (To modify behaviour of third <tab>, set `third_tab_lists_all` to False)" | and a third press will print all remaining matches.
| (To modify behaviour of third <tab>, set `third_tab_lists_all` to False)"""
def handle(self, *args, **options): def handle(self, *args, **options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
@ -150,22 +124,20 @@ class Command(BaseCommand):
def on_torrents_status(status): def on_torrents_status(status):
# Print out the information for each torrent # Print out the information for each torrent
sort_key = options['sort'] sort_key = options["sort"]
sort_reverse = False sort_reverse = False
if not sort_key: if not sort_key:
sort_key = options['sort_rev'] sort_key = options["sort_rev"]
sort_reverse = True sort_reverse = True
if not sort_key: if not sort_key:
sort_key = 'name' sort_key = "name"
sort_reverse = False sort_reverse = False
if sort_key not in status_keys: if sort_key not in STATUS_KEYS:
self.console.write('') self.console.write("")
self.console.write("{!error!}Unknown sort key: " + sort_key + ", will sort on name") self.console.write("{!error!}Unknown sort key: " + sort_key + ", will sort on name")
sort_key = 'name' sort_key = "name"
sort_reverse = False sort_reverse = False
for key, value in sorted(status.items(), for key, value in sorted(status.items(), key=lambda x: x[1].get(sort_key), reverse=sort_reverse):
key=lambda x : x[1].get(sort_key),
reverse=sort_reverse):
self.show_info(key, status[key], options["verbose"], options["detailed"]) self.show_info(key, status[key], options["verbose"], options["detailed"])
def on_torrents_status_fail(reason): def on_torrents_status_fail(reason):
@ -178,8 +150,8 @@ class Command(BaseCommand):
if options["state"] in STATES: if options["state"] in STATES:
status_dict["state"] = options["state"] status_dict["state"] = options["state"]
else: else:
self.console.write("Invalid state: %s"%options["state"]) self.console.write("Invalid state: %s" % options["state"])
self.console.write("Possible values are: %s."%(", ".join(STATES))) self.console.write("Possible values are: %s." % (", ".join(STATES)))
return return
d = client.core.get_torrents_status(status_dict, STATUS_KEYS) d = client.core.get_torrents_status(status_dict, STATUS_KEYS)
@ -188,29 +160,28 @@ class Command(BaseCommand):
return d return d
def show_file_info(self, torrent_id, status): def show_file_info(self, torrent_id, status):
SPACES_PER_LEVEL = 2 spaces_per_level = 2
if hasattr(self.console, "screen"): if hasattr(self.console, "screen"):
cols = self.console.screen.cols cols = self.console.screen.cols
else: else:
cols = 80 cols = 80
dirs = []
prevpath = [] prevpath = []
for i, file in enumerate(status["files"]): for i, file in enumerate(status["files"]):
filename = file["path"].split(dirsep)[-1] filename = file["path"].split(dirsep)[-1]
filepath = file["path"].split(dirsep)[:-1] filepath = file["path"].split(dirsep)[:-1]
for depth, subdir in enumerate(filepath): for depth, subdir in enumerate(filepath):
indent = " "*depth*SPACES_PER_LEVEL indent = " " * depth * spaces_per_level
if depth >= len(prevpath): if depth >= len(prevpath):
self.console.write("%s{!cyan!}%s" % (indent, subdir)) self.console.write("%s{!cyan!}%s" % (indent, subdir))
elif subdir != prevpath[depth]: elif subdir != prevpath[depth]:
self.console.write("%s{!cyan!}%s" % (indent, subdir)) self.console.write("%s{!cyan!}%s" % (indent, subdir))
depth = len(filepath) depth = len(filepath)
indent = " " * depth * SPACES_PER_LEVEL indent = " " * depth * spaces_per_level
col_filename = indent + filename col_filename = indent + filename
col_size = " ({!cyan!}%s{!input!})" % common.fsize(file["size"]) col_size = " ({!cyan!}%s{!input!})" % common.fsize(file["size"])
@ -226,20 +197,20 @@ class Command(BaseCommand):
col_priority += "{!success!}" col_priority += "{!success!}"
else: else:
col_priority += "{!input!}" col_priority += "{!input!}"
col_priority+= fp col_priority += fp
rf = format_utils.remove_formatting rf = format_utils.remove_formatting
tlen = lambda s: strwidth(rf(s)) tlen = lambda s: strwidth(rf(s))
if not isinstance(col_filename, unicode): if not isinstance(col_filename, unicode):
col_filename = unicode(col_filename, 'utf-8') col_filename = unicode(col_filename, "utf-8")
col_all_info = col_size + col_progress + col_priority col_all_info = col_size + col_progress + col_priority
#Check how much space we've got left after writing all the info #Check how much space we've got left after writing all the info
space_left = cols - tlen(col_all_info) space_left = cols - tlen(col_all_info)
#And how much we will potentially have with the longest possible column #And how much we will potentially have with the longest possible column
maxlen_space_left = cols - tlen(" (1000.0 MiB) 100.00% Priority: Do Not Download") maxlen_space_left = cols - tlen(" (1000.0 MiB) 100.00% Priority: Do Not Download")
if maxlen_space_left > tlen(col_filename) + 1: if maxlen_space_left > tlen(col_filename) + 1:
#If there is enough space, pad it all nicely #If there is enough space, pad it all nicely
col_all_info = "" col_all_info = ""
col_all_info += " (" col_all_info += " ("
@ -253,16 +224,15 @@ class Command(BaseCommand):
col_all_info += col_priority col_all_info += col_priority
col_all_info += " " * spaces_to_add col_all_info += " " * spaces_to_add
#And remember to put it to the left! #And remember to put it to the left!
col_filename = format_utils.pad_string(col_filename, maxlen_space_left - 2, side = "right") col_filename = format_utils.pad_string(col_filename, maxlen_space_left - 2, side="right")
elif space_left > tlen(col_filename) + 1: elif space_left > tlen(col_filename) + 1:
#If there is enough space, put the info to the right #If there is enough space, put the info to the right
col_filename = format_utils.pad_string(col_filename, space_left - 2, side = "right") col_filename = format_utils.pad_string(col_filename, space_left - 2, side="right")
else: else:
#And if there is not, shorten the name #And if there is not, shorten the name
col_filename = format_utils.trim_string( col_filename, space_left, True) col_filename = format_utils.trim_string(col_filename, space_left, True)
self.console.write(col_filename + col_all_info) self.console.write(col_filename + col_all_info)
prevpath = filepath prevpath = filepath
def show_peer_info(self, torrent_id, status): def show_peer_info(self, torrent_id, status):
@ -375,43 +345,43 @@ class Command(BaseCommand):
s = "%s%s" % (colors.state_color[status["state"]], "[" + status["state"][0] + "]") s = "%s%s" % (colors.state_color[status["state"]], "[" + status["state"][0] + "]")
s+= " {!info!}" + ("%.2f%%" % status["progress"]).ljust(7, " ") s += " {!info!}" + ("%.2f%%" % status["progress"]).ljust(7, " ")
s+= " {!input!}%s" % (status["name"]) s += " {!input!}%s" % (status["name"])
#Shorten the ID if it's necessary. Pretty hacky #Shorten the ID if it's necessary. Pretty hacky
#I _REALLY_ should make a nice function for it that can partition and shorten stuff #I _REALLY_ should make a nice function for it that can partition and shorten stuff
space_left = cols - strwidth("[s] 100.00% " + status["name"] + " "*3) - 2 space_left = cols - strwidth("[s] 100.00% " + status["name"] + " " * 3) - 2
if space_left >= len(torrent_id) - 2: if space_left >= len(torrent_id) - 2:
#There's enough space, print it #There's enough space, print it
s += " {!cyan!}%s" % torrent_id s += " {!cyan!}%s" % torrent_id
else: else:
#Shorten the ID #Shorten the ID
a = int(space_left * 2/3.0) a = int(space_left * 2 / 3.0)
b = space_left - a b = space_left - a
if a < 8: if a < 8:
b = b - (8-a) b = b - (8 - a)
a = 8 a = 8
if b < 0: if b < 0:
a+= b a += b
b = 0 b = 0
if a > 8: if a > 8:
#Print the shortened ID #Print the shortened ID
s += " {!cyan!}%s" % (torrent_id[0:a] + ".." + torrent_id[-b-1:-1]) s += " {!cyan!}%s" % (torrent_id[0:a] + ".." + torrent_id[-b - 1:-1])
else: else:
#It has wrapped over to the second row anyway #It has wrapped over to the second row anyway
s += " {!cyan!}%s" % torrent_id s += " {!cyan!}%s" % torrent_id
self.console.write(s) self.console.write(s)
dl_info = "{!info!}DL: {!input!}" dl_info = "{!info!}DL: {!input!}"
dl_info+= "%s" % common.fsize(status["total_done"]) dl_info += "%s" % common.fsize(status["total_done"])
if status["total_done"] != status["total_size"]: if status["total_done"] != status["total_size"]:
dl_info+= "/%s" % common.fsize(status["total_size"]) dl_info += "/%s" % common.fsize(status["total_size"])
if status["download_payload_rate"] > 0: if status["download_payload_rate"] > 0:
dl_info += " @ %s%s" % (down_color, common.fspeed(status["download_payload_rate"])) dl_info += " @ %s%s" % (down_color, common.fspeed(status["download_payload_rate"]))
ul_info = " {!info!}UL: {!input!}" ul_info = " {!info!}UL: {!input!}"
ul_info+= "%s" % common.fsize(status["ratio"] * status["total_done"]) ul_info += "%s" % common.fsize(status["ratio"] * status["total_done"])
if status["upload_payload_rate"] > 0: if status["upload_payload_rate"] > 0:
ul_info += " @ %s%s" % (up_color, common.fspeed(status["upload_payload_rate"])) ul_info += " @ %s%s" % (up_color, common.fspeed(status["upload_payload_rate"]))

View File

@ -1,48 +1,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# manage.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import re
from optparse import make_option from optparse import make_option
from twisted.internet import defer from twisted.internet import defer
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
from deluge.log import LOG as log
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
@ -59,15 +29,14 @@ torrent_options = {
"remove_at_ratio": bool, "remove_at_ratio": bool,
"move_on_completed": bool, "move_on_completed": bool,
"move_on_completed_path": str "move_on_completed_path": str
} }
class Command(BaseCommand): class Command(BaseCommand):
"""Show and manage per-torrent options""" """Show and manage per-torrent options"""
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('-s', '--set', action='store', nargs=2, dest='set', make_option("-s", "--set", action="store", nargs=2, dest="set", help="set value for key"),
help='set value for key'),
) )
usage = "Usage: manage <torrent-id> [<key1> [<key2> ...]]\n"\ usage = "Usage: manage <torrent-id> [<key1> [<key2> ...]]\n"\
" manage <torrent-id> --set <key> <value>" " manage <torrent-id> --set <key> <value>"
@ -79,7 +48,6 @@ class Command(BaseCommand):
else: else:
return self._get_option(*args, **options) return self._get_option(*args, **options)
def _get_option(self, *args, **options): def _get_option(self, *args, **options):
def on_torrents_status(status): def on_torrents_status(status):
@ -105,7 +73,7 @@ class Command(BaseCommand):
return return
request_options.append(opt) request_options.append(opt)
if not request_options: if not request_options:
request_options = [ opt for opt in torrent_options.keys() ] request_options = [opt for opt in torrent_options.keys()]
request_options.append('name') request_options.append('name')
d = client.core.get_torrents_status({"id": torrent_ids}, request_options) d = client.core.get_torrents_status({"id": torrent_ids}, request_options)
@ -113,7 +81,6 @@ class Command(BaseCommand):
d.addErrback(on_torrents_status_fail) d.addErrback(on_torrents_status_fail)
return d return d
def _set_option(self, *args, **options): def _set_option(self, *args, **options):
deferred = defer.Deferred() deferred = defer.Deferred()
torrent_ids = [] torrent_ids = []
@ -133,7 +100,6 @@ class Command(BaseCommand):
self.console.write("Setting %s to %s for torrents %s.." % (key, val, torrent_ids)) self.console.write("Setting %s to %s for torrents %s.." % (key, val, torrent_ids))
for tid in torrent_ids: for tid in torrent_ids:
if key == "move_on_completed_path": if key == "move_on_completed_path":
client.core.set_torrent_move_completed_path(tid, val).addCallback(on_set_config) client.core.set_torrent_move_completed_path(tid, val).addCallback(on_set_config)

View File

@ -1,36 +1,10 @@
# # -*- coding: utf-8 -*-
# move.py
# #
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import os.path import os.path

View File

@ -1,40 +1,14 @@
# # -*- coding: utf-8 -*-
# pause.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
@ -42,6 +16,7 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Pause a torrent""" """Pause a torrent"""
usage = "Usage: pause [ * | <torrent-id> [<torrent-id> ...] ]" usage = "Usage: pause [ * | <torrent-id> [<torrent-id> ...] ]"
def handle(self, *args, **options): def handle(self, *args, **options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")

View File

@ -1,43 +1,16 @@
# # -*- coding: utf-8 -*-
# plugin.py
# #
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
# #
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
#
import re
from optparse import make_option from optparse import make_option
import deluge.component as component import deluge.component as component
import deluge.configmanager import deluge.configmanager
import deluge.ui.console.colors as colors
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
@ -45,25 +18,25 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Manage plugins with this command""" """Manage plugins with this command"""
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('-l', '--list', action='store_true', default=False, dest='list', make_option("-l", "--list", action="store_true", default=False, dest="list",
help='lists available plugins'), help="Lists available plugins"),
make_option('-s', '--show', action='store_true', default=False, dest='show', make_option("-s", "--show", action="store_true", default=False, dest="show",
help='shows enabled plugins'), help="Shows enabled plugins"),
make_option('-e', '--enable', dest='enable', make_option("-e", "--enable", dest="enable",
help='enables a plugin'), help="Enables a plugin"),
make_option('-d', '--disable', dest='disable', make_option("-d", "--disable", dest="disable",
help='disables a plugin'), help="Disables a plugin"),
make_option('-r', '--reload', action='store_true', default=False, dest='reload', make_option("-r", "--reload", action="store_true", default=False, dest="reload",
help='reload list of available plugins'), help="Reload list of available plugins"),
make_option('-i', '--install', dest='plugin_file', make_option("-i", "--install", dest="plugin_file",
help='install a plugin from an .egg file'), help="Install a plugin from an .egg file"),
) )
usage = "Usage: plugin [ -l | --list ]\n"\ usage = """Usage: plugin [ -l | --list ]
" plugin [ -s | --show ]\n"\ plugin [ -s | --show ]
" plugin [ -e | --enable ] <plugin-name>\n"\ plugin [ -e | --enable ] <plugin-name>
" plugin [ -d | --disable ] <plugin-name>\n"\ plugin [ -d | --disable ] <plugin-name>
" plugin [ -i | --install ] <plugin-file>\n"\ plugin [ -i | --install ] <plugin-file>
" plugin [ -r | --reload]" plugin [ -r | --reload]"""
def handle(self, *args, **options): def handle(self, *args, **options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
@ -131,7 +104,6 @@ class Command(BaseCommand):
self.console.write("{!error!}Invalid path: %s" % filepath) self.console.write("{!error!}Invalid path: %s" % filepath)
return return
config_dir = deluge.configmanager.get_config_dir() config_dir = deluge.configmanager.get_config_dir()
filename = os.path.split(filepath)[1] filename = os.path.split(filepath)[1]
@ -153,6 +125,5 @@ class Command(BaseCommand):
self.console.write("{!green!}Plugin was successfully installed: %s" % filename) self.console.write("{!green!}Plugin was successfully installed: %s" % filename)
def complete(self, line): def complete(self, line):
return component.get("ConsoleUI").tab_complete_path(line, ext=".egg", sort="name", dirs_first=-1) return component.get("ConsoleUI").tab_complete_path(line, ext=".egg", sort="name", dirs_first=-1)

View File

@ -1,38 +1,13 @@
# # -*- coding: utf-8 -*-
# quit.py
# #
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Coptright (C) 2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
# #
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
# #
from twisted.internet import error, reactor from twisted.internet import error, reactor
from deluge.ui.client import client from deluge.ui.client import client
@ -41,8 +16,9 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Exit from the client.""" """Exit from the client."""
aliases = ['exit'] aliases = ["exit"]
interactive_only = True interactive_only = True
def handle(self, *args, **options): def handle(self, *args, **options):
if client.connected(): if client.connected():
def on_disconnect(result): def on_disconnect(result):

View File

@ -1,39 +1,13 @@
# # -*- coding: utf-8 -*-
# recheck.py
# #
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
@ -41,13 +15,14 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Forces a recheck of the torrent data""" """Forces a recheck of the torrent data"""
usage = "Usage: recheck [ * | <torrent-id> [<torrent-id> ...] ]" usage = "Usage: recheck [ * | <torrent-id> [<torrent-id> ...] ]"
def handle(self, *args, **options): def handle(self, *args, **options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if len(args) == 0: if len(args) == 0:
self.console.write(self.usage) self.console.write(self.usage)
return return
if len(args) > 0 and args[0].lower() == '*': if len(args) > 0 and args[0].lower() == "*":
client.core.force_recheck(self.console.match_torrent("")) client.core.force_recheck(self.console.match_torrent(""))
return return

View File

@ -1,41 +1,14 @@
# # -*- coding: utf-8 -*-
# resume.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
@ -43,13 +16,14 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Resume a torrent""" """Resume a torrent"""
usage = "Usage: resume [ * | <torrent-id> [<torrent-id> ...] ]" usage = "Usage: resume [ * | <torrent-id> [<torrent-id> ...] ]"
def handle(self, *args, **options): def handle(self, *args, **options):
self.console = component.get("ConsoleUI") self.console = component.get("ConsoleUI")
if len(args) == 0: if len(args) == 0:
self.console.write(self.usage) self.console.write(self.usage)
return return
if len(args) > 0 and args[0] == '*': if len(args) > 0 and args[0] == "*":
client.core.resume_session() client.core.resume_session()
return return

View File

@ -1,42 +1,16 @@
# # -*- coding: utf-8 -*-
# rm.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
from optparse import make_option from optparse import make_option
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand
@ -44,11 +18,11 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Remove a torrent""" """Remove a torrent"""
usage = "Usage: rm <torrent-id>" usage = "Usage: rm <torrent-id>"
aliases = ['del'] aliases = ["del"]
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('--remove_data', action='store_true', default=False, make_option("--remove_data", action="store_true", default=False,
help="remove the torrent's data"), help="remove the torrent's data"),
) )
def handle(self, *args, **options): def handle(self, *args, **options):
@ -61,7 +35,7 @@ class Command(BaseCommand):
torrent_ids.extend(self.console.match_torrent(arg)) torrent_ids.extend(self.console.match_torrent(arg))
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
client.core.remove_torrent(torrent_id, options['remove_data']) client.core.remove_torrent(torrent_id, options["remove_data"])
def complete(self, line): def complete(self, line):
# We use the ConsoleUI torrent tab complete method # We use the ConsoleUI torrent tab complete method

View File

@ -1,35 +1,10 @@
# # -*- coding: utf-8 -*-
# status.py
# #
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
# #
from optparse import make_option from optparse import make_option
@ -45,11 +20,11 @@ from deluge.ui.console.main import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
"""Shows a various status information from the daemon.""" """Shows a various status information from the daemon."""
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('-r', '--raw', action='store_true', default=False, dest='raw', make_option("-r", "--raw", action="store_true", default=False, dest="raw",
help='Don\'t format upload/download rates in KiB/s \ help="Don't format upload/download rates in KiB/s \
(useful for scripts that want to do their own parsing)'), (useful for scripts that want to do their own parsing)"),
make_option('-n', '--no-torrents', action='store_false', default=True, dest='show_torrents', make_option("-n", "--no-torrents", action="store_false", default=True, dest="show_torrents",
help='Don\'t show torrent status (this will make the command a bit faster)'), help="Don't show torrent status (this will make the command a bit faster)"),
) )
usage = "Usage: status [-r] [-n]" usage = "Usage: status [-r] [-n]"

View File

@ -1,42 +1,14 @@
# # -*- coding: utf-8 -*-
# rm.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
# #
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
#
from optparse import make_option
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console.main import BaseCommand from deluge.ui.console.main import BaseCommand

View File

@ -1,38 +1,11 @@
# # -*- coding: utf-8 -*-
# eventlog.py
# #
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
# #
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
#
import logging import logging
import time import time

View File

@ -1,37 +1,11 @@
# # -*- coding: utf-8 -*-
# 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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
from __future__ import print_function from __future__ import print_function
@ -59,6 +33,7 @@ from deluge.ui.ui import _UI
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Console(_UI): class Console(_UI):
help = """Starts the Deluge console interface""" help = """Starts the Deluge console interface"""
@ -74,7 +49,8 @@ class Console(_UI):
self.parser.add_option_group(group) self.parser.add_option_group(group)
self.parser.disable_interspersed_args() self.parser.disable_interspersed_args()
self.console_cmds = load_commands(os.path.join(UI_PATH, 'commands')) self.console_cmds = load_commands(os.path.join(UI_PATH, "commands"))
class CommandOptionGroup(optparse.OptionGroup): class CommandOptionGroup(optparse.OptionGroup):
def __init__(self, parser, title, description=None, cmds=None): def __init__(self, parser, title, description=None, cmds=None):
optparse.OptionGroup.__init__(self, parser, title, description) optparse.OptionGroup.__init__(self, parser, title, description)
@ -84,16 +60,17 @@ class Console(_UI):
result = formatter.format_heading(self.title) result = formatter.format_heading(self.title)
formatter.indent() formatter.indent()
if self.description: if self.description:
result += "%s\n"%formatter.format_description(self.description) result += "%s\n" % formatter.format_description(self.description)
for cname in self.cmds: for cname in self.cmds:
cmd = self.cmds[cname] cmd = self.cmds[cname]
if cmd.interactive_only or cname in cmd.aliases: continue if cmd.interactive_only or cname in cmd.aliases:
continue
allnames = [cname] allnames = [cname]
allnames.extend(cmd.aliases) allnames.extend(cmd.aliases)
cname = "/".join(allnames) cname = "/".join(allnames)
result += formatter.format_heading(" - ".join([cname, cmd.__doc__])) result += formatter.format_heading(" - ".join([cname, cmd.__doc__]))
formatter.indent() formatter.indent()
result += "%*s%s\n" % (formatter.current_indent, "", cmd.usage.split('\n')[0]) result += "%*s%s\n" % (formatter.current_indent, "", cmd.usage.split("\n")[0])
formatter.dedent() formatter.dedent()
formatter.dedent() formatter.dedent()
return result return result
@ -110,9 +87,11 @@ class Console(_UI):
ConsoleUI(self.args, self.console_cmds, (self.options.daemon_addr, self.options.daemon_port, ConsoleUI(self.args, self.console_cmds, (self.options.daemon_addr, self.options.daemon_port,
self.options.daemon_user, self.options.daemon_pass)) self.options.daemon_user, self.options.daemon_pass))
def start(): def start():
Console().start() Console().start()
class DelugeHelpFormatter (optparse.IndentedHelpFormatter): class DelugeHelpFormatter (optparse.IndentedHelpFormatter):
""" """
Format help in a way suited to deluge Legacy mode - colors, format, indentation... Format help in a way suited to deluge Legacy mode - colors, format, indentation...
@ -165,7 +144,7 @@ class DelugeHelpFormatter (optparse.IndentedHelpFormatter):
opts = "%*s%s\n" % (self.current_indent, "", opts) opts = "%*s%s\n" % (self.current_indent, "", opts)
opts = self._format_colors(opts) opts = self._format_colors(opts)
indent_first = self.help_position indent_first = self.help_position
else: # start help on same line as opts else: # start help on same line as opts
opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts)
opts = self._format_colors(opts) opts = self._format_colors(opts)
indent_first = 0 indent_first = 0
@ -181,6 +160,7 @@ class DelugeHelpFormatter (optparse.IndentedHelpFormatter):
result.append("\n") result.append("\n")
return "".join(result) return "".join(result)
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."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -202,13 +182,13 @@ class OptionParser(optparse.OptionParser):
""" """
raise Exception(msg) raise Exception(msg)
def print_usage(self, file = None): def print_usage(self, file=None):
console = component.get("ConsoleUI") console = component.get("ConsoleUI")
if self.usage: if self.usage:
for line in self.get_usage().splitlines(): for line in self.get_usage().splitlines():
console.write(line) console.write(line)
def print_help(self, file = None): def print_help(self, file=None):
console = component.get("ConsoleUI") console = component.get("ConsoleUI")
console.set_batch_write(True) console.set_batch_write(True)
for line in self.format_help().splitlines(): for line in self.format_help().splitlines():
@ -235,19 +215,20 @@ class OptionParser(optparse.OptionParser):
class BaseCommand(object): class BaseCommand(object):
usage = 'usage' usage = "usage"
interactive_only = False interactive_only = False
option_list = tuple() option_list = tuple()
aliases = [] aliases = []
def complete(self, text, *args): def complete(self, text, *args):
return [] return []
def handle(self, *args, **options): def handle(self, *args, **options):
pass pass
@property @property
def name(self): def name(self):
return 'base' return "base"
@property @property
def epilog(self): def epilog(self):
@ -255,33 +236,30 @@ class BaseCommand(object):
def split(self, text): def split(self, text):
if deluge.common.windows_check(): if deluge.common.windows_check():
text = text.replace('\\', '\\\\') text = text.replace("\\", "\\\\")
result = shlex.split(text) result = shlex.split(text)
for i, s in enumerate(result): for i, s in enumerate(result):
result[i] = s.replace(r'\ ', ' ') result[i] = s.replace(r"\ ", " ")
result = filter(lambda s: s != '', result) result = filter(lambda s: s != "", result)
return result return result
def create_parser(self): def create_parser(self):
return OptionParser(prog = self.name, return OptionParser(prog=self.name, usage=self.usage, epilog=self.epilog, option_list=self.option_list)
usage = self.usage,
epilog = self.epilog,
option_list = self.option_list)
def load_commands(command_dir, exclude=[]): def load_commands(command_dir, 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")()
try: try:
commands = [] commands = []
for filename in os.listdir(command_dir): for filename in os.listdir(command_dir):
if filename.split('.')[0] in exclude or filename.startswith('_'): if filename.split(".")[0] in exclude or filename.startswith("_"):
continue continue
if not (filename.endswith('.py') or filename.endswith('.pyc')): if not (filename.endswith(".py") or filename.endswith(".pyc")):
continue continue
cmd = get_command(filename.split('.')[len(filename.split('.')) - 2]) cmd = get_command(filename.split(".")[len(filename.split(".")) - 2])
aliases = [ filename.split('.')[len(filename.split('.')) - 2] ] aliases = [filename.split(".")[len(filename.split(".")) - 2]]
aliases.extend(cmd.aliases) aliases.extend(cmd.aliases)
for a in aliases: for a in aliases:
commands.append((a, cmd)) commands.append((a, cmd))
@ -298,14 +276,13 @@ class ConsoleUI(component.Component):
self.events = [] self.events = []
try: try:
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, "")
self.encoding = locale.getpreferredencoding() self.encoding = locale.getpreferredencoding()
except: except:
self.encoding = sys.getdefaultencoding() self.encoding = sys.getdefaultencoding()
log.debug("Using encoding: %s", self.encoding) log.debug("Using encoding: %s", self.encoding)
# start up the session proxy # start up the session proxy
self.sessionproxy = SessionProxy() self.sessionproxy = SessionProxy()
@ -315,7 +292,7 @@ class ConsoleUI(component.Component):
self.interactive = True self.interactive = True
self._commands = cmds self._commands = cmds
if args: if args:
args = ' '.join(args) args = " ".join(args)
self.interactive = False self.interactive = False
if not cmds: if not cmds:
print("Sorry, couldn't find any commands") print("Sorry, couldn't find any commands")
@ -373,12 +350,12 @@ Please use commands from the command line, eg:\n
# Start the twisted mainloop # Start the twisted mainloop
reactor.run() reactor.run()
def start(self): def start(self):
# Maintain a list of (torrent_id, name) for use in tab completion # Maintain a list of (torrent_id, name) for use in tab completion
self.torrents = [] self.torrents = []
if not self.interactive: if not self.interactive:
self.started_deferred = defer.Deferred() self.started_deferred = defer.Deferred()
def on_session_state(result): def on_session_state(result):
def on_torrents_status(torrents): def on_torrents_status(torrents):
for torrent_id, status in torrents.items(): for torrent_id, status in torrents.items():
@ -388,7 +365,6 @@ Please use commands from the command line, eg:\n
client.core.get_torrents_status({"id": result}, ["name"]).addCallback(on_torrents_status) client.core.get_torrents_status({"id": result}, ["name"]).addCallback(on_torrents_status)
client.core.get_session_state().addCallback(on_session_state) client.core.get_session_state().addCallback(on_session_state)
def match_torrent(self, string): def match_torrent(self, string):
""" """
Returns a list of torrent_id matches for the string. It will search both Returns a list of torrent_id matches for the string. It will search both
@ -402,7 +378,7 @@ Please use commands from the command line, eg:\n
""" """
if self.interactive and isinstance(self.screen, deluge.ui.console.modes.legacy.Legacy): if self.interactive and isinstance(self.screen, deluge.ui.console.modes.legacy.Legacy):
return self.screen.match_torrent(string) return self.screen.match_torrent(string)
matches = [] matches = []
string = string.decode(self.encoding) string = string.decode(self.encoding)
for tid, name in self.torrents: for tid, name in self.torrents:
@ -411,7 +387,6 @@ Please use commands from the command line, eg:\n
return matches return matches
def get_torrent_name(self, torrent_id): def get_torrent_name(self, torrent_id):
if self.interactive and hasattr(self.screen, "get_torrent_name"): if self.interactive and hasattr(self.screen, "get_torrent_name"):
return self.screen.get_torrent_name(torrent_id) return self.screen.get_torrent_name(torrent_id)
@ -422,7 +397,6 @@ Please use commands from the command line, eg:\n
return None return None
def set_batch_write(self, batch): def set_batch_write(self, batch):
if self.interactive and isinstance(self.screen, deluge.ui.console.modes.legacy.Legacy): if self.interactive and isinstance(self.screen, deluge.ui.console.modes.legacy.Legacy):
return self.screen.set_batch_write(batch) return self.screen.set_batch_write(batch)

View File

@ -1,61 +1,33 @@
# -*- coding: utf-8 -*-
# #
# add_util.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
# Modified function from commands/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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import base64 import base64
import glob import glob
import logging import logging
import os import os
import deluge.common import deluge.common
import deluge.component as component
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.common import TorrentInfo from deluge.ui.common import TorrentInfo
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def __bracket_fixup(path): def __bracket_fixup(path):
if (path.find("[") == -1 and if (path.find("[") == -1 and path.find("]") == -1):
path.find("]") == -1):
return path return path
sentinal = 256 sentinal = 256
while (path.find(unichr(sentinal)) != -1): while (path.find(unichr(sentinal)) != -1):
sentinal+=1 sentinal += 1
if sentinal > 65535: if sentinal > 65535:
log.error("Can't fix brackets in path, path contains all possible sentinal characters") log.error("Can't fix brackets in path, path contains all possible sentinal characters")
return path return path
newpath = path.replace("]", unichr(sentinal)) newpath = path.replace("]", unichr(sentinal))
@ -63,16 +35,17 @@ def __bracket_fixup(path):
newpath = newpath.replace(unichr(sentinal), "[]]") newpath = newpath.replace(unichr(sentinal), "[]]")
return newpath return newpath
def add_torrent(t_file, options, success_cb, fail_cb, ress): def add_torrent(t_file, options, success_cb, fail_cb, ress):
t_options = {} t_options = {}
if options["path"]: if options["path"]:
t_options["download_location"] = os.path.expanduser(options["path"]) t_options["download_location"] = os.path.expanduser(options["path"])
t_options["add_paused"] = options["add_paused"] t_options["add_paused"] = options["add_paused"]
is_url = (not (options["path_type"]==1)) and (deluge.common.is_url(t_file) or options["path_type"]==2) is_url = (not (options["path_type"] == 1)) and (deluge.common.is_url(t_file) or options["path_type"] == 2)
is_mag = not(is_url) and (not (options["path_type"]==1)) and deluge.common.is_magnet(t_file) is_magnet = not(is_url) and (not (options["path_type"] == 1)) and deluge.common.is_magnet(t_file)
if is_url or is_mag: if is_url or is_magnet:
files = [t_file] files = [t_file]
else: else:
files = glob.glob(__bracket_fixup(t_file)) files = glob.glob(__bracket_fixup(t_file))
@ -85,7 +58,7 @@ def add_torrent(t_file, options, success_cb, fail_cb, ress):
for f in files: for f in files:
if is_url: if is_url:
client.core.add_torrent_url(f, t_options).addCallback(success_cb, f, ress).addErrback(fail_cb, f, ress) client.core.add_torrent_url(f, t_options).addCallback(success_cb, f, ress).addErrback(fail_cb, f, ress)
elif is_mag: elif is_magnet:
client.core.add_torrent_magnet(f, t_options).addCallback(success_cb, f, ress).addErrback(fail_cb, f, ress) client.core.add_torrent_magnet(f, t_options).addCallback(success_cb, f, ress).addErrback(fail_cb, f, ress)
else: else:
if not os.path.exists(f): if not os.path.exists(f):
@ -104,4 +77,5 @@ def add_torrent(t_file, options, success_cb, fail_cb, ress):
filename = os.path.split(f)[-1] filename = os.path.split(f)[-1]
filedump = base64.encodestring(open(f).read()) filedump = base64.encodestring(open(f).read())
client.core.add_torrent_file(filename, filedump, t_options).addCallback(success_cb, f, ress).addErrback(fail_cb, f, ress) client.core.add_torrent_file(filename, filedump, t_options
).addCallback(success_cb, f, ress).addErrback(fail_cb, f, ress)

View File

@ -1,37 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# addtorrents.py
#
# Copyright (C) 2012 Arek Stefański <asmageddon@gmail.com> # Copyright (C) 2012 Arek Stefański <asmageddon@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import base64 import base64
@ -40,13 +13,11 @@ import os
import deluge.common as common import deluge.common as common
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors
import format_utils import format_utils
from basemode import BaseMode from basemode import BaseMode
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.sessionproxy import SessionProxy
from input_popup import InputPopup from input_popup import InputPopup
from popup import MessagePopup, Popup from popup import MessagePopup
try: try:
import curses import curses
@ -83,6 +54,7 @@ if a file is highlighted
{!info!}'q'{!normal!} - Go back to torrent overview {!info!}'q'{!normal!} - Go back to torrent overview
""" """
class AddTorrents(BaseMode, component.Component): class AddTorrents(BaseMode, component.Component):
def __init__(self, alltorrentmode, stdscr, console_config, encoding=None): def __init__(self, alltorrentmode, stdscr, console_config, encoding=None):
@ -118,7 +90,6 @@ class AddTorrents(BaseMode, component.Component):
self.__refresh_listing() self.__refresh_listing()
component.Component.__init__(self, "AddTorrents", 1, depend=["SessionProxy"]) component.Component.__init__(self, "AddTorrents", 1, depend=["SessionProxy"])
component.start(["AddTorrents"]) component.start(["AddTorrents"])
@ -129,6 +100,7 @@ class AddTorrents(BaseMode, component.Component):
# component start/update # component start/update
def start(self): def start(self):
pass pass
def update(self): def update(self):
pass pass
@ -168,8 +140,8 @@ class AddTorrents(BaseMode, component.Component):
row = [dirname, size, time, full_path, 1] row = [dirname, size, time, full_path, 1]
self.raw_rows.append( row ) self.raw_rows.append(row)
self.raw_rows_dirs.append( row ) self.raw_rows_dirs.append(row)
#Highlight the directory we came from #Highlight the directory we came from
if self.path_stack_pos < len(self.path_stack): if self.path_stack_pos < len(self.path_stack):
@ -189,8 +161,8 @@ class AddTorrents(BaseMode, component.Component):
row = [filename, size, time, full_path, 0] row = [filename, size, time, full_path, 0]
self.raw_rows.append( row ) self.raw_rows.append(row)
self.raw_rows_files.append( row ) self.raw_rows_files.append(row)
self.__sort_rows() self.__sort_rows()
@ -201,7 +173,7 @@ class AddTorrents(BaseMode, component.Component):
self.raw_rows_dirs.sort(key=lambda r: r[0].lower()) self.raw_rows_dirs.sort(key=lambda r: r[0].lower())
if self.sort_column == "name": if self.sort_column == "name":
self.raw_rows_files.sort(key=lambda r: r[0].lower(), reverse=self.reverse_sort) self.raw_rows_files.sort(key=lambda r: r[0].lower(), reverse=self.reverse_sort)
elif self.sort_column == "date": elif self.sort_column == "date":
self.raw_rows_files.sort(key=lambda r: r[2], reverse=self.reverse_sort) self.raw_rows_files.sort(key=lambda r: r[2], reverse=self.reverse_sort)
@ -275,7 +247,7 @@ class AddTorrents(BaseMode, component.Component):
self.refresh() self.refresh()
def refresh(self,lines=None): def refresh(self, lines=None):
# Update the status bars # Update the status bars
self.stdscr.erase() self.stdscr.erase()
@ -287,7 +259,7 @@ class AddTorrents(BaseMode, component.Component):
string = self.statusbars.bottombar string = self.statusbars.bottombar
hstr = "Press {!magenta,blue,bold!}[h]{!status!} for help" hstr = "Press {!magenta,blue,bold!}[h]{!status!} for help"
string += " " * ( self.cols - len(rf(string)) - len(rf(hstr))) + hstr string += " " * (self.cols - len(rf(string)) - len(rf(hstr))) + hstr
self.add_string(self.rows - 1, string) self.add_string(self.rows - 1, string)
except: except:
@ -321,8 +293,10 @@ class AddTorrents(BaseMode, component.Component):
s = "" s = ""
for i, (c, w) in enumerate(zip(cols, widths)): for i, (c, w) in enumerate(zip(cols, widths)):
cn = "" cn = ""
if i == 0: cn = "name" if i == 0:
elif i == 2: cn = "date" cn = "name"
elif i == 2:
cn = "date"
if cn == self.sort_column: if cn == self.sort_column:
s += "{!black,green,bold!}" + c.ljust(w - 2) s += "{!black,green,bold!}" + c.ljust(w - 2)
@ -361,7 +335,7 @@ class AddTorrents(BaseMode, component.Component):
color_string = "{!white,blue,bold!}" color_string = "{!white,blue,bold!}"
self.add_string(off, color_string + row) self.add_string(off, color_string + row)
off+= 1 off += 1
if off > self.rows - 2: if off > self.rows - 2:
break break
@ -403,7 +377,7 @@ class AddTorrents(BaseMode, component.Component):
self.path_stack = self.path_stack[:self.path_stack_pos] self.path_stack = self.path_stack[:self.path_stack_pos]
self.path_stack.append(dirname) self.path_stack.append(dirname)
path = os.path.join(*self.path_stack[:self.path_stack_pos+1]) path = os.path.join(*self.path_stack[:self.path_stack_pos + 1])
if not os.access(path, os.R_OK): if not os.access(path, os.R_OK):
self.path_stack = self.path_stack[:self.path_stack_pos] self.path_stack = self.path_stack[:self.path_stack_pos]
@ -423,23 +397,20 @@ class AddTorrents(BaseMode, component.Component):
def _show_add_dialog(self): def _show_add_dialog(self):
def _do_add(result): def _do_add(result):
ress = {"succ":0, ress = {"succ": 0, "fail": 0, "total": len(self.marked), "fmsg": []}
"fail":0,
"total": len(self.marked),
"fmsg":[]}
def fail_cb(msg, t_file, ress): def fail_cb(msg, t_file, ress):
log.debug("failed to add torrent: %s: %s"%(t_file, msg)) log.debug("failed to add torrent: %s: %s" % (t_file, msg))
ress["fail"]+=1 ress["fail"] += 1
ress["fmsg"].append("{!input!} * %s: {!error!}%s"%(t_file, msg)) ress["fmsg"].append("{!input!} * %s: {!error!}%s" % (t_file, msg))
if (ress["succ"]+ress["fail"]) >= ress["total"]: if (ress["succ"] + ress["fail"]) >= ress["total"]:
self.alltorrentmode._report_add_status(ress["succ"], ress["fail"], ress["fmsg"]) self.alltorrentmode._report_add_status(ress["succ"], ress["fail"], ress["fmsg"])
def success_cb(tid, t_file, ress): def success_cb(tid, t_file, ress):
if tid: if tid:
log.debug("added torrent: %s (%s)"%(t_file, tid)) log.debug("added torrent: %s (%s)" % (t_file, tid))
ress["succ"]+=1 ress["succ"] += 1
if (ress["succ"]+ress["fail"]) >= ress["total"]: if (ress["succ"] + ress["fail"]) >= ress["total"]:
self.alltorrentmode._report_add_status(ress["succ"], ress["fail"], ress["fmsg"]) self.alltorrentmode._report_add_status(ress["succ"], ress["fail"], ress["fmsg"])
else: else:
fail_cb("Already in session (probably)", t_file, ress) fail_cb("Already in session (probably)", t_file, ress)
@ -485,7 +456,6 @@ class AddTorrents(BaseMode, component.Component):
self.popup.add_text_input("Download Folder:", "location", dl) self.popup.add_text_input("Download Folder:", "location", dl)
self.popup.add_select_input("Add Paused:", "add_paused", ["Yes", "No"], [True, False], ap) self.popup.add_select_input("Add Paused:", "add_paused", ["Yes", "No"], [True, False], ap)
def _go_up(self): def _go_up(self):
#Go up in directory hierarchy #Go up in directory hierarchy
if self.path_stack_pos > 1: if self.path_stack_pos > 1:
@ -508,7 +478,7 @@ class AddTorrents(BaseMode, component.Component):
return return
if c > 31 and c < 256: if c > 31 and c < 256:
if chr(c) == 'Q': if chr(c) == "Q":
from twisted.internet import reactor from twisted.internet import reactor
if client.connected(): if client.connected():
def on_disconnect(result): def on_disconnect(result):
@ -517,7 +487,7 @@ class AddTorrents(BaseMode, component.Component):
else: else:
reactor.stop() reactor.stop()
return return
elif chr(c) == 'q': elif chr(c) == "q":
self.back_to_overview() self.back_to_overview()
return return
@ -549,23 +519,23 @@ class AddTorrents(BaseMode, component.Component):
self.back_to_overview() self.back_to_overview()
else: else:
if c > 31 and c < 256: if c > 31 and c < 256:
if chr(c) == 'h': if chr(c) == "h":
self.popup = MessagePopup(self, "Help", HELP_STR, width_req=0.75) self.popup = MessagePopup(self, "Help", HELP_STR, width_req=0.75)
elif chr(c) == '>': elif chr(c) == ">":
if self.sort_column == "date": if self.sort_column == "date":
self.reverse_sort = not self.reverse_sort self.reverse_sort = not self.reverse_sort
else: else:
self.sort_column = "date" self.sort_column = "date"
self.reverse_sort = True self.reverse_sort = True
self.__sort_rows() self.__sort_rows()
elif chr(c) == '<': elif chr(c) == "<":
if self.sort_column == "name": if self.sort_column == "name":
self.reverse_sort = not self.reverse_sort self.reverse_sort = not self.reverse_sort
else: else:
self.sort_column = "name" self.sort_column = "name"
self.reverse_sort = False self.reverse_sort = False
self.__sort_rows() self.__sort_rows()
elif chr(c) == 'm': elif chr(c) == "m":
s = self.raw_rows[self.cursel][0] s = self.raw_rows[self.cursel][0]
if s in self.marked: if s in self.marked:
self.marked.remove(s) self.marked.remove(s)
@ -573,11 +543,11 @@ class AddTorrents(BaseMode, component.Component):
self.marked.add(s) self.marked.add(s)
self.last_mark = self.cursel self.last_mark = self.cursel
elif chr(c) == 'j': elif chr(c) == "j":
self.scroll_list_up(1) self.scroll_list_up(1)
elif chr(c) == 'k': elif chr(c) == "k":
self.scroll_list_down(1) self.scroll_list_down(1)
elif chr(c) == 'M': elif chr(c) == "M":
if self.last_mark != -1: if self.last_mark != -1:
if self.last_mark > self.cursel: if self.last_mark > self.cursel:
m = range(self.cursel, self.last_mark) m = range(self.cursel, self.last_mark)

View File

@ -1,58 +1,27 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# alltorrents.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import logging import logging
from collections import deque from collections import deque
from twisted.internet import defer import column as console_column
import column
import deluge.common import deluge.common
import deluge.component as component import deluge.component as component
import format_utils import format_utils
from add_util import add_torrent
from addtorrents import AddTorrents from addtorrents import AddTorrents
from basemode import BaseMode from basemode import BaseMode
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.sessionproxy import SessionProxy
from eventview import EventView from eventview import EventView
from input_popup import ALIGN, InputPopup from input_popup import InputPopup
from legacy import Legacy from legacy import Legacy
from popup import ALIGN, MessagePopup, Popup, SelectablePopup from popup import MessagePopup, Popup, SelectablePopup
from preferences import Preferences from preferences import Preferences
from torrent_actions import ACTION, torrent_actions_popup from torrent_actions import ACTION, torrent_actions_popup
from torrentdetail import TorrentDetail from torrentdetail import TorrentDetail
@ -133,17 +102,18 @@ files in the torrent and the ability to set file priorities.
input something input something
""" """
class FILTER: class FILTER:
ALL=0 ALL = 0
ACTIVE=1 ACTIVE = 1
DOWNLOADING=2 DOWNLOADING = 2
SEEDING=3 SEEDING = 3
PAUSED=4 PAUSED = 4
CHECKING=5 CHECKING = 5
ERROR=6 ERROR = 6
QUEUED=7 QUEUED = 7
ALLOCATING=8 ALLOCATING = 8
MOVING=9 MOVING = 9
DEFAULT_PREFS = { DEFAULT_PREFS = {
"show_queue": True, "show_queue": True,
@ -199,21 +169,21 @@ DEFAULT_PREFS = {
"down_limit_width": 7, "down_limit_width": 7,
"up_limit_width": 7, "up_limit_width": 7,
"shared_width": 10, "shared_width": 10,
"ignore_duplicate_lines": False, "ignore_duplicate_lines": False,
"move_selection": True, "move_selection": True,
"third_tab_lists_all": False, "third_tab_lists_all": False,
"torrents_per_tab_press": 15, "torrents_per_tab_press": 15,
"sort_primary": "queue", "sort_primary": "queue",
"sort_secondary": "name", "sort_secondary": "name",
"separate_complete": True, "separate_complete": True,
"ring_bell": False, "ring_bell": False,
"save_legacy_history": True, "save_legacy_history": True,
"first_run": True, "first_run": True,
"addtorrents_show_misc_files": False, #TODO: Showing/hiding this "addtorrents_show_misc_files": False, # TODO: Showing/hiding this
"addtorrents_show_hidden_folders": False, #TODO: Showing/hiding this "addtorrents_show_hidden_folders": False, # TODO: Showing/hiding this
"addtorrents_sort_column": "date", "addtorrents_sort_column": "date",
"addtorrents_reverse_sort": True, "addtorrents_reverse_sort": True,
"addtorrents_last_path": "~" "addtorrents_last_path": "~"
} }
column_pref_names = ["queue", "name", "size", "state", "progress", "seeds", column_pref_names = ["queue", "name", "size", "state", "progress", "seeds",
@ -300,13 +270,14 @@ SEARCH_SUCCESS = 2
SEARCH_START_REACHED = 3 SEARCH_START_REACHED = 3
SEARCH_END_REACHED = 4 SEARCH_END_REACHED = 4
class AllTorrents(BaseMode, component.Component): class AllTorrents(BaseMode, component.Component):
def __init__(self, stdscr, encoding=None): def __init__(self, stdscr, encoding=None):
self.torrent_names = None self.torrent_names = None
self.numtorrents = -1 self.numtorrents = -1
self._cached_rows = {} self._cached_rows = {}
self.cursel = 1 self.cursel = 1
self.curoff = 1 # TODO: this should really be 0 indexed self.curoff = 1 # TODO: this should really be 0 indexed
self.column_string = "" self.column_string = ""
self.popup = None self.popup = None
self.messages = deque() self.messages = deque()
@ -357,15 +328,15 @@ class AllTorrents(BaseMode, component.Component):
("Availability", format_utils.format_float, ("distributed_copies",)), ("Availability", format_utils.format_float, ("distributed_copies",)),
("Pieces", format_utils.format_pieces, ("num_pieces", "piece_length")), ("Pieces", format_utils.format_pieces, ("num_pieces", "piece_length")),
("Seed Rank", str, ("seed_rank",)), ("Seed Rank", str, ("seed_rank",)),
] ]
self.__status_keys = ["name", "state", "download_payload_rate", "upload_payload_rate", self.__status_keys = ["name", "state", "download_payload_rate", "upload_payload_rate",
"progress", "eta", "download_location", "all_time_download", "total_uploaded", "progress", "eta", "download_location", "all_time_download", "total_uploaded",
"ratio", "num_seeds", "total_seeds", "num_peers", "total_peers", "ratio", "num_seeds", "total_seeds", "num_peers", "total_peers",
"active_time", "seeding_time", "last_seen_complete", "time_added", "active_time", "seeding_time", "last_seen_complete", "time_added",
"completed_time", "distributed_copies", "num_pieces", "piece_length", "completed_time", "distributed_copies", "num_pieces", "piece_length",
"seed_rank" "seed_rank"
] ]
self.legacy_mode = Legacy(self.stdscr, self.encoding) self.legacy_mode = Legacy(self.stdscr, self.encoding)
@ -376,25 +347,25 @@ class AllTorrents(BaseMode, component.Component):
# component start/update # component start/update
def start(self): def start(self):
component.get("SessionProxy").get_torrents_status(self.__status_dict, self.__status_fields).addCallback(self.set_state, False) component.get("SessionProxy").get_torrents_status(self.__status_dict, self.__status_fields
).addCallback(self.set_state, False)
def update(self): def update(self):
component.get("SessionProxy").get_torrents_status(self.__status_dict, self.__status_fields).addCallback(self.set_state, True) component.get("SessionProxy").get_torrents_status(self.__status_dict, self.__status_fields
).addCallback(self.set_state, True)
if self.__torrent_info_id: if self.__torrent_info_id:
component.get("SessionProxy").get_torrent_status(self.__torrent_info_id, self.__status_keys).addCallback(self._on_torrent_status) component.get("SessionProxy").get_torrent_status(self.__torrent_info_id, self.__status_keys
).addCallback(self._on_torrent_status)
def update_config(self): def update_config(self):
self.config = ConfigManager("console.conf", DEFAULT_PREFS) self.config = ConfigManager("console.conf", DEFAULT_PREFS)
s_primary = self.config["sort_primary"] s_primary = self.config["sort_primary"]
s_secondary = self.config["sort_secondary"] s_secondary = self.config["sort_secondary"]
self.__cols_to_show = [ self.__cols_to_show = [pref for pref in column_pref_names
pref for pref in column_pref_names if ("show_%s" % pref) not in self.config or self.config["show_%s" % pref]]
if ("show_%s" % pref) not in self.config
or self.config["show_%s"%pref]
]
self.__columns = [prefs_to_names[col] for col in self.__cols_to_show] self.__columns = [prefs_to_names[col] for col in self.__cols_to_show]
self.__status_fields = column.get_required_fields(self.__columns) self.__status_fields = console_column.get_required_fields(self.__columns)
# we always need these, even if we're not displaying them # we always need these, even if we're not displaying them
for rf in ["state", "name", "queue", "progress"]: for rf in ["state", "name", "queue", "progress"]:
@ -414,17 +385,17 @@ class AllTorrents(BaseMode, component.Component):
self.refresh() self.refresh()
def __update_columns(self): def __update_columns(self):
self.column_widths = [self.config["%s_width"%c] for c in self.__cols_to_show] self.column_widths = [self.config["%s_width" % c] for c in self.__cols_to_show]
req = sum(filter(lambda x:x >= 0, self.column_widths)) req = sum(filter(lambda x: x >= 0, self.column_widths))
if (req > self.cols): # can't satisfy requests, just spread out evenly if (req > self.cols): # can't satisfy requests, just spread out evenly
cw = int(self.cols/len(self.__columns)) cw = int(self.cols / len(self.__columns))
for i in range(0, len(self.column_widths)): for i in range(0, len(self.column_widths)):
self.column_widths[i] = cw self.column_widths[i] = cw
else: else:
rem = self.cols - req rem = self.cols - req
var_cols = len(filter(lambda x: x < 0, self.column_widths)) var_cols = len(filter(lambda x: x < 0, self.column_widths))
if (var_cols > 0): if (var_cols > 0):
vw = int(rem/var_cols) vw = int(rem / var_cols)
for i in range(0, len(self.column_widths)): for i in range(0, len(self.column_widths)):
if (self.column_widths[i] < 0): if (self.column_widths[i] < 0):
self.column_widths[i] = vw self.column_widths[i] = vw
@ -457,7 +428,7 @@ class AllTorrents(BaseMode, component.Component):
self.column_string += ccol self.column_string += ccol
def set_state(self, state, refresh): def set_state(self, state, refresh):
self.curstate = state # cache in case we change sort order self.curstate = state # cache in case we change sort order
newnames = [] newnames = []
self._cached_rows = {} self._cached_rows = {}
self._sorted_ids = self._sort_torrents(self.curstate) self._sorted_ids = self._sort_torrents(self.curstate)
@ -492,50 +463,48 @@ class AllTorrents(BaseMode, component.Component):
def current_torrent_id(self): def current_torrent_id(self):
if self._sorted_ids: if self._sorted_ids:
return self._sorted_ids[self.cursel-1] return self._sorted_ids[self.cursel - 1]
else: else:
return None return None
def _selected_torrent_ids(self): def _selected_torrent_ids(self):
ret = [] ret = []
for i in self.marked: for i in self.marked:
ret.append(self._sorted_ids[i-1]) ret.append(self._sorted_ids[i - 1])
return ret return ret
def _on_torrent_status(self, state): def _on_torrent_status(self, state):
if (self.popup): if (self.popup):
self.popup.clear() self.popup.clear()
name = state["name"] name = state["name"]
off = int((self.cols/4)-(len(name)/2))
self.popup.set_title(name) self.popup.set_title(name)
for i, f in enumerate(self._info_fields): for i, f in enumerate(self._info_fields):
if f[1] != None: if f[1] is not None:
args = [] args = []
try: try:
for key in f[2]: for key in f[2]:
args.append(state[key]) args.append(state[key])
except: except Exception as ex:
log.debug("Could not get info field: %s", e) log.debug("Could not get info field: %s", ex)
continue continue
info = f[1](*args) info = f[1](*args)
else: else:
info = state[f[2][0]] info = state[f[2][0]]
nl = len(f[0])+4 nl = len(f[0]) + 4
if (nl+len(info))>self.popup.width: if (nl + len(info)) > self.popup.width:
self.popup.add_line("{!info!}%s: {!input!}%s"%(f[0], info[:(self.popup.width - nl)])) self.popup.add_line("{!info!}%s: {!input!}%s" % (f[0], info[:(self.popup.width - nl)]))
info = info[(self.popup.width - nl):] info = info[(self.popup.width - nl):]
n = self.popup.width-3 n = self.popup.width - 3
chunks = [info[i:i+n] for i in xrange(0, len(info), n)] chunks = [info[i:i + n] for i in xrange(0, len(info), n)]
for c in chunks: for c in chunks:
self.popup.add_line(" %s"%c) self.popup.add_line(" %s" % c)
else: else:
self.popup.add_line("{!info!}%s: {!input!}%s"%(f[0], info)) self.popup.add_line("{!info!}%s: {!input!}%s" % (f[0], info))
self.refresh() self.refresh()
else: else:
self.__torrent_info_id = None self.__torrent_info_id = None
def on_resize(self, *args): def on_resize(self, *args):
BaseMode.on_resize_norefresh(self, *args) BaseMode.on_resize_norefresh(self, *args)
if self.popup: if self.popup:
@ -564,7 +533,7 @@ class AllTorrents(BaseMode, component.Component):
if not state: if not state:
return {} return {}
s_primary = self.config["sort_primary"] s_primary = self.config["sort_primary"]
s_secondary = self.config["sort_secondary"] s_secondary = self.config["sort_secondary"]
result = state result = state
@ -575,6 +544,7 @@ class AllTorrents(BaseMode, component.Component):
cmp_func = self._queue_sort cmp_func = self._queue_sort
sg = state.get sg = state.get
def sort_by_field(state, result, field): def sort_by_field(state, result, field):
if field in column_names_to_state_keys: if field in column_names_to_state_keys:
field = column_names_to_state_keys[field] field = column_names_to_state_keys[field]
@ -585,10 +555,10 @@ class AllTorrents(BaseMode, component.Component):
# and if it's a string # and if it's a string
first_element = state[state.keys()[0]] first_element = state[state.keys()[0]]
if field in first_element: if field in first_element:
is_string = isinstance( first_element[field], basestring) is_string = isinstance(first_element[field], basestring)
sort_key = lambda s:sg(s)[field] sort_key = lambda s: sg(s)[field]
sort_key2 = lambda s:sg(s)[field].lower() sort_key2 = lambda s: sg(s)[field].lower()
#If it's a string, sort case-insensitively but preserve A>a order #If it's a string, sort case-insensitively but preserve A>a order
if is_string: if is_string:
@ -617,7 +587,7 @@ class AllTorrents(BaseMode, component.Component):
def _format_queue(self, qnum): def _format_queue(self, qnum):
if (qnum >= 0): if (qnum >= 0):
return "%d"%(qnum+1) return "%d" % (qnum + 1)
else: else:
return "" return ""
@ -650,14 +620,14 @@ class AllTorrents(BaseMode, component.Component):
def doprefs(arg): def doprefs(arg):
if arg and True in arg[0]: if arg and True in arg[0]:
self.stdscr.erase() self.stdscr.erase()
component.get("ConsoleUI").set_mode(Preferences(self, config, self.config, port, status, self.stdscr, self.encoding)) component.get("ConsoleUI").set_mode(Preferences(self, config, self.config, port,
status, self.stdscr, self.encoding))
else: else:
self.messages.append(("Error", "An error occurred trying to display preferences")) self.messages.append(("Error", "An error occurred trying to display preferences"))
component.stop(["AllTorrents"]).addCallback(doprefs) component.stop(["AllTorrents"]).addCallback(doprefs)
client.core.get_config().addCallback(_on_get_config) client.core.get_config().addCallback(_on_get_config)
def __show_events(self): def __show_events(self):
def doevents(arg): def doevents(arg):
if arg and True in arg[0]: if arg and True in arg[0]:
@ -679,35 +649,35 @@ class AllTorrents(BaseMode, component.Component):
component.stop(["AllTorrents"]).addCallback(dolegacy) component.stop(["AllTorrents"]).addCallback(dolegacy)
def _torrent_filter(self, idx, data): def _torrent_filter(self, idx, data):
if data==FILTER.ALL: if data == FILTER.ALL:
self.__status_dict = {} self.__status_dict = {}
self._curr_filter = None self._curr_filter = None
elif data==FILTER.ACTIVE: elif data == FILTER.ACTIVE:
self.__status_dict = {"state":"Active"} self.__status_dict = {"state": "Active"}
self._curr_filter = "Active" self._curr_filter = "Active"
elif data==FILTER.DOWNLOADING: elif data == FILTER.DOWNLOADING:
self.__status_dict = {"state":"Downloading"} self.__status_dict = {"state": "Downloading"}
self._curr_filter = "Downloading" self._curr_filter = "Downloading"
elif data==FILTER.SEEDING: elif data == FILTER.SEEDING:
self.__status_dict = {"state":"Seeding"} self.__status_dict = {"state": "Seeding"}
self._curr_filter = "Seeding" self._curr_filter = "Seeding"
elif data==FILTER.PAUSED: elif data == FILTER.PAUSED:
self.__status_dict = {"state":"Paused"} self.__status_dict = {"state": "Paused"}
self._curr_filter = "Paused" self._curr_filter = "Paused"
elif data==FILTER.CHECKING: elif data == FILTER.CHECKING:
self.__status_dict = {"state":"Checking"} self.__status_dict = {"state": "Checking"}
self._curr_filter = "Checking" self._curr_filter = "Checking"
elif data==FILTER.ERROR: elif data == FILTER.ERROR:
self.__status_dict = {"state":"Error"} self.__status_dict = {"state": "Error"}
self._curr_filter = "Error" self._curr_filter = "Error"
elif data==FILTER.QUEUED: elif data == FILTER.QUEUED:
self.__status_dict = {"state":"Queued"} self.__status_dict = {"state": "Queued"}
self._curr_filter = "Queued" self._curr_filter = "Queued"
elif data==FILTER.ALLOCATING: elif data == FILTER.ALLOCATING:
self.__status_dict = {"state":"Allocating"} self.__status_dict = {"state": "Allocating"}
self._curr_filter = "Allocating" self._curr_filter = "Allocating"
elif data==FILTER.MOVING: elif data == FILTER.MOVING:
self.__status_dict = {"state":"Moving"} self.__status_dict = {"state": "Moving"}
self._curr_filter = "Moving" self._curr_filter = "Moving"
self._go_top = True self._go_top = True
@ -728,11 +698,11 @@ class AllTorrents(BaseMode, component.Component):
def _report_add_status(self, succ_cnt, fail_cnt, fail_msgs): def _report_add_status(self, succ_cnt, fail_cnt, fail_msgs):
if fail_cnt == 0: if fail_cnt == 0:
self.report_message("Torrents Added", "{!success!}Successfully added %d torrent(s)"%succ_cnt) self.report_message("Torrents Added", "{!success!}Successfully added %d torrent(s)" % succ_cnt)
else: else:
msg = ("{!error!}Failed to add the following %d torrent(s):\n {!input!}"%fail_cnt)+"\n ".join(fail_msgs) msg = ("{!error!}Failed to add the following %d torrent(s):\n {!input!}" % fail_cnt) + "\n ".join(fail_msgs)
if succ_cnt != 0: if succ_cnt != 0:
msg += "\n \n{!success!}Successfully added %d torrent(s)"%succ_cnt msg += "\n \n{!success!}Successfully added %d torrent(s)" % succ_cnt
self.report_message("Torrent Add Report", msg) self.report_message("Torrent Add Report", msg)
def _show_torrent_add_popup(self): def _show_torrent_add_popup(self):
@ -741,11 +711,11 @@ class AllTorrents(BaseMode, component.Component):
def fail_cb(msg, url): def fail_cb(msg, url):
log.debug("failed to add torrent: %s: %s" % (url, msg)) log.debug("failed to add torrent: %s: %s" % (url, msg))
error_msg = "{!input!} * %s: {!error!}%s" % (url, msg) error_msg = "{!input!} * %s: {!error!}%s" % (url, msg)
self._report_add_status(0, 1, [error_msg] ) self._report_add_status(0, 1, [error_msg])
def success_cb(tid, url): def success_cb(tid, url):
if tid: if tid:
log.debug("added torrent: %s (%s)"%(url, tid)) log.debug("added torrent: %s (%s)" % (url, tid))
self._report_add_status(1, 0, []) self._report_add_status(1, 0, [])
else: else:
fail_cb("Already in session (probably)", url) fail_cb("Already in session (probably)", url)
@ -794,7 +764,7 @@ class AllTorrents(BaseMode, component.Component):
if not data: if not data:
return return
if data == 1: if data == 1:
self.show_addtorrents_screen() self.show_addtorrents_screen()
elif data == 2: elif data == 2:
show_add_url_popup() show_add_url_popup()
@ -804,7 +774,6 @@ class AllTorrents(BaseMode, component.Component):
self.popup.add_line("From _URL or Magnet", data=2) self.popup.add_line("From _URL or Magnet", data=2)
self.popup.add_line("_Cancel", data=0) self.popup.add_line("_Cancel", data=0)
def _do_set_column_visibility(self, data): def _do_set_column_visibility(self, data):
for key, value in data.items(): for key, value in data.items():
self.config[key] = value self.config[key] = value
@ -815,13 +784,9 @@ class AllTorrents(BaseMode, component.Component):
def _show_visible_columns_popup(self): def _show_visible_columns_popup(self):
title = "Visible columns (Enter to exit)" title = "Visible columns (Enter to exit)"
self.popup = InputPopup(self, self.popup = InputPopup(self, title, close_cb=self._do_set_column_visibility,
title, immediate_action=True, height_req=len(column_pref_names) + 1,
close_cb=self._do_set_column_visibility, width_req=max([len(col) for col in column_pref_names + [title]]) + 8)
immediate_action=True,
height_req= len(column_pref_names) + 1,
width_req= max([len(col) for col in column_pref_names + [title]]) + 8
)
for col in column_pref_names: for col in column_pref_names:
name = prefs_to_names[col] name = prefs_to_names[col]
@ -843,7 +808,7 @@ class AllTorrents(BaseMode, component.Component):
self.popup = pu self.popup = pu
self.refresh() self.refresh()
def refresh(self,lines=None): def refresh(self, lines=None):
#log.error("ref") #log.error("ref")
#import traceback #import traceback
#traceback.print_stack() #traceback.print_stack()
@ -854,7 +819,7 @@ class AllTorrents(BaseMode, component.Component):
self._go_top = False self._go_top = False
# show a message popup if there's anything queued # show a message popup if there's anything queued
if self.popup == None and self.messages: if self.popup is None and self.messages:
title, msg = self.messages.popleft() title, msg = self.messages.popleft()
self.popup = MessagePopup(self, title, msg, width_req=1.0) self.popup = MessagePopup(self, title, msg, width_req=1.0)
@ -864,10 +829,10 @@ class AllTorrents(BaseMode, component.Component):
self.stdscr.erase() self.stdscr.erase()
# Update the status bars # Update the status bars
if self._curr_filter == None: if self._curr_filter is None:
self.add_string(0, self.statusbars.topbar) self.add_string(0, self.statusbars.topbar)
else: else:
self.add_string(0, "%s {!filterstatus!}Current filter: %s"%(self.statusbars.topbar, self._curr_filter)) self.add_string(0, "%s {!filterstatus!}Current filter: %s" % (self.statusbars.topbar, self._curr_filter))
self.add_string(1, self.column_string) self.add_string(1, self.column_string)
if self.entering_search: if self.entering_search:
@ -875,7 +840,8 @@ class AllTorrents(BaseMode, component.Component):
SEARCH_EMPTY: "{!black,white!}Search torrents: %s{!black,white!}", SEARCH_EMPTY: "{!black,white!}Search torrents: %s{!black,white!}",
SEARCH_SUCCESS: "{!black,white!}Search torrents: {!black,green!}%s{!black,white!}", SEARCH_SUCCESS: "{!black,white!}Search torrents: {!black,green!}%s{!black,white!}",
SEARCH_FAILING: "{!black,white!}Search torrents: {!black,red!}%s{!black,white!}", SEARCH_FAILING: "{!black,white!}Search torrents: {!black,red!}%s{!black,white!}",
SEARCH_START_REACHED: "{!black,white!}Search torrents: {!black,yellow!}%s{!black,white!} (start reached)", SEARCH_START_REACHED:
"{!black,white!}Search torrents: {!black,yellow!}%s{!black,white!} (start reached)",
SEARCH_END_REACHED: "{!black,white!}Search torrents: {!black,yellow!}%s{!black,white!} (end reached)" SEARCH_END_REACHED: "{!black,white!}Search torrents: {!black,yellow!}%s{!black,white!} (end reached)"
}[self.search_state] % self.search_string }[self.search_state] % self.search_string
@ -887,7 +853,7 @@ class AllTorrents(BaseMode, component.Component):
string = self.statusbars.bottombar string = self.statusbars.bottombar
hstr = "Press {!magenta,blue,bold!}[h]{!status!} for help" hstr = "Press {!magenta,blue,bold!}[h]{!status!} for help"
string += " " * ( self.cols - len(rf(string)) - len(rf(hstr))) + hstr string += " " * (self.cols - len(rf(string)) - len(rf(hstr))) + hstr
self.add_string(self.rows - 1, string) self.add_string(self.rows - 1, string)
except: except:
@ -896,7 +862,7 @@ class AllTorrents(BaseMode, component.Component):
# add all the torrents # add all the torrents
if self.numtorrents == 0: if self.numtorrents == 0:
msg = "No torrents match filter".center(self.cols) msg = "No torrents match filter".center(self.cols)
self.add_string(3, "{!info!}%s"%msg) self.add_string(3, "{!info!}%s" % msg)
elif self.numtorrents > 0: elif self.numtorrents > 0:
tidx = self.curoff tidx = self.curoff
currow = 2 currow = 2
@ -904,11 +870,12 @@ class AllTorrents(BaseMode, component.Component):
#Because dots are slow #Because dots are slow
sorted_ids = self._sorted_ids sorted_ids = self._sorted_ids
curstate = self.curstate curstate = self.curstate
gcv = column.get_column_value gcv = console_column.get_column_value
fr = format_utils.format_row fr = format_utils.format_row
cols = self.__columns cols = self.__columns
colw = self.column_widths colw = self.column_widths
cr = self._cached_rows cr = self._cached_rows
def draw_row(index): def draw_row(index):
if index not in cr: if index not in cr:
ts = curstate[sorted_ids[index]] ts = curstate[sorted_ids[index]]
@ -918,15 +885,19 @@ class AllTorrents(BaseMode, component.Component):
if lines: if lines:
todraw = [] todraw = []
for l in lines: for l in lines:
if l < tidx - 1: continue if l < tidx - 1:
if l >= tidx - 1 + self.rows - 3: break continue
if l >= self.numtorrents: break if l >= tidx - 1 + self.rows - 3:
break
if l >= self.numtorrents:
break
todraw.append(draw_row(l)) todraw.append(draw_row(l))
lines.reverse() lines.reverse()
else: else:
todraw = [] todraw = []
for i in range(tidx-1, tidx-1 + self.rows - 3): for i in range(tidx - 1, tidx - 1 + self.rows - 3):
if i >= self.numtorrents: break if i >= self.numtorrents:
break
todraw += [draw_row(i)] todraw += [draw_row(i)]
for row in todraw: for row in todraw:
@ -935,8 +906,8 @@ class AllTorrents(BaseMode, component.Component):
bg = "black" bg = "black"
attr = None attr = None
if lines: if lines:
tidx = lines.pop()+1 tidx = lines.pop() + 1
currow = tidx-self.curoff+2 currow = tidx - self.curoff + 2
if tidx in self.marked: if tidx in self.marked:
bg = "blue" bg = "blue"
@ -964,7 +935,7 @@ class AllTorrents(BaseMode, component.Component):
fg = "green" fg = "green"
if self.entering_search and len(self.search_string) > 1: if self.entering_search and len(self.search_string) > 1:
lcase_name = self.torrent_names[tidx-1].lower() lcase_name = self.torrent_names[tidx - 1].lower()
sstring_lower = self.search_string.lower() sstring_lower = self.search_string.lower()
if lcase_name.find(sstring_lower) != -1: if lcase_name.find(sstring_lower) != -1:
if tidx == self.cursel: if tidx == self.cursel:
@ -978,12 +949,12 @@ class AllTorrents(BaseMode, component.Component):
attr = "bold" attr = "bold"
if attr: if attr:
colorstr = "{!%s,%s,%s!}"%(fg, bg, attr) colorstr = "{!%s,%s,%s!}" % (fg, bg, attr)
else: else:
colorstr = "{!%s,%s!}"%(fg, bg) colorstr = "{!%s,%s!}" % (fg, bg)
try: try:
self.add_string(currow, "%s%s"%(colorstr, row[0]), trim=False) self.add_string(currow, "%s%s" % (colorstr, row[0]), trim=False)
except: except:
#Yeah, this should be fixed in some better way #Yeah, this should be fixed in some better way
pass pass
@ -997,7 +968,7 @@ class AllTorrents(BaseMode, component.Component):
#self.stdscr.redrawwin() #self.stdscr.redrawwin()
if self.entering_search: if self.entering_search:
curses.curs_set(2) curses.curs_set(2)
self.stdscr.move(self.rows-1, len(self.search_string)+17) self.stdscr.move(self.rows - 1, len(self.search_string) + 17)
else: else:
curses.curs_set(0) curses.curs_set(0)
@ -1011,7 +982,6 @@ class AllTorrents(BaseMode, component.Component):
curses.doupdate() curses.doupdate()
def _mark_unmark(self, idx): def _mark_unmark(self, idx):
if idx in self.marked: if idx in self.marked:
self.marked.remove(idx) self.marked.remove(idx)
@ -1041,7 +1011,7 @@ class AllTorrents(BaseMode, component.Component):
:returns: Nothing :returns: Nothing
""" """
if direction == "first": if direction == "first":
search_space = enumerate(self.torrent_names) search_space = enumerate(self.torrent_names)
elif direction == "last": elif direction == "last":
search_space = enumerate(self.torrent_names) search_space = enumerate(self.torrent_names)
@ -1053,7 +1023,7 @@ class AllTorrents(BaseMode, component.Component):
search_space = search_space[self.cursel:] search_space = search_space[self.cursel:]
elif direction == "previous": elif direction == "previous":
search_space = enumerate(self.torrent_names) search_space = enumerate(self.torrent_names)
search_space = list(search_space)[:self.cursel-1] search_space = list(search_space)[:self.cursel - 1]
search_space = reversed(search_space) search_space = reversed(search_space)
search_string = self.search_string.lower() search_string = self.search_string.lower()
@ -1063,10 +1033,10 @@ class AllTorrents(BaseMode, component.Component):
if skip > 0: if skip > 0:
skip -= 1 skip -= 1
continue continue
self.cursel = (i+1) self.cursel = (i + 1)
if ((self.curoff + self.rows - 5) < self.cursel): if ((self.curoff + self.rows - 5) < self.cursel):
self.curoff = self.cursel - self.rows + 5 self.curoff = self.cursel - self.rows + 5
elif ((self.curoff +1) > self.cursel): elif ((self.curoff + 1) > self.cursel):
self.curoff = max(1, self.cursel - 1) self.curoff = max(1, self.cursel - 1)
self.search_state = SEARCH_SUCCESS self.search_state = SEARCH_SUCCESS
return return
@ -1078,7 +1048,7 @@ class AllTorrents(BaseMode, component.Component):
self.search_state = SEARCH_START_REACHED self.search_state = SEARCH_START_REACHED
def __update_search(self, c): def __update_search(self, c):
cname = self.torrent_names[self.cursel-1] cname = self.torrent_names[self.cursel - 1]
if c == curses.KEY_BACKSPACE or c == 127: if c == curses.KEY_BACKSPACE or c == 127:
if self.search_string: if self.search_string:
self.search_string = self.search_string[:-1] self.search_string = self.search_string[:-1]
@ -1108,7 +1078,7 @@ class AllTorrents(BaseMode, component.Component):
self.search_state = SEARCH_EMPTY self.search_state = SEARCH_EMPTY
self.refresh([]) self.refresh([])
elif c == ord('/'): elif c == ord("/"):
self.entering_search = False self.entering_search = False
self.search_state = SEARCH_EMPTY self.search_state = SEARCH_EMPTY
self.refresh([]) self.refresh([])
@ -1180,7 +1150,7 @@ class AllTorrents(BaseMode, component.Component):
return return
if c > 31 and c < 256: if c > 31 and c < 256:
if chr(c) == 'Q': if chr(c) == "Q":
from twisted.internet import reactor from twisted.internet import reactor
if client.connected(): if client.connected():
def on_disconnect(result): def on_disconnect(result):
@ -1198,21 +1168,23 @@ class AllTorrents(BaseMode, component.Component):
return return
if c == curses.KEY_UP: if c == curses.KEY_UP:
if self.cursel == 1: return if self.cursel == 1:
return
if not self._scroll_up(1): if not self._scroll_up(1):
effected_lines = [self.cursel-1, self.cursel] effected_lines = [self.cursel - 1, self.cursel]
elif c == curses.KEY_PPAGE: elif c == curses.KEY_PPAGE:
self._scroll_up(int(self.rows/2)) self._scroll_up(int(self.rows / 2))
elif c == curses.KEY_DOWN: elif c == curses.KEY_DOWN:
if self.cursel >= self.numtorrents: return if self.cursel >= self.numtorrents:
return
if not self._scroll_down(1): if not self._scroll_down(1):
effected_lines = [self.cursel-2, self.cursel-1] effected_lines = [self.cursel - 2, self.cursel - 1]
elif c == curses.KEY_NPAGE: elif c == curses.KEY_NPAGE:
self._scroll_down(int(self.rows/2)) self._scroll_down(int(self.rows / 2))
elif c == curses.KEY_HOME: elif c == curses.KEY_HOME:
self._scroll_up(self.cursel) self._scroll_up(self.cursel)
elif c == curses.KEY_END: elif c == curses.KEY_END:
self._scroll_down(self.numtorrents-self.cursel) self._scroll_down(self.numtorrents - self.cursel)
elif c == curses.KEY_DC: elif c == curses.KEY_DC:
if self.cursel not in self.marked: if self.cursel not in self.marked:
self.marked.append(self.cursel) self.marked.append(self.cursel)
@ -1235,46 +1207,47 @@ class AllTorrents(BaseMode, component.Component):
return return
else: else:
if c > 31 and c < 256: if c > 31 and c < 256:
if chr(c) == '/': if chr(c) == "/":
self.search_string = "" self.search_string = ""
self.entering_search = True self.entering_search = True
elif chr(c) == 'n' and self.search_string: elif chr(c) == "n" and self.search_string:
self.__do_search("next") self.__do_search("next")
elif chr(c) == 'j': elif chr(c) == "j":
if not self._scroll_up(1): if not self._scroll_up(1):
effected_lines = [self.cursel-1, self.cursel] effected_lines = [self.cursel - 1, self.cursel]
elif chr(c) == 'k': elif chr(c) == "k":
if not self._scroll_down(1): if not self._scroll_down(1):
effected_lines = [self.cursel-2, self.cursel-1] effected_lines = [self.cursel - 2, self.cursel - 1]
elif chr(c) == 'i': elif chr(c) == "i":
cid = self.current_torrent_id() cid = self.current_torrent_id()
if cid: if cid:
def cb(): self.__torrent_info_id = None def cb():
self.__torrent_info_id = None
self.popup = Popup(self, "Info", close_cb=cb, height_req=20) self.popup = Popup(self, "Info", close_cb=cb, height_req=20)
self.popup.add_line("Getting torrent info...") self.popup.add_line("Getting torrent info...")
self.__torrent_info_id = cid self.__torrent_info_id = cid
elif chr(c) == 'm': elif chr(c) == "m":
self._mark_unmark(self.cursel) self._mark_unmark(self.cursel)
effected_lines = [self.cursel-1] effected_lines = [self.cursel - 1]
elif chr(c) == 'M': elif chr(c) == "M":
if self.last_mark >= 0: if self.last_mark >= 0:
if (self.cursel+1) > self.last_mark: if (self.cursel + 1) > self.last_mark:
mrange = range(self.last_mark, self.cursel+1) mrange = range(self.last_mark, self.cursel + 1)
else: else:
mrange = range(self.cursel-1, self.last_mark) mrange = range(self.cursel - 1, self.last_mark)
self.marked.extend(mrange[1:]) self.marked.extend(mrange[1:])
effected_lines = mrange effected_lines = mrange
else: else:
self._mark_unmark(self.cursel) self._mark_unmark(self.cursel)
effected_lines = [self.cursel-1] effected_lines = [self.cursel - 1]
elif chr(c) == 'c': elif chr(c) == "c":
self.marked = [] self.marked = []
self.last_mark = -1 self.last_mark = -1
elif chr(c) == 'a': elif chr(c) == "a":
self._show_torrent_add_popup() self._show_torrent_add_popup()
elif chr(c) == 'v': elif chr(c) == "v":
self._show_visible_columns_popup() self._show_visible_columns_popup()
elif chr(c) == 'o': elif chr(c) == "o":
if not self.marked: if not self.marked:
self.marked = [self.cursel] self.marked = [self.cursel]
self.last_mark = self.cursel self.last_mark = self.cursel
@ -1282,7 +1255,7 @@ class AllTorrents(BaseMode, component.Component):
self.last_mark = -1 self.last_mark = -1
torrent_actions_popup(self, self._selected_torrent_ids(), action=ACTION.TORRENT_OPTIONS) torrent_actions_popup(self, self._selected_torrent_ids(), action=ACTION.TORRENT_OPTIONS)
elif chr(c) == '<': elif chr(c) == "<":
i = len(self.__cols_to_show) i = len(self.__cols_to_show)
try: try:
i = self.__cols_to_show.index(self.config["sort_primary"]) - 1 i = self.__cols_to_show.index(self.config["sort_primary"]) - 1
@ -1298,7 +1271,7 @@ class AllTorrents(BaseMode, component.Component):
self.__update_columns() self.__update_columns()
self.refresh([]) self.refresh([])
elif chr(c) == '>': elif chr(c) == ">":
i = 0 i = 0
try: try:
i = self.__cols_to_show.index(self.config["sort_primary"]) + 1 i = self.__cols_to_show.index(self.config["sort_primary"]) + 1
@ -1314,17 +1287,17 @@ class AllTorrents(BaseMode, component.Component):
self.__update_columns() self.__update_columns()
self.refresh([]) self.refresh([])
elif chr(c) == 'f': elif chr(c) == "f":
self._show_torrent_filter_popup() self._show_torrent_filter_popup()
elif chr(c) == 'h': elif chr(c) == "h":
self.popup = MessagePopup(self, "Help", HELP_STR, width_req=0.75) self.popup = MessagePopup(self, "Help", HELP_STR, width_req=0.75)
elif chr(c) == 'p': elif chr(c) == "p":
self.show_preferences() self.show_preferences()
return return
elif chr(c) == 'e': elif chr(c) == "e":
self.__show_events() self.__show_events()
return return
elif chr(c) == 'l': elif chr(c) == "l":
self.__legacy_mode() self.__legacy_mode()
return return

View File

@ -1,39 +1,11 @@
# # -*- coding: utf-8 -*-
# basemode.py
# #
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Most code in this file taken from screen.py: # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # the additional special exception to link portions of this program with the OpenSSL library.
# # See LICENSE for more details.
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import logging import logging
@ -60,6 +32,7 @@ except:
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class CursesStdIO(object): class CursesStdIO(object):
"""fake fd to be registered as a reader with the twisted reactor. """fake fd to be registered as a reader with the twisted reactor.
Curses classes needing input should extend this""" Curses classes needing input should extend this"""
@ -71,7 +44,9 @@ class CursesStdIO(object):
def doRead(self): def doRead(self):
"""called when input is ready""" """called when input is ready"""
pass pass
def logPrefix(self): return 'CursesClient'
def logPrefix(self):
return "CursesClient"
class BaseMode(CursesStdIO): class BaseMode(CursesStdIO):
@ -125,7 +100,7 @@ class BaseMode(CursesStdIO):
def on_resize_norefresh(self, *args): def on_resize_norefresh(self, *args):
log.debug("on_resize_from_signal") log.debug("on_resize_from_signal")
# Get the new rows and cols value # Get the new rows and cols value
self.rows, self.cols = struct.unpack("hhhh", ioctl(0, termios.TIOCGWINSZ, "\000"*8))[0:2] self.rows, self.cols = struct.unpack("hhhh", ioctl(0, termios.TIOCGWINSZ, "\000" * 8))[0:2]
curses.resizeterm(self.rows, self.cols) curses.resizeterm(self.rows, self.cols)
def on_resize(self, *args): def on_resize(self, *args):
@ -135,7 +110,7 @@ class BaseMode(CursesStdIO):
def connectionLost(self, reason): def connectionLost(self, reason):
self.close() self.close()
def add_string(self, row, string, scr=None, col = 0, pad=True, trim=True): def add_string(self, row, string, scr=None, col=0, pad=True, trim=True):
""" """
Adds a string to the desired `:param:row`. Adds a string to the desired `:param:row`.
@ -181,8 +156,8 @@ class BaseMode(CursesStdIO):
s += " " * (self.cols - (col + len(s)) - 1) s += " " * (self.cols - (col + len(s)) - 1)
if trim: if trim:
y, x = screen.getmaxyx() y, x = screen.getmaxyx()
if (col+len(s)) > x: if (col + len(s)) > x:
s = "%s..."%s[0:x-4-col] s = "%s..." % s[0:x - 4 - col]
screen.addstr(row, col, s, color) screen.addstr(row, col, s, color)
col += len(s) col += len(s)
@ -230,7 +205,7 @@ class BaseMode(CursesStdIO):
def _doRead(self): def _doRead(self):
# Read the character # Read the character
c = self.stdscr.getch() self.stdscr.getch()
self.stdscr.refresh() self.stdscr.refresh()
def close(self): def close(self):

View File

@ -1,37 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# column.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import logging import logging
@ -44,7 +17,7 @@ log = logging.getLogger(__name__)
def format_queue(qnum): def format_queue(qnum):
if (qnum >= 0): if (qnum >= 0):
return "%d" % (qnum+1) return "%d" % (qnum + 1)
else: else:
return "" return ""
@ -76,7 +49,7 @@ columns = {
"Seeds:Peers": (("seeds_peers_ratio",), format_utils.format_float), "Seeds:Peers": (("seeds_peers_ratio",), format_utils.format_float),
"Down Limit": (("max_download_speed",), format_utils.format_speed), "Down Limit": (("max_download_speed",), format_utils.format_speed),
"Up Limit": (("max_upload_speed",), format_utils.format_speed), "Up Limit": (("max_upload_speed",), format_utils.format_speed),
} }
def get_column_value(name, state): def get_column_value(name, state):

View File

@ -1,39 +1,13 @@
# -*- coding: utf-8 -*-
# #
# connectionmanager.py # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
# Copyright (C) 2007-2009 Nick Lanham <nick@afternight.org>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
# #
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
# #
# a mode that show's a popup to select which host to connect to """ A mode that show's a popup to select which host to connect to """
import hashlib import hashlib
import logging import logging
@ -46,7 +20,6 @@ from alltorrents import AllTorrents
from basemode import BaseMode from basemode import BaseMode
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.coreconfig import CoreConfig
from input_popup import InputPopup from input_popup import InputPopup
from popup import MessagePopup, SelectablePopup from popup import MessagePopup, SelectablePopup
@ -77,12 +50,14 @@ class ConnectionManager(BaseMode):
def __update_popup(self): def __update_popup(self):
self.popup = SelectablePopup(self, "Select Host", self.__host_selected) self.popup = SelectablePopup(self, "Select Host", self.__host_selected)
self.popup.add_line("{!white,black,bold!}'Q'=quit, 'r'=refresh, 'a'=add new host, 'D'=delete host", selectable=False) self.popup.add_line("{!white,black,bold!}'Q'=quit, 'r'=refresh, 'a'=add new host, 'D'=delete host",
selectable=False)
for host in self.config["hosts"]: for host in self.config["hosts"]:
if host[0] in self.statuses: if host[0] in self.statuses:
self.popup.add_line("%s:%d [Online] (%s)"%(host[1], host[2], self.statuses[host[0]]), data=host[0], foreground="green") self.popup.add_line("%s:%d [Online] (%s)" % (host[1], host[2], self.statuses[host[0]]),
data=host[0], foreground="green")
else: else:
self.popup.add_line("%s:%d [Offline]"%(host[1], host[2]), data=host[0], foreground="red") self.popup.add_line("%s:%d [Offline]" % (host[1], host[2]), data=host[0], foreground="red")
self.inlist = True self.inlist = True
self.refresh() self.refresh()
@ -174,7 +149,7 @@ class ConnectionManager(BaseMode):
self.draw_statusbars() self.draw_statusbars()
self.stdscr.noutrefresh() self.stdscr.noutrefresh()
if self.popup == None and self.messages: if self.popup is None and self.messages:
title, msg = self.messages.popleft() title, msg = self.messages.popleft()
self.popup = MessagePopup(self, title, msg) self.popup = MessagePopup(self, title, msg)
@ -198,8 +173,9 @@ class ConnectionManager(BaseMode):
c = self.stdscr.getch() c = self.stdscr.getch()
if c > 31 and c < 256: if c > 31 and c < 256:
if chr(c) == 'q' and self.inlist: return if chr(c) == "q" and self.inlist:
if chr(c) == 'Q': return
if chr(c) == "Q":
from twisted.internet import reactor from twisted.internet import reactor
if client.connected(): if client.connected():
def on_disconnect(result): def on_disconnect(result):
@ -208,13 +184,13 @@ class ConnectionManager(BaseMode):
else: else:
reactor.stop() reactor.stop()
return return
if chr(c) == 'D' and self.inlist: if chr(c) == "D" and self.inlist:
self.__delete_current_host() self.__delete_current_host()
self.__update_popup() self.__update_popup()
return return
if chr(c) == 'r' and self.inlist: if chr(c) == "r" and self.inlist:
self.__update_statuses() self.__update_statuses()
if chr(c) == 'a' and self.inlist: if chr(c) == "a" and self.inlist:
self.__add_popup() self.__add_popup()
return return

View File

@ -1,37 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# eventview.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import logging import logging
@ -62,14 +35,14 @@ class EventView(BaseMode):
self.stdscr.erase() self.stdscr.erase()
self.add_string(0, self.statusbars.topbar) self.add_string(0, self.statusbars.topbar)
hstr = "%sPress [h] for help"%(" "*(self.cols - len(self.statusbars.bottombar) - 10)) hstr = "%sPress [h] for help" % (" " * (self.cols - len(self.statusbars.bottombar) - 10))
#This will quite likely fail when switching modes #This will quite likely fail when switching modes
try: try:
rf = format_utils.remove_formatting rf = format_utils.remove_formatting
string = self.statusbars.bottombar string = self.statusbars.bottombar
hstr = "Press {!magenta,blue,bold!}[h]{!status!} for help" hstr = "Press {!magenta,blue,bold!}[h]{!status!} for help"
string += " " * ( self.cols - len(rf(string)) - len(rf(hstr))) + hstr string += " " * (self.cols - len(rf(string)) - len(rf(hstr))) + hstr
self.add_string(self.rows - 1, string) self.add_string(self.rows - 1, string)
except: except:
@ -80,15 +53,15 @@ class EventView(BaseMode):
if i - self.offset >= self.rows - 2: if i - self.offset >= self.rows - 2:
more = len(events) - self.offset - self.rows + 2 more = len(events) - self.offset - self.rows + 2
if more > 0: if more > 0:
self.add_string(i-self.offset, " (And %i more)" % more) self.add_string(i - self.offset, " (And %i more)" % more)
break break
elif i - self.offset < 0: elif i - self.offset < 0:
continue continue
try: try:
self.add_string(i+1-self.offset, event) self.add_string(i + 1 - self.offset, event)
except curses.error: except curses.error:
pass #This'll just cut the line. Note: This seriously should be fixed in a better way pass # This'll just cut the line. Note: This seriously should be fixed in a better way
else: else:
self.add_string(1, "{!white,black,bold!}No events to show yet") self.add_string(1, "{!white,black,bold!}No events to show yet")
@ -116,7 +89,7 @@ class EventView(BaseMode):
c = self.stdscr.getch() c = self.stdscr.getch()
if c > 31 and c < 256: if c > 31 and c < 256:
if chr(c) == 'Q': if chr(c) == "Q":
from twisted.internet import reactor from twisted.internet import reactor
if client.connected(): if client.connected():
def on_disconnect(result): def on_disconnect(result):
@ -125,7 +98,7 @@ class EventView(BaseMode):
else: else:
reactor.stop() reactor.stop()
return return
elif chr(c) == 'q': elif chr(c) == "q":
self.back_to_overview() self.back_to_overview()
return return
@ -135,7 +108,7 @@ class EventView(BaseMode):
# TODO: Scroll event list # TODO: Scroll event list
jumplen = self.rows - 3 jumplen = self.rows - 3
num_events = len( component.get("ConsoleUI").events ) num_events = len(component.get("ConsoleUI").events)
if c == curses.KEY_UP: if c == curses.KEY_UP:
self.offset -= 1 self.offset -= 1
@ -149,9 +122,9 @@ class EventView(BaseMode):
self.offset += jumplen self.offset += jumplen
elif c == curses.KEY_END: elif c == curses.KEY_END:
self.offset += num_events self.offset += num_events
elif c == ord('j'): elif c == ord("j"):
self.offset -= 1 self.offset -= 1
elif c == ord('k'): elif c == ord("k"):
self.offset += 1 self.offset += 1
if self.offset <= 0: if self.offset <= 0:

View File

@ -1,37 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# format_utils.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import re import re
@ -46,57 +19,69 @@ try:
except: except:
haveud = False haveud = False
def format_speed(speed): def format_speed(speed):
if (speed > 0): if (speed > 0):
return deluge.common.fspeed(speed) return deluge.common.fspeed(speed)
else: else:
return "-" return "-"
def format_time(time): def format_time(time):
if (time > 0): if (time > 0):
return deluge.common.ftime(time) return deluge.common.ftime(time)
else: else:
return "-" return "-"
def format_date(time): def format_date(time):
if (time > 0): if (time > 0):
return deluge.common.fdate(time) return deluge.common.fdate(time)
else: else:
return "" return ""
def format_date_never(time): def format_date_never(time):
if (time > 0): if (time > 0):
return deluge.common.fdate(time) return deluge.common.fdate(time)
else: else:
return "Never" return "Never"
def format_float(x): def format_float(x):
if x < 0: if x < 0:
return "-" return "-"
else: else:
return "%.3f"%x return "%.3f" % x
def format_seeds_peers(num, total): def format_seeds_peers(num, total):
return "%d (%d)"%(num, total) return "%d (%d)" % (num, total)
def format_progress(perc): def format_progress(perc):
if perc < 100: if perc < 100:
return "%.2f%%"%perc return "%.2f%%" % perc
else: else:
return "100%" return "100%"
def format_pieces(num, size): def format_pieces(num, size):
return "%d (%s)"%(num, deluge.common.fsize(size)) return "%d (%s)" % (num, deluge.common.fsize(size))
def format_priority(prio): def format_priority(prio):
if prio == -2: return "[Mixed]" if prio == - 2:
if prio < 0: return "-" return "[Mixed]"
if prio < 0:
return "-"
pstring = deluge.common.FILE_PRIORITY[prio] pstring = deluge.common.FILE_PRIORITY[prio]
if prio > 0: if prio > 0:
return pstring[:pstring.index("Priority")-1] return pstring[:pstring.index("Priority") - 1]
else: else:
return pstring return pstring
def trim_string(string, w, have_dbls): def trim_string(string, w, have_dbls):
if w <= 0: if w <= 0:
return "" return ""
@ -109,21 +94,23 @@ def trim_string(string, w, have_dbls):
idx = 0 idx = 0
while width < w: while width < w:
chrs.append(string[idx]) chrs.append(string[idx])
if unicodedata.east_asian_width(string[idx]) in ['W', 'F']: if unicodedata.east_asian_width(string[idx]) in ["W", "F"]:
width += 2 width += 2
else: else:
width += 1 width += 1
idx += 1 idx += 1
if width != w: if width != w:
chrs.pop() chrs.pop()
chrs.append('.') chrs.append(".")
return u"%s "%("".join(chrs)) return u"%s " % ("".join(chrs))
else: else:
return u"%s "%(string[0:w-1]) return u"%s " % (string[0:w - 1])
#Dots are slow #Dots are slow
eaw = unicodedata.east_asian_width eaw = unicodedata.east_asian_width
ud_normalize = unicodedata.normalize ud_normalize = unicodedata.normalize
def format_column(col, lim): def format_column(col, lim):
dbls = 0 dbls = 0
#Chosen over isinstance(col, unicode) and col.__class__ == unicode #Chosen over isinstance(col, unicode) and col.__class__ == unicode
@ -132,22 +119,25 @@ def format_column(col, lim):
if haveud and col.__class__ is unicode: if haveud and col.__class__ is unicode:
# might have some double width chars # might have some double width chars
col = ud_normalize("NFC", col) col = ud_normalize("NFC", col)
dbls = sum(eaw(c) in 'WF' for c in col) dbls = sum(eaw(c) in "WF" for c in col)
size = len(col)+dbls size = len(col) + dbls
if (size >= lim - 1): if (size >= lim - 1):
return trim_string(col, lim, dbls>0) return trim_string(col, lim, dbls > 0)
else: else:
return "%s%s"%(col, " "*(lim-size)) return "%s%s" % (col, " " * (lim - size))
def format_row(row, column_widths): def format_row(row, column_widths):
return "".join([format_column(row[i], column_widths[i]) for i in range(0, len(row))]) return "".join([format_column(row[i], column_widths[i]) for i in range(0, len(row))])
_strip_re = re.compile("\{!.*?!\}") _strip_re = re.compile("\{!.*?!\}")
def remove_formatting(string): def remove_formatting(string):
return re.sub(_strip_re, "", string) return re.sub(_strip_re, "", string)
def wrap_string(string,width,min_lines=0,strip_colors=True):
def wrap_string(string, width, min_lines=0, strip_colors=True):
""" """
Wrap a string to fit in a particular width. Returns a list of output lines. Wrap a string to fit in a particular width. Returns a list of output lines.
@ -161,27 +151,27 @@ def wrap_string(string,width,min_lines=0,strip_colors=True):
s1 = string.split("\n") s1 = string.split("\n")
def insert_clr(s, offset, mtchs, clrs): def insert_clr(s, offset, mtchs, clrs):
end_pos = offset+len(s) end_pos = offset + len(s)
while mtchs and (mtchs[0] <= end_pos) and (mtchs[0] >= offset): while mtchs and (mtchs[0] <= end_pos) and (mtchs[0] >= offset):
mtc = mtchs.popleft()-offset mtc = mtchs.popleft() - offset
clr = clrs.popleft() clr = clrs.popleft()
end_pos += len(clr) end_pos += len(clr)
s = "%s%s%s"%(s[:mtc], clr, s[mtc:]) s = "%s%s%s" % (s[:mtc], clr, s[mtc:])
return s return s
for s in s1: for s in s1:
cur_pos = offset = 0 offset = 0
if strip_colors: if strip_colors:
mtchs = deque() mtchs = deque()
clrs = deque() clrs = deque()
for m in _strip_re.finditer(s): for m in _strip_re.finditer(s):
mtchs.append(m.start()) mtchs.append(m.start())
clrs.append(m.group()) clrs.append(m.group())
cstr = _strip_re.sub('', s) cstr = _strip_re.sub("", s)
else: else:
cstr = s cstr = s
while len(cstr) > width: while len(cstr) > width:
sidx = cstr.rfind(" ", 0, width-1) sidx = cstr.rfind(" ", 0, width - 1)
sidx += 1 sidx += 1
if sidx > 0: if sidx > 0:
if strip_colors: if strip_colors:
@ -208,13 +198,13 @@ def wrap_string(string,width,min_lines=0,strip_colors=True):
if not cstr: if not cstr:
cstr = None cstr = None
break break
if cstr != None: if cstr is not None:
if strip_colors: if strip_colors:
ret.append(insert_clr(cstr, offset, mtchs, clrs)) ret.append(insert_clr(cstr, offset, mtchs, clrs))
else: else:
ret.append(cstr) ret.append(cstr)
if min_lines>0: if min_lines > 0:
for i in range(len(ret), min_lines): for i in range(len(ret), min_lines):
ret.append(" ") ret.append(" ")
@ -236,17 +226,9 @@ def strwidth(string):
Measure width of a string considering asian double width characters Measure width of a string considering asian double width characters
""" """
if not isinstance(string, unicode): if not isinstance(string, unicode):
string = unicode(string, 'utf-8') string = unicode(string, "utf-8")
eaw = east_asian_width return sum([1 + (east_asian_width(char) in ["W", "F"]) for char in string])
length = sum( [1 + (eaw(c) in ['W', 'F']) for c in string] )
#Using list comprehenstion for improved performance
#The code above is equal to:
#length = 0
#for char in string:
#length += 1
#if east_asian_width(char) in ['W','F']:
#length += 1
return length
def pad_string(string, length, character=" ", side="right"): def pad_string(string, length, character=" ", side="right"):
""" """
@ -254,7 +236,7 @@ def pad_string(string, length, character=" ", side="right"):
""" """
w = strwidth(string) w = strwidth(string)
diff = length - w diff = length - w
if side == "left": if side == "left":
return "%s%s" % (character * diff, string) return "%s%s" % (character * diff, string)
elif side == "right": elif side == "right":
return "%s%s" % (string, character * diff) return "%s%s" % (string, character * diff)

View File

@ -1,40 +1,12 @@
# # -*- coding: utf-8 -*-
# input_popup.py
# #
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
# Complete function from commands/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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
try: try:
@ -51,6 +23,7 @@ from popup import ALIGN, Popup
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class InputField: class InputField:
depend = None depend = None
# render the input. return number of rows taken up # render the input. return number of rows taken up
@ -58,18 +31,21 @@ class InputField:
def get_height(self): def get_height(self):
return 0 return 0
def render(self,screen,row,width,selected,col=1): def render(self, screen, row, width, selected, col=1):
return 0 return 0
def handle_read(self, c): def handle_read(self, c):
if c in [curses.KEY_ENTER, 10, 127, 113]: if c in [curses.KEY_ENTER, 10, 127, 113]:
return True return True
return False return False
def get_value(self): def get_value(self):
return None return None
def set_value(self, value): def set_value(self, value):
pass pass
def set_depend(self,i,inverse=False): def set_depend(self, i, inverse=False):
if not isinstance(i, CheckedInput): if not isinstance(i, CheckedInput):
raise Exception("Can only depend on CheckedInputs") raise Exception("Can only depend on CheckedInputs")
self.depend = i self.depend = i
@ -83,14 +59,15 @@ class InputField:
else: else:
return not self.depend.checked return not self.depend.checked
class CheckedInput(InputField): class CheckedInput(InputField):
def __init__(self, parent, message, name, checked=False, additional_formatting=False): def __init__(self, parent, message, name, checked=False, additional_formatting=False):
self.parent = parent self.parent = parent
self.additional_formatting = additional_formatting self.additional_formatting = additional_formatting
self.chkd_inact = "[X] %s"%message self.chkd_inact = "[X] %s" % message
self.unchkd_inact = "[ ] %s"%message self.unchkd_inact = "[ ] %s" % message
self.chkd_act = "[{!black,white,bold!}X{!white,black!}] %s"%message self.chkd_act = "[{!black,white,bold!}X{!white,black!}] %s" % message
self.unchkd_act = "[{!black,white,bold!} {!white,black!}] %s"%message self.unchkd_act = "[{!black,white,bold!} {!white,black!}] %s" % message
self.name = name self.name = name
self.checked = checked self.checked = checked
@ -118,21 +95,22 @@ class CheckedInput(InputField):
def set_value(self, c): def set_value(self, c):
self.checked = c self.checked = c
class CheckedPlusInput(InputField): class CheckedPlusInput(InputField):
def __init__(self, parent, message, name, child, checked=False, additional_formatting=False): def __init__(self, parent, message, name, child, checked=False, additional_formatting=False):
self.parent = parent self.parent = parent
self.additional_formatting = additional_formatting self.additional_formatting = additional_formatting
self.chkd_inact = "[X] %s"%message self.chkd_inact = "[X] %s" % message
self.unchkd_inact = "[ ] %s"%message self.unchkd_inact = "[ ] %s" % message
self.chkd_act = "[{!black,white,bold!}X{!white,black!}] %s"%message self.chkd_act = "[{!black,white,bold!}X{!white,black!}] %s" % message
self.unchkd_act = "[{!black,white,bold!} {!white,black!}] %s"%message self.unchkd_act = "[{!black,white,bold!} {!white,black!}] %s" % message
self.name = name self.name = name
self.checked = checked self.checked = checked
self.msglen = len(self.chkd_inact)+1 self.msglen = len(self.chkd_inact) + 1
self.child = child self.child = child
self.child_active = False self.child_active = False
def get_height(): def get_height(self):
return max(2, self.child.height) return max(2, self.child.height)
def render(self, screen, row, width, active, col=1): def render(self, screen, row, width, active, col=1):
@ -147,24 +125,26 @@ class CheckedPlusInput(InputField):
self.parent.add_string(row, self.unchkd_inact, screen, col, False, True) self.parent.add_string(row, self.unchkd_inact, screen, col, False, True)
if active and self.checked and self.child_active: if active and self.checked and self.child_active:
self.parent.add_string(row+1, "(esc to leave)", screen, col, False, True) self.parent.add_string(row + 1, "(esc to leave)", screen, col, False, True)
elif active and self.checked: elif active and self.checked:
self.parent.add_string(row+1, "(right arrow to edit)", screen, col, False, True) self.parent.add_string(row + 1, "(right arrow to edit)", screen, col, False, True)
rows = 2 rows = 2
# show child # show child
if self.checked: if self.checked:
if isinstance(self.child, (TextInput, IntSpinInput, FloatSpinInput)): if isinstance(self.child, (TextInput, IntSpinInput, FloatSpinInput)):
crows = self.child.render(screen, row, width-self.msglen, self.child_active and active, col+self.msglen, self.msglen) crows = self.child.render(screen, row, width - self.msglen,
self.child_active and active, col + self.msglen, self.msglen)
else: else:
crows = self.child.render(screen, row, width-self.msglen, self.child_active and active, col+self.msglen) crows = self.child.render(screen, row, width - self.msglen,
self.child_active and active, col + self.msglen)
rows = max(rows, crows) rows = max(rows, crows)
else: else:
self.parent.add_string(row, "(enable to view/edit value)", screen, col+self.msglen, False, True) self.parent.add_string(row, "(enable to view/edit value)", screen, col + self.msglen, False, True)
return rows return rows
def handle_read(self, c): def handle_read(self, c):
if self.child_active: if self.child_active:
if c == 27: # leave child on esc if c == 27: # leave child on esc
self.child_active = False self.child_active = False
return return
# pass keys through to child # pass keys through to child
@ -186,7 +166,8 @@ class CheckedPlusInput(InputField):
class IntSpinInput(InputField): class IntSpinInput(InputField):
def __init__(self, parent, message, name, move_func, value, min_val=None, max_val=None, additional_formatting=False): def __init__(self, parent, message, name, move_func, value, min_val=None, max_val=None,
additional_formatting=False):
self.parent = parent self.parent = parent
self.message = message self.message = message
self.name = name self.name = name
@ -194,11 +175,11 @@ class IntSpinInput(InputField):
self.additional_formatting = additional_formatting self.additional_formatting = additional_formatting
self.default_str = str(value) self.default_str = str(value)
self.set_value( value) self.set_value(value)
self.default_value = self.value self.default_value = self.value
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
self.cursoff = colors.get_line_width(self.message)+4 # + 4 for the " [ " in the rendered string self.cursoff = colors.get_line_width(self.message) + 4 # + 4 for the " [ " in the rendered string
self.move_func = move_func self.move_func = move_func
self.min_val = min_val self.min_val = min_val
self.max_val = max_val self.max_val = max_val
@ -208,14 +189,14 @@ class IntSpinInput(InputField):
return 1 return 1
def __limit_value(self): def __limit_value(self):
if (self.min_val != None) and self.value < self.min_val: if (self.min_val is not None) and self.value < self.min_val:
self.value = self.min_val self.value = self.min_val
if (self.max_val != None) and self.value > self.max_val: if (self.max_val is not None) and self.value > self.max_val:
self.value = self.max_val self.value = self.max_val
def render(self, screen, row, width, active, col=1, cursor_offset=0): def render(self, screen, row, width, active, col=1, cursor_offset=0):
if not active and self.need_update: if not active and self.need_update:
if not self.valstr or self.valstr == '-': if not self.valstr or self.valstr == "-":
self.value = self.default_value self.value = self.default_value
self.valstr = self.default_str self.valstr = self.default_str
try: try:
@ -225,11 +206,11 @@ class IntSpinInput(InputField):
else: else:
self.value = int(self.valstr) self.value = int(self.valstr)
self.__limit_value() self.__limit_value()
self.valstr = "%d"%self.value self.valstr = "%d" % self.value
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
self.cursor = colors.get_line_width(self.valstr) self.cursor = colors.get_line_width(self.valstr)
self.need_update = False self.need_update = False
elif self.need_update and self.valstr != '-': elif self.need_update and self.valstr != "-":
self.real_value = True self.real_value = True
try: try:
self.value = int(self.valstr) self.value = int(self.valstr)
@ -240,49 +221,55 @@ class IntSpinInput(InputField):
except: except:
self.real_value = False self.real_value = False
if not self.valstr: if not self.valstr:
self.parent.add_string(row, "%s {!input!}[ ]"%self.message, screen, col, False, True) self.parent.add_string(row, "%s {!input!}[ ]" % self.message, screen, col, False, True)
elif active: elif active:
self.parent.add_string(row, "%s {!input!}[ {!black,white,bold!}%s{!input!} ]"%(self.message, self.valstr), screen, col, False, True) self.parent.add_string(row, "%s {!input!}[ {!black,white,bold!}%s{!input!} ]" % (
self.message, self.valstr), screen, col, False, True)
elif self.additional_formatting and self.valstr == self.default_str: elif self.additional_formatting and self.valstr == self.default_str:
self.parent.add_string(row, "%s {!input!}[ {!magenta,black!}%s{!input!} ]"%(self.message, self.valstr), screen, col, False, True) self.parent.add_string(row, "%s {!input!}[ {!magenta,black!}%s{!input!} ]" % (
self.message, self.valstr), screen, col, False, True)
else: else:
self.parent.add_string(row, "%s {!input!}[ %s ]"%(self.message, self.valstr), screen, col, False, True) self.parent.add_string(row, "%s {!input!}[ %s ]" % (self.message, self.valstr), screen, col, False, True)
if active: if active:
self.move_func(row, self.cursor+self.cursoff+cursor_offset) self.move_func(row, self.cursor + self.cursoff + cursor_offset)
return 1 return 1
def handle_read(self, c): def handle_read(self, c):
if c == curses.KEY_PPAGE and self.value < self.max_val: if c == curses.KEY_PPAGE and self.value < self.max_val:
if not self.real_value: if not self.real_value:
self.value = self.min_val self.value = self.min_val
self.valstr = "%d"%self.value self.valstr = "%d" % self.value
self.real_value = True self.real_value = True
else: else:
self.value+=1 self.value += 1
self.valstr = "%d"%self.value self.valstr = "%d" % self.value
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
elif c == curses.KEY_NPAGE and self.value > self.min_val: elif c == curses.KEY_NPAGE and self.value > self.min_val:
if not self.real_value: if not self.real_value:
self.value = self.min_val self.value = self.min_val
self.valstr = "%d"%self.value self.valstr = "%d" % self.value
self.real_value = True self.real_value = True
else: else:
self.value-=1 self.value -= 1
self.valstr = "%d"%self.value self.valstr = "%d" % self.value
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
elif c == curses.KEY_LEFT: elif c == curses.KEY_LEFT:
if not self.real_value: return None if not self.real_value:
self.cursor = max(0, self.cursor-1) return None
self.cursor = max(0, self.cursor - 1)
elif c == curses.KEY_RIGHT: elif c == curses.KEY_RIGHT:
if not self.real_value: return None if not self.real_value:
self.cursor = min(len(self.valstr), self.cursor+1) return None
self.cursor = min(len(self.valstr), self.cursor + 1)
elif c == curses.KEY_HOME: elif c == curses.KEY_HOME:
if not self.real_value: return None if not self.real_value:
return None
self.cursor = 0 self.cursor = 0
elif c == curses.KEY_END: elif c == curses.KEY_END:
if not self.real_value: return None if not self.real_value:
return None
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
elif c == curses.KEY_BACKSPACE or c == 127: elif c == curses.KEY_BACKSPACE or c == 127:
if not self.real_value: if not self.real_value:
@ -290,14 +277,15 @@ class IntSpinInput(InputField):
self.cursor = 0 self.cursor = 0
self.real_value = True self.real_value = True
self.need_update = True self.need_update = True
elif self.valstr and self.cursor > 0: elif self.valstr and self.cursor > 0:
self.valstr = self.valstr[:self.cursor - 1] + self.valstr[self.cursor:] self.valstr = self.valstr[:self.cursor - 1] + self.valstr[self.cursor:]
self.cursor-=1 self.cursor -= 1
self.need_update = True self.need_update = True
elif c == curses.KEY_DC: elif c == curses.KEY_DC:
if not self.real_value: return None if not self.real_value:
return None
if self.valstr and self.cursor < len(self.valstr): if self.valstr and self.cursor < len(self.valstr):
self.valstr = self.valstr[:self.cursor] + self.valstr[self.cursor+1:] self.valstr = self.valstr[:self.cursor] + self.valstr[self.cursor + 1:]
self.need_update = True self.need_update = True
elif c == 45 and self.min_val < 0: elif c == 45 and self.min_val < 0:
if not self.real_value: if not self.real_value:
@ -305,10 +293,12 @@ class IntSpinInput(InputField):
self.cursor = 1 self.cursor = 1
self.real_value = True self.real_value = True
self.need_update = True self.need_update = True
if self.cursor != 0: return if self.cursor != 0:
minus_place = self.valstr.find('-') return
if minus_place >= 0: return minus_place = self.valstr.find("-")
self.valstr = chr(c)+self.valstr if minus_place >= 0:
return
self.valstr = chr(c) + self.valstr
self.cursor += 1 self.cursor += 1
self.need_update = True self.need_update = True
elif c > 47 and c < 58: elif c > 47 and c < 58:
@ -317,9 +307,11 @@ class IntSpinInput(InputField):
self.cursor = 0 self.cursor = 0
self.real_value = True self.real_value = True
self.need_update = True self.need_update = True
if c == 48 and self.cursor == 0: return if c == 48 and self.cursor == 0:
minus_place = self.valstr.find('-') return
if self.cursor <= minus_place: return minus_place = self.valstr.find("-")
if self.cursor <= minus_place:
return
if self.cursor == len(self.valstr): if self.cursor == len(self.valstr):
self.valstr += chr(c) self.valstr += chr(c)
else: else:
@ -327,8 +319,7 @@ class IntSpinInput(InputField):
self.valstr = self.valstr[:self.cursor] + chr(c) + self.valstr[self.cursor:] self.valstr = self.valstr[:self.cursor] + chr(c) + self.valstr[self.cursor:]
self.need_update = True self.need_update = True
# Move the cursor forward # Move the cursor forward
self.cursor+=1 self.cursor += 1
def get_value(self): def get_value(self):
if self.real_value: if self.real_value:
@ -348,9 +339,10 @@ class IntSpinInput(InputField):
self.valstr = val self.valstr = val
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
#TODO: This vvvvv
class FloatSpinInput(InputField): class FloatSpinInput(InputField):
def __init__(self, parent, message, name, move_func, value, inc_amt, precision, min_val=None, max_val=None, additional_formatting = False): def __init__(self, parent, message, name, move_func, value, inc_amt, precision, min_val=None,
max_val=None, additional_formatting=False):
self.parent = parent self.parent = parent
self.message = message self.message = message
self.name = name self.name = name
@ -359,14 +351,14 @@ class FloatSpinInput(InputField):
self.additional_formatting = additional_formatting self.additional_formatting = additional_formatting
self.fmt = "%%.%df"%precision self.fmt = "%%.%df" % precision
self.default_str = str(value) self.default_str = str(value)
self.set_value(value) self.set_value(value)
self.default_value = self.value self.default_value = self.value
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
self.cursoff = colors.get_line_width(self.message)+4 # + 4 for the " [ " in the rendered string self.cursoff = colors.get_line_width(self.message) + 4 # + 4 for the " [ " in the rendered string
self.move_func = move_func self.move_func = move_func
self.min_val = min_val self.min_val = min_val
self.max_val = max_val self.max_val = max_val
@ -376,15 +368,15 @@ class FloatSpinInput(InputField):
return 1 return 1
def __limit_value(self): def __limit_value(self):
if (self.min_val != None) and self.value < self.min_val: if (self.min_val is not None) and self.value < self.min_val:
self.value = self.min_val self.value = self.min_val
if (self.max_val != None) and self.value > self.max_val: if (self.max_val is not None) and self.value > self.max_val:
self.value = self.max_val self.value = self.max_val
self.valstr = self.fmt % self.value self.valstr = self.fmt % self.value
def render(self, screen, row, width, active, col=1, cursor_offset=0): def render(self, screen, row, width, active, col=1, cursor_offset=0):
if not active and self.need_update: if not active and self.need_update:
if not self.valstr or self.valstr == '-': if not self.valstr or self.valstr == "-":
self.value = self.default_value self.value = self.default_value
self.valstr = self.default_str self.valstr = self.default_str
try: try:
@ -398,7 +390,7 @@ class FloatSpinInput(InputField):
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
self.cursor = colors.get_line_width(self.valstr) self.cursor = colors.get_line_width(self.valstr)
self.need_update = False self.need_update = False
elif self.need_update and self.valstr != '-': elif self.need_update and self.valstr != "-":
self.real_value = True self.real_value = True
try: try:
self.value = round(float(self.valstr), self.precision) self.value = round(float(self.valstr), self.precision)
@ -410,50 +402,56 @@ class FloatSpinInput(InputField):
self.real_value = False self.real_value = False
if not self.valstr: if not self.valstr:
self.parent.add_string(row, "%s {!input!}[ ]"%self.message, screen, col, False, True) self.parent.add_string(row, "%s {!input!}[ ]" % self.message, screen, col, False, True)
elif active: elif active:
self.parent.add_string(row, "%s {!input!}[ {!black,white,bold!}%s{!white,black!} ]"%(self.message, self.valstr), screen, col, False, True) self.parent.add_string(row, "%s {!input!}[ {!black,white,bold!}%s{!white,black!} ]" % (
self.message, self.valstr), screen, col, False, True)
elif self.additional_formatting and self.valstr == self.default_str: elif self.additional_formatting and self.valstr == self.default_str:
self.parent.add_string(row, "%s {!input!}[ {!magenta,black!}%s{!input!} ]"%(self.message, self.valstr), screen, col, False, True) self.parent.add_string(row, "%s {!input!}[ {!magenta,black!}%s{!input!} ]" % (
self.message, self.valstr), screen, col, False, True)
else: else:
self.parent.add_string(row, "%s {!input!}[ %s ]"%(self.message, self.valstr), screen, col, False, True) self.parent.add_string(row, "%s {!input!}[ %s ]" % (self.message, self.valstr), screen, col, False, True)
if active: if active:
self.move_func(row, self.cursor+self.cursoff+cursor_offset) self.move_func(row, self.cursor + self.cursoff + cursor_offset)
return 1 return 1
def handle_read(self, c): def handle_read(self, c):
if c == curses.KEY_PPAGE: if c == curses.KEY_PPAGE:
if not self.real_value: if not self.real_value:
self.value = self.min_val self.value = self.min_val
self.valstr = "%d"%self.value self.valstr = "%d" % self.value
self.real_value = True self.real_value = True
else: else:
self.value+=self.inc_amt self.value += self.inc_amt
self.__limit_value() self.__limit_value()
self.valstr = self.fmt%self.value self.valstr = self.fmt % self.value
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
elif c == curses.KEY_NPAGE: elif c == curses.KEY_NPAGE:
if not self.real_value: if not self.real_value:
self.value = self.min_val self.value = self.min_val
self.valstr = "%d"%self.value self.valstr = "%d" % self.value
self.real_value = True self.real_value = True
else: else:
self.value-=self.inc_amt self.value -= self.inc_amt
self.__limit_value() self.__limit_value()
self.valstr = self.fmt%self.value self.valstr = self.fmt % self.value
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
elif c == curses.KEY_LEFT: elif c == curses.KEY_LEFT:
if not self.real_value: return None if not self.real_value:
self.cursor = max(0, self.cursor-1) return None
self.cursor = max(0, self.cursor - 1)
elif c == curses.KEY_RIGHT: elif c == curses.KEY_RIGHT:
if not self.real_value: return None if not self.real_value:
self.cursor = min(len(self.valstr), self.cursor+1) return None
self.cursor = min(len(self.valstr), self.cursor + 1)
elif c == curses.KEY_HOME: elif c == curses.KEY_HOME:
if not self.real_value: return None if not self.real_value:
return None
self.cursor = 0 self.cursor = 0
elif c == curses.KEY_END: elif c == curses.KEY_END:
if not self.real_value: return None if not self.real_value:
return None
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
elif c == curses.KEY_BACKSPACE or c == 127: elif c == curses.KEY_BACKSPACE or c == 127:
if not self.real_value: if not self.real_value:
@ -461,14 +459,15 @@ class FloatSpinInput(InputField):
self.cursor = 0 self.cursor = 0
self.real_value = True self.real_value = True
self.need_update = True self.need_update = True
elif self.valstr and self.cursor > 0: elif self.valstr and self.cursor > 0:
self.valstr = self.valstr[:self.cursor - 1] + self.valstr[self.cursor:] self.valstr = self.valstr[:self.cursor - 1] + self.valstr[self.cursor:]
self.cursor-=1 self.cursor -= 1
self.need_update = True self.need_update = True
elif c == curses.KEY_DC: elif c == curses.KEY_DC:
if not self.real_value: return None if not self.real_value:
return None
if self.valstr and self.cursor < len(self.valstr): if self.valstr and self.cursor < len(self.valstr):
self.valstr = self.valstr[:self.cursor] + self.valstr[self.cursor+1:] self.valstr = self.valstr[:self.cursor] + self.valstr[self.cursor + 1:]
self.need_update = True self.need_update = True
elif c == 45 and self.min_val < 0: elif c == 45 and self.min_val < 0:
if not self.real_value: if not self.real_value:
@ -476,10 +475,12 @@ class FloatSpinInput(InputField):
self.cursor = 1 self.cursor = 1
self.need_update = True self.need_update = True
self.real_value = True self.real_value = True
if self.cursor != 0: return if self.cursor != 0:
minus_place = self.valstr.find('-') return
if minus_place >= 0: return minus_place = self.valstr.find("-")
self.valstr = chr(c)+self.valstr if minus_place >= 0:
return
self.valstr = chr(c) + self.valstr
self.cursor += 1 self.cursor += 1
self.need_update = True self.need_update = True
elif c == 46: elif c == 46:
@ -488,10 +489,12 @@ class FloatSpinInput(InputField):
self.cursor = 2 self.cursor = 2
self.real_value = True self.real_value = True
self.need_update = True self.need_update = True
minus_place = self.valstr.find('-') minus_place = self.valstr.find("-")
if self.cursor <= minus_place: return if self.cursor <= minus_place:
point_place = self.valstr.find('.') return
if point_place >= 0: return point_place = self.valstr.find(".")
if point_place >= 0:
return
if self.cursor == len(self.valstr): if self.cursor == len(self.valstr):
self.valstr += chr(c) self.valstr += chr(c)
else: else:
@ -499,7 +502,7 @@ class FloatSpinInput(InputField):
self.valstr = self.valstr[:self.cursor] + chr(c) + self.valstr[self.cursor:] self.valstr = self.valstr[:self.cursor] + chr(c) + self.valstr[self.cursor:]
self.need_update = True self.need_update = True
# Move the cursor forward # Move the cursor forward
self.cursor+=1 self.cursor += 1
elif (c > 47 and c < 58): elif (c > 47 and c < 58):
if (not self.real_value) and self.valstr: if (not self.real_value) and self.valstr:
self.valstr = "" self.valstr = ""
@ -508,8 +511,9 @@ class FloatSpinInput(InputField):
self.need_update = True self.need_update = True
if self.value == "mixed": if self.value == "mixed":
self.value = "" self.value = ""
minus_place = self.valstr.find('-') minus_place = self.valstr.find("-")
if self.cursor <= minus_place: return if self.cursor <= minus_place:
return
if self.cursor == len(self.valstr): if self.cursor == len(self.valstr):
self.valstr += chr(c) self.valstr += chr(c)
else: else:
@ -517,7 +521,7 @@ class FloatSpinInput(InputField):
self.valstr = self.valstr[:self.cursor] + chr(c) + self.valstr[self.cursor:] self.valstr = self.valstr[:self.cursor] + chr(c) + self.valstr[self.cursor:]
self.need_update = True self.need_update = True
# Move the cursor forward # Move the cursor forward
self.cursor+=1 self.cursor += 1
def get_value(self): def get_value(self):
if self.real_value: if self.real_value:
@ -537,8 +541,9 @@ class FloatSpinInput(InputField):
self.valstr = val self.valstr = val
self.cursor = len(self.valstr) self.cursor = len(self.valstr)
class SelectInput(InputField): class SelectInput(InputField):
def __init__(self, parent, message, name, opts, vals, selidx, additional_formatting = False): def __init__(self, parent, message, name, opts, vals, selidx, additional_formatting=False):
self.parent = parent self.parent = parent
self.message = message self.message = message
self.additional_formatting = additional_formatting self.additional_formatting = additional_formatting
@ -555,20 +560,21 @@ class SelectInput(InputField):
if self.message: if self.message:
self.parent.add_string(row, self.message, screen, col, False, True) self.parent.add_string(row, self.message, screen, col, False, True)
row += 1 row += 1
off = col+1 off = col + 1
for i, opt in enumerate(self.opts): for i, opt in enumerate(self.opts):
if selected and i == self.selidx: if selected and i == self.selidx:
self.parent.add_string(row, "{!black,white,bold!}[%s]"%opt, screen, off, False, True) self.parent.add_string(row, "{!black,white,bold!}[%s]" % opt, screen, off, False, True)
elif i == self.selidx: elif i == self.selidx:
if self.additional_formatting and i == self.default_option: if self.additional_formatting and i == self.default_option:
self.parent.add_string(row, "[{!magenta,black!}%s{!white,black!}]"%opt, screen, off, False, True) self.parent.add_string(row, "[{!magenta,black!}%s{!white,black!}]" % opt, screen, off, False, True)
elif self.additional_formatting: elif self.additional_formatting:
self.parent.add_string(row, "[{!white,blue!}%s{!white,black!}]"%opt, screen, off, False, True) self.parent.add_string(row, "[{!white,blue!}%s{!white,black!}]" % opt, screen, off, False, True)
else: else:
self.parent.add_string(row, "[{!white,black,underline!}%s{!white,black!}]"%opt, screen, off, False, True) self.parent.add_string(row, "[{!white,black,underline!}%s{!white,black!}]" %
opt, screen, off, False, True)
else: else:
self.parent.add_string(row, "[%s]"%opt, screen, off, False, True) self.parent.add_string(row, "[%s]" % opt, screen, off, False, True)
off += len(opt)+3 off += len(opt) + 3
if self.message: if self.message:
return 2 return 2
else: else:
@ -576,9 +582,9 @@ class SelectInput(InputField):
def handle_read(self, c): def handle_read(self, c):
if c == curses.KEY_LEFT: if c == curses.KEY_LEFT:
self.selidx = max(0, self.selidx-1) self.selidx = max(0, self.selidx - 1)
if c == curses.KEY_RIGHT: if c == curses.KEY_RIGHT:
self.selidx = min(len(self.opts)-1, self.selidx+1) self.selidx = min(len(self.opts) - 1, self.selidx + 1)
def get_value(self): def get_value(self):
return self.vals[self.selidx] return self.vals[self.selidx]
@ -590,11 +596,12 @@ class SelectInput(InputField):
return return
raise Exception("Invalid value for SelectInput") raise Exception("Invalid value for SelectInput")
class TextInput(InputField): class TextInput(InputField):
def __init__(self, parent, move_func, width, message, name, value, docmp, additional_formatting=False): def __init__(self, parent, move_func, width, message, name, value, docmp, additional_formatting=False):
self.parent = parent self.parent = parent
self.move_func = move_func self.move_func = move_func
self.width = width self.width = width
self.additional_formatting = additional_formatting self.additional_formatting = additional_formatting
@ -612,7 +619,7 @@ class TextInput(InputField):
def get_height(self): def get_height(self):
return 2 + bool(self.message) return 2 + bool(self.message)
def render(self,screen,row,width,selected,col=1,cursor_offset=0): def render(self, screen, row, width, selected, col=1, cursor_offset=0):
if not self.value and not selected and len(self.default_value) != 0: if not self.value and not selected and len(self.default_value) != 0:
self.value = self.default_value self.value = self.default_value
self.cursor = len(self.value) self.cursor = len(self.value)
@ -622,21 +629,21 @@ class TextInput(InputField):
row += 1 row += 1
if selected: if selected:
if self.opts: if self.opts:
self.parent.add_string(row+1, self.opts[self.opt_off:], screen, col, False, True) self.parent.add_string(row + 1, self.opts[self.opt_off:], screen, col, False, True)
if self.cursor > (width-3): if self.cursor > (width - 3):
self.move_func(row, width-2) self.move_func(row, width - 2)
else: else:
self.move_func(row, self.cursor+1+cursor_offset) self.move_func(row, self.cursor + 1 + cursor_offset)
slen = len(self.value)+3 slen = len(self.value) + 3
if slen > width: if slen > width:
vstr = self.value[(slen-width):] vstr = self.value[(slen - width):]
else: else:
vstr = self.value.ljust(width-2) vstr = self.value.ljust(width - 2)
if self.additional_formatting and len(self.value) != 0 and self.value == self.default_value: if self.additional_formatting and len(self.value) != 0 and self.value == self.default_value:
self.parent.add_string(row, "{!magenta,white!}%s"%vstr, screen, col, False, False) self.parent.add_string(row, "{!magenta,white!}%s" % vstr, screen, col, False, False)
else: else:
self.parent.add_string(row, "{!black,white,bold!}%s"%vstr, screen, col, False, False) self.parent.add_string(row, "{!black,white,bold!}%s" % vstr, screen, col, False, False)
if self.message: if self.message:
return 3 return 3
@ -666,15 +673,15 @@ class TextInput(InputField):
if self.cursor == len(self.value) or self.value[self.cursor] == " ": if self.cursor == len(self.value) or self.value[self.cursor] == " ":
if self.opts: if self.opts:
prev = self.opt_off prev = self.opt_off
self.opt_off += self.width-3 self.opt_off += self.width - 3
# now find previous double space, best guess at a split point # now find previous double space, best guess at a split point
# in future could keep opts unjoined to get this really right # in future could keep opts unjoined to get this really right
self.opt_off = self.opts.rfind(" ", 0, self.opt_off)+2 self.opt_off = self.opts.rfind(" ", 0, self.opt_off) + 2
if second_hit and self.opt_off == prev: # double tap and we're at the end if second_hit and self.opt_off == prev: # double tap and we're at the end
self.opt_off = 0 self.opt_off = 0
else: else:
opts = self.complete(self.value) opts = self.complete(self.value)
if len(opts) == 1: # only one option, just complete it if len(opts) == 1: # only one option, just complete it
self.value = opts[0] self.value = opts[0]
self.cursor = len(opts[0]) self.cursor = len(opts[0])
self.tab_count = 0 self.tab_count = 0
@ -684,15 +691,15 @@ class TextInput(InputField):
self.value = prefix self.value = prefix
self.cursor = len(prefix) self.cursor = len(prefix)
if len(opts) > 1 and second_hit: # display multiple options on second tab hit if len(opts) > 1 and second_hit: # display multiple options on second tab hit
sp = self.value.rfind(os.sep)+1 sp = self.value.rfind(os.sep) + 1
self.opts = " ".join([o[sp:] for o in opts]) self.opts = " ".join([o[sp:] for o in opts])
# Cursor movement # Cursor movement
elif c == curses.KEY_LEFT: elif c == curses.KEY_LEFT:
self.cursor = max(0, self.cursor-1) self.cursor = max(0, self.cursor - 1)
elif c == curses.KEY_RIGHT: elif c == curses.KEY_RIGHT:
self.cursor = min(len(self.value), self.cursor+1) self.cursor = min(len(self.value), self.cursor + 1)
elif c == curses.KEY_HOME: elif c == curses.KEY_HOME:
self.cursor = 0 self.cursor = 0
elif c == curses.KEY_END: elif c == curses.KEY_END:
@ -705,12 +712,12 @@ class TextInput(InputField):
# Delete a character in the input string based on cursor position # Delete a character in the input string based on cursor position
if c == curses.KEY_BACKSPACE or c == 127: if c == curses.KEY_BACKSPACE or c == 127:
if self.value and self.cursor > 0: if self.value and self.cursor > 0:
self.value = self.value[:self.cursor - 1] + self.value[self.cursor:] self.value = self.value[:self.cursor - 1] + self.value[self.cursor:]
self.cursor-=1 self.cursor -= 1
elif c == curses.KEY_DC: elif c == curses.KEY_DC:
if self.value and self.cursor < len(self.value): if self.value and self.cursor < len(self.value):
self.value = self.value[:self.cursor] + self.value[self.cursor+1:] self.value = self.value[:self.cursor] + self.value[self.cursor + 1:]
elif c > 31 and c < 256: elif c > 31 and c < 256:
# Emulate getwch # Emulate getwch
stroke = chr(c) stroke = chr(c)
@ -728,8 +735,7 @@ class TextInput(InputField):
# Insert into string # Insert into string
self.value = self.value[:self.cursor] + uchar + self.value[self.cursor:] self.value = self.value[:self.cursor] + uchar + self.value[self.cursor:]
# Move the cursor forward # Move the cursor forward
self.cursor+=1 self.cursor += 1
def complete(self, line): def complete(self, line):
line = os.path.abspath(os.path.expanduser(line)) line = os.path.abspath(os.path.expanduser(line))
@ -752,7 +758,7 @@ class TextInput(InputField):
# shares a common prefix. # shares a common prefix.
for f in os.listdir(os.path.dirname(line)): for f in os.listdir(os.path.dirname(line)):
if f.startswith(os.path.split(line)[1]): if f.startswith(os.path.split(line)[1]):
ret.append(os.path.join( os.path.dirname(line), f)) ret.append(os.path.join(os.path.dirname(line), f))
else: else:
# This path does not exist, so lets do a listdir on it's parent # This path does not exist, so lets do a listdir on it's parent
# and find any matches. # and find any matches.
@ -769,12 +775,10 @@ class TextInput(InputField):
class InputPopup(Popup): class InputPopup(Popup):
def __init__(self,parent_mode,title,width_req=0,height_req=0, def __init__(self, parent_mode, title, width_req=0, height_req=0, align=ALIGN.DEFAULT, close_cb=None,
align=ALIGN.DEFAULT, additional_formatting=True, immediate_action=False):
close_cb=None, Popup.__init__(self, parent_mode, title, width_req=width_req, height_req=height_req,
additional_formatting=True, align=align, close_cb=close_cb)
immediate_action=False):
Popup.__init__(self, parent_mode, title, width_req=width_req, height_req=height_req, align=align, close_cb=close_cb)
self.inputs = [] self.inputs = []
self.lines = [] self.lines = []
self.current_input = 0 self.current_input = 0
@ -798,17 +802,16 @@ class InputPopup(Popup):
:param value: initial value of the field :param value: initial value of the field
:param complete: should completion be run when tab is hit and this field is active :param complete: should completion be run when tab is hit and this field is active
""" """
self.inputs.append(TextInput(self, self.move, self.width, message, self.inputs.append(TextInput(self, self.move, self.width, message, name, value, complete,
name, value, complete, additional_formatting=self.additional_formatting))
additional_formatting = self.additional_formatting))
def getmaxyx(self): def getmaxyx(self):
return self.screen.getmaxyx() return self.screen.getmaxyx()
def add_string(self, row, string, scr=None, col = 0, pad=True, trim=True): def add_string(self, row, string, scr=None, col=0, pad=True, trim=True):
if row <= 0: if row <= 0:
return False return False
elif row >= self.height -1: elif row >= self.height - 1:
return False return False
self.parent.add_string(row, string, scr, col, pad, trim) self.parent.add_string(row, string, scr, col, pad, trim)
return True return True
@ -820,26 +823,26 @@ class InputPopup(Popup):
def add_text(self, string): def add_text(self, string):
lines = string.split("\n") lines = string.split("\n")
for line in lines: for line in lines:
self.lines.append( (len(self.inputs), line) ) self.lines.append((len(self.inputs), line))
def add_select_input(self, message, name, opts, vals, default_index=0): def add_select_input(self, message, name, opts, vals, default_index=0):
self.inputs.append(SelectInput(self, message, name, opts, vals, default_index, self.inputs.append(SelectInput(self, message, name, opts, vals, default_index,
additional_formatting = self.additional_formatting)) additional_formatting=self.additional_formatting))
def add_checked_input(self, message, name, checked=False): def add_checked_input(self, message, name, checked=False):
self.inputs.append(CheckedInput(self, message, name, checked, self.inputs.append(CheckedInput(self, message, name, checked,
additional_formatting = self.additional_formatting)) additional_formatting=self.additional_formatting))
#def add_checked_plus_input(self, message, name, child) #def add_checked_plus_input(self, message, name, child)
def add_float_spin_input(self, message, name, value=0.0, inc_amt = 1.0, precision = 1, min_val = None, max_val = None): def add_float_spin_input(self, message, name, value=0.0, inc_amt=1.0, precision=1, min_val=None, max_val=None):
i = FloatSpinInput(self, message, name, self.move, value, inc_amt, precision, min_val, max_val, i = FloatSpinInput(self, message, name, self.move, value, inc_amt, precision, min_val, max_val,
additional_formatting = self.additional_formatting) additional_formatting=self.additional_formatting)
self.inputs.append(i) self.inputs.append(i)
def add_int_spin_input(self, message, name, value = 0, min_val = None, max_val = None): def add_int_spin_input(self, message, name, value=0, min_val=None, max_val=None):
i = IntSpinInput(self, message, name, self.move, value, min_val, max_val, i = IntSpinInput(self, message, name, self.move, value, min_val, max_val,
additional_formatting = self.additional_formatting) additional_formatting=self.additional_formatting)
self.inputs.append(i) self.inputs.append(i)
def _refresh_lines(self): def _refresh_lines(self):
@ -849,7 +852,6 @@ class InputPopup(Popup):
start_row = 0 start_row = 0
end_row = 0 end_row = 0
spos = 0
for i, ipt in enumerate(self.inputs): for i, ipt in enumerate(self.inputs):
for line in self.lines: for line in self.lines:
if line[0] == i: if line[0] == i:
@ -859,37 +861,36 @@ class InputPopup(Popup):
active = (i == self.current_input) active = (i == self.current_input)
if active: if active:
if end_row + 1 >= self.height + self.lineoff : if end_row + 1 >= self.height + self.lineoff:
self.lineoff += ipt.get_height() self.lineoff += ipt.get_height()
elif start_row < self.lineoff: elif start_row < self.lineoff:
self.lineoff -= ipt.get_height() self.lineoff -= ipt.get_height()
self.content_height = end_row self.content_height = end_row
crow = 1 - self.lineoff crow = 1 - self.lineoff
spos = 0
for i, ipt in enumerate(self.inputs): for i, ipt in enumerate(self.inputs):
for line in self.lines: for line in self.lines:
if line[0] == i: if line[0] == i:
self.add_string(crow, line[1], self.screen, 1, pad=False) self.add_string(crow, line[1], self.screen, 1, pad=False)
crow += 1 crow += 1
crow += ipt.render(self.screen, crow, self.width, i==self.current_input) crow += ipt.render(self.screen, crow, self.width, i == self.current_input)
if (self.content_height > (self.height-2)): if (self.content_height > (self.height - 2)):
lts = self.content_height-(self.height-3) lts = self.content_height - (self.height - 3)
perc_sc = float(self.lineoff)/lts perc_sc = float(self.lineoff) / lts
sb_pos = int((self.height-2)*perc_sc)+1 sb_pos = int((self.height - 2) * perc_sc) + 1
if (sb_pos == 1) and (self.lineoff != 0): if (sb_pos == 1) and (self.lineoff != 0):
sb_pos += 1 sb_pos += 1
self.add_string(sb_pos, "{!red,black,bold!}#", self.screen, col=(self.width-1), pad=False, trim=False) self.add_string(sb_pos, "{!red,black,bold!}#", self.screen, col=(self.width - 1), pad=False, trim=False)
if self._cursor_row >= 0: if self._cursor_row >= 0:
curses.curs_set(2) curses.curs_set(2)
self.screen.move(self._cursor_row, self._cursor_col) self.screen.move(self._cursor_row, self._cursor_col)
def handle_read(self, c): def handle_read(self, c):
if c == curses.KEY_UP: if c == curses.KEY_UP:
self.current_input = max(0, self.current_input-1) self.current_input = max(0, self.current_input - 1)
elif c == curses.KEY_DOWN: elif c == curses.KEY_DOWN:
self.current_input = min(len(self.inputs)-1, self.current_input+1) self.current_input = min(len(self.inputs) - 1, self.current_input + 1)
elif c == curses.KEY_ENTER or c == 10: elif c == curses.KEY_ENTER or c == 10:
if self.close_cb: if self.close_cb:
vals = {} vals = {}
@ -897,8 +898,8 @@ class InputPopup(Popup):
vals[ipt.name] = ipt.get_value() vals[ipt.name] = ipt.get_value()
curses.curs_set(0) curses.curs_set(0)
self.close_cb(vals) self.close_cb(vals)
return True # close the popup return True # close the popup
elif c == 27: # close on esc, no action elif c == 27: # close on esc, no action
return True return True
elif self.inputs: elif self.inputs:
self.inputs[self.current_input].handle_read(c) self.inputs[self.current_input].handle_read(c)

View File

@ -1,38 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# legacy.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> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
try: try:
@ -45,7 +18,7 @@ import logging
import os import os
import re import re
from twisted.internet import defer, reactor from twisted.internet import defer
import deluge.component as component import deluge.component as component
import deluge.configmanager import deluge.configmanager
@ -64,10 +37,12 @@ INPUT_HISTORY_SIZE = 500
MAX_HISTFILE_SIZE = 2000 MAX_HISTFILE_SIZE = 2000
def complete_line(line, possible_matches): def complete_line(line, possible_matches):
"Find the common prefix of possible matches, proritizing matching-case elements" "Find the common prefix of possible matches, proritizing matching-case elements"
if not possible_matches: return line if not possible_matches:
return line
line = line.replace(r"\ ", " ") line = line.replace(r"\ ", " ")
@ -79,13 +54,16 @@ def complete_line(line, possible_matches):
match = match.replace(r"\ ", " ") match = match.replace(r"\ ", " ")
m1, m2 = "", "" m1, m2 = "", ""
for i, c in enumerate(line): for i, c in enumerate(line):
if m1 and m2: break if m1 and m2:
break
if not m1 and c != line[i]: if not m1 and c != line[i]:
m1 = line[:i] m1 = line[:i]
if not m2 and c.lower() != line[i].lower(): if not m2 and c.lower() != line[i].lower():
m2 = line[:i] m2 = line[:i]
if not m1: matches1.append(match) if not m1:
elif not m2: matches2.append(match) matches1.append(match)
elif not m2:
matches2.append(match)
possible_matches = matches1 + matches2 possible_matches = matches1 + matches2
@ -103,15 +81,17 @@ def complete_line(line, possible_matches):
return possible_matches[0][:maxlen].replace(" ", r"\ ") return possible_matches[0][:maxlen].replace(" ", r"\ ")
def commonprefix(m): def commonprefix(m):
"Given a list of pathnames, returns the longest common leading component" "Given a list of pathnames, returns the longest common leading component"
if not m: return '' if not m:
return ""
s1 = min(m) s1 = min(m)
s2 = max(m) s2 = max(m)
for i, c in enumerate(s1): for i, c in enumerate(s1):
if c != s2[i]: if c != s2[i]:
return s1[:i] return s1[:i]
return s return s2
class Legacy(BaseMode, component.Component): class Legacy(BaseMode, component.Component):
@ -131,7 +111,7 @@ class Legacy(BaseMode, component.Component):
self.input_incomplete = "" self.input_incomplete = ""
self._old_char = 0 self._old_char = 0
self._last_char = 0 self._last_char = 0
self._last_del_char = '' self._last_del_char = ""
# Keep track of where the cursor is # Keep track of where the cursor is
self.input_cursor = 0 self.input_cursor = 0
@ -160,14 +140,14 @@ class Legacy(BaseMode, component.Component):
if self.console_config["save_legacy_history"]: if self.console_config["save_legacy_history"]:
try: try:
lines1 = open(self.history_file[0], 'r').read().splitlines() lines1 = open(self.history_file[0], "r").read().splitlines()
self._hf_lines[0] = len(lines1) self._hf_lines[0] = len(lines1)
except: except:
lines1 = [] lines1 = []
self._hf_lines[0] = 0 self._hf_lines[0] = 0
try: try:
lines2 = open(self.history_file[1], 'r').read().splitlines() lines2 = open(self.history_file[1], "r").read().splitlines()
self._hf_lines[1] = len(lines2) self._hf_lines[1] = len(lines2)
except: except:
lines2 = [] lines2 = []
@ -202,7 +182,6 @@ class Legacy(BaseMode, component.Component):
component.start("LegacyUI") component.start("LegacyUI")
# show the cursor # show the cursor
curses.curs_set(2) curses.curs_set(2)
@ -213,6 +192,7 @@ class Legacy(BaseMode, component.Component):
# Maintain a list of (torrent_id, name) for use in tab completion # Maintain a list of (torrent_id, name) for use in tab completion
self.torrents = [] self.torrents = []
def on_session_state(result): def on_session_state(result):
def on_torrents_status(torrents): def on_torrents_status(torrents):
for torrent_id, status in torrents.items(): for torrent_id, status in torrents.items():
@ -258,7 +238,7 @@ class Legacy(BaseMode, component.Component):
if self.input: if self.input:
self.input = self.input.encode(self.encoding) self.input = self.input.encode(self.encoding)
if self.input.endswith('\\'): if self.input.endswith("\\"):
self.input = self.input[:-1] self.input = self.input[:-1]
self.input_cursor -= 1 self.input_cursor -= 1
self.add_line("{!yellow,black,bold!}>>>{!input!} %s" % self.input) self.add_line("{!yellow,black,bold!}>>>{!input!} %s" % self.input)
@ -456,7 +436,6 @@ class Legacy(BaseMode, component.Component):
self.stdscr.redrawwin() self.stdscr.redrawwin()
self.stdscr.refresh() self.stdscr.refresh()
def add_line(self, text, refresh=True): def add_line(self, text, refresh=True):
""" """
Add a line to the screen. This will be showed between the two bars. Add a line to the screen. This will be showed between the two bars.
@ -495,13 +474,13 @@ class Legacy(BaseMode, component.Component):
active_file = 0 active_file = 0
#Write the line #Write the line
f = open(self.history_file[active_file], 'a') f = open(self.history_file[active_file], "a")
if isinstance(text, unicode): if isinstance(text, unicode):
text = text.encode(self.encoding) text = text.encode(self.encoding)
f.write(text) f.write(text)
f.write( os.linesep ) f.write(os.linesep)
#And increment line counter #And increment line counter
self._hf_lines[active_file] += 1 self._hf_lines[active_file] += 1
@ -510,7 +489,7 @@ class Legacy(BaseMode, component.Component):
# therefore swapping the currently active file # therefore swapping the currently active file
if self._hf_lines[active_file] == MAX_HISTFILE_SIZE: if self._hf_lines[active_file] == MAX_HISTFILE_SIZE:
self._hf_lines[1 - active_file] = 0 self._hf_lines[1 - active_file] = 0
f = open(self.history_file[1 - active_file], 'w') f = open(self.history_file[1 - active_file], "w")
f.truncate(0) f.truncate(0)
def get_line_chunks(line): def get_line_chunks(line):
@ -522,20 +501,20 @@ class Legacy(BaseMode, component.Component):
return [] return []
chunks = [] chunks = []
if not line.startswith('{!'): if not line.startswith("{!"):
begin = line.find('{!') begin = line.find("{!")
if begin == -1: if begin == -1:
begin = len(line) begin = len(line)
chunks.append( ('', line[:begin]) ) chunks.append(("", line[:begin]))
line = line[begin:] line = line[begin:]
while line: while line:
# We know the line starts with '{!' here # We know the line starts with "{!" here
end_color = line.find('!}') end_color = line.find("!}")
next_color = line.find('{!', end_color) next_color = line.find("{!", end_color)
if next_color == -1: if next_color == -1:
next_color = len(line) next_color = len(line)
chunks.append( (line[:end_color+2], line[end_color+2:next_color]) ) chunks.append((line[:end_color + 2], line[end_color + 2:next_color]))
line = line[next_color:] line = line[next_color:]
return chunks return chunks
@ -582,7 +561,6 @@ class Legacy(BaseMode, component.Component):
if refresh: if refresh:
self.refresh() self.refresh()
def add_string(self, row, string): def add_string(self, row, string):
""" """
Adds a string to the desired `:param:row`. Adds a string to the desired `:param:row`.
@ -608,7 +586,6 @@ class Legacy(BaseMode, component.Component):
col += strwidth(s) col += strwidth(s)
def do_command(self, cmd): def do_command(self, cmd):
""" """
Processes a command. Processes a command.
@ -618,7 +595,7 @@ class Legacy(BaseMode, component.Component):
""" """
if not cmd: if not cmd:
return return
cmd, _, line = cmd.partition(' ') cmd, _, line = cmd.partition(" ")
try: try:
parser = self.console._commands[cmd].create_parser() parser = self.console._commands[cmd].create_parser()
except KeyError: except KeyError:
@ -633,6 +610,7 @@ class Legacy(BaseMode, component.Component):
# Do a little hack here to print 'command --help' properly # Do a little hack here to print 'command --help' properly
parser._print_help = parser.print_help parser._print_help = parser.print_help
def print_help(f=None): def print_help(f=None):
parser._print_help(f) parser._print_help(f)
parser.print_help = print_help parser.print_help = print_help
@ -654,7 +632,7 @@ class Legacy(BaseMode, component.Component):
self.write("{!error!}Error parsing options: %s" % ex) self.write("{!error!}Error parsing options: %s" % ex)
return return
if not getattr(options, '_exit', False): if not getattr(options, "_exit", False):
try: try:
ret = self.console._commands[cmd].handle(*args, **options.__dict__) ret = self.console._commands[cmd].handle(*args, **options.__dict__)
except Exception as ex: except Exception as ex:
@ -666,8 +644,6 @@ class Legacy(BaseMode, component.Component):
else: else:
return ret return ret
def set_batch_write(self, batch): def set_batch_write(self, batch):
""" """
When this is set the screen is not refreshed after a `:meth:write` until When this is set the screen is not refreshed after a `:meth:write` until
@ -691,7 +667,6 @@ class Legacy(BaseMode, component.Component):
self.add_line(line, not self.batch_write) self.add_line(line, not self.batch_write)
def tab_completer(self, line, cursor, hits): def tab_completer(self, line, cursor, hits):
""" """
Called when the user hits 'tab' and will autocomplete or show options. Called when the user hits 'tab' and will autocomplete or show options.
@ -747,7 +722,7 @@ class Legacy(BaseMode, component.Component):
new_line = format_utils.remove_formatting(new_line) new_line = format_utils.remove_formatting(new_line)
return (new_line, len(new_line)) return (new_line, len(new_line))
else: else:
if hits == 1: if hits == 1:
p = " ".join(split(line)[:-1]) p = " ".join(split(line)[:-1])
try: try:
@ -755,7 +730,7 @@ class Legacy(BaseMode, component.Component):
except IndexError: except IndexError:
l_arg = "" l_arg = ""
new_line = " ".join( [p, complete_line(l_arg, possible_matches)] ).lstrip() new_line = " ".join([p, complete_line(l_arg, possible_matches)]).lstrip()
if len(format_utils.remove_formatting(new_line)) > len(line): if len(format_utils.remove_formatting(new_line)) > len(line):
line = new_line line = new_line
@ -763,8 +738,8 @@ class Legacy(BaseMode, component.Component):
elif hits >= 2: elif hits >= 2:
max_list = self.console_config["torrents_per_tab_press"] max_list = self.console_config["torrents_per_tab_press"]
match_count = len(possible_matches) match_count = len(possible_matches)
listed = (hits-2) * max_list listed = (hits - 2) * max_list
pages = (match_count-1) // max_list + 1 pages = (match_count - 1) // max_list + 1
left = match_count - listed left = match_count - listed
if hits == 2: if hits == 2:
self.write(" ") self.write(" ")
@ -777,7 +752,7 @@ class Legacy(BaseMode, component.Component):
for i in range(listed, listed + max_list): for i in range(listed, listed + max_list):
match = possible_matches[i] match = possible_matches[i]
self.write(match.replace(r"\ ", " ")) self.write(match.replace(r"\ ", " "))
self.write("{!error!}And %i more. Press <tab> to list them" % (left - max_list) ) self.write("{!error!}And %i more. Press <tab> to list them" % (left - max_list))
else: else:
self.tab_count = 0 self.tab_count = 0
for match in possible_matches[listed:]: for match in possible_matches[listed:]:
@ -787,13 +762,14 @@ class Legacy(BaseMode, component.Component):
for i in range(listed, listed + max_list): for i in range(listed, listed + max_list):
match = possible_matches[i] match = possible_matches[i]
self.write(match.replace(r"\ ", " ")) self.write(match.replace(r"\ ", " "))
self.write("{!error!}And %i more (%i/%i). Press <tab> to view more" % (left - max_list, hits-1, pages) ) self.write("{!error!}And %i more (%i/%i). Press <tab> to view more" % (
left - max_list, hits - 1, pages))
else: else:
self.tab_count = 0 self.tab_count = 0
for match in possible_matches[listed:]: for match in possible_matches[listed:]:
self.write(match.replace(r"\ ", " ")) self.write(match.replace(r"\ ", " "))
if hits > 2: if hits > 2:
self.write("{!green!}Finished listing %i torrents (%i/%i)" % (match_count, hits-1, pages) ) self.write("{!green!}Finished listing %i torrents (%i/%i)" % (match_count, hits - 1, pages))
#We only want to print eventual colors or other control characters, not return them #We only want to print eventual colors or other control characters, not return them
line = format_utils.remove_formatting(line) line = format_utils.remove_formatting(line)
@ -818,9 +794,9 @@ class Legacy(BaseMode, component.Component):
continue continue
f = os.path.join(line, f) f = os.path.join(line, f)
if os.path.isdir(f): if os.path.isdir(f):
if os.sep == '\\': # Windows path support :| if os.sep == "\\": # Windows path support
f += "\\" f += "\\"
else: # \o/ Unix else: # Unix
f += "/" f += "/"
elif not f.endswith(ext): elif not f.endswith(ext):
continue continue
@ -833,7 +809,7 @@ class Legacy(BaseMode, component.Component):
# shares a common prefix. # shares a common prefix.
for f in os.listdir(os.path.dirname(line)): for f in os.listdir(os.path.dirname(line)):
if f.startswith(os.path.split(line)[1]): if f.startswith(os.path.split(line)[1]):
ret.append(os.path.join( os.path.dirname(line), f)) ret.append(os.path.join(os.path.dirname(line), f))
except OSError: except OSError:
self.console.write("{!error!}Permission denied: {!info!}%s" % line) self.console.write("{!error!}Permission denied: {!info!}%s" % line)
else: else:
@ -847,9 +823,9 @@ class Legacy(BaseMode, component.Component):
p = os.path.join(os.path.dirname(line), f) p = os.path.join(os.path.dirname(line), f)
if os.path.isdir(p): if os.path.isdir(p):
if os.sep == '\\': # Windows path support :| if os.sep == "\\": # Windows path support
p += "\\" p += "\\"
else: # \o/ Unix else: # Unix
p += "/" p += "/"
ret.append(p) ret.append(p)
except OSError: except OSError:
@ -897,7 +873,7 @@ class Legacy(BaseMode, component.Component):
possible_matches = [] possible_matches = []
possible_matches2 = [] possible_matches2 = []
match_count = 0 match_count = 0
match_count2 = 0 match_count2 = 0
for torrent_id, torrent_name in self.torrents: for torrent_id, torrent_name in self.torrents:
if torrent_id.startswith(line): if torrent_id.startswith(line):
@ -909,9 +885,10 @@ class Legacy(BaseMode, component.Component):
# Find all possible matches # Find all possible matches
for torrent_id, torrent_name in self.torrents: for torrent_id, torrent_name in self.torrents:
#Escape spaces to avoid, for example, expanding "Doc" into "Doctor Who" and removing everything containing one of these words # Escape spaces to avoid, for example, expanding "Doc" into "Doctor Who" and removing
# everything containing one of these words
escaped_name = torrent_name.replace(" ", "\\ ") escaped_name = torrent_name.replace(" ", "\\ ")
#If we only matched one torrent, don't add the full name or it'll also get autocompleted # If we only matched one torrent, don't add the full name or it'll also get autocompleted
if match_count == 1: if match_count == 1:
if torrent_id.startswith(line): if torrent_id.startswith(line):
possible_matches.append(torrent_id) possible_matches.append(torrent_id)
@ -932,10 +909,12 @@ class Legacy(BaseMode, component.Component):
text = "{!info!}%s{!input!}%s - '%s'" % (torrent_id[:l], torrent_id[l:], torrent_name) text = "{!info!}%s{!input!}%s - '%s'" % (torrent_id[:l], torrent_id[l:], torrent_name)
possible_matches.append(text) possible_matches.append(text)
if torrent_name.startswith(line): if torrent_name.startswith(line):
text = "{!info!}%s{!input!}%s ({!cyan!}%s{!input!})" % (escaped_name[:l], escaped_name[l:], torrent_id) text = "{!info!}%s{!input!}%s ({!cyan!}%s{!input!})" % (
escaped_name[:l], escaped_name[l:], torrent_id)
possible_matches.append(text) possible_matches.append(text)
elif torrent_name.lower().startswith(line.lower()): elif torrent_name.lower().startswith(line.lower()):
text = "{!info!}%s{!input!}%s ({!cyan!}%s{!input!})" % (escaped_name[:l], escaped_name[l:], torrent_id) text = "{!info!}%s{!input!}%s ({!cyan!}%s{!input!})" % (
escaped_name[:l], escaped_name[l:], torrent_id)
possible_matches2.append(text) possible_matches2.append(text)
return possible_matches + possible_matches2 return possible_matches + possible_matches2

View File

@ -1,42 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# popup.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
try: try:
import curses import curses
import signal
except ImportError: except ImportError:
pass pass
@ -46,20 +18,23 @@ import format_utils
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class ALIGN: class ALIGN:
TOP_LEFT = 1 TOP_LEFT = 1
TOP_CENTER = 2 TOP_CENTER = 2
TOP_RIGHT = 3 TOP_RIGHT = 3
MIDDLE_LEFT = 4 MIDDLE_LEFT = 4
MIDDLE_CENTER= 5 MIDDLE_CENTER = 5
MIDDLE_RIGHT = 6 MIDDLE_RIGHT = 6
BOTTOM_LEFT = 7 BOTTOM_LEFT = 7
BOTTOM_CENTER= 8 BOTTOM_CENTER = 8
BOTTOM_RIGHT = 9 BOTTOM_RIGHT = 9
DEFAULT = MIDDLE_CENTER DEFAULT = MIDDLE_CENTER
class Popup: class Popup:
def __init__(self,parent_mode,title,width_req=0,height_req=0,align=ALIGN.DEFAULT, close_cb=None,init_lines=None): def __init__(self, parent_mode, title, width_req=0, height_req=0, align=ALIGN.DEFAULT,
close_cb=None, init_lines=None):
""" """
Init a new popup. The default constructor will handle sizing and borders and the like. Init a new popup. The default constructor will handle sizing and borders and the like.
@ -77,14 +52,14 @@ class Popup:
refresh(self) - draw the popup window to screen. this default mode simply draws a bordered window refresh(self) - draw the popup window to screen. this default mode simply draws a bordered window
with the supplied title to the screen with the supplied title to the screen
add_string(self, row, string) - add string at row. handles triming/ignoring if the string won't fit in the popup add_string(self, row, string) - add string at row. handles triming/ignoring if the string won't fit in the popup
_doRead(self) - handle user input to the popup. _doRead(self) - handle user input to the popup.
""" """
self.parent = parent_mode self.parent = parent_mode
self.height_req = height_req self.height_req = height_req
self.width_req = width_req self.width_req = width_req
self.align = align self.align = align
self.handle_resize() self.handle_resize()
@ -102,19 +77,19 @@ class Popup:
def _refresh_lines(self): def _refresh_lines(self):
crow = 1 crow = 1
for line in self._lines[self.lineoff:]: for line in self._lines[self.lineoff:]:
if (crow >= self.height-1): if (crow >= self.height - 1):
break break
self.parent.add_string(crow, line, self.screen, 1, False, True) self.parent.add_string(crow, line, self.screen, 1, False, True)
crow+=1 crow += 1
def handle_resize(self): def handle_resize(self):
if isinstance(self.height_req, float) and 0.0 < self.height_req <= 1.0: if isinstance(self.height_req, float) and 0.0 < self.height_req <= 1.0:
hr = int( (self.parent.rows - 2) * self.height_req ) hr = int((self.parent.rows - 2) * self.height_req)
else: else:
hr = self.height_req hr = self.height_req
if isinstance(self.width_req, float) and 0.0 < self.width_req <= 1.0: if isinstance(self.width_req, float) and 0.0 < self.width_req <= 1.0:
wr = int( (self.parent.cols - 2) * self.width_req ) wr = int((self.parent.cols - 2) * self.width_req)
else: else:
wr = self.width_req wr = self.width_req
@ -122,31 +97,31 @@ class Popup:
#Height #Height
if hr == 0: if hr == 0:
hr = int(self.parent.rows/2) hr = int(self.parent.rows / 2)
elif hr == -1: elif hr == -1:
hr = self.parent.rows - 2 hr = self.parent.rows - 2
elif hr > self.parent.rows - 2: elif hr > self.parent.rows - 2:
hr = self.parent.rows - 2 hr = self.parent.rows - 2
#Width #Width
if wr == 0: if wr == 0:
wr = int(self.parent.cols/2) wr = int(self.parent.cols / 2)
elif wr == -1: elif wr == -1:
wr = self.parent.cols wr = self.parent.cols
elif wr >= self.parent.cols: elif wr >= self.parent.cols:
wr = self.parent.cols wr = self.parent.cols
if self.align in [ALIGN.TOP_CENTER, ALIGN.TOP_LEFT, ALIGN.TOP_RIGHT]: if self.align in [ALIGN.TOP_CENTER, ALIGN.TOP_LEFT, ALIGN.TOP_RIGHT]:
by = 1 by = 1
elif self.align in [ALIGN.MIDDLE_CENTER, ALIGN.MIDDLE_LEFT, ALIGN.MIDDLE_RIGHT]: elif self.align in [ALIGN.MIDDLE_CENTER, ALIGN.MIDDLE_LEFT, ALIGN.MIDDLE_RIGHT]:
by = (self.parent.rows/2)-(hr/2) by = (self.parent.rows / 2) - (hr / 2)
elif self.align in [ALIGN.BOTTOM_CENTER, ALIGN.BOTTOM_LEFT, ALIGN.BOTTOM_RIGHT]: elif self.align in [ALIGN.BOTTOM_CENTER, ALIGN.BOTTOM_LEFT, ALIGN.BOTTOM_RIGHT]:
by = self.parent.rows - hr - 1 by = self.parent.rows - hr - 1
if self.align in [ALIGN.TOP_LEFT, ALIGN.MIDDLE_LEFT, ALIGN.BOTTOM_LEFT]: if self.align in [ALIGN.TOP_LEFT, ALIGN.MIDDLE_LEFT, ALIGN.BOTTOM_LEFT]:
bx = 0 bx = 0
elif self.align in [ALIGN.TOP_CENTER, ALIGN.MIDDLE_CENTER, ALIGN.BOTTOM_CENTER]: elif self.align in [ALIGN.TOP_CENTER, ALIGN.MIDDLE_CENTER, ALIGN.BOTTOM_CENTER]:
bx = (self.parent.cols/2)-(wr/2) bx = (self.parent.cols / 2) - (wr / 2)
elif self.align in [ALIGN.TOP_RIGHT, ALIGN.MIDDLE_RIGHT, ALIGN.BOTTOM_RIGHT]: elif self.align in [ALIGN.TOP_RIGHT, ALIGN.MIDDLE_RIGHT, ALIGN.BOTTOM_RIGHT]:
bx = self.parent.cols - wr - 1 bx = self.parent.cols - wr - 1
@ -155,21 +130,21 @@ class Popup:
self.x, self.y = bx, by self.x, self.y = bx, by
self.height, self.width = self.screen.getmaxyx() self.height, self.width = self.screen.getmaxyx()
def refresh(self): def refresh(self):
self.screen.erase() self.screen.erase()
self.screen.border(0, 0, 0, 0) self.screen.border(0, 0, 0, 0)
toff = max(1, (self.width//2) - (len(self.title)//2)) toff = max(1, (self.width // 2) - (len(self.title) // 2))
self.parent.add_string(0, "{!white,black,bold!}%s"%self.title, self.screen, toff, False, True) self.parent.add_string(0, "{!white,black,bold!}%s" % self.title, self.screen, toff, False, True)
self._refresh_lines() self._refresh_lines()
if (len(self._lines) > (self.height-2)): if (len(self._lines) > (self.height - 2)):
lts = len(self._lines)-(self.height-3) lts = len(self._lines) - (self.height - 3)
perc_sc = float(self.lineoff)/lts perc_sc = float(self.lineoff) / lts
sb_pos = int((self.height-2)*perc_sc)+1 sb_pos = int((self.height - 2) * perc_sc) + 1
if (sb_pos == 1) and (self.lineoff != 0): if (sb_pos == 1) and (self.lineoff != 0):
sb_pos += 1 sb_pos += 1
self.parent.add_string(sb_pos, "{!red,black,bold!}#", self.screen, col=(self.width-1), pad=False, trim=False) self.parent.add_string(sb_pos, "{!red,black,bold!}#", self.screen, col=(self.width - 1),
pad=False, trim=False)
self.screen.redrawwin() self.screen.redrawwin()
self.screen.noutrefresh() self.screen.noutrefresh()
@ -180,28 +155,28 @@ class Popup:
def handle_read(self, c): def handle_read(self, c):
p_off = self.height - 3 p_off = self.height - 3
if c == curses.KEY_UP: if c == curses.KEY_UP:
self.lineoff = max(0, self.lineoff -1) self.lineoff = max(0, self.lineoff - 1)
elif c == curses.KEY_PPAGE: elif c == curses.KEY_PPAGE:
self.lineoff = max(0, self.lineoff - p_off) self.lineoff = max(0, self.lineoff - p_off)
elif c == curses.KEY_HOME: elif c == curses.KEY_HOME:
self.lineoff = 0 self.lineoff = 0
elif c == curses.KEY_DOWN: elif c == curses.KEY_DOWN:
if len(self._lines)-self.lineoff > (self.height-2): if len(self._lines) - self.lineoff > (self.height - 2):
self.lineoff += 1 self.lineoff += 1
elif c == curses.KEY_NPAGE: elif c == curses.KEY_NPAGE:
self.lineoff = min(len(self._lines) - self.height+2, self.lineoff + p_off) self.lineoff = min(len(self._lines) - self.height + 2, self.lineoff + p_off)
elif c == curses.KEY_END: elif c == curses.KEY_END:
self.lineoff = len(self._lines) - self.height+2 self.lineoff = len(self._lines) - self.height + 2
elif c == curses.KEY_ENTER or c == 10 or c == 27: # close on enter/esc elif c == curses.KEY_ENTER or c == 10 or c == 27: # close on enter/esc
if self.close_cb: if self.close_cb:
self.close_cb() self.close_cb()
return True # close the popup return True # close the popup
if c > 31 and c < 256 and chr(c) == 'q': if c > 31 and c < 256 and chr(c) == "q":
if self.close_cb: if self.close_cb:
self.close_cb() self.close_cb()
return True # close the popup return True # close the popup
self.refresh() self.refresh()
@ -215,7 +190,7 @@ class Popup:
def add_divider(self): def add_divider(self):
if not self.divider: if not self.divider:
self.divider = "-"*(self.width-2) self.divider = "-" * (self.width - 2)
self._lines.append(self.divider) self._lines.append(self.divider)
@ -224,7 +199,7 @@ class SelectablePopup(Popup):
A popup which will let the user select from some of the lines that A popup which will let the user select from some of the lines that
are added. are added.
""" """
def __init__(self,parent_mode,title, selection_callback, args=(), align=ALIGN.DEFAULT, immediate_action=False): def __init__(self, parent_mode, title, selection_callback, args=(), align=ALIGN.DEFAULT, immediate_action=False):
Popup.__init__(self, parent_mode, title, align=align) Popup.__init__(self, parent_mode, title, align=align)
self._selection_callback = selection_callback self._selection_callback = selection_callback
self._selection_args = args self._selection_args = args
@ -240,63 +215,67 @@ class SelectablePopup(Popup):
def add_line(self, string, selectable=True, use_underline=True, data=None, foreground=None): def add_line(self, string, selectable=True, use_underline=True, data=None, foreground=None):
if use_underline: if use_underline:
udx = string.find('_') udx = string.find("_")
if udx >= 0: if udx >= 0:
string = string[:udx]+string[udx+1:] string = string[:udx] + string[udx + 1:]
self._udxs[len(self._lines)+1] = udx self._udxs[len(self._lines) + 1] = udx
c = string[udx].lower() c = string[udx].lower()
self._hotkeys[c] = len(self._lines) self._hotkeys[c] = len(self._lines)
Popup.add_line(self, string) Popup.add_line(self, string)
self._line_foregrounds.append(foreground) self._line_foregrounds.append(foreground)
if selectable: if selectable:
self._selectable_lines.append(len(self._lines)-1) self._selectable_lines.append(len(self._lines) - 1)
self._select_data.append(data) self._select_data.append(data)
if self._selected < 0: if self._selected < 0:
self._selected = (len(self._lines)-1) self._selected = (len(self._lines) - 1)
def _refresh_lines(self): def _refresh_lines(self):
crow = 1 crow = 1
for row, line in enumerate(self._lines): for row, line in enumerate(self._lines):
if (crow >= self.height-1): if (crow >= self.height - 1):
break break
if (row < self.lineoff): if (row < self.lineoff):
continue continue
fg = self._line_foregrounds[row] fg = self._line_foregrounds[row]
udx = self._udxs.get(crow) udx = self._udxs.get(crow)
if row == self._selected: if row == self._selected:
if fg == None: fg = "black" if fg is None:
colorstr = "{!%s,white,bold!}"%fg fg = "black"
colorstr = "{!%s,white,bold!}" % fg
if udx >= 0: if udx >= 0:
ustr = "{!%s,white,bold,underline!}"%fg ustr = "{!%s,white,bold,underline!}" % fg
else: else:
if fg == None: fg = "white" if fg is None:
colorstr = "{!%s,black!}"%fg fg = "white"
colorstr = "{!%s,black!}" % fg
if udx >= 0: if udx >= 0:
ustr = "{!%s,black,underline!}"%fg ustr = "{!%s,black,underline!}" % fg
if udx == 0: if udx == 0:
self.parent.add_string(crow, "- %s%c%s%s"%(ustr, line[0], colorstr, line[1:]), self.screen, 1, False, True) self.parent.add_string(crow, "- %s%c%s%s" % (
ustr, line[0], colorstr, line[1:]), self.screen, 1, False, True)
elif udx > 0: elif udx > 0:
# well, this is a litte gross # well, this is a litte gross
self.parent.add_string(crow, "- %s%s%s%c%s%s"%(colorstr, line[:udx], ustr, line[udx], colorstr, line[udx+1:]), self.screen, 1, False, True) self.parent.add_string(crow, "- %s%s%s%c%s%s" % (
colorstr, line[:udx], ustr, line[udx], colorstr, line[udx + 1:]), self.screen, 1, False, True)
else: else:
self.parent.add_string(crow, "- %s%s"%(colorstr, line), self.screen, 1, False, True) self.parent.add_string(crow, "- %s%s" % (colorstr, line), self.screen, 1, False, True)
crow+=1 crow += 1
def current_selection(self): def current_selection(self):
"Returns a tuple of (selected index, selected data)" "Returns a tuple of (selected index, selected data)"
idx = self._selectable_lines.index(self._selected) idx = self._selectable_lines.index(self._selected)
return (idx, self._select_data[idx]) return (idx, self._select_data[idx])
def add_divider(self,color="white"): def add_divider(self, color="white"):
if not self.divider: if not self.divider:
self.divider = "-"*(self.width-6)+" -" self.divider = "-" * (self.width - 6) + " -"
self._lines.append(self.divider) self._lines.append(self.divider)
self._line_foregrounds.append(color) self._line_foregrounds.append(color)
def _move_cursor_up(self, amount): def _move_cursor_up(self, amount):
if self._selectable_lines.index(self._selected) > amount: if self._selectable_lines.index(self._selected) > amount:
idx = self._selectable_lines.index(self._selected) idx = self._selectable_lines.index(self._selected)
self._selected = self._selectable_lines[idx-amount] self._selected = self._selectable_lines[idx - amount]
else: else:
self._selected = self._selectable_lines[0] self._selected = self._selectable_lines[0]
@ -306,7 +285,7 @@ class SelectablePopup(Popup):
def _move_cursor_down(self, amount): def _move_cursor_down(self, amount):
idx = self._selectable_lines.index(self._selected) idx = self._selectable_lines.index(self._selected)
if (idx < len(self._selectable_lines) - amount): if (idx < len(self._selectable_lines) - amount):
self._selected = self._selectable_lines[idx+amount] self._selected = self._selectable_lines[idx + amount]
else: else:
self._selected = self._selectable_lines[-1] self._selected = self._selectable_lines[-1]
@ -329,7 +308,7 @@ class SelectablePopup(Popup):
elif c == curses.KEY_END: elif c == curses.KEY_END:
self._move_cursor_down(len(self._selectable_lines)) self._move_cursor_down(len(self._selectable_lines))
elif c == 27: # close on esc, no action elif c == 27: # close on esc, no action
return True return True
elif c == curses.KEY_ENTER or c == 10: elif c == curses.KEY_ENTER or c == 10:
@ -337,13 +316,13 @@ class SelectablePopup(Popup):
return self._selection_callback(idx, self._select_data[idx], *self._selection_args) return self._selection_callback(idx, self._select_data[idx], *self._selection_args)
if c > 31 and c < 256: if c > 31 and c < 256:
if chr(c) == 'q': if chr(c) == "q":
return True # close the popup return True # close the popup
uc = chr(c).lower() uc = chr(c).lower()
if uc in self._hotkeys: if uc in self._hotkeys:
# exec hotkey action # exec hotkey action
idx = self._selectable_lines.index(self._hotkeys[uc]) idx = self._selectable_lines.index(self._hotkeys[uc])
return self._selection_callback(idx,self._select_data[idx],*self._selection_args) return self._selection_callback(idx, self._select_data[idx], *self._selection_args)
self.refresh() self.refresh()
return False return False
@ -357,12 +336,12 @@ class MessagePopup(Popup):
self.message = message self.message = message
#self.width= int(parent_mode.cols/2) #self.width= int(parent_mode.cols/2)
Popup.__init__(self, parent_mode, title, align=align, width_req=width_req) Popup.__init__(self, parent_mode, title, align=align, width_req=width_req)
lns = format_utils.wrap_string(self.message, self.width-2, 3, True) lns = format_utils.wrap_string(self.message, self.width - 2, 3, True)
self.height_req = min(len(lns)+2, int(parent_mode.rows*2/3)) self.height_req = min(len(lns) + 2, int(parent_mode.rows * 2 / 3))
self.handle_resize() self.handle_resize()
self._lines = lns self._lines = lns
def handle_resize(self): def handle_resize(self):
Popup.handle_resize(self) Popup.handle_resize(self)
self.clear() self.clear()
self._lines = format_utils.wrap_string(self.message, self.width-2, 3, True) self._lines = format_utils.wrap_string(self.message, self.width - 2, 3, True)

View File

@ -1,36 +1,10 @@
# # -*- coding: utf-8 -*-
# preference_panes.py
# #
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import logging import logging
@ -52,10 +26,11 @@ class NoInput:
def depend_skip(self): def depend_skip(self):
return False return False
class Header(NoInput): class Header(NoInput):
def __init__(self, parent, header, space_above, space_below): def __init__(self, parent, header, space_above, space_below):
self.parent = parent self.parent = parent
self.header = "{!white,black,bold!}%s"%header self.header = "{!white,black,bold!}%s" % header
self.space_above = space_above self.space_above = space_above
self.space_below = space_below self.space_below = space_below
self.name = header self.name = header
@ -65,32 +40,35 @@ class Header(NoInput):
if self.space_above: if self.space_above:
row += 1 row += 1
rows += 1 rows += 1
self.parent.add_string(row, self.header, screen, offset-1, False, True) self.parent.add_string(row, self.header, screen, offset - 1, False, True)
if self.space_below: rows += 1 if self.space_below:
rows += 1
return rows return rows
class InfoField(NoInput): class InfoField(NoInput):
def __init__(self, parent, label, value, name): def __init__(self, parent, label, value, name):
self.parent = parent self.parent = parent
self.label = label self.label = label
self.value = value self.value = value
self.txt = "%s %s"%(label, value) self.txt = "%s %s" % (label, value)
self.name = name self.name = name
def render(self, screen, row, width, active, offset): def render(self, screen, row, width, active, offset):
self.parent.add_string(row, self.txt, screen, offset-1, False, True) self.parent.add_string(row, self.txt, screen, offset - 1, False, True)
return 1 return 1
def set_value(self, v): def set_value(self, v):
self.value = v self.value = v
if type(v) == float: if type(v) == float:
self.txt = "%s %.2f"%(self.label, self.value) self.txt = "%s %.2f" % (self.label, self.value)
else: else:
self.txt = "%s %s"%(self.label, self.value) self.txt = "%s %s" % (self.label, self.value)
class BasePane: class BasePane:
def __init__(self, offset, parent, width): def __init__(self, offset, parent, width):
self.offset = offset+1 self.offset = offset + 1
self.parent = parent self.parent = parent
self.width = width self.width = width
self.inputs = [] self.inputs = []
@ -137,8 +115,6 @@ class BasePane:
conf_dict.setdefault("proxy", {})["proxy_hostnames"] = ipt.get_value() conf_dict.setdefault("proxy", {})["proxy_hostnames"] = ipt.get_value()
elif ipt.name == "proxy_peer_connections": elif ipt.name == "proxy_peer_connections":
conf_dict.setdefault("proxy", {})["proxy_peer_connections"] = ipt.get_value() conf_dict.setdefault("proxy", {})["proxy_peer_connections"] = ipt.get_value()
else: else:
conf_dict[ipt.name] = ipt.get_value() conf_dict[ipt.name] = ipt.get_value()
if hasattr(ipt, "get_child"): if hasattr(ipt, "get_child"):
@ -150,13 +126,13 @@ class BasePane:
if not isinstance(ipt, NoInput): if not isinstance(ipt, NoInput):
try: try:
ipt.set_value(conf_dict[ipt.name]) ipt.set_value(conf_dict[ipt.name])
except KeyError: # just ignore if it's not in dict except KeyError: # just ignore if it's not in dict
pass pass
if hasattr(ipt, "get_child"): if hasattr(ipt, "get_child"):
try: try:
c = ipt.get_child() c = ipt.get_child()
c.set_value(conf_dict[c.name]) c.set_value(conf_dict[c.name])
except KeyError: # just ignore if it's not in dict except KeyError: # just ignore if it's not in dict
pass pass
def render(self, mode, screen, width, active): def render(self, mode, screen, width, active):
@ -169,26 +145,27 @@ class BasePane:
drew_act = not active drew_act = not active
crow = 1 crow = 1
for i, ipt in enumerate(self.inputs): for i, ipt in enumerate(self.inputs):
if ipt.depend_skip() or i<self.input_offset: if ipt.depend_skip() or i < self.input_offset:
if active and i==self.active_input: if active and i == self.active_input:
self.input_offset-=1 self.input_offset -= 1
mode.refresh() mode.refresh()
return 0 return 0
continue continue
act = active and i==self.active_input act = active and i == self.active_input
if act: drew_act = True if act:
drew_act = True
crow += ipt.render(screen, crow, width, act, self.offset) crow += ipt.render(screen, crow, width, act, self.offset)
if crow >= (mode.prefs_height): if crow >= (mode.prefs_height):
break break
if not drew_act: if not drew_act:
self.input_offset+=1 self.input_offset += 1
mode.refresh() mode.refresh()
return 0 return 0
if active and self._cursor_row >= 0: if active and self._cursor_row >= 0:
curses.curs_set(2) curses.curs_set(2)
screen.move(self._cursor_row, self._cursor_col+self.offset-1) screen.move(self._cursor_row, self._cursor_col + self.offset - 1)
else: else:
curses.curs_set(0) curses.curs_set(0)
@ -196,30 +173,30 @@ class BasePane:
# just handles setting the active input # just handles setting the active input
def handle_read(self, c): def handle_read(self, c):
if not self.inputs: # no inputs added yet if not self.inputs: # no inputs added yet
return return
if c == curses.KEY_UP: if c == curses.KEY_UP:
nc = max(0, self.active_input-1) nc = max(0, self.active_input - 1)
while isinstance(self.inputs[nc], NoInput) or self.inputs[nc].depend_skip(): while isinstance(self.inputs[nc], NoInput) or self.inputs[nc].depend_skip():
nc-=1 nc -= 1
if nc <= 0: break if nc <= 0:
break
if not isinstance(self.inputs[nc], NoInput) or self.inputs[nc].depend_skip(): if not isinstance(self.inputs[nc], NoInput) or self.inputs[nc].depend_skip():
self.active_input = nc self.active_input = nc
elif c == curses.KEY_DOWN: elif c == curses.KEY_DOWN:
ilen = len(self.inputs) ilen = len(self.inputs)
nc = min(self.active_input+1, ilen-1) nc = min(self.active_input + 1, ilen - 1)
while isinstance(self.inputs[nc], NoInput) or self.inputs[nc].depend_skip(): while isinstance(self.inputs[nc], NoInput) or self.inputs[nc].depend_skip():
nc+=1 nc += 1
if nc >= ilen: if nc >= ilen:
nc-=1 nc -= 1
break break
if not isinstance(self.inputs[nc], NoInput) or self.inputs[nc].depend_skip(): if not isinstance(self.inputs[nc], NoInput) or self.inputs[nc].depend_skip():
self.active_input = nc self.active_input = nc
else: else:
self.inputs[self.active_input].handle_read(c) self.inputs[self.active_input].handle_read(c)
def add_header(self, header, space_above=False, space_below=False): def add_header(self, header, space_above=False, space_below=False):
self.inputs.append(Header(self.parent, header, space_above, space_below)) self.inputs.append(Header(self.parent, header, space_above, space_below))
@ -242,7 +219,8 @@ class BasePane:
self.inputs.append(IntSpinInput(self.parent, message, name, self.move, value, min_val, max_val)) self.inputs.append(IntSpinInput(self.parent, message, name, self.move, value, min_val, max_val))
def add_float_spin_input(self, name, message, value, inc_amt, precision, min_val, max_val): def add_float_spin_input(self, name, message, value, inc_amt, precision, min_val, max_val):
self.inputs.append(FloatSpinInput(self.parent, message, name, self.move, value, inc_amt, precision, min_val, max_val)) self.inputs.append(FloatSpinInput(self.parent, message, name, self.move, value,
inc_amt, precision, min_val, max_val))
class InterfacePane(BasePane): class InterfacePane(BasePane):
@ -250,22 +228,30 @@ class InterfacePane(BasePane):
BasePane.__init__(self, offset, parent, width) BasePane.__init__(self, offset, parent, width)
self.add_header("General options", False) self.add_header("General options", False)
self.add_checked_input("ring_bell", "Ring system bell when a download finishes", parent.console_config["ring_bell"]) self.add_checked_input("ring_bell", "Ring system bell when a download finishes",
parent.console_config["ring_bell"])
self.add_header("New Console UI", True) self.add_header("New Console UI", True)
self.add_checked_input("separate_complete", "List complete torrents after incomplete regardless of sorting order", parent.console_config["separate_complete"]) self.add_checked_input("separate_complete",
self.add_checked_input("move_selection", "Move selection when moving torrents in the queue", parent.console_config["move_selection"]) "List complete torrents after incomplete regardless of sorting order",
parent.console_config["separate_complete"])
self.add_checked_input("move_selection", "Move selection when moving torrents in the queue",
parent.console_config["move_selection"])
self.add_header("Legacy Mode", True) self.add_header("Legacy Mode", True)
self.add_checked_input("ignore_duplicate_lines", "Do not store duplicate input in history", parent.console_config["ignore_duplicate_lines"]) self.add_checked_input("ignore_duplicate_lines", "Do not store duplicate input in history",
self.add_checked_input("save_legacy_history", "Store and load command line history in Legacy mode", parent.console_config["save_legacy_history"]) parent.console_config["ignore_duplicate_lines"])
self.add_checked_input("save_legacy_history", "Store and load command line history in Legacy mode",
parent.console_config["save_legacy_history"])
self.add_header("", False) self.add_header("", False)
self.add_checked_input("third_tab_lists_all", "Third tab lists all remaining torrents in legacy mode", parent.console_config["third_tab_lists_all"]) self.add_checked_input("third_tab_lists_all", "Third tab lists all remaining torrents in legacy mode",
self.add_int_spin_input("torrents_per_tab_press", "Torrents per tab press", parent.console_config["torrents_per_tab_press"], 5, 100) parent.console_config["third_tab_lists_all"])
self.add_int_spin_input("torrents_per_tab_press", "Torrents per tab press",
parent.console_config["torrents_per_tab_press"], 5, 100)
class ColumnsPane(BasePane): class ColumnsPane(BasePane):
@ -276,7 +262,7 @@ class ColumnsPane(BasePane):
default_prefs = deluge.ui.console.modes.alltorrents.DEFAULT_PREFS default_prefs = deluge.ui.console.modes.alltorrents.DEFAULT_PREFS
for cpn in deluge.ui.console.modes.alltorrents.column_pref_names: for cpn in deluge.ui.console.modes.alltorrents.column_pref_names:
pn = "show_%s"%cpn pn = "show_%s" % cpn
#If there is no option for it, it's not togglable #If there is no option for it, it's not togglable
# We check in default_prefs because it might still exist in config files # We check in default_prefs because it might still exist in config files
if pn not in default_prefs: if pn not in default_prefs:
@ -286,7 +272,7 @@ class ColumnsPane(BasePane):
parent.console_config[pn]) parent.console_config[pn])
self.add_header("Column Widths (-1 = expand)", True) self.add_header("Column Widths (-1 = expand)", True)
for cpn in deluge.ui.console.modes.alltorrents.column_pref_names: for cpn in deluge.ui.console.modes.alltorrents.column_pref_names:
pn = "%s_width"%cpn pn = "%s_width" % cpn
if pn not in default_prefs: if pn not in default_prefs:
continue continue
self.add_int_spin_input(pn, self.add_int_spin_input(pn,
@ -300,11 +286,15 @@ class DownloadsPane(BasePane):
self.add_header("Folders") self.add_header("Folders")
self.add_text_input("download_location", "Download To:", parent.core_config["download_location"]) self.add_text_input("download_location", "Download To:", parent.core_config["download_location"])
cmptxt = TextInput(self.parent, self.move, self.width, None, "move_completed_path", parent.core_config["move_completed_path"], False) cmptxt = TextInput(self.parent, self.move, self.width, None, "move_completed_path",
parent.core_config["move_completed_path"], False)
self.add_checkedplus_input("move_completed", "Move completed to:", cmptxt, parent.core_config["move_completed"]) self.add_checkedplus_input("move_completed", "Move completed to:", cmptxt, parent.core_config["move_completed"])
copytxt = TextInput(self.parent, self.move, self.width, None, "torrentfiles_location", parent.core_config["torrentfiles_location"], False) copytxt = TextInput(self.parent, self.move, self.width, None, "torrentfiles_location",
self.add_checkedplus_input("copy_torrent_file", "Copy of .torrent files to:", copytxt, parent.core_config["copy_torrent_file"]) parent.core_config["torrentfiles_location"], False)
self.add_checked_input("del_copy_torrent_file", "Delete copy of torrent file on remove", parent.core_config["del_copy_torrent_file"]) self.add_checkedplus_input("copy_torrent_file", "Copy of .torrent files to:", copytxt,
parent.core_config["copy_torrent_file"])
self.add_checked_input("del_copy_torrent_file", "Delete copy of torrent file on remove",
parent.core_config["del_copy_torrent_file"])
self.add_header("Options", True) self.add_header("Options", True)
self.add_checked_input("prioritize_first_last_pieces", "Prioritize first and last pieces of torrent", self.add_checked_input("prioritize_first_last_pieces", "Prioritize first and last pieces of torrent",
@ -320,7 +310,8 @@ class NetworkPane(BasePane):
def __init__(self, offset, parent, width): def __init__(self, offset, parent, width):
BasePane.__init__(self, offset, parent, width) BasePane.__init__(self, offset, parent, width)
self.add_header("Incomming Ports") self.add_header("Incomming Ports")
inrand = CheckedInput(parent, "Use Random Ports Active Port: %d"%parent.active_port, "random_port", parent.core_config["random_port"]) inrand = CheckedInput(parent, "Use Random Ports Active Port: %d" % parent.active_port,
"random_port", parent.core_config["random_port"])
self.inputs.append(inrand) self.inputs.append(inrand)
listen_ports = parent.core_config["listen_ports"] listen_ports = parent.core_config["listen_ports"]
self.infrom = IntSpinInput(self.parent, " From:", "listen_ports_from", self.move, listen_ports[0], 0, 65535) self.infrom = IntSpinInput(self.parent, " From:", "listen_ports_from", self.move, listen_ports[0], 0, 65535)
@ -330,9 +321,9 @@ class NetworkPane(BasePane):
self.inputs.append(self.infrom) self.inputs.append(self.infrom)
self.inputs.append(self.into) self.inputs.append(self.into)
self.add_header("Outgoing Ports", True) self.add_header("Outgoing Ports", True)
outrand = CheckedInput(parent, "Use Random Ports", "random_outgoing_ports", parent.core_config["random_outgoing_ports"]) outrand = CheckedInput(parent, "Use Random Ports", "random_outgoing_ports",
parent.core_config["random_outgoing_ports"])
self.inputs.append(outrand) self.inputs.append(outrand)
out_ports = parent.core_config["outgoing_ports"] out_ports = parent.core_config["outgoing_ports"]
self.outfrom = IntSpinInput(self.parent, " From:", "out_ports_from", self.move, out_ports[0], 0, 65535) self.outfrom = IntSpinInput(self.parent, " From:", "out_ports_from", self.move, out_ports[0], 0, 65535)
@ -342,9 +333,9 @@ class NetworkPane(BasePane):
self.inputs.append(self.outfrom) self.inputs.append(self.outfrom)
self.inputs.append(self.outto) self.inputs.append(self.outto)
self.add_header("Interface", True) self.add_header("Interface", True)
self.add_text_input("listen_interface", "IP address of the interface to listen on (leave empty for default):", parent.core_config["listen_interface"]) self.add_text_input("listen_interface", "IP address of the interface to listen on (leave empty for default):",
parent.core_config["listen_interface"])
self.add_header("TOS", True) self.add_header("TOS", True)
self.add_text_input("peer_tos", "Peer TOS Byte:", parent.core_config["peer_tos"]) self.add_text_input("peer_tos", "Peer TOS Byte:", parent.core_config["peer_tos"])
@ -358,28 +349,44 @@ class NetworkPane(BasePane):
self.add_checked_input("dht", "DHT", parent.core_config["dht"]) self.add_checked_input("dht", "DHT", parent.core_config["dht"])
self.add_header("Encryption", True) self.add_header("Encryption", True)
self.add_select_input("enc_in_policy", "Inbound:", ["Forced", "Enabled", "Disabled"], [0, 1, 2], parent.core_config["enc_in_policy"]) self.add_select_input("enc_in_policy", "Inbound:", ["Forced", "Enabled", "Disabled"], [0, 1, 2],
self.add_select_input("enc_out_policy", "Outbound:", ["Forced", "Enabled", "Disabled"], [0, 1, 2], parent.core_config["enc_out_policy"]) parent.core_config["enc_in_policy"])
self.add_select_input("enc_level", "Level:", ["Handshake", "Full Stream", "Either"], [0, 1, 2], parent.core_config["enc_level"]) self.add_select_input("enc_out_policy", "Outbound:", ["Forced", "Enabled", "Disabled"], [0, 1, 2],
parent.core_config["enc_out_policy"])
self.add_select_input("enc_level", "Level:", ["Handshake", "Full Stream", "Either"], [0, 1, 2],
parent.core_config["enc_level"])
class BandwidthPane(BasePane): class BandwidthPane(BasePane):
def __init__(self, offset, parent, width): def __init__(self, offset, parent, width):
BasePane.__init__(self, offset, parent, width) BasePane.__init__(self, offset, parent, width)
self.add_header("Global Bandwidth Usage") self.add_header("Global Bandwidth Usage")
self.add_int_spin_input("max_connections_global", "Maximum Connections:", parent.core_config["max_connections_global"], -1, 9000) self.add_int_spin_input("max_connections_global", "Maximum Connections:",
self.add_int_spin_input("max_upload_slots_global", "Maximum Upload Slots:", parent.core_config["max_upload_slots_global"], -1, 9000) parent.core_config["max_connections_global"], -1, 9000)
self.add_float_spin_input("max_download_speed", "Maximum Download Speed (KiB/s):", parent.core_config["max_download_speed"], 1.0, 1, -1.0, 60000.0) self.add_int_spin_input("max_upload_slots_global", "Maximum Upload Slots:",
self.add_float_spin_input("max_upload_speed", "Maximum Upload Speed (KiB/s):", parent.core_config["max_upload_speed"], 1.0, 1, -1.0, 60000.0) parent.core_config["max_upload_slots_global"], -1, 9000)
self.add_int_spin_input("max_half_open_connections", "Maximum Half-Open Connections:", parent.core_config["max_half_open_connections"], -1, 9999) self.add_float_spin_input("max_download_speed", "Maximum Download Speed (KiB/s):",
self.add_int_spin_input("max_connections_per_second", "Maximum Connection Attempts per Second:", parent.core_config["max_connections_per_second"], -1, 9999) parent.core_config["max_download_speed"], 1.0, 1, -1.0, 60000.0)
self.add_checked_input("ignore_limits_on_local_network", "Ignore limits on local network", parent.core_config["ignore_limits_on_local_network"]) self.add_float_spin_input("max_upload_speed", "Maximum Upload Speed (KiB/s):",
self.add_checked_input("rate_limit_ip_overhead", "Rate Limit IP Overhead", parent.core_config["rate_limit_ip_overhead"]) parent.core_config["max_upload_speed"], 1.0, 1, -1.0, 60000.0)
self.add_int_spin_input("max_half_open_connections", "Maximum Half-Open Connections:",
parent.core_config["max_half_open_connections"], -1, 9999)
self.add_int_spin_input("max_connections_per_second", "Maximum Connection Attempts per Second:",
parent.core_config["max_connections_per_second"], -1, 9999)
self.add_checked_input("ignore_limits_on_local_network", "Ignore limits on local network",
parent.core_config["ignore_limits_on_local_network"])
self.add_checked_input("rate_limit_ip_overhead", "Rate Limit IP Overhead",
parent.core_config["rate_limit_ip_overhead"])
self.add_header("Per Torrent Bandwidth Usage", True) self.add_header("Per Torrent Bandwidth Usage", True)
self.add_int_spin_input("max_connections_per_torrent", "Maximum Connections:", parent.core_config["max_connections_per_torrent"], -1, 9000) self.add_int_spin_input("max_connections_per_torrent", "Maximum Connections:",
self.add_int_spin_input("max_upload_slots_per_torrent", "Maximum Upload Slots:", parent.core_config["max_upload_slots_per_torrent"], -1, 9000) parent.core_config["max_connections_per_torrent"], -1, 9000)
self.add_float_spin_input("max_download_speed_per_torrent", "Maximum Download Speed (KiB/s):", parent.core_config["max_download_speed_per_torrent"], 1.0, 1, -1.0, 60000.0) self.add_int_spin_input("max_upload_slots_per_torrent", "Maximum Upload Slots:",
self.add_float_spin_input("max_upload_speed_per_torrent", "Maximum Upload Speed (KiB/s):", parent.core_config["max_upload_speed_per_torrent"], 1.0, 1, -1.0, 60000.0) parent.core_config["max_upload_slots_per_torrent"], -1, 9000)
self.add_float_spin_input("max_download_speed_per_torrent", "Maximum Download Speed (KiB/s):",
parent.core_config["max_download_speed_per_torrent"], 1.0, 1, -1.0, 60000.0)
self.add_float_spin_input("max_upload_speed_per_torrent", "Maximum Upload Speed (KiB/s):",
parent.core_config["max_upload_speed_per_torrent"], 1.0, 1, -1.0, 60000.0)
class OtherPane(BasePane): class OtherPane(BasePane):
def __init__(self, offset, parent, width): def __init__(self, offset, parent, width):
@ -392,6 +399,7 @@ class OtherPane(BasePane):
self.add_header("GeoIP Database", True) self.add_header("GeoIP Database", True)
self.add_text_input("geoip_db_location", "Location:", parent.core_config["geoip_db_location"]) self.add_text_input("geoip_db_location", "Location:", parent.core_config["geoip_db_location"])
class DaemonPane(BasePane): class DaemonPane(BasePane):
def __init__(self, offset, parent, width): def __init__(self, offset, parent, width):
BasePane.__init__(self, offset, parent, width) BasePane.__init__(self, offset, parent, width)
@ -400,7 +408,9 @@ class DaemonPane(BasePane):
self.add_header("Connections", True) self.add_header("Connections", True)
self.add_checked_input("allow_remote", "Allow remote connections", parent.core_config["allow_remote"]) self.add_checked_input("allow_remote", "Allow remote connections", parent.core_config["allow_remote"])
self.add_header("Other", True) self.add_header("Other", True)
self.add_checked_input("new_release_check", "Periodically check the website for new releases", parent.core_config["new_release_check"]) self.add_checked_input("new_release_check", "Periodically check the website for new releases",
parent.core_config["new_release_check"])
class QueuePane(BasePane): class QueuePane(BasePane):
def __init__(self, offset, parent, width): def __init__(self, offset, parent, width):
@ -409,17 +419,27 @@ class QueuePane(BasePane):
self.add_checked_input("queue_new_to_top", "Queue new torrents to top", parent.core_config["queue_new_to_top"]) self.add_checked_input("queue_new_to_top", "Queue new torrents to top", parent.core_config["queue_new_to_top"])
self.add_header("Active Torrents", True) self.add_header("Active Torrents", True)
self.add_int_spin_input("max_active_limit", "Total active:", parent.core_config["max_active_limit"], -1, 9999) self.add_int_spin_input("max_active_limit", "Total active:", parent.core_config["max_active_limit"], -1, 9999)
self.add_int_spin_input("max_active_downloading", "Total active downloading:", parent.core_config["max_active_downloading"], -1, 9999) self.add_int_spin_input("max_active_downloading", "Total active downloading:",
self.add_int_spin_input("max_active_seeding", "Total active seeding:", parent.core_config["max_active_seeding"], -1, 9999) parent.core_config["max_active_downloading"], -1, 9999)
self.add_checked_input("dont_count_slow_torrents", "Do not count slow torrents", parent.core_config["dont_count_slow_torrents"]) self.add_int_spin_input("max_active_seeding", "Total active seeding:",
self.add_checked_input("auto_manage_prefer_seeds", "Prefer Seeding over Downloading", parent.core_config["auto_manage_prefer_seeds"]) parent.core_config["max_active_seeding"], -1, 9999)
self.add_checked_input("dont_count_slow_torrents", "Do not count slow torrents",
parent.core_config["dont_count_slow_torrents"])
self.add_checked_input("auto_manage_prefer_seeds", "Prefer Seeding over Downloading",
parent.core_config["auto_manage_prefer_seeds"])
self.add_header("Seeding", True) self.add_header("Seeding", True)
self.add_float_spin_input("share_ratio_limit", "Share Ratio Limit:", parent.core_config["share_ratio_limit"], 1.0, 2, -1.0, 100.0) self.add_float_spin_input("share_ratio_limit", "Share Ratio Limit:",
self.add_float_spin_input("seed_time_ratio_limit", "Share Time Ratio:", parent.core_config["seed_time_ratio_limit"], 1.0, 2, -1.0, 100.0) parent.core_config["share_ratio_limit"], 1.0, 2, -1.0, 100.0)
self.add_float_spin_input("seed_time_ratio_limit", "Share Time Ratio:",
parent.core_config["seed_time_ratio_limit"], 1.0, 2, -1.0, 100.0)
self.add_int_spin_input("seed_time_limit", "Seed time (m):", parent.core_config["seed_time_limit"], -1, 10000) self.add_int_spin_input("seed_time_limit", "Seed time (m):", parent.core_config["seed_time_limit"], -1, 10000)
seedratio = FloatSpinInput(self.parent, "", "stop_seed_ratio", self.move, parent.core_config["stop_seed_ratio"], 0.1, 2, 0.5, 100.0) seedratio = FloatSpinInput(self.parent, "", "stop_seed_ratio", self.move,
self.add_checkedplus_input("stop_seed_at_ratio", "Stop seeding when share ratio reaches:", seedratio, parent.core_config["stop_seed_at_ratio"]) parent.core_config["stop_seed_ratio"], 0.1, 2, 0.5, 100.0)
self.add_checked_input("remove_seed_at_ratio", "Remove torrent when share ratio reached", parent.core_config["remove_seed_at_ratio"]) self.add_checkedplus_input("stop_seed_at_ratio", "Stop seeding when share ratio reaches:", seedratio,
parent.core_config["stop_seed_at_ratio"])
self.add_checked_input("remove_seed_at_ratio", "Remove torrent when share ratio reached",
parent.core_config["remove_seed_at_ratio"])
class ProxyPane(BasePane): class ProxyPane(BasePane):
def __init__(self, offset, parent, width): def __init__(self, offset, parent, width):
@ -454,12 +474,13 @@ class CachePane(BasePane):
self.add_header(" Write") self.add_header(" Write")
self.add_info_field(" Blocks Written:", self.parent.status["blocks_written"], "blocks_written") self.add_info_field(" Blocks Written:", self.parent.status["blocks_written"], "blocks_written")
self.add_info_field(" Writes:", self.parent.status["writes"], "writes") self.add_info_field(" Writes:", self.parent.status["writes"], "writes")
self.add_info_field(" Write Cache Hit Ratio:", "%.2f"%self.parent.status["write_hit_ratio"], "write_hit_ratio") self.add_info_field(" Write Cache Hit Ratio:", "%.2f" % self.parent.status["write_hit_ratio"],
"write_hit_ratio")
self.add_header(" Read") self.add_header(" Read")
self.add_info_field(" Blocks Read:", self.parent.status["blocks_read"], "blocks_read") self.add_info_field(" Blocks Read:", self.parent.status["blocks_read"], "blocks_read")
self.add_info_field(" Blocks Read hit:", self.parent.status["blocks_read_hit"], "blocks_read_hit") self.add_info_field(" Blocks Read hit:", self.parent.status["blocks_read_hit"], "blocks_read_hit")
self.add_info_field(" Reads:", self.parent.status["reads"], "reads") self.add_info_field(" Reads:", self.parent.status["reads"], "reads")
self.add_info_field(" Read Cache Hit Ratio:", "%.2f"%self.parent.status["read_hit_ratio"], "read_hit_ratio") self.add_info_field(" Read Cache Hit Ratio:", "%.2f" % self.parent.status["read_hit_ratio"], "read_hit_ratio")
self.add_header(" Size") self.add_header(" Size")
self.add_info_field(" Cache Size:", self.parent.status["cache_size"], "cache_size") self.add_info_field(" Cache Size:", self.parent.status["cache_size"], "cache_size")
self.add_info_field(" Read Cache Size:", self.parent.status["read_cache_size"], "read_cache_size") self.add_info_field(" Read Cache Size:", self.parent.status["read_cache_size"], "read_cache_size")

View File

@ -1,37 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# preferences.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import logging import logging
@ -40,6 +13,7 @@ from collections import deque
import deluge.component as component import deluge.component as component
from basemode import BaseMode from basemode import BaseMode
from deluge.ui.client import client from deluge.ui.client import client
from popup import MessagePopup
from input_popup import Popup, SelectInput from input_popup import Popup, SelectInput
from preference_panes import (BandwidthPane, CachePane, ColumnsPane, DaemonPane, DownloadsPane, InterfacePane, from preference_panes import (BandwidthPane, CachePane, ColumnsPane, DaemonPane, DownloadsPane, InterfacePane,
NetworkPane, OtherPane, ProxyPane, QueuePane) NetworkPane, OtherPane, ProxyPane, QueuePane)
@ -54,9 +28,7 @@ log = logging.getLogger(__name__)
# Big help string that gets displayed when the user hits 'h' # Big help string that gets displayed when the user hits 'h'
HELP_STR = \ HELP_STR = """This screen lets you view and configure various options in deluge.
"""This screen lets you view and configure various options
in deluge.
There are three main sections to this screen. Only one There are three main sections to this screen. Only one
section is active at a time. You can switch the active section is active at a time. You can switch the active
@ -95,7 +67,7 @@ Special keys for various input types are as follows:
""" """
HELP_LINES = HELP_STR.split('\n') HELP_LINES = HELP_STR.split("\n")
class ZONE: class ZONE:
@ -103,6 +75,7 @@ class ZONE:
PREFRENCES = 1 PREFRENCES = 1
ACTIONS = 2 ACTIONS = 2
class Preferences(BaseMode): class Preferences(BaseMode):
def __init__(self, parent_mode, core_config, console_config, active_port, status, stdscr, encoding=None): def __init__(self, parent_mode, core_config, console_config, active_port, status, stdscr, encoding=None):
self.parent_mode = parent_mode self.parent_mode = parent_mode
@ -132,39 +105,39 @@ class Preferences(BaseMode):
self.refresh() self.refresh()
def __calc_sizes(self): def __calc_sizes(self):
self.prefs_width = self.cols-self.div_off-1 self.prefs_width = self.cols - self.div_off - 1
self.prefs_height = self.rows-4 self.prefs_height = self.rows - 4
# Needs to be same order as self.categories # Needs to be same order as self.categories
self.panes = [ self.panes = [
InterfacePane(self.div_off+2, self, self.prefs_width), InterfacePane(self.div_off + 2, self, self.prefs_width),
ColumnsPane(self.div_off+2, self, self.prefs_width), ColumnsPane(self.div_off + 2, self, self.prefs_width),
DownloadsPane(self.div_off+2, self, self.prefs_width), DownloadsPane(self.div_off + 2, self, self.prefs_width),
NetworkPane(self.div_off+2, self, self.prefs_width), NetworkPane(self.div_off + 2, self, self.prefs_width),
BandwidthPane(self.div_off+2, self, self.prefs_width), BandwidthPane(self.div_off + 2, self, self.prefs_width),
OtherPane(self.div_off+2, self, self.prefs_width), OtherPane(self.div_off + 2, self, self.prefs_width),
DaemonPane(self.div_off+2, self, self.prefs_width), DaemonPane(self.div_off + 2, self, self.prefs_width),
QueuePane(self.div_off+2, self, self.prefs_width), QueuePane(self.div_off + 2, self, self.prefs_width),
ProxyPane(self.div_off+2, self, self.prefs_width), ProxyPane(self.div_off + 2, self, self.prefs_width),
CachePane(self.div_off+2, self, self.prefs_width) CachePane(self.div_off + 2, self, self.prefs_width)
] ]
def __draw_catetories(self): def __draw_catetories(self):
for i, category in enumerate(self.categories): for i, category in enumerate(self.categories):
if i == self.cur_cat and self.active_zone == ZONE.CATEGORIES: if i == self.cur_cat and self.active_zone == ZONE.CATEGORIES:
self.add_string(i+1, "- {!black,white,bold!}%s"%category, pad=False) self.add_string(i + 1, "- {!black,white,bold!}%s" % category, pad=False)
elif i == self.cur_cat: elif i == self.cur_cat:
self.add_string(i+1, "- {!black,white!}%s"%category, pad=False) self.add_string(i + 1, "- {!black,white!}%s" % category, pad=False)
else: else:
self.add_string(i+1, "- %s"%category) self.add_string(i + 1, "- %s" % category)
self.stdscr.vline(1, self.div_off, '|', self.rows-2) self.stdscr.vline(1, self.div_off, "|", self.rows - 2)
def __draw_preferences(self): def __draw_preferences(self):
self.panes[self.cur_cat].render(self, self.stdscr, self.prefs_width, self.active_zone == ZONE.PREFRENCES) self.panes[self.cur_cat].render(self, self.stdscr, self.prefs_width, self.active_zone == ZONE.PREFRENCES)
def __draw_actions(self): def __draw_actions(self):
selected = self.active_zone == ZONE.ACTIONS selected = self.active_zone == ZONE.ACTIONS
self.stdscr.hline(self.rows-3, self.div_off+1, "_", self.cols) self.stdscr.hline(self.rows - 3, self.div_off + 1, "_", self.cols)
self.action_input.render(self.stdscr, self.rows-2, self.cols, selected, self.cols-22) self.action_input.render(self.stdscr, self.rows - 2, self.cols, selected, self.cols - 22)
def on_resize(self, *args): def on_resize(self, *args):
BaseMode.on_resize_norefresh(self, *args) BaseMode.on_resize_norefresh(self, *args)
@ -177,14 +150,14 @@ class Preferences(BaseMode):
self.refresh() self.refresh()
def refresh(self): def refresh(self):
if self.popup == None and self.messages: if self.popup is None and self.messages:
title, msg = self.messages.popleft() title, msg = self.messages.popleft()
self.popup = MessagePopup(self, title, msg) self.popup = MessagePopup(self, title, msg)
self.stdscr.erase() self.stdscr.erase()
self.add_string(0, self.statusbars.topbar) self.add_string(0, self.statusbars.topbar)
hstr = "%sPress [h] for help"%(" "*(self.cols - len(self.statusbars.bottombar) - 10)) hstr = "%sPress [h] for help" % (" " * (self.cols - len(self.statusbars.bottombar) - 10))
self.add_string(self.rows - 1, "%s%s"%(self.statusbars.bottombar, hstr)) self.add_string(self.rows - 1, "%s%s" % (self.statusbars.bottombar, hstr))
self.__draw_catetories() self.__draw_catetories()
self.__draw_actions() self.__draw_actions()
@ -205,9 +178,9 @@ class Preferences(BaseMode):
def __category_read(self, c): def __category_read(self, c):
# Navigate prefs # Navigate prefs
if c == curses.KEY_UP: if c == curses.KEY_UP:
self.cur_cat = max(0, self.cur_cat-1) self.cur_cat = max(0, self.cur_cat - 1)
elif c == curses.KEY_DOWN: elif c == curses.KEY_DOWN:
self.cur_cat = min(len(self.categories)-1, self.cur_cat+1) self.cur_cat = min(len(self.categories) - 1, self.cur_cat + 1)
def __prefs_read(self, c): def __prefs_read(self, c):
self.panes[self.cur_cat].handle_read(c) self.panes[self.cur_cat].handle_read(c)
@ -251,7 +224,6 @@ class Preferences(BaseMode):
self.console_config.save() self.console_config.save()
self.parent_mode.update_config() self.parent_mode.update_config()
def __update_preferences(self, core_config): def __update_preferences(self, core_config):
self.core_config = core_config self.core_config = core_config
for pane in self.panes: for pane in self.panes:
@ -261,16 +233,15 @@ class Preferences(BaseMode):
self.action_input.handle_read(c) self.action_input.handle_read(c)
if c == curses.KEY_ENTER or c == 10: if c == curses.KEY_ENTER or c == 10:
# take action # take action
if self.action_input.selidx == 0: # cancel if self.action_input.selidx == 0: # Cancel
self.back_to_parent() self.back_to_parent()
elif self.action_input.selidx == 1: # apply elif self.action_input.selidx == 1: # Apply
self.__apply_prefs() self.__apply_prefs()
client.core.get_config().addCallback(self.__update_preferences) client.core.get_config().addCallback(self.__update_preferences)
elif self.action_input.selidx == 2: # OK elif self.action_input.selidx == 2: # OK
self.__apply_prefs() self.__apply_prefs()
self.back_to_parent() self.back_to_parent()
def back_to_parent(self): def back_to_parent(self):
self.stdscr.erase() self.stdscr.erase()
component.get("ConsoleUI").set_mode(self.parent_mode) component.get("ConsoleUI").set_mode(self.parent_mode)
@ -286,7 +257,7 @@ class Preferences(BaseMode):
return return
if c > 31 and c < 256: if c > 31 and c < 256:
if chr(c) == 'Q': if chr(c) == "Q":
from twisted.internet import reactor from twisted.internet import reactor
if client.connected(): if client.connected():
def on_disconnect(result): def on_disconnect(result):
@ -295,7 +266,7 @@ class Preferences(BaseMode):
else: else:
reactor.stop() reactor.stop()
return return
elif chr(c) == 'h': elif chr(c) == "h":
self.popup = Popup(self, "Preferences Help") self.popup = Popup(self, "Preferences Help")
for l in HELP_LINES: for l in HELP_LINES:
self.popup.add_line(l) self.popup.add_line(l)

View File

@ -1,35 +1,10 @@
# torrent_actions.py # -*- coding: utf-8 -*-
# #
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import logging import logging
@ -38,7 +13,7 @@ from twisted.internet import defer
import deluge.component as component import deluge.component as component
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.console import colors, modes from deluge.ui.console import colors
from input_popup import InputPopup from input_popup import InputPopup
from popup import Popup, SelectablePopup from popup import Popup, SelectablePopup
@ -74,38 +49,42 @@ torrent_options_to_names = {
"move_on_completed_path": "Folder to move the torrent to" "move_on_completed_path": "Folder to move the torrent to"
} }
class ACTION: class ACTION:
PAUSE=0 PAUSE = 0
RESUME=1 RESUME = 1
REANNOUNCE=2 REANNOUNCE = 2
EDIT_TRACKERS=3 EDIT_TRACKERS = 3
RECHECK=4 RECHECK = 4
REMOVE=5 REMOVE = 5
REMOVE_DATA=6 REMOVE_DATA = 6
REMOVE_NODATA=7 REMOVE_NODATA = 7
DETAILS=8 DETAILS = 8
MOVE_STORAGE=9 MOVE_STORAGE = 9
QUEUE=10 QUEUE = 10
QUEUE_TOP=11 QUEUE_TOP = 11
QUEUE_UP=12 QUEUE_UP = 12
QUEUE_DOWN=13 QUEUE_DOWN = 13
QUEUE_BOTTOM=14 QUEUE_BOTTOM = 14
TORRENT_OPTIONS=15 TORRENT_OPTIONS = 15
def action_error(error, mode): def action_error(error, mode):
rerr = error.value rerr = error.value
mode.report_message("An Error Occurred", "%s got error %s: %s"%(rerr.method, rerr.exception_type, rerr.exception_msg)) mode.report_message("An Error Occurred", "%s got error %s: %s" % (
rerr.method, rerr.exception_type, rerr.exception_msg))
mode.refresh() mode.refresh()
def torrent_action(idx, data, mode, ids): def torrent_action(idx, data, mode, ids):
if ids: if ids:
if data==ACTION.PAUSE: if data == ACTION.PAUSE:
log.debug("Pausing torrents: %s", ids) log.debug("Pausing torrents: %s", ids)
client.core.pause_torrent(ids).addErrback(action_error, mode) client.core.pause_torrent(ids).addErrback(action_error, mode)
elif data==ACTION.RESUME: elif data == ACTION.RESUME:
log.debug("Resuming torrents: %s", ids) log.debug("Resuming torrents: %s", ids)
client.core.resume_torrent(ids).addErrback(action_error, mode) client.core.resume_torrent(ids).addErrback(action_error, mode)
elif data==ACTION.QUEUE: elif data == ACTION.QUEUE:
def do_queue(idx, qact, mode, ids): def do_queue(idx, qact, mode, ids):
def move_selection(r): def move_selection(r):
if mode.config["move_selection"]: if mode.config["move_selection"]:
@ -125,18 +104,18 @@ def torrent_action(idx, data, mode, ids):
mode.marked = range(1, selected_num + 1) mode.marked = range(1, selected_num + 1)
elif qact == ACTION.QUEUE_UP: elif qact == ACTION.QUEUE_UP:
mode.cursel = max(1, mode.cursel - 1) mode.cursel = max(1, mode.cursel - 1)
mode.marked = map(lambda v: v-1, mode.marked) mode.marked = map(lambda v: v - 1, mode.marked)
mode.marked = filter(lambda v: v>0, mode.marked) mode.marked = filter(lambda v: v > 0, mode.marked)
elif qact == ACTION.QUEUE_DOWN: elif qact == ACTION.QUEUE_DOWN:
mode.cursel = min(queue_length, mode.cursel + 1) mode.cursel = min(queue_length, mode.cursel + 1)
mode.marked = map(lambda v: v+1, mode.marked) mode.marked = map(lambda v: v + 1, mode.marked)
mode.marked = filter(lambda v: v<=queue_length, mode.marked) mode.marked = filter(lambda v: v <= queue_length, mode.marked)
elif qact == ACTION.QUEUE_BOTTOM: elif qact == ACTION.QUEUE_BOTTOM:
if mode.marked: if mode.marked:
mode.cursel = queue_length - selected_num + 1 + sorted(mode.marked).index(mode.cursel) mode.cursel = queue_length - selected_num + 1 + sorted(mode.marked).index(mode.cursel)
else: else:
mode.cursel = queue_length mode.cursel = queue_length
mode.marked = range(queue_length - selected_num + 1, queue_length+1) mode.marked = range(queue_length - selected_num + 1, queue_length + 1)
if qact == ACTION.QUEUE_TOP: if qact == ACTION.QUEUE_TOP:
log.debug("Queuing torrents top") log.debug("Queuing torrents top")
@ -161,9 +140,10 @@ def torrent_action(idx, data, mode, ids):
popup.add_line("_Bottom", data=ACTION.QUEUE_BOTTOM) popup.add_line("_Bottom", data=ACTION.QUEUE_BOTTOM)
mode.set_popup(popup) mode.set_popup(popup)
return False return False
elif data==ACTION.REMOVE: elif data == ACTION.REMOVE:
def do_remove(data): def do_remove(data):
if not data: return if not data:
return
mode.clear_marks() mode.clear_marks()
wd = data["remove_files"] wd = data["remove_files"]
@ -171,17 +151,13 @@ def torrent_action(idx, data, mode, ids):
log.debug("Removing torrent: %s, %d", tid, wd) log.debug("Removing torrent: %s, %d", tid, wd)
client.core.remove_torrent(tid, wd).addErrback(action_error, mode) client.core.remove_torrent(tid, wd).addErrback(action_error, mode)
rem_msg = ""
def got_status(status): def got_status(status):
return (status["name"], status["state"]) return (status["name"], status["state"])
callbacks = [] callbacks = []
for tid in ids: for tid in ids:
d = client.core.get_torrent_status(tid, ["name", "state"]) d = client.core.get_torrent_status(tid, ["name", "state"])
callbacks.append( d.addCallback(got_status) ) callbacks.append(d.addCallback(got_status))
def finish_up(status): def finish_up(status):
status = map(lambda x: x[1], status) status = map(lambda x: x[1], status)
@ -202,15 +178,17 @@ def torrent_action(idx, data, mode, ids):
popup = InputPopup(mode, "(Esc to cancel, Enter to remove)", close_cb=do_remove) popup = InputPopup(mode, "(Esc to cancel, Enter to remove)", close_cb=do_remove)
popup.add_text(rem_msg) popup.add_text(rem_msg)
popup.add_spaces(1) popup.add_spaces(1)
popup.add_select_input("{!info!}Torrent files:", 'remove_files', ["Keep", "Remove"], [False, True], False) popup.add_select_input("{!info!}Torrent files:", "remove_files",
["Keep", "Remove"], [False, True], False)
mode.set_popup(popup) mode.set_popup(popup)
defer.DeferredList(callbacks).addCallback(finish_up) defer.DeferredList(callbacks).addCallback(finish_up)
return False return False
elif data==ACTION.MOVE_STORAGE: elif data == ACTION.MOVE_STORAGE:
def do_move(res): def do_move(res):
import os.path import os.path
if os.path.exists(res["path"]) and not os.path.isdir(res["path"]): if os.path.exists(res["path"]) and not os.path.isdir(res["path"]):
mode.report_message("Cannot Move Download Folder", "{!error!}%s exists and is not a directory"%res["path"]) mode.report_message("Cannot Move Download Folder",
"{!error!}%s exists and is not a directory" % res["path"])
else: else:
log.debug("Moving %s to: %s", ids, res["path"]) log.debug("Moving %s to: %s", ids, res["path"])
client.core.move_storage(ids, res["path"]).addErrback(action_error, mode) client.core.move_storage(ids, res["path"]).addErrback(action_error, mode)
@ -221,20 +199,20 @@ def torrent_action(idx, data, mode, ids):
popup.add_text_input("Enter path to move to:", "path") popup.add_text_input("Enter path to move to:", "path")
mode.set_popup(popup) mode.set_popup(popup)
return False return False
elif data==ACTION.RECHECK: elif data == ACTION.RECHECK:
log.debug("Rechecking torrents: %s", ids) log.debug("Rechecking torrents: %s", ids)
client.core.force_recheck(ids).addErrback(action_error, mode) client.core.force_recheck(ids).addErrback(action_error, mode)
elif data==ACTION.REANNOUNCE: elif data == ACTION.REANNOUNCE:
log.debug("Reannouncing torrents: %s", ids) log.debug("Reannouncing torrents: %s", ids)
client.core.force_reannounce(ids).addErrback(action_error, mode) client.core.force_reannounce(ids).addErrback(action_error, mode)
elif data==ACTION.DETAILS: elif data == ACTION.DETAILS:
log.debug("Torrent details") log.debug("Torrent details")
tid = mode.current_torrent_id() tid = mode.current_torrent_id()
if tid: if tid:
mode.show_torrent_details(tid) mode.show_torrent_details(tid)
else: else:
log.error("No current torrent in _torrent_action, this is a bug") log.error("No current torrent in _torrent_action, this is a bug")
elif data==ACTION.TORRENT_OPTIONS: elif data == ACTION.TORRENT_OPTIONS:
mode.popup = Popup(mode, "Torrent options") mode.popup = Popup(mode, "Torrent options")
mode.popup.add_line("Querying core, please wait...") mode.popup.add_line("Querying core, please wait...")
@ -247,7 +225,7 @@ def torrent_action(idx, data, mode, ids):
for opt in result: for opt in result:
if result[opt] not in ["multiple", None]: if result[opt] not in ["multiple", None]:
options[opt] = result[opt] options[opt] = result[opt]
client.core.set_torrent_options( ids, options ) client.core.set_torrent_options(ids, options)
for tid in ids: for tid in ids:
if "move_on_completed_path" in options: if "move_on_completed_path" in options:
client.core.set_torrent_move_completed_path(tid, options["move_on_completed_path"]) client.core.set_torrent_move_completed_path(tid, options["move_on_completed_path"])
@ -262,7 +240,7 @@ def torrent_action(idx, data, mode, ids):
def on_torrent_status(status): def on_torrent_status(status):
for key in status: for key in status:
if key not in options: if key not in options:
options[key] = status[key] options[key] = status[key]
elif options[key] != status[key]: elif options[key] != status[key]:
options[key] = "multiple" options[key] = "multiple"
@ -274,7 +252,7 @@ def torrent_action(idx, data, mode, ids):
for (field, field_type) in torrent_options: for (field, field_type) in torrent_options:
caption = "{!info!}" + torrent_options_to_names[field] caption = "{!info!}" + torrent_options_to_names[field]
value = options[field] value = options[field]
if field_type == str: if field_type == str:
if not isinstance(value, basestring): if not isinstance(value, basestring):
value = str(value) value = str(value)
option_popup.add_text_input(caption, field, value) option_popup.add_text_input(caption, field, value)
@ -293,9 +271,9 @@ def torrent_action(idx, data, mode, ids):
) )
option_popup.add_select_input(caption, field, choices[0], choices[1], choices[2]) option_popup.add_select_input(caption, field, choices[0], choices[1], choices[2])
elif field_type == float: elif field_type == float:
option_popup.add_float_spin_input(caption, field, value, min_val = -1) option_popup.add_float_spin_input(caption, field, value, min_val=-1)
elif field_type == int: elif field_type == int:
option_popup.add_int_spin_input(caption, field, value, min_val = -1) option_popup.add_int_spin_input(caption, field, value, min_val=-1)
mode.set_popup(option_popup) mode.set_popup(option_popup)
mode.refresh() mode.refresh()
@ -306,7 +284,7 @@ def torrent_action(idx, data, mode, ids):
for tid in torrents: for tid in torrents:
deferred = component.get("SessionProxy").get_torrent_status(tid, field_list) deferred = component.get("SessionProxy").get_torrent_status(tid, field_list)
callbacks.append( deferred.addCallback(on_torrent_status) ) callbacks.append(deferred.addCallback(on_torrent_status))
callbacks = defer.DeferredList(callbacks) callbacks = defer.DeferredList(callbacks)
callbacks.addCallback(create_popup) callbacks.addCallback(create_popup)
@ -315,9 +293,10 @@ def torrent_action(idx, data, mode, ids):
mode.clear_marks() mode.clear_marks()
return True return True
# Creates the popup. mode is the calling mode, tids is a list of torrents to take action upon # Creates the popup. mode is the calling mode, tids is a list of torrents to take action upon
def torrent_actions_popup(mode,tids,details=False, action = None): def torrent_actions_popup(mode, tids, details=False, action=None):
if action != None: if action is not None:
torrent_action(-1, action, mode, tids) torrent_action(-1, action, mode, tids)
return return
popup = SelectablePopup(mode, "Torrent Actions", torrent_action, (mode, tids)) popup = SelectablePopup(mode, "Torrent Actions", torrent_action, (mode, tids))

View File

@ -1,53 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# torrentdetail.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import logging import logging
from collections import deque from collections import deque
from sys import maxint from sys import maxint
import deluge.common as common
import deluge.component as component import deluge.component as component
import deluge.ui.console.colors as colors import deluge.ui.console.colors as colors
import format_utils import format_utils
from add_util import add_torrent
from basemode import BaseMode from basemode import BaseMode
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.sessionproxy import SessionProxy from deluge.common import fsize, fdate, ftime, FILE_PRIORITY
from input_popup import InputPopup from input_popup import InputPopup
from popup import MessagePopup, Popup, SelectablePopup from popup import MessagePopup, SelectablePopup
from torrent_actions import ACTION, torrent_actions_popup from torrent_actions import ACTION, torrent_actions_popup
try: try:
@ -90,6 +61,7 @@ download priority of selected files and folders.
{!info!}Left Arrow{!normal!} - Go back to torrent overview. {!info!}Left Arrow{!normal!} - Go back to torrent overview.
""" """
class TorrentDetail(BaseMode, component.Component): class TorrentDetail(BaseMode, component.Component):
def __init__(self, alltorrentmode, torrentid, stdscr, console_config, encoding=None): def __init__(self, alltorrentmode, torrentid, stdscr, console_config, encoding=None):
@ -140,6 +112,7 @@ class TorrentDetail(BaseMode, component.Component):
# component start/update # component start/update
def start(self): def start(self):
component.get("SessionProxy").get_torrent_status(self.torrentid, self._status_keys).addCallback(self.set_state) component.get("SessionProxy").get_torrent_status(self.torrentid, self._status_keys).addCallback(self.set_state)
def update(self): def update(self):
component.get("SessionProxy").get_torrent_status(self.torrentid, self._status_keys).addCallback(self.set_state) component.get("SessionProxy").get_torrent_status(self.torrentid, self._status_keys).addCallback(self.set_state)
@ -147,17 +120,19 @@ class TorrentDetail(BaseMode, component.Component):
log.debug("got state") log.debug("got state")
if state.get("files"): if state.get("files"):
self.full_names = dict([ (x['index'], x['path']) for x in state["files"]]) self.full_names = dict([(x["index"], x["path"]) for x in state["files"]])
need_prio_update = False need_prio_update = False
if not self.file_list: if not self.file_list:
# don't keep getting the files once we've got them once # don't keep getting the files once we've got them once
if state.get("files"): if state.get("files"):
self.files_sep = "{!green,black,bold,underline!}%s"%(("Files (torrent has %d files)"%len(state["files"])).center(self.cols)) self.files_sep = "{!green,black,bold,underline!}%s" % (
self.file_list, self.file_dict = self.build_file_list(state["files"], state["file_progress"], state["file_priorities"]) ("Files (torrent has %d files)" % len(state["files"])).center(self.cols))
self.file_list, self.file_dict = self.build_file_list(state["files"], state["file_progress"],
state["file_priorities"])
self._status_keys.remove("files") self._status_keys.remove("files")
else: else:
self.files_sep = "{!green,black,bold,underline!}%s"%(("Files (File list unknown)").center(self.cols)) self.files_sep = "{!green,black,bold,underline!}%s" % (("Files (File list unknown)").center(self.cols))
need_prio_update = True need_prio_update = True
self.__fill_progress(self.file_list, state["file_progress"]) self.__fill_progress(self.file_list, state["file_progress"])
for i, prio in enumerate(state["file_priorities"]): for i, prio in enumerate(state["file_priorities"]):
@ -194,13 +169,13 @@ class TorrentDetail(BaseMode, component.Component):
cl = [] cl = []
if p == fin: if p == fin:
ent = [p, f["index"], f["size"], cl, False, ent = [p, f["index"], f["size"], cl, False,
format_utils.format_progress(prog[f["index"]]*100), format_utils.format_progress(prog[f["index"]] * 100),
prio[f["index"]]] prio[f["index"]]]
retdict[f["index"]] = ent retdict[f["index"]] = ent
else: else:
ent = [p, diridx, -1, cl, False, 0, -1] ent = [p, diridx, -1, cl, False, 0, -1]
retdict[diridx] = ent retdict[diridx] = ent
diridx-=1 diridx -= 1
cur.append(ent) cur.append(ent)
cur = cl cur = cl
else: else:
@ -224,21 +199,22 @@ class TorrentDetail(BaseMode, component.Component):
# fills in progress fields in all entries based on progs # fills in progress fields in all entries based on progs
# returns the # of bytes complete in all the children of fs # returns the # of bytes complete in all the children of fs
def __fill_progress(self, fs, progs): def __fill_progress(self, fs, progs):
if not progs: return 0 if not progs:
return 0
tb = 0 tb = 0
for f in fs: for f in fs:
if f[3]: # dir, has some children if f[3]: # dir, has some children
bd = self.__fill_progress(f[3], progs) bd = self.__fill_progress(f[3], progs)
f[5] = format_utils.format_progress((bd/f[2])*100) f[5] = format_utils.format_progress((bd / f[2]) * 100)
else: # file, update own prog and add to total else: # file, update own prog and add to total
bd = f[2]*progs[f[1]] bd = f[2] * progs[f[1]]
f[5] = format_utils.format_progress(progs[f[1]]*100) f[5] = format_utils.format_progress(progs[f[1]] * 100)
tb += bd tb += bd
return tb return tb
def __fill_prio(self, fs): def __fill_prio(self, fs):
for f in fs: for f in fs:
if f[3]: # dir, so fill in children and compute our prio if f[3]: # dir, so fill in children and compute our prio
self.__fill_prio(f[3]) self.__fill_prio(f[3])
child_prios = [e[6] for e in f[3]] child_prios = [e[6] for e in f[3]]
if len(child_prios) > 1: if len(child_prios) > 1:
@ -248,21 +224,21 @@ class TorrentDetail(BaseMode, component.Component):
def __update_columns(self): def __update_columns(self):
self.column_widths = [-1, 15, 15, 20] self.column_widths = [-1, 15, 15, 20]
req = sum(filter(lambda x:x >= 0, self.column_widths)) req = sum(filter(lambda x: x >= 0, self.column_widths))
if (req > self.cols): # can't satisfy requests, just spread out evenly if (req > self.cols): # can't satisfy requests, just spread out evenly
cw = int(self.cols/len(self.column_names)) cw = int(self.cols / len(self.column_names))
for i in range(0, len(self.column_widths)): for i in range(0, len(self.column_widths)):
self.column_widths[i] = cw self.column_widths[i] = cw
else: else:
rem = self.cols - req rem = self.cols - req
var_cols = len(filter(lambda x: x < 0, self.column_widths)) var_cols = len(filter(lambda x: x < 0, self.column_widths))
vw = int(rem/var_cols) vw = int(rem / var_cols)
for i in range(0, len(self.column_widths)): for i in range(0, len(self.column_widths)):
if (self.column_widths[i] < 0): if (self.column_widths[i] < 0):
self.column_widths[i] = vw self.column_widths[i] = vw
self.column_string = "{!green,black,bold!}%s"%("".join(["%s%s"%(self.column_names[i], " "*(self.column_widths[i]-len(self.column_names[i]))) for i in range(0, len(self.column_names))])) self.column_string = "{!green,black,bold!}%s" % ("".join(["%s%s" % (self.column_names[i], " " * (
self.column_widths[i] - len(self.column_names[i]))) for i in range(0, len(self.column_names))]))
def report_message(self, title, message): def report_message(self, title, message):
self.messages.append((title, message)) self.messages.append((title, message))
@ -281,7 +257,8 @@ class TorrentDetail(BaseMode, component.Component):
def _on_torrentfilerenamed_event(self, torrent_id, index, new_name): def _on_torrentfilerenamed_event(self, torrent_id, index, new_name):
if torrent_id == self.torrentid: if torrent_id == self.torrentid:
self.file_dict[index][0] = new_name.split("/")[-1] self.file_dict[index][0] = new_name.split("/")[-1]
component.get("SessionProxy").get_torrent_status(self.torrentid, self._status_keys).addCallback(self.set_state) component.get("SessionProxy").get_torrent_status(
self.torrentid, self._status_keys).addCallback(self.set_state)
def _on_torrentfolderrenamed_event(self, torrent_id, old_folder, new_folder): def _on_torrentfolderrenamed_event(self, torrent_id, old_folder, new_folder):
if torrent_id == self.torrentid: if torrent_id == self.torrentid:
@ -298,7 +275,8 @@ class TorrentDetail(BaseMode, component.Component):
fe[0] = new_folder.strip("/").rpartition("/")[-1] fe[0] = new_folder.strip("/").rpartition("/")[-1]
#self.__get_file_by_name(old_folder, self.file_list)[0] = new_folder.strip("/") #self.__get_file_by_name(old_folder, self.file_list)[0] = new_folder.strip("/")
component.get("SessionProxy").get_torrent_status(self.torrentid, self._status_keys).addCallback(self.set_state) component.get("SessionProxy").get_torrent_status(
self.torrentid, self._status_keys).addCallback(self.set_state)
def draw_files(self, files, depth, off, idx): def draw_files(self, files, depth, off, idx):
@ -309,30 +287,27 @@ class TorrentDetail(BaseMode, component.Component):
#from sys import stderr #from sys import stderr
#print >> stderr, fl[6] #print >> stderr, fl[6]
# kick out if we're going to draw too low on the screen # kick out if we're going to draw too low on the screen
if (off >= self.rows-1): if (off >= self.rows - 1):
self.more_to_draw = True self.more_to_draw = True
return -1, -1 return -1, -1
self.file_limit = idx self.file_limit = idx
# default color values # default color values
fg = "white" fg = "white"
bg = "black" bg = "black"
attr = "" attr = ""
if fl[6] == -2: priority = -1 #Mixed if fl[6] == -2:
pass # Mixed
elif fl[6] == 0: elif fl[6] == 0:
priority = 0 #Do Not Download fg = "red" # Do Not Download
fg = "red"
elif fl[6] == 1: elif fl[6] == 1:
priority = 1 #Normal pass # Normal
elif fl[6] <= 6: elif fl[6] <= 6:
priority = 2 #High fg = "yellow" # High
fg = "yellow"
elif fl[6] == 7: elif fl[6] == 7:
priority = 3 #Highest fg = "green" # Highest
fg = "green"
if idx >= self.file_off: if idx >= self.file_off:
# set fg/bg colors based on whether the file is selected/marked or not # set fg/bg colors based on whether the file is selected/marked or not
@ -350,7 +325,7 @@ class TorrentDetail(BaseMode, component.Component):
if fl[1] in self.marked: if fl[1] in self.marked:
fg = color_selected fg = color_selected
if fl[3]: if fl[3]:
if self.marked[fl[1]] < self.__get_contained_files_count(file_list = fl[3]): if self.marked[fl[1]] < self.__get_contained_files_count(file_list=fl[3]):
fg = color_partially_selected fg = color_partially_selected
else: else:
if fg == "white": if fg == "white":
@ -358,30 +333,31 @@ class TorrentDetail(BaseMode, component.Component):
attr = "bold" attr = "bold"
if attr: if attr:
color_string = "{!%s,%s,%s!}"%(fg, bg, attr) color_string = "{!%s,%s,%s!}" % (fg, bg, attr)
else: else:
color_string = "{!%s,%s!}"%(fg, bg) color_string = "{!%s,%s!}" % (fg, bg)
#actually draw the dir/file string #actually draw the dir/file string
if fl[3] and fl[4]: # this is an expanded directory if fl[3] and fl[4]: # this is an expanded directory
xchar = 'v' xchar = "v"
elif fl[3]: # collapsed directory elif fl[3]: # collapsed directory
xchar = '>' xchar = ">"
else: # file else: # file
xchar = '-' xchar = "-"
r = format_utils.format_row(["%s%s %s"%(" "*depth, xchar, fl[0]), r = format_utils.format_row(["%s%s %s" % (" " * depth, xchar, fl[0]),
deluge.common.fsize(fl[2]), fl[5], fsize(fl[2]), fl[5],
format_utils.format_priority(fl[6])], format_utils.format_priority(fl[6])],
self.column_widths) self.column_widths)
self.add_string(off, "%s%s"%(color_string, r), trim=False) self.add_string(off, "%s%s" % (color_string, r), trim=False)
off += 1 off += 1
if fl[3] and fl[4]: if fl[3] and fl[4]:
# recurse if we have children and are expanded # recurse if we have children and are expanded
off, idx = self.draw_files(fl[3], depth+1, off, idx+1) off, idx = self.draw_files(fl[3], depth + 1, off, idx + 1)
if off < 0: return (off, idx) if off < 0:
return (off, idx)
else: else:
idx += 1 idx += 1
@ -391,7 +367,7 @@ class TorrentDetail(BaseMode, component.Component):
""" """
Counts length of the displayed file list. Counts length of the displayed file list.
""" """
if file_list == None: if file_list is None:
file_list = self.file_list file_list = self.file_list
length = 0 length = 0
if file_list: if file_list:
@ -401,16 +377,16 @@ class TorrentDetail(BaseMode, component.Component):
length += self.__get_file_list_length(element[3]) length += self.__get_file_list_length(element[3])
return length return length
def __get_contained_files_count(self, file_list=None, idx = None): def __get_contained_files_count(self, file_list=None, idx=None):
length = 0 length = 0
if file_list == None: if file_list is None:
file_list = self.file_list file_list = self.file_list
if idx != None: if idx is not None:
for element in file_list: for element in file_list:
if element[1] == idx: if element[1] == idx:
return self.__get_contained_files_count(file_list = element[3]) return self.__get_contained_files_count(file_list=element[3])
elif element[3]: elif element[3]:
c = self.__get_contained_files_count(file_list = element[3], idx=idx) c = self.__get_contained_files_count(file_list=element[3], idx=idx)
if c > 0: if c > 0:
return c return c
else: else:
@ -443,67 +419,80 @@ class TorrentDetail(BaseMode, component.Component):
#Name #Name
s = "{!info!}Name: {!input!}%s" % status["name"] s = "{!info!}Name: {!input!}%s" % status["name"]
self.add_string(off, s); off += 1 self.add_string(off, s)
off += 1
#Print DL info and ETA #Print DL info and ETA
if status["download_payload_rate"] > 0: if status["download_payload_rate"] > 0:
s = "%sDownloading: {!input!}" % down_color s = "%sDownloading: {!input!}" % down_color
else: else:
s = "{!info!}Downloaded: {!input!}" s = "{!info!}Downloaded: {!input!}"
s+= common.fsize(status["all_time_download"]) s += fsize(status["all_time_download"])
if status["progress"] != 100.0: if status["progress"] != 100.0:
s+= "/%s" % common.fsize(status["total_wanted"]) s += "/%s" % fsize(status["total_wanted"])
if status["download_payload_rate"] > 0: if status["download_payload_rate"] > 0:
s+= " {!yellow!}@ %s%s" % (down_color, common.fsize(status["download_payload_rate"])) s += " {!yellow!}@ %s%s" % (down_color, fsize(status["download_payload_rate"]))
s+= "{!info!} ETA: {!input!}%s" % format_utils.format_time(status["eta"]) s += "{!info!} ETA: {!input!}%s" % format_utils.format_time(status["eta"])
self.add_string(off, s); off += 1 self.add_string(off, s)
off += 1
#Print UL info and ratio #Print UL info and ratio
if status["upload_payload_rate"] > 0: if status["upload_payload_rate"] > 0:
s = "%sUploading: {!input!}" % up_color s = "%sUploading: {!input!}" % up_color
else: else:
s = "{!info!}Uploaded: {!input!}" s = "{!info!}Uploaded: {!input!}"
s+= common.fsize(status["total_uploaded"]) s += fsize(status["total_uploaded"])
if status["upload_payload_rate"] > 0: if status["upload_payload_rate"] > 0:
s+= " {!yellow!}@ %s%s" % (up_color, common.fsize(status["upload_payload_rate"])) s += " {!yellow!}@ %s%s" % (up_color, fsize(status["upload_payload_rate"]))
ratio_str = format_utils.format_float(status["ratio"]) ratio_str = format_utils.format_float(status["ratio"])
if ratio_str == "-": ratio_str = "inf" if ratio_str == "-":
s+= " {!info!}Ratio: {!input!}%s" % ratio_str ratio_str = "inf"
self.add_string(off, s); off += 1 s += " {!info!}Ratio: {!input!}%s" % ratio_str
self.add_string(off, s)
off += 1
#Seed/peer info #Seed/peer info
s = "{!info!}Seeds:{!green!} %s {!input!}(%s)" % (status["num_seeds"], status["total_seeds"]) s = "{!info!}Seeds:{!green!} %s {!input!}(%s)" % (status["num_seeds"], status["total_seeds"])
self.add_string(off, s); off += 1 self.add_string(off, s)
off += 1
s = "{!info!}Peers:{!red!} %s {!input!}(%s)" % (status["num_peers"], status["total_peers"]) s = "{!info!}Peers:{!red!} %s {!input!}(%s)" % (status["num_peers"], status["total_peers"])
self.add_string(off, s); off += 1 self.add_string(off, s)
off += 1
#Tracker #Tracker
if status["message"] == "OK": if status["message"] == "OK":
color = "{!green!}" color = "{!green!}"
else: else:
color = "{!red!}" color = "{!red!}"
s = "{!info!}Tracker: {!magenta!}%s{!input!} says \"%s%s{!input!}\"" % (status["tracker_host"], color, status["message"]) s = "{!info!}Tracker: {!magenta!}%s{!input!} says \"%s%s{!input!}\"" % (
self.add_string(off, s); off += 1 status["tracker_host"], color, status["message"])
self.add_string(off, s)
off += 1
#Pieces and availability #Pieces and availability
s = "{!info!}Pieces: {!yellow!}%s {!input!}x {!yellow!}%s" % (status["num_pieces"], common.fsize(status["piece_length"])) s = "{!info!}Pieces: {!yellow!}%s {!input!}x {!yellow!}%s" % (
status["num_pieces"], fsize(status["piece_length"]))
if status["distributed_copies"]: if status["distributed_copies"]:
s+= " {!info!}Availability: {!input!}%s" % format_utils.format_float(status["distributed_copies"]) s += " {!info!}Availability: {!input!}%s" % format_utils.format_float(status["distributed_copies"])
self.add_string(off, s); off += 1 self.add_string(off, s)
off += 1
#Time added #Time added
s = "{!info!}Added: {!input!}%s" % common.fdate(status["time_added"]) s = "{!info!}Added: {!input!}%s" % fdate(status["time_added"])
self.add_string(off, s); off += 1 self.add_string(off, s)
off += 1
#Time active #Time active
s = "{!info!}Time active: {!input!}%s" % ( common.ftime(status["active_time"]) ) s = "{!info!}Time active: {!input!}%s" % (ftime(status["active_time"]))
if status["seeding_time"]: if status["seeding_time"]:
s+= ", {!cyan!}%s{!input!} seeding" % ( common.ftime(status["seeding_time"]) ) s += ", {!cyan!}%s{!input!} seeding" % (ftime(status["seeding_time"]))
self.add_string(off, s); off += 1 self.add_string(off, s)
off += 1
#Download Folder #Download Folder
s = "{!info!}Download Folder: {!input!}%s" % status["download_location"] s = "{!info!}Download Folder: {!input!}%s" % status["download_location"]
self.add_string(off, s); off += 1 self.add_string(off, s)
off += 1
#Owner #Owner
if status["owner"]: if status["owner"]:
@ -511,9 +500,9 @@ class TorrentDetail(BaseMode, component.Component):
return off return off
def refresh(self,lines=None): def refresh(self, lines=None):
# show a message popup if there's anything queued # show a message popup if there's anything queued
if self.popup == None and self.messages: if self.popup is None and self.messages:
title, msg = self.messages.popleft() title, msg = self.messages.popleft()
self.popup = MessagePopup(self, title, msg) self.popup = MessagePopup(self, title, msg)
@ -527,7 +516,7 @@ class TorrentDetail(BaseMode, component.Component):
string = self.statusbars.bottombar string = self.statusbars.bottombar
hstr = "Press {!magenta,blue,bold!}[h]{!status!} for help" hstr = "Press {!magenta,blue,bold!}[h]{!status!} for help"
string += " " * ( self.cols - len(rf(string)) - len(rf(hstr))) + hstr string += " " * (self.cols - len(rf(string)) - len(rf(hstr))) + hstr
self.add_string(self.rows - 1, string) self.add_string(self.rows - 1, string)
except: except:
@ -579,13 +568,13 @@ class TorrentDetail(BaseMode, component.Component):
if self.current_file_idx > maxlen: if self.current_file_idx > maxlen:
self.current_file_idx = maxlen self.current_file_idx = maxlen
if self.current_file_idx > self.file_off + (self._listing_space - 3): if self.current_file_idx > self.file_off + (self._listing_space - 3):
self.file_off = self.current_file_idx - (self._listing_space - 3) self.file_off = self.current_file_idx - (self._listing_space - 3)
self.refresh() self.refresh()
def file_list_up(self, rows=1): def file_list_up(self, rows=1):
self.current_file_idx = max(0, self.current_file_idx-rows) self.current_file_idx = max(0, self.current_file_idx - rows)
self.file_off = min(self.file_off, self.current_file_idx) self.file_off = min(self.file_off, self.current_file_idx)
self.refresh() self.refresh()
@ -605,7 +594,7 @@ class TorrentDetail(BaseMode, component.Component):
#Do not set priorities for the whole dir, just selected contents #Do not set priorities for the whole dir, just selected contents
if f[3]: if f[3]:
self.build_prio_list(f[3], ret_list, parent_prio, selected_prio) self.build_prio_list(f[3], ret_list, parent_prio, selected_prio)
else: # file, need to add to list else: # file, need to add to list
if f[1] in self.marked or parent_prio >= 0: if f[1] in self.marked or parent_prio >= 0:
# selected (or parent selected), use requested priority # selected (or parent selected), use requested priority
ret_list.append((f[1], selected_prio)) ret_list.append((f[1], selected_prio))
@ -631,10 +620,10 @@ class TorrentDetail(BaseMode, component.Component):
func = lambda idx, data, we=was_empty: self.do_priority(idx, data, we) func = lambda idx, data, we=was_empty: self.do_priority(idx, data, we)
if self.marked: if self.marked:
self.popup = SelectablePopup(self, "Set File Priority", func) self.popup = SelectablePopup(self, "Set File Priority", func)
self.popup.add_line("_Do Not Download", data=deluge.common.FILE_PRIORITY["Do Not Download"], foreground="red") self.popup.add_line("_Do Not Download", data=FILE_PRIORITY["Do Not Download"], foreground="red")
self.popup.add_line("_Normal Priority", data=deluge.common.FILE_PRIORITY["Normal Priority"]) self.popup.add_line("_Normal Priority", data=FILE_PRIORITY["Normal Priority"])
self.popup.add_line("_High Priority", data=deluge.common.FILE_PRIORITY["High Priority"], foreground="yellow") self.popup.add_line("_High Priority", data=FILE_PRIORITY["High Priority"], foreground="yellow")
self.popup.add_line("H_ighest Priority", data=deluge.common.FILE_PRIORITY["Highest Priority"], foreground="green") self.popup.add_line("H_ighest Priority", data=FILE_PRIORITY["Highest Priority"], foreground="green")
self.popup._selected = 1 self.popup._selected = 1
def __mark_unmark(self, idx): def __mark_unmark(self, idx):
@ -652,7 +641,7 @@ class TorrentDetail(BaseMode, component.Component):
#Selected, unselect it #Selected, unselect it
self.__unmark_tree(self.file_list, idx) self.__unmark_tree(self.file_list, idx)
def __mark_tree(self, file_list, idx, mark_all = False): def __mark_tree(self, file_list, idx, mark_all=False):
""" """
Given file_list of TorrentDetail and index of file or folder, Given file_list of TorrentDetail and index of file or folder,
recursively selects all files contained recursively selects all files contained
@ -687,7 +676,7 @@ class TorrentDetail(BaseMode, component.Component):
return total_marked return total_marked
def __get_file_by_num(self, num, file_list, idx = 0): def __get_file_by_num(self, num, file_list, idx=0):
for element in file_list: for element in file_list:
if idx == num: if idx == num:
return element return element
@ -703,7 +692,7 @@ class TorrentDetail(BaseMode, component.Component):
return idx return idx
def __get_file_by_name(self, name, file_list, idx = 0): def __get_file_by_name(self, name, file_list, idx=0):
for element in file_list: for element in file_list:
if element[0].strip("/") == name.strip("/"): if element[0].strip("/") == name.strip("/"):
return element return element
@ -719,7 +708,7 @@ class TorrentDetail(BaseMode, component.Component):
return idx return idx
def __unmark_tree(self, file_list, idx, unmark_all = False): def __unmark_tree(self, file_list, idx, unmark_all=False):
""" """
Given file_list of TorrentDetail and index of file or folder, Given file_list of TorrentDetail and index of file or folder,
recursively deselects all files contained recursively deselects all files contained
@ -756,8 +745,9 @@ class TorrentDetail(BaseMode, component.Component):
total_marked += marked total_marked += marked
return total_marked return total_marked
def _selection_to_file_idx(self, file_list = None, idx = 0, true_idx = 0, closed=False): def _selection_to_file_idx(self, file_list=None, idx=0, true_idx=0, closed=False):
if not file_list: file_list = self.file_list if not file_list:
file_list = self.file_list
for element in file_list: for element in file_list:
if idx == self.current_file_idx: if idx == self.current_file_idx:
@ -782,8 +772,9 @@ class TorrentDetail(BaseMode, component.Component):
return (idx, true_idx) return (idx, true_idx)
def _get_full_folder_path(self, num, file_list = None, path = "", idx = 0): def _get_full_folder_path(self, num, file_list=None, path="", idx=0):
if not file_list: file_list = self.file_list if not file_list:
file_list = self.file_list
for element in file_list: for element in file_list:
if not element[3]: if not element[3]:
@ -794,7 +785,7 @@ class TorrentDetail(BaseMode, component.Component):
return "%s%s/" % (path, element[0]) return "%s%s/" % (path, element[0])
if element[4]: if element[4]:
i = self._get_full_folder_path(num, element[3], path + element[0] + "/", idx + 1 ) i = self._get_full_folder_path(num, element[3], path + element[0] + "/", idx + 1)
if not isinstance(i, int): if not isinstance(i, int):
return i return i
else: else:
@ -863,7 +854,7 @@ class TorrentDetail(BaseMode, component.Component):
return return
if c > 31 and c < 256: if c > 31 and c < 256:
if chr(c) == 'Q': if chr(c) == "Q":
from twisted.internet import reactor from twisted.internet import reactor
if client.connected(): if client.connected():
def on_disconnect(result): def on_disconnect(result):
@ -872,7 +863,7 @@ class TorrentDetail(BaseMode, component.Component):
else: else:
reactor.stop() reactor.stop()
return return
elif chr(c) == 'q': elif chr(c) == "q":
self.back_to_overview() self.back_to_overview()
return return
@ -888,14 +879,14 @@ class TorrentDetail(BaseMode, component.Component):
if c == curses.KEY_UP: if c == curses.KEY_UP:
self.file_list_up() self.file_list_up()
elif c == curses.KEY_PPAGE: elif c == curses.KEY_PPAGE:
self.file_list_up(self._listing_space-2) self.file_list_up(self._listing_space - 2)
elif c == curses.KEY_HOME: elif c == curses.KEY_HOME:
self.file_off = 0 self.file_off = 0
self.current_file_idx = 0 self.current_file_idx = 0
elif c == curses.KEY_DOWN: elif c == curses.KEY_DOWN:
self.file_list_down() self.file_list_down()
elif c == curses.KEY_NPAGE: elif c == curses.KEY_NPAGE:
self.file_list_down(self._listing_space-2) self.file_list_down(self._listing_space - 2)
elif c == curses.KEY_END: elif c == curses.KEY_END:
self.current_file_idx = self.__get_file_list_length() - 1 self.current_file_idx = self.__get_file_list_length() - 1
self.file_off = self.current_file_idx - (self._listing_space - 3) self.file_off = self.current_file_idx - (self._listing_space - 3)
@ -912,24 +903,24 @@ class TorrentDetail(BaseMode, component.Component):
self.expcol_cur_file() self.expcol_cur_file()
else: else:
if c > 31 and c < 256: if c > 31 and c < 256:
if chr(c) == 'm': if chr(c) == "m":
if self.current_file: if self.current_file:
self.__mark_unmark(self.current_file[1]) self.__mark_unmark(self.current_file[1])
elif chr(c) == 'r': elif chr(c) == "r":
self._show_rename_popup() self._show_rename_popup()
elif chr(c) == 'c': elif chr(c) == "c":
self.marked = {} self.marked = {}
elif chr(c) == 'a': elif chr(c) == "a":
torrent_actions_popup(self, [self.torrentid], details=False) torrent_actions_popup(self, [self.torrentid], details=False)
return return
elif chr(c) == 'o': elif chr(c) == "o":
torrent_actions_popup(self, [self.torrentid], action=ACTION.TORRENT_OPTIONS) torrent_actions_popup(self, [self.torrentid], action=ACTION.TORRENT_OPTIONS)
return return
elif chr(c) == 'h': elif chr(c) == "h":
self.popup = MessagePopup(self, "Help", HELP_STR, width_req=0.75) self.popup = MessagePopup(self, "Help", HELP_STR, width_req=0.75)
elif chr(c) == 'j': elif chr(c) == "j":
self.file_list_up() self.file_list_up()
if chr(c) == 'k': if chr(c) == "k":
self.file_list_down() self.file_list_down()
self.refresh() self.refresh()

View File

@ -1,36 +1,10 @@
# # -*- coding: utf-8 -*-
# statusbars.py
# #
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
# #
import deluge.common import deluge.common