[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,14 +15,14 @@ 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, ...}
@ -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:
@ -200,7 +180,7 @@ def parse_color_string(s, encoding="UTF-8"):
# 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):

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()

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,42 +1,16 @@
# 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
@ -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,7 +21,8 @@ from deluge.ui.console.modes import format_utils
strwidth = format_utils.strwidth strwidth = format_utils.strwidth
STATUS_KEYS = ["state", STATUS_KEYS = [
"state",
"download_location", "download_location",
"tracker_host", "tracker_host",
"tracker_status", "tracker_status",
@ -80,6 +55,7 @@ STATUS_KEYS = ["state",
# 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.
@ -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):
@ -188,21 +160,20 @@ 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]:
@ -210,7 +181,7 @@ class Command(BaseCommand):
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"])
@ -232,7 +203,7 @@ class Command(BaseCommand):
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
@ -262,7 +233,6 @@ class Command(BaseCommand):
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):

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
@ -66,8 +36,7 @@ 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):
@ -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,10 +18,10 @@ 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"),
) )
@ -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)
@ -87,13 +63,14 @@ class Console(_UI):
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...
@ -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):
@ -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
@ -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,56 +1,28 @@
# -*- 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):
@ -63,6 +35,7 @@ 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"]:
@ -70,9 +43,9 @@ def add_torrent(t_file, options, success_cb, fail_cb, ress):
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
@ -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)
@ -423,10 +397,7 @@ 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))
@ -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,6 +102,7 @@ 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
@ -300,6 +270,7 @@ 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
@ -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"]:
@ -506,16 +477,15 @@ class AllTorrents(BaseMode, component.Component):
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:
@ -535,7 +505,6 @@ class AllTorrents(BaseMode, component.Component):
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:
@ -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]
@ -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]:
@ -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]
@ -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,7 +829,7 @@ 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))
@ -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
@ -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:
@ -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)
@ -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,13 +1168,15 @@ 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:
@ -1235,28 +1207,29 @@ 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)
@ -1267,14 +1240,14 @@ class AllTorrents(BaseMode, component.Component):
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>
#
# Most code in this file taken from screen.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 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):
@ -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

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,10 +50,12 @@ 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
@ -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
@ -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
@ -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,14 +94,14 @@ 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])
@ -124,6 +109,8 @@ def trim_string(string, w, have_dbls):
#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,21 +119,24 @@ 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.
@ -170,14 +160,14 @@ def wrap_string(string,width,min_lines=0,strip_colors=True):
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:
@ -208,7 +198,7 @@ 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:
@ -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"):
""" """

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
@ -60,12 +33,15 @@ class InputField:
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
@ -83,6 +59,7 @@ 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
@ -118,6 +95,7 @@ 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
@ -132,7 +110,7 @@ class CheckedPlusInput(InputField):
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):
@ -154,9 +132,11 @@ class CheckedPlusInput(InputField):
# 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)
@ -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
@ -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:
@ -229,7 +210,7 @@ class IntSpinInput(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 = int(self.valstr) self.value = int(self.valstr)
@ -242,9 +223,11 @@ class IntSpinInput(InputField):
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)
@ -273,16 +256,20 @@ class IntSpinInput(InputField):
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:
return None
self.cursor = max(0, self.cursor - 1) 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:
return None
self.cursor = min(len(self.valstr), self.cursor + 1) 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:
@ -295,7 +282,8 @@ class IntSpinInput(InputField):
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
@ -305,9 +293,11 @@ 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("-")
if minus_place >= 0:
return
self.valstr = chr(c) + self.valstr self.valstr = chr(c) + self.valstr
self.cursor += 1 self.cursor += 1
self.need_update = True self.need_update = True
@ -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:
@ -329,7 +321,6 @@ class IntSpinInput(InputField):
# 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:
self.__limit_value() self.__limit_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
@ -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)
@ -412,9 +404,11 @@ class FloatSpinInput(InputField):
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:
@ -444,16 +438,20 @@ class FloatSpinInput(InputField):
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:
return None
self.cursor = max(0, self.cursor - 1) 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:
return None
self.cursor = min(len(self.valstr), self.cursor + 1) 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:
@ -466,7 +464,8 @@ class FloatSpinInput(InputField):
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
@ -476,9 +475,11 @@ 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("-")
if minus_place >= 0:
return
self.valstr = chr(c) + self.valstr self.valstr = chr(c) + self.valstr
self.cursor += 1 self.cursor += 1
self.need_update = True self.need_update = True
@ -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:
@ -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:
@ -537,6 +541,7 @@ 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
@ -565,7 +570,8 @@ class SelectInput(InputField):
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
@ -590,6 +596,7 @@ 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
@ -730,7 +737,6 @@ class TextInput(InputField):
# 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))
ret = [] ret = []
@ -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,8 +802,7 @@ 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):
@ -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:
@ -866,7 +868,6 @@ class InputPopup(Popup):
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:

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,7 +474,7 @@ 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)
@ -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,17 +501,17 @@ 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]))
@ -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.
@ -787,7 +762,8 @@ 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:]:
@ -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
@ -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:
@ -909,7 +885,8 @@ 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:
@ -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,6 +18,7 @@ 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
@ -58,8 +31,10 @@ class ALIGN:
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.
@ -155,7 +130,6 @@ 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)
@ -169,7 +143,8 @@ class Popup:
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()
@ -198,7 +173,7 @@ class Popup:
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
@ -240,7 +215,7 @@ 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
@ -264,20 +239,24 @@ class SelectablePopup(Popup):
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:
fg = "black"
colorstr = "{!%s,white,bold!}" % fg 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:
fg = "white"
colorstr = "{!%s,black!}" % fg 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
@ -337,7 +316,7 @@ 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:

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,6 +26,7 @@ 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
@ -66,9 +41,11 @@ class Header(NoInput):
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
@ -88,6 +65,7 @@ class InfoField(NoInput):
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
@ -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"):
@ -176,7 +152,8 @@ class BasePane:
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
@ -203,7 +180,8 @@ class BasePane:
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:
@ -219,7 +197,6 @@ class BasePane:
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):
@ -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,7 +474,8 @@ 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")

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
@ -156,7 +129,7 @@ class Preferences(BaseMode):
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)
@ -177,7 +150,7 @@ 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)
@ -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,6 +49,7 @@ 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
@ -92,11 +68,14 @@ class ACTION:
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:
@ -163,7 +142,8 @@ def torrent_action(idx, data, mode, ids):
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,9 +151,6 @@ 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"])
@ -182,7 +159,6 @@ def torrent_action(idx, data, mode, 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,7 +178,8 @@ 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
@ -210,7 +187,8 @@ def torrent_action(idx, data, mode, ids):
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)
@ -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,14 +120,16 @@ 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))
@ -224,7 +199,8 @@ 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
@ -261,8 +237,8 @@ class TorrentDetail(BaseMode, component.Component):
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):
@ -315,24 +293,21 @@ class TorrentDetail(BaseMode, component.Component):
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
@ -364,14 +339,14 @@ class TorrentDetail(BaseMode, component.Component):
#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)
@ -381,7 +356,8 @@ class TorrentDetail(BaseMode, component.Component):
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:
@ -403,9 +379,9 @@ class TorrentDetail(BaseMode, component.Component):
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])
@ -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 == "-":
ratio_str = "inf"
s += " {!info!}Ratio: {!input!}%s" % ratio_str s += " {!info!}Ratio: {!input!}%s" % ratio_str
self.add_string(off, s); off += 1 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"]:
@ -513,7 +502,7 @@ class TorrentDetail(BaseMode, component.Component):
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)
@ -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):
@ -757,7 +746,8 @@ class TorrentDetail(BaseMode, component.Component):
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:
@ -783,7 +773,8 @@ 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]:
@ -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
@ -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