[Lint] Use Black to auto-format code

The move to using auto-formatter makes it easier to read, submit and
speeds up development time. https://github.com/ambv/black/

Although I would prefer 79 chars, the default line length of 88 chars
used by black suffices. The flake8 line length remains at 120 chars
since black does not touch comments or docstrings and this will require
another round of fixes.

The only black setting that is not standard is the use of double-quotes
for strings so disabled any formatting of these. Note however that
flake8 will still flag usage of double-quotes. I may change my mind on
double vs single quotes but for now leave them.

A new pyproject.toml file has been created for black configuration.
This commit is contained in:
Calum Lind 2018-10-02 15:39:51 +01:00
parent bcca07443c
commit b1cdc32f73
233 changed files with 9460 additions and 4112 deletions

View File

@ -18,9 +18,7 @@ class RpcApi(object):
def scan_for_methods(obj):
methods = {
'__doc__': 'Methods available in %s' % obj.__name__.lower(),
}
methods = {'__doc__': 'Methods available in %s' % obj.__name__.lower()}
for d in dir(obj):
if not hasattr(getattr(obj, d), '_rpcserver_export'):
continue

View File

@ -28,4 +28,6 @@ REQUIRED_VERSION = '1.1.2.0'
LT_VERSION = lt.__version__
if VersionSplit(LT_VERSION) < VersionSplit(REQUIRED_VERSION):
raise ImportError('Deluge %s requires libtorrent >= %s' % (get_version(), REQUIRED_VERSION))
raise ImportError(
'Deluge %s requires libtorrent >= %s' % (get_version(), REQUIRED_VERSION)
)

View File

@ -33,9 +33,9 @@ def decode_int(x, f):
f += 1
newf = x.index(END_DELIM, f)
n = int(x[f:newf])
if x[f:f + 1] == b'-' and x[f + 1:f + 2] == b'0':
if x[f : f + 1] == b'-' and x[f + 1 : f + 2] == b'0':
raise ValueError
elif x[f:f + 1] == b'0' and newf != f + 1:
elif x[f : f + 1] == b'0' and newf != f + 1:
raise ValueError
return (n, newf + 1)
@ -43,25 +43,25 @@ def decode_int(x, f):
def decode_string(x, f):
colon = x.index(BYTE_SEP, f)
n = int(x[f:colon])
if x[f:f + 1] == b'0' and colon != f + 1:
if x[f : f + 1] == b'0' and colon != f + 1:
raise ValueError
colon += 1
return (x[colon:colon + n], colon + n)
return (x[colon : colon + n], colon + n)
def decode_list(x, f):
r, f = [], f + 1
while x[f:f + 1] != END_DELIM:
v, f = decode_func[x[f:f + 1]](x, f)
while x[f : f + 1] != END_DELIM:
v, f = decode_func[x[f : f + 1]](x, f)
r.append(v)
return (r, f + 1)
def decode_dict(x, f):
r, f = {}, f + 1
while x[f:f + 1] != END_DELIM:
while x[f : f + 1] != END_DELIM:
k, f = decode_string(x, f)
r[k], f = decode_func[x[f:f + 1]](x, f)
r[k], f = decode_func[x[f : f + 1]](x, f)
return (r, f + 1)

View File

@ -44,6 +44,7 @@ except ImportError:
# see: https://twistedmatrix.com/trac/ticket/9209
if platform.system() in ('Windows', 'Microsoft'):
from certifi import where
os.environ['SSL_CERT_FILE'] = where()
DBUS_FILEMAN = None
@ -56,7 +57,9 @@ if platform.system() not in ('Windows', 'Microsoft', 'Darwin'):
else:
try:
bus = dbus.SessionBus()
DBUS_FILEMAN = bus.get_object('org.freedesktop.FileManager1', '/org/freedesktop/FileManager1')
DBUS_FILEMAN = bus.get_object(
'org.freedesktop.FileManager1', '/org/freedesktop/FileManager1'
)
except dbus.DBusException:
pass
@ -101,6 +104,7 @@ def get_default_config_dir(filename=None):
"""
if windows_check():
def save_config_path(resource):
app_data_path = os.environ.get('APPDATA')
if not app_data_path:
@ -116,6 +120,7 @@ def get_default_config_dir(filename=None):
app_data_path = app_data_reg[0]
winreg.CloseKey(hkey)
return os.path.join(app_data_path, resource)
else:
from xdg.BaseDirectory import save_config_path
if not filename:
@ -136,11 +141,14 @@ def get_default_download_dir():
download_dir = ''
if not windows_check():
from xdg.BaseDirectory import xdg_config_home
try:
with open(os.path.join(xdg_config_home, 'user-dirs.dirs'), 'r') as _file:
for line in _file:
if not line.startswith('#') and line.startswith('XDG_DOWNLOAD_DIR'):
download_dir = os.path.expandvars(line.partition('=')[2].rstrip().strip('"'))
download_dir = os.path.expandvars(
line.partition('=')[2].rstrip().strip('"')
)
break
except IOError:
pass
@ -151,46 +159,50 @@ def get_default_download_dir():
def archive_files(arc_name, filepaths):
"""Compress a list of filepaths into timestamped tarball in config dir.
"""Compress a list of filepaths into timestamped tarball in config dir.
The archiving config directory is 'archive'.
The archiving config directory is 'archive'.
Args:
arc_name (str): The archive output filename (appended with timestamp).
filepaths (list): A list of the files to be archived into tarball.
Args:
arc_name (str): The archive output filename (appended with timestamp).
filepaths (list): A list of the files to be archived into tarball.
Returns:
str: The full archive filepath.
Returns:
str: The full archive filepath.
"""
"""
from deluge.configmanager import get_config_dir
from deluge.configmanager import get_config_dir
# Set archive compression to lzma with bz2 fallback.
arc_comp = 'xz' if not PY2 else 'bz2'
# Set archive compression to lzma with bz2 fallback.
arc_comp = 'xz' if not PY2 else 'bz2'
archive_dir = os.path.join(get_config_dir(), 'archive')
timestamp = datetime.datetime.now().replace(microsecond=0).isoformat().replace(':', '-')
arc_filepath = os.path.join(archive_dir, arc_name + '-' + timestamp + '.tar.' + arc_comp)
max_num_arcs = 20
archive_dir = os.path.join(get_config_dir(), 'archive')
timestamp = (
datetime.datetime.now().replace(microsecond=0).isoformat().replace(':', '-')
)
arc_filepath = os.path.join(
archive_dir, arc_name + '-' + timestamp + '.tar.' + arc_comp
)
max_num_arcs = 20
if not os.path.exists(archive_dir):
os.makedirs(archive_dir)
else:
old_arcs = glob.glob(os.path.join(archive_dir, arc_name) + '*')
if len(old_arcs) > max_num_arcs:
# TODO: Remove oldest timestamped archives.
log.warning('More than %s tarballs in config archive', max_num_arcs)
if not os.path.exists(archive_dir):
os.makedirs(archive_dir)
else:
old_arcs = glob.glob(os.path.join(archive_dir, arc_name) + '*')
if len(old_arcs) > max_num_arcs:
# TODO: Remove oldest timestamped archives.
log.warning('More than %s tarballs in config archive', max_num_arcs)
try:
with tarfile.open(arc_filepath, 'w:' + arc_comp) as tf:
for filepath in filepaths:
tf.add(filepath, arcname=os.path.basename(filepath))
except OSError:
log.error('Problem occurred archiving filepaths: %s', filepaths)
return False
else:
return arc_filepath
try:
with tarfile.open(arc_filepath, 'w:' + arc_comp) as tf:
for filepath in filepaths:
tf.add(filepath, arcname=os.path.basename(filepath))
except OSError:
log.error('Problem occurred archiving filepaths: %s', filepaths)
return False
else:
return arc_filepath
def windows_check():
@ -254,7 +266,7 @@ def get_os_version():
elif linux_check():
os_version = platform.linux_distribution()
else:
os_version = (platform.release(), )
os_version = (platform.release(),)
return ' '.join(filter(None, os_version))
@ -281,7 +293,7 @@ def resource_filename(module, path):
This is a work-around that.
"""
return pkg_resources.require('Deluge>=%s' % get_version())[0].get_resource_filename(
pkg_resources._manager, os.path.join(*(module.split('.') + [path])),
pkg_resources._manager, os.path.join(*(module.split('.') + [path]))
)
@ -301,8 +313,12 @@ def open_file(path, timestamp=None):
if timestamp is None:
timestamp = int(time.time())
env = os.environ.copy()
env['DESKTOP_STARTUP_ID'] = '%s-%u-%s-xdg_open_TIME%d' % \
(os.path.basename(sys.argv[0]), os.getpid(), os.uname()[1], timestamp)
env['DESKTOP_STARTUP_ID'] = '%s-%u-%s-xdg_open_TIME%d' % (
os.path.basename(sys.argv[0]),
os.getpid(),
os.uname()[1],
timestamp,
)
subprocess.Popen(['xdg-open', '%s' % path], env=env)
@ -321,10 +337,17 @@ def show_file(path, timestamp=None):
else:
if timestamp is None:
timestamp = int(time.time())
startup_id = '%s_%u_%s-dbus_TIME%d' % (os.path.basename(sys.argv[0]), os.getpid(), os.uname()[1], timestamp)
startup_id = '%s_%u_%s-dbus_TIME%d' % (
os.path.basename(sys.argv[0]),
os.getpid(),
os.uname()[1],
timestamp,
)
if DBUS_FILEMAN:
paths = [urljoin('file:', pathname2url(path))]
DBUS_FILEMAN.ShowItems(paths, startup_id, dbus_interface='org.freedesktop.FileManager1')
DBUS_FILEMAN.ShowItems(
paths, startup_id, dbus_interface='org.freedesktop.FileManager1'
)
else:
env = os.environ.copy()
env['DESKTOP_STARTUP_ID'] = startup_id.replace('dbus', 'xdg-open')
@ -341,6 +364,7 @@ def open_url_in_browser(url):
"""
import webbrowser
webbrowser.open(url)
@ -396,13 +420,29 @@ def fsize(fsize_b, precision=1, shortform=False):
"""
if fsize_b >= 1024 ** 4:
return '%.*f %s' % (precision, fsize_b / 1024 ** 4, tib_txt_short if shortform else tib_txt)
return '%.*f %s' % (
precision,
fsize_b / 1024 ** 4,
tib_txt_short if shortform else tib_txt,
)
elif fsize_b >= 1024 ** 3:
return '%.*f %s' % (precision, fsize_b / 1024 ** 3, gib_txt_short if shortform else gib_txt)
return '%.*f %s' % (
precision,
fsize_b / 1024 ** 3,
gib_txt_short if shortform else gib_txt,
)
elif fsize_b >= 1024 ** 2:
return '%.*f %s' % (precision, fsize_b / 1024 ** 2, mib_txt_short if shortform else mib_txt)
return '%.*f %s' % (
precision,
fsize_b / 1024 ** 2,
mib_txt_short if shortform else mib_txt,
)
elif fsize_b >= 1024:
return '%.*f %s' % (precision, fsize_b / 1024, kib_txt_short if shortform else kib_txt)
return '%.*f %s' % (
precision,
fsize_b / 1024,
kib_txt_short if shortform else kib_txt,
)
else:
return '%d %s' % (fsize_b, byte_txt)
@ -425,7 +465,7 @@ def fpcnt(dec, precision=2):
"""
pcnt = (dec * 100)
pcnt = dec * 100
if pcnt == 0 or pcnt == 100:
precision = 0
return '%.*f%%' % (precision, pcnt)
@ -447,13 +487,29 @@ def fspeed(bps, precision=1, shortform=False):
"""
if bps < 1024 ** 2:
return '%.*f %s' % (precision, bps / 1024, _('K/s') if shortform else _('KiB/s'))
return '%.*f %s' % (
precision,
bps / 1024,
_('K/s') if shortform else _('KiB/s'),
)
elif bps < 1024 ** 3:
return '%.*f %s' % (precision, bps / 1024 ** 2, _('M/s') if shortform else _('MiB/s'))
return '%.*f %s' % (
precision,
bps / 1024 ** 2,
_('M/s') if shortform else _('MiB/s'),
)
elif bps < 1024 ** 4:
return '%.*f %s' % (precision, bps / 1024 ** 3, _('G/s') if shortform else _('GiB/s'))
return '%.*f %s' % (
precision,
bps / 1024 ** 3,
_('G/s') if shortform else _('GiB/s'),
)
else:
return '%.*f %s' % (precision, bps / 1024 ** 4, _('T/s') if shortform else _('TiB/s'))
return '%.*f %s' % (
precision,
bps / 1024 ** 4,
_('T/s') if shortform else _('TiB/s'),
)
def fpeer(num_peers, total_peers):
@ -566,17 +622,17 @@ def tokenize(text):
size_units = [
{'prefix': 'b', 'divider': 1, 'singular': 'byte', 'plural': 'bytes'},
{'prefix': 'KiB', 'divider': 1024**1},
{'prefix': 'MiB', 'divider': 1024**2},
{'prefix': 'GiB', 'divider': 1024**3},
{'prefix': 'TiB', 'divider': 1024**4},
{'prefix': 'PiB', 'divider': 1024**5},
{'prefix': 'KB', 'divider': 1000**1},
{'prefix': 'MB', 'divider': 1000**2},
{'prefix': 'GB', 'divider': 1000**3},
{'prefix': 'TB', 'divider': 1000**4},
{'prefix': 'PB', 'divider': 1000**5},
{'prefix': 'm', 'divider': 1000**2},
{'prefix': 'KiB', 'divider': 1024 ** 1},
{'prefix': 'MiB', 'divider': 1024 ** 2},
{'prefix': 'GiB', 'divider': 1024 ** 3},
{'prefix': 'TiB', 'divider': 1024 ** 4},
{'prefix': 'PiB', 'divider': 1024 ** 5},
{'prefix': 'KB', 'divider': 1000 ** 1},
{'prefix': 'MB', 'divider': 1000 ** 2},
{'prefix': 'GB', 'divider': 1000 ** 3},
{'prefix': 'TB', 'divider': 1000 ** 4},
{'prefix': 'PB', 'divider': 1000 ** 5},
{'prefix': 'm', 'divider': 1000 ** 2},
]
@ -706,9 +762,9 @@ def get_magnet_info(uri):
info_hash = None
trackers = {}
tier = 0
for param in uri[len(MAGNET_SCHEME):].split('&'):
for param in uri[len(MAGNET_SCHEME) :].split('&'):
if param.startswith(XT_BTIH_PARAM):
xt_hash = param[len(XT_BTIH_PARAM):]
xt_hash = param[len(XT_BTIH_PARAM) :]
if len(xt_hash) == 32:
try:
infohash_str = base64.b32decode(xt_hash.upper())
@ -721,9 +777,9 @@ def get_magnet_info(uri):
else:
break
elif param.startswith(DN_PARAM):
name = unquote_plus(param[len(DN_PARAM):])
name = unquote_plus(param[len(DN_PARAM) :])
elif param.startswith(TR_PARAM):
tracker = unquote_plus(param[len(TR_PARAM):])
tracker = unquote_plus(param[len(TR_PARAM) :])
trackers[tracker] = tier
tier += 1
elif param.startswith(tr0_param):
@ -736,7 +792,12 @@ def get_magnet_info(uri):
if info_hash:
if not name:
name = info_hash
return {'name': name, 'info_hash': info_hash, 'files_tree': '', 'trackers': trackers}
return {
'name': name,
'info_hash': info_hash,
'files_tree': '',
'trackers': trackers,
}
else:
return {}
@ -758,11 +819,7 @@ def create_magnet_uri(infohash, name=None, trackers=None):
except TypeError:
infohash.encode('utf-8')
uri = [
MAGNET_SCHEME,
XT_BTIH_PARAM,
base64.b32encode(infohash).decode('utf-8'),
]
uri = [MAGNET_SCHEME, XT_BTIH_PARAM, base64.b32encode(infohash).decode('utf-8')]
if name:
uri.extend(['&', DN_PARAM, name])
if trackers:
@ -817,6 +874,7 @@ def free_space(path):
if windows_check():
from win32file import GetDiskFreeSpaceEx
return GetDiskFreeSpaceEx(path)[0]
else:
disk_data = os.statvfs(path.encode('utf8'))
@ -860,6 +918,7 @@ def is_ipv4(ip):
"""
import socket
try:
if windows_check():
return socket.inet_aton(ip)
@ -888,6 +947,7 @@ def is_ipv6(ip):
import ipaddress
except ImportError:
import socket
try:
return socket.inet_pton(socket.AF_INET6, ip)
except (socket.error, AttributeError):
@ -969,8 +1029,7 @@ def utf8_encode_structure(data):
return type(data)([utf8_encode_structure(d) for d in data])
elif isinstance(data, dict):
return {
utf8_encode_structure(k): utf8_encode_structure(v)
for k, v in data.items()
utf8_encode_structure(k): utf8_encode_structure(v) for k, v in data.items()
}
elif not isinstance(data, bytes):
try:
@ -989,6 +1048,7 @@ class VersionSplit(object):
:type ver: string
"""
def __init__(self, ver):
version_re = re.compile(
r"""
@ -1001,7 +1061,8 @@ class VersionSplit(object):
(?P<prerelversion>\d+(?:\.\d+)*)
)?
(?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?
$""", re.VERBOSE,
$""",
re.VERBOSE,
)
# Check for PEP 386 compliant version
@ -1086,48 +1147,52 @@ def create_localclient_account(append=False):
with open(auth_file, 'a' if append else 'w') as _file:
_file.write(
':'.join([
'localclient',
sha(str(random.random()).encode('utf8')).hexdigest(),
str(AUTH_LEVEL_ADMIN),
]) + '\n',
':'.join(
[
'localclient',
sha(str(random.random()).encode('utf8')).hexdigest(),
str(AUTH_LEVEL_ADMIN),
]
)
+ '\n'
)
_file.flush()
os.fsync(_file.fileno())
def get_localhost_auth():
"""Grabs the localclient auth line from the 'auth' file and creates a localhost uri.
"""Grabs the localclient auth line from the 'auth' file and creates a localhost uri.
Returns:
tuple: With the username and password to login as.
Returns:
tuple: With the username and password to login as.
"""
from deluge.configmanager import get_config_dir
"""
from deluge.configmanager import get_config_dir
auth_file = get_config_dir('auth')
if not os.path.exists(auth_file):
from deluge.common import create_localclient_account
create_localclient_account()
auth_file = get_config_dir('auth')
if not os.path.exists(auth_file):
from deluge.common import create_localclient_account
with open(auth_file) as auth:
for line in auth:
line = line.strip()
if line.startswith('#') or not line:
# This is a comment or blank line
continue
create_localclient_account()
lsplit = line.split(':')
with open(auth_file) as auth:
for line in auth:
line = line.strip()
if line.startswith('#') or not line:
# This is a comment or blank line
continue
if len(lsplit) == 2:
username, password = lsplit
elif len(lsplit) == 3:
username, password, level = lsplit
else:
log.error('Your auth file is malformed: Incorrect number of fields!')
continue
lsplit = line.split(':')
if username == 'localclient':
return (username, password)
if len(lsplit) == 2:
username, password = lsplit
elif len(lsplit) == 3:
username, password, level = lsplit
else:
log.error('Your auth file is malformed: Incorrect number of fields!')
continue
if username == 'localclient':
return (username, password)
def set_env_variable(name, value):
@ -1169,9 +1234,16 @@ def set_env_variable(name, value):
if result == 0:
raise Warning
except Exception:
log.warning('Failed to set Env Var \'%s\' (\'kernel32.SetEnvironmentVariableW\')', name)
log.warning(
'Failed to set Env Var \'%s\' (\'kernel32.SetEnvironmentVariableW\')',
name,
)
else:
log.debug('Set Env Var \'%s\' to \'%s\' (\'kernel32.SetEnvironmentVariableW\')', name, value)
log.debug(
'Set Env Var \'%s\' to \'%s\' (\'kernel32.SetEnvironmentVariableW\')',
name,
value,
)
# Update the copy maintained by msvcrt (used by gtk+ runtime)
try:
@ -1191,9 +1263,13 @@ def set_env_variable(name, value):
if result != 0:
raise Warning
except Exception:
log.warning('Failed to set Env Var \'%s\' (\'%s._putenv\')', name, msvcrtname)
log.warning(
'Failed to set Env Var \'%s\' (\'%s._putenv\')', name, msvcrtname
)
else:
log.debug('Set Env Var \'%s\' to \'%s\' (\'%s._putenv\')', name, value, msvcrtname)
log.debug(
'Set Env Var \'%s\' to \'%s\' (\'%s._putenv\')', name, value, msvcrtname
)
def unicode_argv():
@ -1219,8 +1295,7 @@ def unicode_argv():
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
range(start, argc.value)]
return [argv[i] for i in range(start, argc.value)]
else:
# On other platforms, we have to find the likely encoding of the args and decode
# First check if sys.stdout or stdin have encoding set
@ -1253,6 +1328,7 @@ def run_profiled(func, *args, **kwargs):
"""
if kwargs.get('do_profile', True) is not False:
import cProfile
profiler = cProfile.Profile()
def on_shutdown():
@ -1264,6 +1340,7 @@ def run_profiled(func, *args, **kwargs):
else:
import pstats
from io import StringIO
strio = StringIO()
ps = pstats.Stats(profiler, stream=strio).sort_stats('cumulative')
ps.print_stats()
@ -1291,6 +1368,7 @@ def is_process_running(pid):
if windows_check():
from win32process import EnumProcesses
return pid in EnumProcesses()
else:
try:

View File

@ -27,7 +27,6 @@ class ComponentAlreadyRegistered(Exception):
class ComponentException(Exception):
def __init__(self, message, tb):
super(ComponentException, self).__init__(message)
self.message = message
@ -93,6 +92,7 @@ class Component(object):
still be considered in a Started state.
"""
def __init__(self, name, interval=1, depend=None):
"""Initialize component.
@ -146,12 +146,14 @@ class Component(object):
elif self._component_state == 'Started':
d = succeed(True)
else:
d = fail(ComponentException(
'Trying to start component "%s" but it is '
'not in a stopped state. Current state: %s' %
(self._component_name, self._component_state),
traceback.format_stack(limit=4),
))
d = fail(
ComponentException(
'Trying to start component "%s" but it is '
'not in a stopped state. Current state: %s'
% (self._component_name, self._component_state),
traceback.format_stack(limit=4),
)
)
return d
def _component_stop(self):
@ -195,12 +197,14 @@ class Component(object):
elif self._component_state == 'Paused':
d = succeed(None)
else:
d = fail(ComponentException(
'Trying to pause component "%s" but it is '
'not in a started state. Current state: %s' %
(self._component_name, self._component_state),
traceback.format_stack(limit=4),
))
d = fail(
ComponentException(
'Trying to pause component "%s" but it is '
'not in a started state. Current state: %s'
% (self._component_name, self._component_state),
traceback.format_stack(limit=4),
)
)
return d
def _component_resume(self):
@ -211,12 +215,14 @@ class Component(object):
d = maybeDeferred(self._component_start_timer)
d.addCallback(on_resume)
else:
d = fail(ComponentException(
'Trying to resume component "%s" but it is '
'not in a paused state. Current state: %s' %
(self._component_name, self._component_state),
traceback.format_stack(limit=4),
))
d = fail(
ComponentException(
'Trying to resume component "%s" but it is '
'not in a paused state. Current state: %s'
% (self._component_name, self._component_state),
traceback.format_stack(limit=4),
)
)
return d
def _component_shutdown(self):
@ -250,6 +256,7 @@ class ComponentRegistry(object):
It is used to manage the Components by starting, stopping, pausing and shutting them down.
"""
def __init__(self):
self.components = {}
# Stores all of the components that are dependent on a particular component
@ -270,7 +277,9 @@ class ComponentRegistry(object):
"""
name = obj._component_name
if name in self.components:
raise ComponentAlreadyRegistered('Component already registered with name %s' % name)
raise ComponentAlreadyRegistered(
'Component already registered with name %s' % name
)
self.components[obj._component_name] = obj
if obj._component_depend:
@ -295,6 +304,7 @@ class ComponentRegistry(object):
def on_stop(result, name):
# Component may have been removed, so pop to ensure it doesn't fail
self.components.pop(name, None)
return d.addCallback(on_stop, obj._component_name)
else:
return succeed(None)
@ -364,7 +374,9 @@ class ComponentRegistry(object):
if name in self.components:
if name in self.dependents:
# If other components depend on this component, stop them first
d = self.stop(self.dependents[name]).addCallback(on_dependents_stopped, name)
d = self.stop(self.dependents[name]).addCallback(
on_dependents_stopped, name
)
deferreds.append(d)
stopped_in_deferred.update(self.dependents[name])
else:
@ -434,8 +446,11 @@ class ComponentRegistry(object):
Deferred: Fired once all Components have been successfully shut down.
"""
def on_stopped(result):
return DeferredList([comp._component_shutdown() for comp in self.components.values()])
return DeferredList(
[comp._component_shutdown() for comp in self.components.values()]
)
return self.stop(list(self.components)).addCallback(on_stopped)

View File

@ -121,16 +121,14 @@ class Config(object):
setup to convert old config files. (default: 1)
"""
def __init__(self, filename, defaults=None, config_dir=None, file_version=1):
self.__config = {}
self.__set_functions = {}
self.__change_callbacks = []
# These hold the version numbers and they will be set when loaded
self.__version = {
'format': 1,
'file': file_version,
}
self.__version = {'format': 1, 'file': file_version}
# This will get set with a reactor.callLater whenever a config option
# is set.
@ -210,7 +208,9 @@ class Config(object):
global callLater
if callLater is None:
# Must import here and not at the top or it will throw ReactorAlreadyInstalledError
from twisted.internet.reactor import callLater # pylint: disable=redefined-outer-name
from twisted.internet.reactor import (
callLater,
) # pylint: disable=redefined-outer-name
# Run the set_function for this key if any
try:
for func in self.__set_functions[key]:
@ -218,9 +218,11 @@ class Config(object):
except KeyError:
pass
try:
def do_change_callbacks(key, value):
for func in self.__change_callbacks:
func(key, value)
callLater(0, do_change_callbacks, key, value)
except Exception:
pass
@ -306,7 +308,9 @@ class Config(object):
global callLater
if callLater is None:
# Must import here and not at the top or it will throw ReactorAlreadyInstalledError
from twisted.internet.reactor import callLater # pylint: disable=redefined-outer-name
from twisted.internet.reactor import (
callLater,
) # pylint: disable=redefined-outer-name
# We set the save_timer for 5 seconds if not already set
if not self._save_timer or not self._save_timer.active():
@ -432,8 +436,11 @@ class Config(object):
log.warning('Unable to load config file: %s', filename)
log.debug(
'Config %s version: %s.%s loaded: %s', filename,
self.__version['format'], self.__version['file'], self.__config,
'Config %s version: %s.%s loaded: %s',
filename,
self.__version['format'],
self.__version['file'],
self.__config,
)
def save(self, filename=None):
@ -518,7 +525,8 @@ class Config(object):
if self.__version['file'] not in input_range:
log.debug(
'File version %s is not in input_range %s, ignoring converter function..',
self.__version['file'], input_range,
self.__version['file'],
input_range,
)
return
@ -528,7 +536,9 @@ class Config(object):
log.exception(ex)
log.error(
'There was an exception try to convert config file %s %s to %s',
self.__config_file, self.__version['file'], output_version,
self.__config_file,
self.__version['file'],
output_version,
)
raise ex
else:
@ -542,9 +552,11 @@ class Config(object):
@prop
def config(): # pylint: disable=no-method-argument
"""The config dictionary"""
def fget(self):
return self.__config
def fdel(self):
return self.save()
return locals()

View File

@ -95,7 +95,8 @@ class _ConfigManager(object):
# Create the config object if not already created
if config_file not in self.config_files:
self.config_files[config_file] = Config(
config_file, defaults,
config_file,
defaults,
config_dir=self.config_directory,
file_version=file_version,
)
@ -108,7 +109,9 @@ _configmanager = _ConfigManager()
def ConfigManager(config, defaults=None, file_version=1): # NOQA: N802
return _configmanager.get_config(config, defaults=defaults, file_version=file_version)
return _configmanager.get_config(
config, defaults=defaults, file_version=file_version
)
def set_config_dir(directory):

View File

@ -30,6 +30,7 @@ log = logging.getLogger(__name__)
class AlertManager(component.Component):
"""AlertManager fetches and processes libtorrent alerts"""
def __init__(self):
log.debug('AlertManager init...')
component.Component.__init__(self, 'AlertManager', interval=0.3)
@ -40,13 +41,13 @@ class AlertManager(component.Component):
self.set_alert_queue_size(self.alert_queue_size)
alert_mask = (
lt.alert.category_t.error_notification |
lt.alert.category_t.port_mapping_notification |
lt.alert.category_t.storage_notification |
lt.alert.category_t.tracker_notification |
lt.alert.category_t.status_notification |
lt.alert.category_t.ip_block_notification |
lt.alert.category_t.performance_warning
lt.alert.category_t.error_notification
| lt.alert.category_t.port_mapping_notification
| lt.alert.category_t.storage_notification
| lt.alert.category_t.tracker_notification
| lt.alert.category_t.status_notification
| lt.alert.category_t.ip_block_notification
| lt.alert.category_t.performance_warning
)
self.session.apply_settings({'alert_mask': alert_mask})
@ -107,7 +108,10 @@ class AlertManager(component.Component):
if log.isEnabledFor(logging.DEBUG):
log.debug('Alerts queued: %s', num_alerts)
if num_alerts > 0.9 * self.alert_queue_size:
log.warning('Warning total alerts queued, %s, passes 90%% of queue size.', num_alerts)
log.warning(
'Warning total alerts queued, %s, passes 90%% of queue size.',
num_alerts,
)
# Loop through all alerts in the queue
for alert in alerts:
@ -126,4 +130,6 @@ class AlertManager(component.Component):
"""Sets the maximum size of the libtorrent alert queue"""
log.info('Alert Queue Size set to %s', queue_size)
self.alert_queue_size = queue_size
component.get('Core').apply_session_setting('alert_queue_size', self.alert_queue_size)
component.get('Core').apply_session_setting(
'alert_queue_size', self.alert_queue_size
)

View File

@ -17,8 +17,14 @@ from io import open
import deluge.component as component
import deluge.configmanager as configmanager
from deluge.common import (AUTH_LEVEL_ADMIN, AUTH_LEVEL_DEFAULT, AUTH_LEVEL_NONE, AUTH_LEVEL_NORMAL,
AUTH_LEVEL_READONLY, create_localclient_account)
from deluge.common import (
AUTH_LEVEL_ADMIN,
AUTH_LEVEL_DEFAULT,
AUTH_LEVEL_NONE,
AUTH_LEVEL_NORMAL,
AUTH_LEVEL_READONLY,
create_localclient_account,
)
from deluge.error import AuthenticationRequired, AuthManagerError, BadLoginError
log = logging.getLogger(__name__)
@ -50,8 +56,10 @@ class Account(object):
}
def __repr__(self):
return ('<Account username="%(username)s" authlevel=%(authlevel)s>' %
{'username': self.username, 'authlevel': self.authlevel})
return '<Account username="%(username)s" authlevel=%(authlevel)s>' % {
'username': self.username,
'authlevel': self.authlevel,
}
class AuthManager(component.Component):
@ -99,7 +107,7 @@ class AuthManager(component.Component):
"""
if not username:
raise AuthenticationRequired(
'Username and Password are required.', username,
'Username and Password are required.', username
)
if username not in self.__auth:
@ -131,8 +139,7 @@ class AuthManager(component.Component):
raise AuthManagerError('Invalid auth level: %s' % authlevel)
try:
self.__auth[username] = Account(
username, password,
AUTH_LEVELS_MAPPING[authlevel],
username, password, AUTH_LEVELS_MAPPING[authlevel]
)
self.write_auth_file()
return True
@ -160,7 +167,7 @@ class AuthManager(component.Component):
raise AuthManagerError('Username not known', username)
elif username == component.get('RPCServer').get_session_user():
raise AuthManagerError(
'You cannot delete your own account while logged in!', username,
'You cannot delete your own account while logged in!', username
)
del self.__auth[username]
@ -184,7 +191,10 @@ class AuthManager(component.Component):
try:
with open(filepath_tmp, 'w', encoding='utf8') as _file:
for account in self.__auth.values():
_file.write('%(username)s:%(password)s:%(authlevel_int)s\n' % account.data())
_file.write(
'%(username)s:%(password)s:%(authlevel_int)s\n'
% account.data()
)
_file.flush()
os.fsync(_file.fileno())
shutil.move(filepath_tmp, filepath)
@ -237,7 +247,9 @@ class AuthManager(component.Component):
username, password = lsplit
log.warning(
'Your auth entry for %s contains no auth level, '
'using AUTH_LEVEL_DEFAULT(%s)..', username, AUTH_LEVEL_DEFAULT,
'using AUTH_LEVEL_DEFAULT(%s)..',
username,
AUTH_LEVEL_DEFAULT,
)
if username == 'localclient':
authlevel = AUTH_LEVEL_ADMIN
@ -259,7 +271,10 @@ class AuthManager(component.Component):
try:
authlevel = AUTH_LEVELS_MAPPING[authlevel]
except KeyError:
log.error('Your auth file is malformed: %r is not a valid auth level', authlevel)
log.error(
'Your auth file is malformed: %r is not a valid auth level',
authlevel,
)
continue
self.__auth[username] = Account(username, password, authlevel)

View File

@ -28,8 +28,13 @@ from deluge._libtorrent import LT_VERSION, lt
from deluge.common import PY2
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.core.alertmanager import AlertManager
from deluge.core.authmanager import (AUTH_LEVEL_ADMIN, AUTH_LEVEL_NONE, AUTH_LEVELS_MAPPING,
AUTH_LEVELS_MAPPING_REVERSE, AuthManager)
from deluge.core.authmanager import (
AUTH_LEVEL_ADMIN,
AUTH_LEVEL_NONE,
AUTH_LEVELS_MAPPING,
AUTH_LEVELS_MAPPING_REVERSE,
AuthManager,
)
from deluge.core.eventmanager import EventManager
from deluge.core.filtermanager import FilterManager
from deluge.core.pluginmanager import PluginManager
@ -37,8 +42,18 @@ from deluge.core.preferencesmanager import PreferencesManager
from deluge.core.rpcserver import export
from deluge.core.torrentmanager import TorrentManager
from deluge.decorators import deprecated
from deluge.error import AddTorrentError, DelugeError, InvalidPathError, InvalidTorrentError
from deluge.event import NewVersionAvailableEvent, SessionPausedEvent, SessionResumedEvent, TorrentQueueChangedEvent
from deluge.error import (
AddTorrentError,
DelugeError,
InvalidPathError,
InvalidTorrentError,
)
from deluge.event import (
NewVersionAvailableEvent,
SessionPausedEvent,
SessionResumedEvent,
TorrentQueueChangedEvent,
)
from deluge.httpdownloader import download_file
try:
@ -99,16 +114,15 @@ DELUGE_VER = deluge.common.get_version()
class Core(component.Component):
def __init__(self, listen_interface=None, outgoing_interface=None, read_only_config_keys=None):
def __init__(
self, listen_interface=None, outgoing_interface=None, read_only_config_keys=None
):
component.Component.__init__(self, 'Core')
# Start the libtorrent session.
user_agent = 'Deluge/{} libtorrent/{}'.format(DELUGE_VER, LT_VERSION)
peer_id = self._create_peer_id(DELUGE_VER)
log.debug(
'Starting session (peer_id: %s, user_agent: %s)',
peer_id, user_agent,
)
log.debug('Starting session (peer_id: %s, user_agent: %s)', peer_id, user_agent)
settings_pack = {
'peer_fingerprint': peer_id,
'user_agent': user_agent,
@ -139,7 +153,9 @@ class Core(component.Component):
# External IP Address from libtorrent
self.external_ip = None
self.eventmanager.register_event_handler('ExternalIPEvent', self._on_external_ip_event)
self.eventmanager.register_event_handler(
'ExternalIPEvent', self._on_external_ip_event
)
# GeoIP instance with db loaded
self.geoip_instance = None
@ -161,7 +177,10 @@ class Core(component.Component):
self._old_listen_interface = self.config['listen_interface']
self.config['listen_interface'] = listen_interface
else:
log.error('Invalid listen interface (must be IP Address): %s', listen_interface)
log.error(
'Invalid listen interface (must be IP Address): %s',
listen_interface,
)
self._old_outgoing_interface = None
if outgoing_interface:
@ -186,10 +205,10 @@ class Core(component.Component):
self.session_status.update({k: 0.0 for k in hit_ratio_keys})
self.session_status_timer_interval = 0.5
self.session_status_timer = task.LoopingCall(
self.session.post_session_stats)
self.session_status_timer = task.LoopingCall(self.session.post_session_stats)
self.alertmanager.register_handler(
'session_stats_alert', self._on_alert_session_stats)
'session_stats_alert', self._on_alert_session_stats
)
self.session_rates_timer_interval = 2
self.session_rates_timer = task.LoopingCall(self._update_session_rates)
@ -264,7 +283,7 @@ class Core(component.Component):
def substitute_chr(string, idx, char):
"""Fast substitute single char in string."""
return string[:idx] + char + string[idx + 1:]
return string[:idx] + char + string[idx + 1 :]
if split.dev:
release_chr = 'D'
@ -358,16 +377,20 @@ class Core(component.Component):
for rate_key, prev_bytes in list(self._session_prev_bytes.items()):
new_bytes = self.session_status[SESSION_RATES_MAPPING[rate_key]]
self.session_status[rate_key] = (
new_bytes - prev_bytes) / self.session_rates_timer_interval
new_bytes - prev_bytes
) / self.session_rates_timer_interval
# Store current value for next update.
self._session_prev_bytes[rate_key] = new_bytes
def get_new_release(self):
log.debug('get_new_release')
try:
self.new_release = urlopen(
'http://download.deluge-torrent.org/version-2.0',
).read().decode().strip()
self.new_release = (
urlopen('http://download.deluge-torrent.org/version-2.0')
.read()
.decode()
.strip()
)
except URLError as ex:
log.debug('Unable to get release info from website: %s', ex)
else:
@ -376,8 +399,12 @@ class Core(component.Component):
def check_new_release(self):
if self.new_release:
log.debug('new_release: %s', self.new_release)
if deluge.common.VersionSplit(self.new_release) > deluge.common.VersionSplit(deluge.common.get_version()):
component.get('EventManager').emit(NewVersionAvailableEvent(self.new_release))
if deluge.common.VersionSplit(
self.new_release
) > deluge.common.VersionSplit(deluge.common.get_version()):
component.get('EventManager').emit(
NewVersionAvailableEvent(self.new_release)
)
return self.new_release
return False
@ -403,7 +430,10 @@ class Core(component.Component):
try:
d = self.torrentmanager.add_async(
filedump=filedump, options=options, filename=filename, save_state=save_state,
filedump=filedump,
options=options,
filename=filename,
save_state=save_state,
)
except RuntimeError as ex:
log.error('There was an error adding the torrent file %s: %s', filename, ex)
@ -425,6 +455,7 @@ class Core(component.Component):
The metadata is base64 encoded.
"""
def on_metadata(result, result_d):
torrent_id, metadata = result
result_d.callback((torrent_id, b64encode(metadata)))
@ -456,7 +487,7 @@ class Core(component.Component):
try:
return self.torrentmanager.add(
filedump=filedump, options=options, filename=filename,
filedump=filedump, options=options, filename=filename
)
except RuntimeError as ex:
log.error('There was an error adding the torrent file %s: %s', filename, ex)
@ -473,6 +504,7 @@ class Core(component.Component):
Deferred
"""
@defer.inlineCallbacks
def add_torrents():
errors = []
@ -480,12 +512,13 @@ class Core(component.Component):
for idx, torrent in enumerate(torrent_files):
try:
yield self.add_torrent_file_async(
torrent[0], torrent[1], torrent[2], save_state=idx == last_index,
torrent[0], torrent[1], torrent[2], save_state=idx == last_index
)
except AddTorrentError as ex:
log.warning('Error when adding torrent: %s', ex)
errors.append(ex)
defer.returnValue(errors)
return task.deferLater(reactor, 0, add_torrents)
@export
@ -583,14 +616,19 @@ class Core(component.Component):
errors = []
for torrent_id in torrent_ids:
try:
self.torrentmanager.remove(torrent_id, remove_data=remove_data, save_state=False)
self.torrentmanager.remove(
torrent_id, remove_data=remove_data, save_state=False
)
except InvalidTorrentError as ex:
errors.append((torrent_id, str(ex)))
# Save the session state
self.torrentmanager.save_state()
if errors:
log.warning('Failed to remove %d of %d torrents.', len(errors), len(torrent_ids))
log.warning(
'Failed to remove %d of %d torrents.', len(errors), len(torrent_ids)
)
return errors
return task.deferLater(reactor, 0, do_remove_torrents)
@export
@ -617,9 +655,7 @@ class Core(component.Component):
if key in DEPR_SESSION_STATUS_KEYS:
new_key = DEPR_SESSION_STATUS_KEYS[key]
log.debug(
'Deprecated session status key %s, please use %s',
key,
new_key,
'Deprecated session status key %s, please use %s', key, new_key
)
status[key] = self.session_status[new_key]
else:
@ -700,11 +736,22 @@ class Core(component.Component):
for torrent_id in torrent_ids:
self.resume_torrent(torrent_id)
def create_torrent_status(self, torrent_id, torrent_keys, plugin_keys, diff=False, update=False, all_keys=False):
def create_torrent_status(
self,
torrent_id,
torrent_keys,
plugin_keys,
diff=False,
update=False,
all_keys=False,
):
try:
status = self.torrentmanager[torrent_id].get_status(torrent_keys, diff, update=update, all_keys=all_keys)
status = self.torrentmanager[torrent_id].get_status(
torrent_keys, diff, update=update, all_keys=all_keys
)
except KeyError:
import traceback
traceback.print_exc()
# Torrent was probaly removed meanwhile
return {}
@ -716,9 +763,15 @@ class Core(component.Component):
@export
def get_torrent_status(self, torrent_id, keys, diff=False):
torrent_keys, plugin_keys = self.torrentmanager.separate_keys(keys, [torrent_id])
torrent_keys, plugin_keys = self.torrentmanager.separate_keys(
keys, [torrent_id]
)
return self.create_torrent_status(
torrent_id, torrent_keys, plugin_keys, diff=diff, update=True,
torrent_id,
torrent_keys,
plugin_keys,
diff=diff,
update=True,
all_keys=not keys,
)
@ -735,8 +788,11 @@ class Core(component.Component):
# Ask the plugin manager to fill in the plugin keys
if len(plugin_keys) > 0:
for key in status_dict:
status_dict[key].update(self.pluginmanager.get_status(key, plugin_keys))
status_dict[key].update(
self.pluginmanager.get_status(key, plugin_keys)
)
return status_dict
d.addCallback(add_plugin_fields)
return d
@ -798,7 +854,9 @@ class Core(component.Component):
settings = self.session.get_settings()
proxy_type = settings['proxy_type']
proxy_hostname = settings['i2p_hostname'] if proxy_type == 6 else settings['proxy_hostname']
proxy_hostname = (
settings['i2p_hostname'] if proxy_type == 6 else settings['proxy_hostname']
)
proxy_port = settings['i2p_port'] if proxy_type == 6 else settings['proxy_port']
proxy_dict = {
'type': proxy_type,
@ -939,8 +997,17 @@ class Core(component.Component):
@export
def create_torrent(
self, path, tracker, piece_length, comment, target,
webseeds, private, created_by, trackers, add_to_session,
self,
path,
tracker,
piece_length,
comment,
target,
webseeds,
private,
created_by,
trackers,
add_to_session,
):
log.debug('creating torrent..')
@ -961,10 +1028,20 @@ class Core(component.Component):
).start()
def _create_torrent_thread(
self, path, tracker, piece_length, comment, target,
webseeds, private, created_by, trackers, add_to_session,
self,
path,
tracker,
piece_length,
comment,
target,
webseeds,
private,
created_by,
trackers,
add_to_session,
):
from deluge import metafile
metafile.make_meta_file(
path,
tracker,
@ -1058,7 +1135,9 @@ class Core(component.Component):
def queue_top(self, torrent_ids):
log.debug('Attempting to queue %s to top', torrent_ids)
# torrent_ids must be sorted in reverse before moving to preserve order
for torrent_id in sorted(torrent_ids, key=self.torrentmanager.get_queue_position, reverse=True):
for torrent_id in sorted(
torrent_ids, key=self.torrentmanager.get_queue_position, reverse=True
):
try:
# If the queue method returns True, then we should emit a signal
if self.torrentmanager.queue_top(torrent_id):
@ -1069,7 +1148,10 @@ class Core(component.Component):
@export
def queue_up(self, torrent_ids):
log.debug('Attempting to queue %s to up', torrent_ids)
torrents = ((self.torrentmanager.get_queue_position(torrent_id), torrent_id) for torrent_id in torrent_ids)
torrents = (
(self.torrentmanager.get_queue_position(torrent_id), torrent_id)
for torrent_id in torrent_ids
)
torrent_moved = True
prev_queue_position = None
# torrent_ids must be sorted before moving.
@ -1079,7 +1161,9 @@ class Core(component.Component):
try:
torrent_moved = self.torrentmanager.queue_up(torrent_id)
except KeyError:
log.warning('torrent_id: %s does not exist in the queue', torrent_id)
log.warning(
'torrent_id: %s does not exist in the queue', torrent_id
)
# If the torrent moved, then we should emit a signal
if torrent_moved:
component.get('EventManager').emit(TorrentQueueChangedEvent())
@ -1089,7 +1173,10 @@ class Core(component.Component):
@export
def queue_down(self, torrent_ids):
log.debug('Attempting to queue %s to down', torrent_ids)
torrents = ((self.torrentmanager.get_queue_position(torrent_id), torrent_id) for torrent_id in torrent_ids)
torrents = (
(self.torrentmanager.get_queue_position(torrent_id), torrent_id)
for torrent_id in torrent_ids
)
torrent_moved = True
prev_queue_position = None
# torrent_ids must be sorted before moving.
@ -1099,7 +1186,9 @@ class Core(component.Component):
try:
torrent_moved = self.torrentmanager.queue_down(torrent_id)
except KeyError:
log.warning('torrent_id: %s does not exist in the queue', torrent_id)
log.warning(
'torrent_id: %s does not exist in the queue', torrent_id
)
# If the torrent moved, then we should emit a signal
if torrent_moved:
component.get('EventManager').emit(TorrentQueueChangedEvent())
@ -1110,7 +1199,9 @@ class Core(component.Component):
def queue_bottom(self, torrent_ids):
log.debug('Attempting to queue %s to bottom', torrent_ids)
# torrent_ids must be sorted before moving to preserve order
for torrent_id in sorted(torrent_ids, key=self.torrentmanager.get_queue_position):
for torrent_id in sorted(
torrent_ids, key=self.torrentmanager.get_queue_position
):
try:
# If the queue method returns True, then we should emit a signal
if self.torrentmanager.queue_bottom(torrent_id):

View File

@ -66,7 +66,12 @@ class Daemon(object):
"""The Deluge Daemon class"""
def __init__(
self, listen_interface=None, outgoing_interface=None, interface=None, port=None, standalone=False,
self,
listen_interface=None,
outgoing_interface=None,
interface=None,
port=None,
standalone=False,
read_only_config_keys=None,
):
"""
@ -84,19 +89,23 @@ class Daemon(object):
self.pid_file = get_config_dir('deluged.pid')
log.info('Deluge daemon %s', get_version())
if is_daemon_running(self.pid_file):
raise DaemonRunningError('Deluge daemon already running with this config directory!')
raise DaemonRunningError(
'Deluge daemon already running with this config directory!'
)
# Twisted catches signals to terminate, so just have it call the shutdown method.
reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown)
# Catch some Windows specific signals
if windows_check():
def win_handler(ctrl_type):
"""Handle the Windows shutdown or close events."""
log.debug('windows handler ctrl_type: %s', ctrl_type)
if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT:
self._shutdown()
return 1
SetConsoleCtrlHandler(win_handler)
# Start the core as a thread and join it until it's done
@ -123,7 +132,10 @@ class Daemon(object):
log.debug(
'Listening to UI on: %s:%s and bittorrent on: %s Making connections out on: %s',
interface, port, listen_interface, outgoing_interface,
interface,
port,
listen_interface,
outgoing_interface,
)
def start(self):
@ -179,4 +191,6 @@ class Daemon(object):
if rpc not in self.get_method_list():
return False
return self.rpcserver.get_session_auth_level() >= self.rpcserver.get_rpc_auth_level(rpc)
return self.rpcserver.get_session_auth_level() >= self.rpcserver.get_rpc_auth_level(
rpc
)

View File

@ -24,15 +24,26 @@ from deluge.ui.translations_util import set_dummy_trans
def add_daemon_options(parser):
group = parser.add_argument_group(_('Daemon Options'))
group.add_argument(
'-u', '--ui-interface', metavar='<ip-addr>', action='store',
'-u',
'--ui-interface',
metavar='<ip-addr>',
action='store',
help=_('IP address to listen for UI connections'),
)
group.add_argument(
'-p', '--port', metavar='<port>', action='store', type=int,
'-p',
'--port',
metavar='<port>',
action='store',
type=int,
help=_('Port to listen for UI connections on'),
)
group.add_argument(
'-i', '--interface', metavar='<ip-addr>', dest='listen_interface', action='store',
'-i',
'--interface',
metavar='<ip-addr>',
dest='listen_interface',
action='store',
help=_('IP address to listen for BitTorrent connections'),
)
group.add_argument(
@ -44,8 +55,12 @@ def add_daemon_options(parser):
help=_('The interface adapter name for outgoing BitTorrent connections.'),
)
group.add_argument(
'--read-only-config-keys', metavar='<comma-separated-keys>', action='store',
help=_('Config keys to be unmodified by `set_config` RPC'), type=str, default='',
'--read-only-config-keys',
metavar='<comma-separated-keys>',
action='store',
help=_('Config keys to be unmodified by `set_config` RPC'),
type=str,
default='',
)
parser.add_process_arg_group()
@ -71,11 +86,12 @@ def start_daemon(skip_start=False):
# Check for any daemons running with this same config
from deluge.core.daemon import is_daemon_running
pid_file = get_config_dir('deluged.pid')
if is_daemon_running(pid_file):
print(
'Cannot run multiple daemons with same config directory.\n'
'If you believe this is an error, force starting by deleting: %s' % pid_file,
'If you believe this is an error, force starting by deleting: %s' % pid_file
)
sys.exit(1)
@ -90,6 +106,7 @@ def start_daemon(skip_start=False):
def run_daemon(options):
try:
from deluge.core.daemon import Daemon
daemon = Daemon(
listen_interface=options.listen_interface,
outgoing_interface=options.outgoing_interface,
@ -105,7 +122,8 @@ def start_daemon(skip_start=False):
log.error(
'Cannot start deluged, listen port in use.\n'
' Check for other running daemons or services using this port: %s:%s',
ex.interface, ex.port,
ex.interface,
ex.port,
)
sys.exit(1)
except Exception as ex:
@ -118,4 +136,6 @@ def start_daemon(skip_start=False):
if options.pidfile:
os.remove(options.pidfile)
return run_profiled(run_daemon, options, output_file=options.profile, do_profile=options.profile)
return run_profiled(
run_daemon, options, output_file=options.profile, do_profile=options.profile
)

View File

@ -36,7 +36,12 @@ class EventManager(component.Component):
try:
handler(*event.args)
except Exception as ex:
log.error('Event handler %s failed in %s with exception %s', event.name, handler, ex)
log.error(
'Event handler %s failed in %s with exception %s',
event.name,
handler,
ex,
)
def register_event_handler(self, event, handler):
"""

View File

@ -101,6 +101,7 @@ class FilterManager(component.Component):
"""FilterManager
"""
def __init__(self, core):
component.Component.__init__(self, 'FilterManager')
log.debug('FilterManager init..')
@ -115,12 +116,14 @@ class FilterManager(component.Component):
def _init_tracker_tree():
return {'Error': 0}
self.register_tree_field('tracker_host', _init_tracker_tree)
self.register_filter('tracker_host', tracker_error_filter)
def _init_users_tree():
return {'': 0}
self.register_tree_field('owner', _init_users_tree)
def filter_torrent_ids(self, filter_dict):
@ -165,16 +168,22 @@ class FilterManager(component.Component):
for field, values in list(filter_dict.items()):
if field in self.registered_filters:
# Filters out doubles
torrent_ids = list(set(self.registered_filters[field](torrent_ids, values)))
torrent_ids = list(
set(self.registered_filters[field](torrent_ids, values))
)
del filter_dict[field]
if not filter_dict:
return torrent_ids
torrent_keys, plugin_keys = self.torrents.separate_keys(list(filter_dict), torrent_ids)
torrent_keys, plugin_keys = self.torrents.separate_keys(
list(filter_dict), torrent_ids
)
# Leftover filter arguments, default filter on status fields.
for torrent_id in list(torrent_ids):
status = self.core.create_torrent_status(torrent_id, torrent_keys, plugin_keys)
status = self.core.create_torrent_status(
torrent_id, torrent_keys, plugin_keys
)
for field, values in filter_dict.items():
if field in status and status[field] in values:
continue
@ -197,14 +206,18 @@ class FilterManager(component.Component):
items = {field: self.tree_fields[field]() for field in tree_keys}
for torrent_id in list(torrent_ids):
status = self.core.create_torrent_status(torrent_id, torrent_keys, plugin_keys) # status={key:value}
status = self.core.create_torrent_status(
torrent_id, torrent_keys, plugin_keys
) # status={key:value}
for field in tree_keys:
value = status[field]
items[field][value] = items[field].get(value, 0) + 1
if 'tracker_host' in items:
items['tracker_host']['All'] = len(torrent_ids)
items['tracker_host']['Error'] = len(tracker_error_filter(torrent_ids, ('Error',)))
items['tracker_host']['Error'] = len(
tracker_error_filter(torrent_ids, ('Error',))
)
if not show_zero_hits:
for cat in ['state', 'owner', 'tracker_host']:
@ -224,7 +237,9 @@ class FilterManager(component.Component):
init_state['All'] = len(self.torrents.get_torrent_list())
for state in TORRENT_STATE:
init_state[state] = 0
init_state['Active'] = len(self.filter_state_active(self.torrents.get_torrent_list()))
init_state['Active'] = len(
self.filter_state_active(self.torrents.get_torrent_list())
)
return init_state
def register_filter(self, filter_id, filter_func, filter_value=None):
@ -242,7 +257,9 @@ class FilterManager(component.Component):
def filter_state_active(self, torrent_ids):
for torrent_id in list(torrent_ids):
status = self.torrents[torrent_id].get_status(['download_payload_rate', 'upload_payload_rate'])
status = self.torrents[torrent_id].get_status(
['download_payload_rate', 'upload_payload_rate']
)
if status['download_payload_rate'] or status['upload_payload_rate']:
pass
else:

View File

@ -33,7 +33,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, component.Compon
# Call the PluginManagerBase constructor
deluge.pluginmanagerbase.PluginManagerBase.__init__(
self, 'core.conf', 'deluge.plugin.core',
self, 'core.conf', 'deluge.plugin.core'
)
def start(self):
@ -77,6 +77,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, component.Compon
if name not in self.plugins:
component.get('EventManager').emit(PluginDisabledEvent(name))
return result
d.addBoth(on_disable_plugin)
return d

View File

@ -73,8 +73,9 @@ DEFAULT_PREFS = {
'max_download_speed': -1.0,
'max_upload_slots_global': 4,
'max_half_open_connections': (
lambda: deluge.common.windows_check() and
(lambda: deluge.common.vista_check() and 4 or 8)() or 50
lambda: deluge.common.windows_check()
and (lambda: deluge.common.vista_check() and 4 or 8)()
or 50
)(),
'max_connections_per_second': 20,
'ignore_limits_on_local_network': True,
@ -134,7 +135,9 @@ class PreferencesManager(component.Component):
component.Component.__init__(self, 'PreferencesManager')
self.config = deluge.configmanager.ConfigManager('core.conf', DEFAULT_PREFS)
if 'proxies' in self.config:
log.warning('Updating config file for proxy, using "peer" values to fill new "proxy" setting')
log.warning(
'Updating config file for proxy, using "peer" values to fill new "proxy" setting'
)
self.config['proxy'].update(self.config['proxies']['peer'])
log.warning('New proxy config is: %s', self.config['proxy'])
del self.config['proxies']
@ -205,7 +208,9 @@ class PreferencesManager(component.Component):
if self.config['random_port']:
if not self.config['listen_random_port']:
self.config['listen_random_port'] = random.randrange(49152, 65525)
listen_ports = [self.config['listen_random_port']] * 2 # use single port range
listen_ports = [
self.config['listen_random_port']
] * 2 # use single port range
else:
self.config['listen_random_port'] = None
listen_ports = self.config['listen_ports']
@ -217,7 +222,9 @@ class PreferencesManager(component.Component):
log.debug(
'Listen Interface: %s, Ports: %s with use_sys_port: %s',
interface, listen_ports, self.config['listen_use_sys_port'],
interface,
listen_ports,
self.config['listen_use_sys_port'],
)
interfaces = [
'%s:%s' % (interface, port)
@ -227,7 +234,7 @@ class PreferencesManager(component.Component):
{
'listen_system_port_fallback': self.config['listen_use_sys_port'],
'listen_interfaces': ''.join(interfaces),
},
}
)
def _on_set_outgoing_ports(self, key, value):
@ -237,14 +244,22 @@ class PreferencesManager(component.Component):
self.__set_outgoing_ports()
def __set_outgoing_ports(self):
port = 0 if self.config['random_outgoing_ports'] else self.config['outgoing_ports'][0]
port = (
0
if self.config['random_outgoing_ports']
else self.config['outgoing_ports'][0]
)
if port:
num_ports = self.config['outgoing_ports'][1] - self.config['outgoing_ports'][0]
num_ports = (
self.config['outgoing_ports'][1] - self.config['outgoing_ports'][0]
)
num_ports = num_ports if num_ports > 1 else 5
else:
num_ports = 0
log.debug('Outgoing port set to %s with range: %s', port, num_ports)
self.core.apply_session_settings({'outgoing_port': port, 'num_outgoing_ports': num_ports})
self.core.apply_session_settings(
{'outgoing_port': port, 'num_outgoing_ports': num_ports}
)
def _on_set_peer_tos(self, key, value):
try:
@ -263,12 +278,11 @@ class PreferencesManager(component.Component):
'router.bitcomet.com:6881',
'dht.transmissionbt.com:6881',
'dht.aelitis.com:6881',
],
]
)
self.core.apply_session_settings(
{'dht_bootstrap_nodes': ','.join(dht_bootstraps), 'enable_dht': value}
)
self.core.apply_session_settings({
'dht_bootstrap_nodes': ','.join(dht_bootstraps),
'enable_dht': value,
})
def _on_set_upnp(self, key, value):
self.core.apply_session_setting('enable_upnp', value)
@ -294,14 +308,20 @@ class PreferencesManager(component.Component):
def _on_set_encryption(self, key, value):
# Convert Deluge enc_level values to libtorrent enc_level values.
pe_enc_level = {0: lt.enc_level.plaintext, 1: lt.enc_level.rc4, 2: lt.enc_level.both}
pe_enc_level = {
0: lt.enc_level.plaintext,
1: lt.enc_level.rc4,
2: lt.enc_level.both,
}
self.core.apply_session_settings(
{
'out_enc_policy': lt.enc_policy(self.config['enc_out_policy']),
'in_enc_policy': lt.enc_policy(self.config['enc_in_policy']),
'allowed_enc_level': lt.enc_level(pe_enc_level[self.config['enc_level']]),
'allowed_enc_level': lt.enc_level(
pe_enc_level[self.config['enc_level']]
),
'prefer_rc4': True,
},
}
)
def _on_set_max_connections_global(self, key, value):
@ -364,20 +384,29 @@ class PreferencesManager(component.Component):
def run(self):
import time
now = time.time()
# check if we've done this within the last week or never
if (now - self.config['info_sent']) >= (60 * 60 * 24 * 7):
try:
url = 'http://deluge-torrent.org/stats_get.php?processor=' + \
platform.machine() + '&python=' + platform.python_version() \
+ '&deluge=' + deluge.common.get_version() \
+ '&os=' + platform.system() \
+ '&plugins=' + quote_plus(':'.join(self.config['enabled_plugins']))
url = (
'http://deluge-torrent.org/stats_get.php?processor='
+ platform.machine()
+ '&python='
+ platform.python_version()
+ '&deluge='
+ deluge.common.get_version()
+ '&os='
+ platform.system()
+ '&plugins='
+ quote_plus(':'.join(self.config['enabled_plugins']))
)
urlopen(url)
except IOError as ex:
log.debug('Network error while trying to send info: %s', ex)
else:
self.config['info_sent'] = now
if value:
SendInfoThread(self.config).start()
@ -389,7 +418,7 @@ class PreferencesManager(component.Component):
self.new_release_timer.stop()
# Set a timer to check for a new release every 3 days
self.new_release_timer = LoopingCall(
self._on_set_new_release_check, 'new_release_check', True,
self._on_set_new_release_check, 'new_release_check', True
)
self.new_release_timer.start(72 * 60 * 60, False)
else:
@ -410,20 +439,23 @@ class PreferencesManager(component.Component):
}
if value['type'] == lt.proxy_type.i2p_proxy:
proxy_settings.update({
'proxy_type': lt.proxy_type.i2p_proxy,
'i2p_hostname': value['hostname'],
'i2p_port': value['port'],
})
proxy_settings.update(
{
'proxy_type': lt.proxy_type.i2p_proxy,
'i2p_hostname': value['hostname'],
'i2p_port': value['port'],
}
)
elif value['type'] != lt.proxy_type.none:
proxy_settings.update({
'proxy_type': value['type'],
'proxy_hostname': value['hostname'],
'proxy_port': value['port'],
'proxy_username': value['username'],
'proxy_password': value['password'],
})
proxy_settings.update(
{
'proxy_type': value['type'],
'proxy_hostname': value['hostname'],
'proxy_port': value['port'],
'proxy_username': value['username'],
'proxy_password': value['password'],
}
)
self.core.apply_session_settings(proxy_settings)
@ -434,7 +466,9 @@ class PreferencesManager(component.Component):
# Load the GeoIP DB for country look-ups if available
if os.path.exists(geoipdb_path):
try:
self.core.geoip_instance = GeoIP.open(geoipdb_path, GeoIP.GEOIP_STANDARD)
self.core.geoip_instance = GeoIP.open(
geoipdb_path, GeoIP.GEOIP_STANDARD
)
except AttributeError:
log.warning('GeoIP Unavailable')
else:

View File

@ -24,9 +24,19 @@ from twisted.internet.protocol import Factory, connectionDone
import deluge.component as component
import deluge.configmanager
from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AUTH_LEVEL_DEFAULT, AUTH_LEVEL_NONE
from deluge.core.authmanager import (
AUTH_LEVEL_ADMIN,
AUTH_LEVEL_DEFAULT,
AUTH_LEVEL_NONE,
)
from deluge.crypto_utils import get_context_factory
from deluge.error import DelugeError, IncompatibleClient, NotAuthorizedError, WrappedException, _ClientSideRecreateError
from deluge.error import (
DelugeError,
IncompatibleClient,
NotAuthorizedError,
WrappedException,
_ClientSideRecreateError,
)
from deluge.event import ClientDisconnectedEvent
from deluge.transfer import DelugeTransferProtocol
@ -48,6 +58,7 @@ def export(auth_level=AUTH_LEVEL_DEFAULT):
:type auth_level: int
"""
def wrap(func, *args, **kwargs):
func._rpcserver_export = True
func._rpcserver_auth_level = auth_level
@ -120,8 +131,8 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
for call in request:
if len(call) != 4:
log.debug(
'Received invalid rpc request: number of items '
'in request is %s', len(call),
'Received invalid rpc request: number of items ' 'in request is %s',
len(call),
)
continue
# log.debug('RPCRequest: %s', format_request(call))
@ -148,14 +159,11 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
This method is called when a new client connects.
"""
peer = self.transport.getPeer()
log.info(
'Deluge Client connection made from: %s:%s',
peer.host, peer.port,
)
log.info('Deluge Client connection made from: %s:%s', peer.host, peer.port)
# Set the initial auth level of this session to AUTH_LEVEL_NONE
self.factory.authorized_sessions[
self.transport.sessionno
] = self.AuthLevel(AUTH_LEVEL_NONE, '')
self.factory.authorized_sessions[self.transport.sessionno] = self.AuthLevel(
AUTH_LEVEL_NONE, ''
)
def connectionLost(self, reason=connectionDone): # NOQA: N802
"""
@ -174,7 +182,9 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
del self.factory.interested_events[self.transport.sessionno]
if self.factory.state == 'running':
component.get('EventManager').emit(ClientDisconnectedEvent(self.factory.session_id))
component.get('EventManager').emit(
ClientDisconnectedEvent(self.factory.session_id)
)
log.info('Deluge client disconnected: %s', reason.value)
def valid_session(self):
@ -196,6 +206,7 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
:type kwargs: dict
"""
def send_error():
"""
Sends an error response with the contents of the exception that was raised.
@ -203,29 +214,34 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
exc_type, exc_value, dummy_exc_trace = sys.exc_info()
formated_tb = traceback.format_exc()
try:
self.sendData((
RPC_ERROR,
request_id,
exc_type.__name__,
exc_value._args,
exc_value._kwargs,
formated_tb,
))
self.sendData(
(
RPC_ERROR,
request_id,
exc_type.__name__,
exc_value._args,
exc_value._kwargs,
formated_tb,
)
)
except AttributeError:
# This is not a deluge exception (object has no attribute '_args), let's wrap it
log.warning(
'An exception occurred while sending RPC_ERROR to '
'client. Wrapping it and resending. Error to '
'send(causing exception goes next):\n%s', formated_tb,
'send(causing exception goes next):\n%s',
formated_tb,
)
try:
raise WrappedException(
str(exc_value), exc_type.__name__, formated_tb,
str(exc_value), exc_type.__name__, formated_tb
)
except WrappedException:
send_error()
except Exception as ex:
log.error('An exception occurred while sending RPC_ERROR to client: %s', ex)
log.error(
'An exception occurred while sending RPC_ERROR to client: %s', ex
)
if method == 'daemon.info':
# This is a special case and used in the initial connection process
@ -285,7 +301,9 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
log.debug('RPC dispatch %s', method)
try:
method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level
auth_level = self.factory.authorized_sessions[self.transport.sessionno].auth_level
auth_level = self.factory.authorized_sessions[
self.transport.sessionno
].auth_level
if auth_level < method_auth_requirement:
# This session is not allowed to call this method
log.debug(
@ -307,6 +325,7 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
# Check if the return value is a deferred, since we'll need to
# wait for it to fire before sending the RPC_RESPONSE
if isinstance(ret, defer.Deferred):
def on_success(result):
try:
self.sendData((RPC_RESPONSE, request_id, result))
@ -380,7 +399,9 @@ class RPCServer(component.Component):
pkey = os.path.join(deluge.configmanager.get_config_dir('ssl'), 'daemon.pkey')
try:
reactor.listenSSL(port, self.factory, get_context_factory(cert, pkey), interface=hostname)
reactor.listenSSL(
port, self.factory, get_context_factory(cert, pkey), interface=hostname
)
except Exception as ex:
log.debug('Daemon already running or port not available.: %s', ex)
raise
@ -513,7 +534,7 @@ class RPCServer(component.Component):
log.debug('Emit Event: %s %s', event.name, event.args)
# This session is interested so send a RPC_EVENT
self.factory.session_protocols[session_id].sendData(
(RPC_EVENT, event.name, event.args),
(RPC_EVENT, event.name, event.args)
)
def emit_event_for_session_id(self, session_id, event):
@ -526,22 +547,35 @@ class RPCServer(component.Component):
:type event: :class:`deluge.event.DelugeEvent`
"""
if not self.is_session_valid(session_id):
log.debug('Session ID %s is not valid. Not sending event "%s".', session_id, event.name)
log.debug(
'Session ID %s is not valid. Not sending event "%s".',
session_id,
event.name,
)
return
if session_id not in self.factory.interested_events:
log.debug(
'Session ID %s is not interested in any events. Not sending event "%s".',
session_id, event.name,
session_id,
event.name,
)
return
if event.name not in self.factory.interested_events[session_id]:
log.debug('Session ID %s is not interested in event "%s". Not sending it.', session_id, event.name)
log.debug(
'Session ID %s is not interested in event "%s". Not sending it.',
session_id,
event.name,
)
return
log.debug(
'Sending event "%s" with args "%s" to session id "%s".',
event.name, event.args, session_id,
event.name,
event.args,
session_id,
)
self.factory.session_protocols[session_id].sendData(
(RPC_EVENT, event.name, event.args)
)
self.factory.session_protocols[session_id].sendData((RPC_EVENT, event.name, event.args))
def stop(self):
self.factory.state = 'stopping'
@ -568,6 +602,7 @@ def generate_ssl_keys():
This method generates a new SSL key/cert.
"""
from deluge.common import PY2
digest = 'sha256' if not PY2 else b'sha256'
# Generate key pair

View File

@ -28,7 +28,11 @@ from deluge.common import decode_bytes
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.core.authmanager import AUTH_LEVEL_ADMIN
from deluge.decorators import deprecated
from deluge.event import TorrentFolderRenamedEvent, TorrentStateChangedEvent, TorrentTrackerStatusEvent
from deluge.event import (
TorrentFolderRenamedEvent,
TorrentStateChangedEvent,
TorrentTrackerStatusEvent,
)
try:
from urllib.parse import urlparse
@ -65,6 +69,7 @@ def sanitize_filepath(filepath, folder=False):
Args:
folder (bool): A trailing slash is appended to the returned filepath.
"""
def clean_filename(filename):
"""Strips whitespace and discards dotted filenames"""
filename = filename.strip()
@ -110,12 +115,14 @@ def convert_lt_files(files):
except AttributeError:
file_path = _file.path
filelist.append({
'index': index,
'path': file_path.replace('\\', '/'),
'size': _file.size,
'offset': _file.offset,
})
filelist.append(
{
'index': index,
'path': file_path.replace('\\', '/'),
'size': _file.size,
'offset': _file.offset,
}
)
return filelist
@ -152,6 +159,7 @@ class TorrentOptions(dict):
stop_ratio (float): The seeding ratio to stop (or remove) the torrent at.
super_seeding (bool): Enable super seeding/initial seeding.
"""
def __init__(self):
super(TorrentOptions, self).__init__()
config = ConfigManager('core.conf').config
@ -227,6 +235,7 @@ class Torrent(object):
we can re-pause it after its done if necessary
forced_error (TorrentError): Keep track if we have forced this torrent to be in Error state.
"""
def __init__(self, handle, options, state=None, filename=None, magnet=None):
self.torrent_id = str(handle.info_hash())
if log.isEnabledFor(logging.DEBUG):
@ -295,7 +304,9 @@ class Torrent(object):
# Skip set_prioritize_first_last if set_file_priorities is in options as it also calls the method.
if 'file_priorities' in options and 'prioritize_first_last_pieces' in options:
self.options['prioritize_first_last_pieces'] = options.pop('prioritize_first_last_pieces')
self.options['prioritize_first_last_pieces'] = options.pop(
'prioritize_first_last_pieces'
)
for key, value in options.items():
if key in self.options:
@ -407,8 +418,12 @@ class Torrent(object):
# Set the pieces in first and last ranges to priority 7
# if they are not marked as do not download
priorities[first_start:first_end] = [p and 7 for p in priorities[first_start:first_end]]
priorities[last_start:last_end] = [p and 7 for p in priorities[last_start:last_end]]
priorities[first_start:first_end] = [
p and 7 for p in priorities[first_start:first_end]
]
priorities[last_start:last_end] = [
p and 7 for p in priorities[last_start:last_end]
]
# Setting the priorites for all the pieces of this torrent
self.handle.prioritize_pieces(priorities)
@ -494,10 +509,15 @@ class Torrent(object):
"""
if log.isEnabledFor(logging.DEBUG):
log.debug('Setting %s file priorities to: %s', self.torrent_id, file_priorities)
log.debug(
'Setting %s file priorities to: %s', self.torrent_id, file_priorities
)
if (self.handle.has_metadata() and file_priorities and
len(file_priorities) == len(self.get_files())):
if (
self.handle.has_metadata()
and file_priorities
and len(file_priorities) == len(self.get_files())
):
self.handle.prioritize_files(file_priorities)
else:
log.debug('Unable to set new file priorities.')
@ -516,7 +536,9 @@ class Torrent(object):
# Set the first/last priorities if needed.
if self.options['prioritize_first_last_pieces']:
self.set_prioritize_first_last_pieces(self.options['prioritize_first_last_pieces'])
self.set_prioritize_first_last_pieces(
self.options['prioritize_first_last_pieces']
)
@deprecated
def set_save_path(self, download_location):
@ -592,11 +614,16 @@ class Torrent(object):
if self.tracker_status != status:
self.tracker_status = status
component.get('EventManager').emit(TorrentTrackerStatusEvent(self.torrent_id, self.tracker_status))
component.get('EventManager').emit(
TorrentTrackerStatusEvent(self.torrent_id, self.tracker_status)
)
def merge_trackers(self, torrent_info):
"""Merges new trackers in torrent_info into torrent"""
log.info('Adding any new trackers to torrent (%s) already in session...', self.torrent_id)
log.info(
'Adding any new trackers to torrent (%s) already in session...',
self.torrent_id,
)
if not torrent_info:
return
# Don't merge trackers if either torrent has private flag set.
@ -634,15 +661,23 @@ class Torrent(object):
self.state = LT_TORRENT_STATE_MAP.get(str(status.state), str(status.state))
if self.state != old_state:
component.get('EventManager').emit(TorrentStateChangedEvent(self.torrent_id, self.state))
component.get('EventManager').emit(
TorrentStateChangedEvent(self.torrent_id, self.state)
)
if log.isEnabledFor(logging.DEBUG):
log.debug(
'State from lt was: %s | Session is paused: %s\nTorrent state set from "%s" to "%s" (%s)',
'error' if status_error else status.state, session_paused, old_state, self.state, self.torrent_id,
'error' if status_error else status.state,
session_paused,
old_state,
self.state,
self.torrent_id,
)
if self.forced_error:
log.debug('Torrent Error state message: %s', self.forced_error.error_message)
log.debug(
'Torrent Error state message: %s', self.forced_error.error_message
)
def set_status_message(self, message=None):
"""Sets the torrent status message.
@ -783,24 +818,30 @@ class Torrent(object):
client = decode_bytes(peer.client)
try:
country = component.get('Core').geoip_instance.country_code_by_addr(peer.ip[0])
country = component.get('Core').geoip_instance.country_code_by_addr(
peer.ip[0]
)
except AttributeError:
country = ''
else:
try:
country = ''.join([char if char.isalpha() else ' ' for char in country])
country = ''.join(
[char if char.isalpha() else ' ' for char in country]
)
except TypeError:
country = ''
ret.append({
'client': client,
'country': country,
'down_speed': peer.payload_down_speed,
'ip': '%s:%s' % (peer.ip[0], peer.ip[1]),
'progress': peer.progress,
'seed': peer.flags & peer.seed,
'up_speed': peer.payload_up_speed,
})
ret.append(
{
'client': client,
'country': country,
'down_speed': peer.payload_down_speed,
'ip': '%s:%s' % (peer.ip[0], peer.ip[1]),
'progress': peer.progress,
'seed': peer.flags & peer.seed,
'up_speed': peer.payload_up_speed,
}
)
return ret
@ -832,8 +873,10 @@ class Torrent(object):
if not self.has_metadata:
return []
return [
progress / _file.size if _file.size else 0.0 for progress, _file in
zip(self.handle.file_progress(), self.torrent_info.files())
progress / _file.size if _file.size else 0.0
for progress, _file in zip(
self.handle.file_progress(), self.torrent_info.files()
)
]
def get_tracker_host(self):
@ -854,7 +897,7 @@ class Torrent(object):
if tracker:
url = urlparse(tracker.replace('udp://', 'http://'))
if hasattr(url, 'hostname'):
host = (url.hostname or 'DHT')
host = url.hostname or 'DHT'
# Check if hostname is an IP address and just return it if that's the case
try:
socket.inet_aton(host)
@ -995,7 +1038,9 @@ class Torrent(object):
'seeding_time': lambda: self.status.seeding_time,
'finished_time': lambda: self.status.finished_time,
'all_time_download': lambda: self.status.all_time_download,
'storage_mode': lambda: self.status.storage_mode.name.split('_')[2], # sparse or allocate
'storage_mode': lambda: self.status.storage_mode.name.split('_')[
2
], # sparse or allocate
'distributed_copies': lambda: max(0.0, self.status.distributed_copies),
'download_payload_rate': lambda: self.status.download_payload_rate,
'file_priorities': self.get_file_priorities,
@ -1008,8 +1053,12 @@ class Torrent(object):
'max_upload_slots': lambda: self.options['max_upload_slots'],
'max_upload_speed': lambda: self.options['max_upload_speed'],
'message': lambda: self.statusmsg,
'move_on_completed_path': lambda: self.options['move_completed_path'], # Deprecated: move_completed_path
'move_on_completed': lambda: self.options['move_completed'], # Deprecated: Use move_completed
'move_on_completed_path': lambda: self.options[
'move_completed_path'
], # Deprecated: move_completed_path
'move_on_completed': lambda: self.options[
'move_completed'
], # Deprecated: Use move_completed
'move_completed_path': lambda: self.options['move_completed_path'],
'move_completed': lambda: self.options['move_completed'],
'next_announce': lambda: self.status.next_announce.seconds,
@ -1017,16 +1066,24 @@ class Torrent(object):
'num_seeds': lambda: self.status.num_seeds,
'owner': lambda: self.options['owner'],
'paused': lambda: self.status.paused,
'prioritize_first_last': lambda: self.options['prioritize_first_last_pieces'],
'prioritize_first_last': lambda: self.options[
'prioritize_first_last_pieces'
],
# Deprecated: Use prioritize_first_last_pieces
'prioritize_first_last_pieces': lambda: self.options['prioritize_first_last_pieces'],
'prioritize_first_last_pieces': lambda: self.options[
'prioritize_first_last_pieces'
],
'sequential_download': lambda: self.options['sequential_download'],
'progress': self.get_progress,
'shared': lambda: self.options['shared'],
'remove_at_ratio': lambda: self.options['remove_at_ratio'],
'save_path': lambda: self.options['download_location'], # Deprecated: Use download_location
'save_path': lambda: self.options[
'download_location'
], # Deprecated: Use download_location
'download_location': lambda: self.options['download_location'],
'seeds_peers_ratio': lambda: -1.0 if self.status.num_incomplete == 0 else ( # Use -1.0 to signify infinity
'seeds_peers_ratio': lambda: -1.0
if self.status.num_incomplete == 0
else ( # Use -1.0 to signify infinity
self.status.num_complete / self.status.num_incomplete
),
'seed_rank': lambda: self.status.seed_rank,
@ -1041,19 +1098,32 @@ class Torrent(object):
'total_seeds': lambda: self.status.num_complete,
'total_uploaded': lambda: self.status.all_time_upload,
'total_wanted': lambda: self.status.total_wanted,
'total_remaining': lambda: self.status.total_wanted - self.status.total_wanted_done,
'total_remaining': lambda: self.status.total_wanted
- self.status.total_wanted_done,
'tracker': lambda: self.status.current_tracker,
'tracker_host': self.get_tracker_host,
'trackers': lambda: self.trackers,
'tracker_status': lambda: self.tracker_status,
'upload_payload_rate': lambda: self.status.upload_payload_rate,
'comment': lambda: decode_bytes(self.torrent_info.comment()) if self.has_metadata else '',
'creator': lambda: decode_bytes(self.torrent_info.creator()) if self.has_metadata else '',
'num_files': lambda: self.torrent_info.num_files() if self.has_metadata else 0,
'num_pieces': lambda: self.torrent_info.num_pieces() if self.has_metadata else 0,
'piece_length': lambda: self.torrent_info.piece_length() if self.has_metadata else 0,
'comment': lambda: decode_bytes(self.torrent_info.comment())
if self.has_metadata
else '',
'creator': lambda: decode_bytes(self.torrent_info.creator())
if self.has_metadata
else '',
'num_files': lambda: self.torrent_info.num_files()
if self.has_metadata
else 0,
'num_pieces': lambda: self.torrent_info.num_pieces()
if self.has_metadata
else 0,
'piece_length': lambda: self.torrent_info.piece_length()
if self.has_metadata
else 0,
'private': lambda: self.torrent_info.priv() if self.has_metadata else False,
'total_size': lambda: self.torrent_info.total_size() if self.has_metadata else 0,
'total_size': lambda: self.torrent_info.total_size()
if self.has_metadata
else 0,
'eta': self.get_eta,
'file_progress': self.get_file_progress,
'files': self.get_files,
@ -1090,7 +1160,9 @@ class Torrent(object):
# show it as 'Paused'. We need to emit a torrent_paused signal because
# the torrent_paused alert from libtorrent will not be generated.
self.update_state()
component.get('EventManager').emit(TorrentStateChangedEvent(self.torrent_id, 'Paused'))
component.get('EventManager').emit(
TorrentStateChangedEvent(self.torrent_id, 'Paused')
)
else:
try:
self.handle.pause()
@ -1102,9 +1174,14 @@ class Torrent(object):
if self.status.paused and self.status.auto_managed:
log.debug('Resume not possible for auto-managed torrent!')
elif self.forced_error and self.forced_error.was_paused:
log.debug('Resume skipped for forced_error torrent as it was originally paused.')
elif (self.status.is_finished and self.options['stop_at_ratio'] and
self.get_ratio() >= self.options['stop_ratio']):
log.debug(
'Resume skipped for forced_error torrent as it was originally paused.'
)
elif (
self.status.is_finished
and self.options['stop_at_ratio']
and self.get_ratio() >= self.options['stop_ratio']
):
log.debug('Resume skipped for torrent as it has reached "stop_seed_ratio".')
else:
# Check if torrent was originally being auto-managed.
@ -1157,7 +1234,9 @@ class Torrent(object):
log.error(
'Could not move storage for torrent %s since %s does '
'not exist and could not create the directory: %s',
self.torrent_id, dest, ex,
self.torrent_id,
dest,
ex,
)
return False
@ -1191,9 +1270,9 @@ class Torrent(object):
flags = lt.save_resume_flags_t.flush_disk_cache if flush_disk_cache else 0
# Don't generate fastresume data if torrent is in a Deluge Error state.
if self.forced_error:
component.get('TorrentManager').waiting_on_resume_data[self.torrent_id].errback(
UserWarning('Skipped creating resume_data while in Error state'),
)
component.get('TorrentManager').waiting_on_resume_data[
self.torrent_id
].errback(UserWarning('Skipped creating resume_data while in Error state'))
else:
self.handle.save_resume_data(flags)
@ -1232,9 +1311,13 @@ class Torrent(object):
def delete_torrentfile(self, delete_copies=False):
"""Deletes the .torrent file in the state directory in config"""
torrent_files = [os.path.join(get_config_dir(), 'state', self.torrent_id + '.torrent')]
torrent_files = [
os.path.join(get_config_dir(), 'state', self.torrent_id + '.torrent')
]
if delete_copies:
torrent_files.append(os.path.join(self.config['torrentfiles_location'], self.filename))
torrent_files.append(
os.path.join(self.config['torrentfiles_location'], self.filename)
)
for torrent_file in torrent_files:
log.debug('Deleting torrent file: %s', torrent_file)
@ -1325,7 +1408,7 @@ class Torrent(object):
if _file['path'].startswith(folder):
# Keep track of filerenames we're waiting on
wait_on_folder[_file['index']] = Deferred().addBoth(
on_file_rename_complete, wait_on_folder, _file['index'],
on_file_rename_complete, wait_on_folder, _file['index']
)
new_path = _file['path'].replace(folder, new_folder, 1)
try:
@ -1335,10 +1418,14 @@ class Torrent(object):
def on_folder_rename_complete(dummy_result, torrent, folder, new_folder):
"""Folder rename complete"""
component.get('EventManager').emit(TorrentFolderRenamedEvent(torrent.torrent_id, folder, new_folder))
component.get('EventManager').emit(
TorrentFolderRenamedEvent(torrent.torrent_id, folder, new_folder)
)
# Empty folders are removed after libtorrent folder renames
self.remove_empty_folders(folder)
torrent.waiting_on_folder_rename = [_dir for _dir in torrent.waiting_on_folder_rename if _dir]
torrent.waiting_on_folder_rename = [
_dir for _dir in torrent.waiting_on_folder_rename if _dir
]
component.get('TorrentManager').save_resume_data((self.torrent_id,))
d = DeferredList(list(wait_on_folder.values()))
@ -1355,7 +1442,9 @@ class Torrent(object):
"""
# Removes leading slashes that can cause join to ignore download_location
download_location = self.options['download_location']
folder_full_path = os.path.normpath(os.path.join(download_location, folder.lstrip('\\/')))
folder_full_path = os.path.normpath(
os.path.join(download_location, folder.lstrip('\\/'))
)
try:
if not os.listdir(folder_full_path):
@ -1366,7 +1455,9 @@ class Torrent(object):
for name in dirs:
try:
os.removedirs(os.path.join(root, name))
log.debug('Removed Empty Folder %s', os.path.join(root, name))
log.debug(
'Removed Empty Folder %s', os.path.join(root, name)
)
except OSError as ex:
log.debug(ex)
@ -1389,16 +1480,24 @@ class Torrent(object):
pieces = None
else:
pieces = []
for piece, avail_piece in zip(self.status.pieces, self.handle.piece_availability()):
for piece, avail_piece in zip(
self.status.pieces, self.handle.piece_availability()
):
if piece:
pieces.append(3) # Completed.
elif avail_piece:
pieces.append(1) # Available, just not downloaded nor being downloaded.
pieces.append(
1
) # Available, just not downloaded nor being downloaded.
else:
pieces.append(0) # Missing, no known peer with piece, or not asked for yet.
pieces.append(
0
) # Missing, no known peer with piece, or not asked for yet.
for peer_info in self.handle.get_peer_info():
if peer_info.downloading_piece_index >= 0:
pieces[peer_info.downloading_piece_index] = 2 # Being downloaded from peer.
pieces[
peer_info.downloading_piece_index
] = 2 # Being downloaded from peer.
return pieces

View File

@ -29,17 +29,25 @@ from deluge.configmanager import ConfigManager, get_config_dir
from deluge.core.authmanager import AUTH_LEVEL_ADMIN
from deluge.core.torrent import Torrent, TorrentOptions, sanitize_filepath
from deluge.error import AddTorrentError, InvalidTorrentError
from deluge.event import (ExternalIPEvent, PreTorrentRemovedEvent, SessionStartedEvent, TorrentAddedEvent,
TorrentFileCompletedEvent, TorrentFileRenamedEvent, TorrentFinishedEvent, TorrentRemovedEvent,
TorrentResumedEvent)
from deluge.event import (
ExternalIPEvent,
PreTorrentRemovedEvent,
SessionStartedEvent,
TorrentAddedEvent,
TorrentFileCompletedEvent,
TorrentFileRenamedEvent,
TorrentFinishedEvent,
TorrentRemovedEvent,
TorrentResumedEvent,
)
log = logging.getLogger(__name__)
LT_DEFAULT_ADD_TORRENT_FLAGS = (
lt.add_torrent_params_flags_t.flag_paused |
lt.add_torrent_params_flags_t.flag_auto_managed |
lt.add_torrent_params_flags_t.flag_update_subscribe |
lt.add_torrent_params_flags_t.flag_apply_ip_filter
lt.add_torrent_params_flags_t.flag_paused
| lt.add_torrent_params_flags_t.flag_auto_managed
| lt.add_torrent_params_flags_t.flag_update_subscribe
| lt.add_torrent_params_flags_t.flag_apply_ip_filter
)
@ -50,6 +58,7 @@ class TorrentState: # pylint: disable=old-style-class
This must be old style class to avoid breaking torrent.state file.
"""
def __init__(
self,
torrent_id=None,
@ -99,11 +108,14 @@ class TorrentManagerState: # pylint: disable=old-style-class
This must be old style class to avoid breaking torrent.state file.
"""
def __init__(self):
self.torrents = []
def __eq__(self, other):
return isinstance(other, TorrentManagerState) and self.torrents == other.torrents
return (
isinstance(other, TorrentManagerState) and self.torrents == other.torrents
)
def __ne__(self, other):
return not self == other
@ -115,11 +127,14 @@ class TorrentManager(component.Component):
This object is also responsible for saving the state of the session for use on restart.
"""
callLater = reactor.callLater
def __init__(self):
component.Component.__init__(
self, 'TorrentManager', interval=5,
self,
'TorrentManager',
interval=5,
depend=['CorePluginManager', 'AlertManager'],
)
log.debug('TorrentManager init...')
@ -163,8 +178,10 @@ class TorrentManager(component.Component):
# Register set functions
set_config_keys = [
'max_connections_per_torrent', 'max_upload_slots_per_torrent',
'max_upload_speed_per_torrent', 'max_download_speed_per_torrent',
'max_connections_per_torrent',
'max_upload_slots_per_torrent',
'max_upload_speed_per_torrent',
'max_download_speed_per_torrent',
]
for config_key in set_config_keys:
@ -173,18 +190,34 @@ class TorrentManager(component.Component):
# Register alert functions
alert_handles = [
'external_ip_alert', 'performance_alert', 'add_torrent_alert',
'metadata_received_alert', 'torrent_finished_alert', 'torrent_paused_alert',
'torrent_checked_alert', 'torrent_resumed_alert', 'tracker_reply_alert',
'tracker_announce_alert', 'tracker_warning_alert', 'tracker_error_alert',
'file_renamed_alert', 'file_error_alert', 'file_completed_alert',
'storage_moved_alert', 'storage_moved_failed_alert', 'state_update_alert',
'state_changed_alert', 'save_resume_data_alert', 'save_resume_data_failed_alert',
'external_ip_alert',
'performance_alert',
'add_torrent_alert',
'metadata_received_alert',
'torrent_finished_alert',
'torrent_paused_alert',
'torrent_checked_alert',
'torrent_resumed_alert',
'tracker_reply_alert',
'tracker_announce_alert',
'tracker_warning_alert',
'tracker_error_alert',
'file_renamed_alert',
'file_error_alert',
'file_completed_alert',
'storage_moved_alert',
'storage_moved_failed_alert',
'state_update_alert',
'state_changed_alert',
'save_resume_data_alert',
'save_resume_data_failed_alert',
'fastresume_rejected_alert',
]
for alert_handle in alert_handles:
on_alert_func = getattr(self, ''.join(['on_alert_', alert_handle.replace('_alert', '')]))
on_alert_func = getattr(
self, ''.join(['on_alert_', alert_handle.replace('_alert', '')])
)
self.alerts.register_handler(alert_handle, on_alert_func)
# Define timers
@ -195,7 +228,9 @@ class TorrentManager(component.Component):
def start(self):
# Check for old temp file to verify safe shutdown
if os.path.isfile(self.temp_file):
log.warning('Potential bad shutdown of Deluge detected, archiving torrent state files...')
log.warning(
'Potential bad shutdown of Deluge detected, archiving torrent state files...'
)
arc_filepaths = []
for filename in ('torrents.fastresume', 'torrents.state'):
filepath = os.path.join(self.state_dir, filename)
@ -240,14 +275,20 @@ class TorrentManager(component.Component):
for torrent_id, torrent in self.torrents.items():
# XXX: Should the state check be those that _can_ be stopped at ratio
if torrent.options['stop_at_ratio'] and torrent.state not in (
'Checking', 'Allocating', 'Paused', 'Queued',
'Checking',
'Allocating',
'Paused',
'Queued',
):
# If the global setting is set, but the per-torrent isn't...
# Just skip to the next torrent.
# This is so that a user can turn-off the stop at ratio option on a per-torrent basis
if not torrent.options['stop_at_ratio']:
continue
if torrent.get_ratio() >= torrent.options['stop_ratio'] and torrent.is_finished:
if (
torrent.get_ratio() >= torrent.options['stop_ratio']
and torrent.is_finished
):
if torrent.options['remove_at_ratio']:
self.remove(torrent_id)
break
@ -324,11 +365,12 @@ class TorrentManager(component.Component):
add_torrent_params = {}
add_torrent_params['save_path'] = gettempdir()
add_torrent_params['url'] = magnet.strip().encode('utf8')
add_torrent_params['flags'] = ((
LT_DEFAULT_ADD_TORRENT_FLAGS
| lt.add_torrent_params_flags_t.flag_duplicate_is_error
| lt.add_torrent_params_flags_t.flag_upload_mode
)
add_torrent_params['flags'] = (
(
LT_DEFAULT_ADD_TORRENT_FLAGS
| lt.add_torrent_params_flags_t.flag_duplicate_is_error
| lt.add_torrent_params_flags_t.flag_upload_mode
)
^ lt.add_torrent_params_flags_t.flag_auto_managed
^ lt.add_torrent_params_flags_t.flag_paused
)
@ -379,7 +421,7 @@ class TorrentManager(component.Component):
return options
def _build_torrent_params(
self, torrent_info=None, magnet=None, options=None, resume_data=None,
self, torrent_info=None, magnet=None, options=None, resume_data=None
):
"""Create the add_torrent_params dict for adding torrent to libtorrent."""
add_torrent_params = {}
@ -387,7 +429,9 @@ class TorrentManager(component.Component):
add_torrent_params['ti'] = torrent_info
name = torrent_info.name()
if not name:
name = torrent_info.file_at(0).path.replace('\\', '/', 1).split('/', 1)[0]
name = (
torrent_info.file_at(0).path.replace('\\', '/', 1).split('/', 1)[0]
)
add_torrent_params['name'] = name
torrent_id = str(torrent_info.info_hash())
elif magnet:
@ -397,7 +441,9 @@ class TorrentManager(component.Component):
add_torrent_params['name'] = magnet_info['name']
torrent_id = magnet_info['info_hash']
else:
raise AddTorrentError('Unable to add magnet, invalid magnet info: %s' % magnet)
raise AddTorrentError(
'Unable to add magnet, invalid magnet info: %s' % magnet
)
# Check for existing torrent in session.
if torrent_id in self.get_torrent_list():
@ -437,13 +483,10 @@ class TorrentManager(component.Component):
# Set flags: enable duplicate_is_error & override_resume_data, disable auto_managed.
add_torrent_params['flags'] = (
(
LT_DEFAULT_ADD_TORRENT_FLAGS |
lt.add_torrent_params_flags_t.flag_duplicate_is_error |
lt.add_torrent_params_flags_t.flag_override_resume_data
) ^
lt.add_torrent_params_flags_t.flag_auto_managed
)
LT_DEFAULT_ADD_TORRENT_FLAGS
| lt.add_torrent_params_flags_t.flag_duplicate_is_error
| lt.add_torrent_params_flags_t.flag_override_resume_data
) ^ lt.add_torrent_params_flags_t.flag_auto_managed
if options['seed_mode']:
add_torrent_params['flags'] |= lt.add_torrent_params_flags_t.flag_seed_mode
@ -480,17 +523,21 @@ class TorrentManager(component.Component):
"""
if not torrent_info and not filedump and not magnet:
raise AddTorrentError('You must specify a valid torrent_info, torrent state or magnet.')
raise AddTorrentError(
'You must specify a valid torrent_info, torrent state or magnet.'
)
if filedump:
try:
torrent_info = lt.torrent_info(lt.bdecode(filedump))
except RuntimeError as ex:
raise AddTorrentError('Unable to add torrent, decoding filedump failed: %s' % ex)
raise AddTorrentError(
'Unable to add torrent, decoding filedump failed: %s' % ex
)
options = self._build_torrent_options(options)
__, add_torrent_params = self._build_torrent_params(
torrent_info, magnet, options, resume_data,
torrent_info, magnet, options, resume_data
)
# We need to pause the AlertManager momentarily to prevent alerts
@ -506,7 +553,7 @@ class TorrentManager(component.Component):
raise AddTorrentError('Unable to add torrent to session: %s' % ex)
torrent = self._add_torrent_obj(
handle, options, state, filename, magnet, resume_data, filedump, save_state,
handle, options, state, filename, magnet, resume_data, filedump, save_state
)
return torrent.torrent_id
@ -541,28 +588,51 @@ class TorrentManager(component.Component):
"""
if not torrent_info and not filedump and not magnet:
raise AddTorrentError('You must specify a valid torrent_info, torrent state or magnet.')
raise AddTorrentError(
'You must specify a valid torrent_info, torrent state or magnet.'
)
if filedump:
try:
torrent_info = lt.torrent_info(lt.bdecode(filedump))
except RuntimeError as ex:
raise AddTorrentError('Unable to add torrent, decoding filedump failed: %s' % ex)
raise AddTorrentError(
'Unable to add torrent, decoding filedump failed: %s' % ex
)
options = self._build_torrent_options(options)
torrent_id, add_torrent_params = self._build_torrent_params(
torrent_info, magnet, options, resume_data,
torrent_info, magnet, options, resume_data
)
d = Deferred()
self.torrents_loading[torrent_id] = (d, options, state, filename, magnet, resume_data, filedump, save_state)
self.torrents_loading[torrent_id] = (
d,
options,
state,
filename,
magnet,
resume_data,
filedump,
save_state,
)
try:
self.session.async_add_torrent(add_torrent_params)
except RuntimeError as ex:
raise AddTorrentError('Unable to add torrent to session: %s' % ex)
return d
def _add_torrent_obj(self, handle, options, state, filename, magnet, resume_data, filedump, save_state):
def _add_torrent_obj(
self,
handle,
options,
state,
filename,
magnet,
resume_data,
filedump,
save_state,
):
# For magnets added with metadata, filename is used so set as magnet.
if not magnet and is_magnet(filename):
magnet = filename
@ -590,7 +660,9 @@ class TorrentManager(component.Component):
# Emit torrent_added signal.
from_state = state is not None
component.get('EventManager').emit(TorrentAddedEvent(torrent.torrent_id, from_state))
component.get('EventManager').emit(
TorrentAddedEvent(torrent.torrent_id, from_state)
)
if log.isEnabledFor(logging.DEBUG):
log.debug('Torrent added: %s', str(handle.info_hash()))
@ -614,10 +686,19 @@ class TorrentManager(component.Component):
return torrent
def add_async_callback(
self, handle, d, options, state, filename, magnet, resume_data, filedump, save_state,
self,
handle,
d,
options,
state,
filename,
magnet,
resume_data,
filedump,
save_state,
):
torrent = self._add_torrent_obj(
handle, options, state, filename, magnet, resume_data, filedump, save_state,
handle, options, state, filename, magnet, resume_data, filedump, save_state
)
d.callback(torrent.torrent_id)
@ -661,7 +742,9 @@ class TorrentManager(component.Component):
self.resume_data.pop(torrent_id, None)
# Remove the .torrent file in the state and copy location, if user requested.
delete_copies = self.config['copy_torrent_file'] and self.config['del_copy_torrent_file']
delete_copies = (
self.config['copy_torrent_file'] and self.config['del_copy_torrent_file']
)
torrent.delete_torrentfile(delete_copies)
# Remove from set if it wasn't finished
@ -670,7 +753,9 @@ class TorrentManager(component.Component):
self.queued_torrents.remove(torrent_id)
except KeyError:
log.debug('%s is not in queued torrents set.', torrent_id)
raise InvalidTorrentError('%s is not in queued torrents set.' % torrent_id)
raise InvalidTorrentError(
'%s is not in queued torrents set.' % torrent_id
)
# Remove the torrent from deluge's session
del self.torrents[torrent_id]
@ -680,7 +765,11 @@ class TorrentManager(component.Component):
# Emit the signal to the clients
component.get('EventManager').emit(TorrentRemovedEvent(torrent_id))
log.info('Torrent %s removed by user: %s', torrent_name, component.get('RPCServer').get_session_user())
log.info(
'Torrent %s removed by user: %s',
torrent_name,
component.get('RPCServer').get_session_user(),
)
return True
def fixup_state(self, state):
@ -701,7 +790,9 @@ class TorrentManager(component.Component):
for t_state in state.torrents:
setattr(t_state, attr, getattr(t_state_tmp, attr, None))
except AttributeError as ex:
log.error('Unable to update state file to a compatible version: %s', ex)
log.error(
'Unable to update state file to a compatible version: %s', ex
)
return state
def open_state(self):
@ -740,7 +831,9 @@ class TorrentManager(component.Component):
state = self.fixup_state(state)
# Reorder the state.torrents list to add torrents in the correct queue order.
state.torrents.sort(key=operator.attrgetter('queue'), reverse=self.config['queue_new_to_top'])
state.torrents.sort(
key=operator.attrgetter('queue'), reverse=self.config['queue_new_to_top']
)
resume_data = self.load_resume_data_file()
deferreds = []
@ -760,7 +853,7 @@ class TorrentManager(component.Component):
magnet = t_state.magnet
torrent_info = self.get_torrent_info_from_file(
os.path.join(self.state_dir, t_state.torrent_id + '.torrent'),
os.path.join(self.state_dir, t_state.torrent_id + '.torrent')
)
try:
@ -773,15 +866,24 @@ class TorrentManager(component.Component):
resume_data=resume_data.get(t_state.torrent_id),
)
except AddTorrentError as ex:
log.warning('Error when adding torrent "%s" to session: %s', t_state.torrent_id, ex)
log.warning(
'Error when adding torrent "%s" to session: %s',
t_state.torrent_id,
ex,
)
else:
deferreds.append(d)
deferred_list = DeferredList(deferreds, consumeErrors=False)
def on_complete(result):
log.info('Finished loading %d torrents in %s', len(state.torrents), str(datetime.datetime.now() - start))
log.info(
'Finished loading %d torrents in %s',
len(state.torrents),
str(datetime.datetime.now() - start),
)
component.get('EventManager').emit(SessionStartedEvent())
deferred_list.addCallback(on_complete)
def create_state(self):
@ -850,6 +952,7 @@ class TorrentManager(component.Component):
self.is_saving_state = False
if self.save_state_timer.running:
self.save_state_timer.reset()
d.addBoth(on_state_saved)
return d
@ -910,7 +1013,11 @@ class TorrentManager(component.Component):
"""
if torrent_ids is None:
torrent_ids = (tid for tid, t in self.torrents.items() if t.handle.need_save_resume_data())
torrent_ids = (
tid
for tid, t in self.torrents.items()
if t.handle.need_save_resume_data()
)
def on_torrent_resume_save(dummy_result, torrent_id):
"""Recieved torrent resume_data alert so remove from waiting list"""
@ -994,8 +1101,10 @@ class TorrentManager(component.Component):
if self.save_resume_data_timer.running:
self.save_resume_data_timer.reset()
return arg
d.addBoth(on_resume_data_file_saved)
return d
return self.save_resume_data_file_lock.run(on_lock_aquired)
def _save_resume_data_file(self):
@ -1066,7 +1175,9 @@ class TorrentManager(component.Component):
def queue_down(self, torrent_id):
"""Queue torrent down one position"""
if self.torrents[torrent_id].get_queue_position() == (len(self.queued_torrents) - 1):
if self.torrents[torrent_id].get_queue_position() == (
len(self.queued_torrents) - 1
):
return False
self.torrents[torrent_id].handle.queue_position_down()
@ -1074,7 +1185,9 @@ class TorrentManager(component.Component):
def queue_bottom(self, torrent_id):
"""Queue torrent to bottom"""
if self.torrents[torrent_id].get_queue_position() == (len(self.queued_torrents) - 1):
if self.torrents[torrent_id].get_queue_position() == (
len(self.queued_torrents) - 1
):
return False
self.torrents[torrent_id].handle.queue_position_bottom()
@ -1140,21 +1253,28 @@ class TorrentManager(component.Component):
# If total_download is 0, do not move, it's likely the torrent wasn't downloaded, but just added.
# Get fresh data from libtorrent, the cache isn't always up to date
total_download = torrent.get_status(['total_payload_download'], update=True)['total_payload_download']
total_download = torrent.get_status(['total_payload_download'], update=True)[
'total_payload_download'
]
if log.isEnabledFor(logging.DEBUG):
log.debug('Finished %s ', torrent_id)
log.debug(
'Torrent settings: is_finished: %s, total_download: %s, move_completed: %s, move_path: %s',
torrent.is_finished, total_download, torrent.options['move_completed'],
torrent.is_finished,
total_download,
torrent.options['move_completed'],
torrent.options['move_completed_path'],
)
torrent.update_state()
if not torrent.is_finished and total_download:
# Move completed download to completed folder if needed
if torrent.options['move_completed'] and \
torrent.options['download_location'] != torrent.options['move_completed_path']:
if (
torrent.options['move_completed']
and torrent.options['download_location']
!= torrent.options['move_completed_path']
):
self.waiting_on_finish_moving.append(torrent_id)
torrent.move_storage(torrent.options['move_completed_path'])
else:
@ -1177,7 +1297,7 @@ class TorrentManager(component.Component):
# worth really to save in resume data, we just read it up in
# self.load_state().
if total_download:
self.save_resume_data((torrent_id, ))
self.save_resume_data((torrent_id,))
def on_alert_torrent_paused(self, alert):
"""Alert handler for libtorrent torrent_paused_alert"""
@ -1217,7 +1337,10 @@ class TorrentManager(component.Component):
torrent.set_tracker_status('Announce OK')
# Check for peer information from the tracker, if none then send a scrape request.
if alert.handle.status().num_complete == -1 or alert.handle.status().num_incomplete == -1:
if (
alert.handle.status().num_complete == -1
or alert.handle.status().num_incomplete == -1
):
torrent.scrape_tracker()
def on_alert_tracker_announce(self, alert):
@ -1249,7 +1372,9 @@ class TorrentManager(component.Component):
error_message = decode_bytes(alert.error_message())
if not error_message:
error_message = decode_bytes(alert.error.message())
log.debug('Tracker Error Alert: %s [%s]', decode_bytes(alert.message()), error_message)
log.debug(
'Tracker Error Alert: %s [%s]', decode_bytes(alert.message()), error_message
)
torrent.set_tracker_status('Error: ' + error_message)
def on_alert_storage_moved(self, alert):
@ -1337,7 +1462,9 @@ class TorrentManager(component.Component):
return
if torrent_id in self.waiting_on_resume_data:
self.waiting_on_resume_data[torrent_id].errback(Exception(decode_bytes(alert.message())))
self.waiting_on_resume_data[torrent_id].errback(
Exception(decode_bytes(alert.message()))
)
def on_alert_fastresume_rejected(self, alert):
"""Alert handler for libtorrent fastresume_rejected_alert"""
@ -1355,7 +1482,9 @@ class TorrentManager(component.Component):
else:
error_msg = 'Missing or invalid torrent data!'
else:
error_msg = 'Problem with resume data: %s' % alert_msg.split(':', 1)[1].strip()
error_msg = (
'Problem with resume data: %s' % alert_msg.split(':', 1)[1].strip()
)
torrent.force_error_state(error_msg, restart_to_resume=True)
def on_alert_file_renamed(self, alert):
@ -1381,7 +1510,9 @@ class TorrentManager(component.Component):
break
else:
# This is just a regular file rename so send the signal
component.get('EventManager').emit(TorrentFileRenamedEvent(torrent_id, alert.index, new_name))
component.get('EventManager').emit(
TorrentFileRenamedEvent(torrent_id, alert.index, new_name)
)
self.save_resume_data((torrent_id,))
def on_alert_metadata_received(self, alert):
@ -1427,7 +1558,9 @@ class TorrentManager(component.Component):
except RuntimeError:
return
if torrent_id in self.torrents:
component.get('EventManager').emit(TorrentFileCompletedEvent(torrent_id, alert.index))
component.get('EventManager').emit(
TorrentFileCompletedEvent(torrent_id, alert.index)
)
def on_alert_state_update(self, alert):
"""Alert handler for libtorrent state_update_alert
@ -1464,7 +1597,11 @@ class TorrentManager(component.Component):
def on_alert_performance(self, alert):
"""Alert handler for libtorrent performance_alert"""
log.warning('on_alert_performance: %s, %s', decode_bytes(alert.message()), alert.warning_code)
log.warning(
'on_alert_performance: %s, %s',
decode_bytes(alert.message()),
alert.warning_code,
)
if alert.warning_code == lt.performance_warning_t.send_buffer_watermark_too_low:
max_send_buffer_watermark = 3 * 1024 * 1024 # 3MiB
settings = self.session.get_settings()
@ -1473,10 +1610,19 @@ class TorrentManager(component.Component):
# If send buffer is too small, try increasing its size by 512KiB (up to max_send_buffer_watermark)
if send_buffer_watermark < max_send_buffer_watermark:
value = send_buffer_watermark + (500 * 1024)
log.info('Increasing send_buffer_watermark from %s to %s Bytes', send_buffer_watermark, value)
component.get('Core').apply_session_setting('send_buffer_watermark', value)
log.info(
'Increasing send_buffer_watermark from %s to %s Bytes',
send_buffer_watermark,
value,
)
component.get('Core').apply_session_setting(
'send_buffer_watermark', value
)
else:
log.warning('send_buffer_watermark reached maximum value: %s Bytes', max_send_buffer_watermark)
log.warning(
'send_buffer_watermark reached maximum value: %s Bytes',
max_send_buffer_watermark,
)
def separate_keys(self, keys, torrent_ids):
"""Separates the input keys into torrent class keys and plugins keys"""
@ -1502,7 +1648,9 @@ class TorrentManager(component.Component):
# Could be the clients cache (sessionproxy) isn't up to speed.
del status_dict[torrent_id]
else:
status_dict[torrent_id] = self.torrents[torrent_id].get_status(torrent_keys, diff, all_keys=not keys)
status_dict[torrent_id] = self.torrents[torrent_id].get_status(
torrent_keys, diff, all_keys=not keys
)
self.status_dict = status_dict
d.callback((status_dict, plugin_keys))
@ -1527,7 +1675,9 @@ class TorrentManager(component.Component):
now = time.time()
# If last update was recent, use cached data instead of request updates from libtorrent
if (now - self.last_state_update_alert_ts) < 1.5:
reactor.callLater(0, self.handle_torrents_status_callback, (d, torrent_ids, keys, diff))
reactor.callLater(
0, self.handle_torrents_status_callback, (d, torrent_ids, keys, diff)
)
else:
# Ask libtorrent for status update
self.torrents_status_requests.insert(0, (d, torrent_ids, keys, diff))

View File

@ -10,7 +10,13 @@
from __future__ import division, print_function, unicode_literals
from OpenSSL.crypto import FILETYPE_PEM
from twisted.internet.ssl import AcceptableCiphers, Certificate, CertificateOptions, KeyPair, TLSVersion
from twisted.internet.ssl import (
AcceptableCiphers,
Certificate,
CertificateOptions,
KeyPair,
TLSVersion,
)
# A TLS ciphers list.
# Sources for more information on TLS ciphers:
@ -25,16 +31,17 @@ from twisted.internet.ssl import AcceptableCiphers, Certificate, CertificateOpti
# - prefer cipher suites that offer perfect forward secrecy (ECDHE),
# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common,
# - disable NULL authentication, MD5 MACs and DSS for security reasons.
TLS_CIPHERS = ':'.join([
'ECDH+AESGCM',
'ECDH+CHACHA20',
'AES256-GCM-SHA384',
'AES128-GCM-SHA256',
'!DSS'
'!aNULL',
'!eNULL',
'!MD5'
])
TLS_CIPHERS = ':'.join(
[
'ECDH+AESGCM',
'ECDH+CHACHA20',
'AES256-GCM-SHA384',
'AES128-GCM-SHA256',
'!DSS' '!aNULL',
'!eNULL',
'!MD5',
]
)
# This value tells OpenSSL to disable all SSL/TLS renegotiation.
SSL_OP_NO_RENEGOTIATION = 0x40000000

View File

@ -23,11 +23,14 @@ def proxy(proxy_func):
:param proxy_func: the proxy function
:type proxy_func: function
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return proxy_func(func, *args, **kwargs)
return wrapper
return decorator
@ -57,6 +60,7 @@ def overrides(*args):
# called with the real function as argument
def ret_func(func, **kwargs):
return _overrides(stack, func, explicit_base_classes=args)
return ret_func
@ -75,7 +79,10 @@ def _overrides(stack, method, explicit_base_classes=None):
check_classes = base_classes
if not base_classes:
raise ValueError('overrides decorator: unable to determine base class of class "%s"' % class_name)
raise ValueError(
'overrides decorator: unable to determine base class of class "%s"'
% class_name
)
def get_class(cls_name):
if '.' not in cls_name:
@ -91,7 +98,9 @@ def _overrides(stack, method, explicit_base_classes=None):
if explicit_base_classes:
# One or more base classes are explicitly given, check only those classes
override_classes = re.search(r'\s*@overrides\((.+)\)\s*', stack[1][4][0]).group(1)
override_classes = re.search(r'\s*@overrides\((.+)\)\s*', stack[1][4][0]).group(
1
)
override_classes = [c.strip() for c in override_classes.split(',')]
check_classes = override_classes
@ -101,21 +110,36 @@ def _overrides(stack, method, explicit_base_classes=None):
# Verify that the excplicit override class is one of base classes
if explicit_base_classes:
from itertools import product
for bc, cc in product(base_classes, check_classes):
if issubclass(classes[bc], classes[cc]):
break
else:
raise Exception('Excplicit override class "%s" is not a super class of: %s'
% (explicit_base_classes, class_name))
raise Exception(
'Excplicit override class "%s" is not a super class of: %s'
% (explicit_base_classes, class_name)
)
if not all(hasattr(classes[cls], method.__name__) for cls in check_classes):
for cls in check_classes:
if not hasattr(classes[cls], method.__name__):
raise Exception('Function override "%s" not found in superclass: %s\n%s'
% (method.__name__, cls, 'File: %s:%s' % (stack[1][1], stack[1][2])))
raise Exception(
'Function override "%s" not found in superclass: %s\n%s'
% (
method.__name__,
cls,
'File: %s:%s' % (stack[1][1], stack[1][2]),
)
)
if not any(hasattr(classes[cls], method.__name__) for cls in check_classes):
raise Exception('Function override "%s" not found in any superclass: %s\n%s'
% (method.__name__, check_classes, 'File: %s:%s' % (stack[1][1], stack[1][2])))
raise Exception(
'Function override "%s" not found in any superclass: %s\n%s'
% (
method.__name__,
check_classes,
'File: %s:%s' % (stack[1][1], stack[1][2]),
)
)
return method
@ -131,7 +155,8 @@ def deprecated(func):
warnings.simplefilter('always', DeprecationWarning) # Turn off filter
warnings.warn(
'Call to deprecated function {}.'.format(func.__name__),
category=DeprecationWarning, stacklevel=2,
category=DeprecationWarning,
stacklevel=2,
)
warnings.simplefilter('default', DeprecationWarning) # Reset filter
return func(*args, **kwargs)

View File

@ -13,7 +13,6 @@ from __future__ import unicode_literals
class DelugeError(Exception):
def __new__(cls, *args, **kwargs):
inst = super(DelugeError, cls).__new__(cls, *args, **kwargs)
inst._args = args
@ -45,7 +44,6 @@ class InvalidPathError(DelugeError):
class WrappedException(DelugeError):
def __init__(self, message, exception_type, traceback):
super(WrappedException, self).__init__(message)
self.type = exception_type
@ -60,7 +58,6 @@ class _ClientSideRecreateError(DelugeError):
class IncompatibleClient(_ClientSideRecreateError):
def __init__(self, daemon_version):
self.daemon_version = daemon_version
msg = (
@ -71,11 +68,8 @@ class IncompatibleClient(_ClientSideRecreateError):
class NotAuthorizedError(_ClientSideRecreateError):
def __init__(self, current_level, required_level):
msg = (
'Auth level too low: %(current_level)s < %(required_level)s'
) % {
msg = ('Auth level too low: %(current_level)s < %(required_level)s') % {
'current_level': current_level,
'required_level': required_level,
}
@ -85,7 +79,6 @@ class NotAuthorizedError(_ClientSideRecreateError):
class _UsernameBasedPasstroughError(_ClientSideRecreateError):
def __init__(self, message, username):
super(_UsernameBasedPasstroughError, self).__init__(message)
self.username = username

View File

@ -25,6 +25,7 @@ class DelugeEventMetaClass(type):
"""
This metaclass simply keeps a list of all events classes created.
"""
def __init__(cls, name, bases, dct): # pylint: disable=bad-mcs-method-argument
super(DelugeEventMetaClass, cls).__init__(name, bases, dct)
if name != 'DelugeEvent':
@ -58,6 +59,7 @@ class TorrentAddedEvent(DelugeEvent):
"""
Emitted when a new torrent is successfully added to the session.
"""
def __init__(self, torrent_id, from_state):
"""
:param torrent_id: the torrent_id of the torrent that was added
@ -72,6 +74,7 @@ class TorrentRemovedEvent(DelugeEvent):
"""
Emitted when a torrent has been removed from the session.
"""
def __init__(self, torrent_id):
"""
:param torrent_id: the torrent_id
@ -84,6 +87,7 @@ class PreTorrentRemovedEvent(DelugeEvent):
"""
Emitted when a torrent is about to be removed from the session.
"""
def __init__(self, torrent_id):
"""
:param torrent_id: the torrent_id
@ -96,6 +100,7 @@ class TorrentStateChangedEvent(DelugeEvent):
"""
Emitted when a torrent changes state.
"""
def __init__(self, torrent_id, state):
"""
:param torrent_id: the torrent_id
@ -110,6 +115,7 @@ class TorrentTrackerStatusEvent(DelugeEvent):
"""
Emitted when a torrents tracker status changes.
"""
def __init__(self, torrent_id, status):
"""
Args:
@ -123,6 +129,7 @@ class TorrentQueueChangedEvent(DelugeEvent):
"""
Emitted when the queue order has changed.
"""
pass
@ -130,6 +137,7 @@ class TorrentFolderRenamedEvent(DelugeEvent):
"""
Emitted when a folder within a torrent has been renamed.
"""
def __init__(self, torrent_id, old, new):
"""
:param torrent_id: the torrent_id
@ -146,6 +154,7 @@ class TorrentFileRenamedEvent(DelugeEvent):
"""
Emitted when a file within a torrent has been renamed.
"""
def __init__(self, torrent_id, index, name):
"""
:param torrent_id: the torrent_id
@ -162,6 +171,7 @@ class TorrentFinishedEvent(DelugeEvent):
"""
Emitted when a torrent finishes downloading.
"""
def __init__(self, torrent_id):
"""
:param torrent_id: the torrent_id
@ -174,6 +184,7 @@ class TorrentResumedEvent(DelugeEvent):
"""
Emitted when a torrent resumes from a paused state.
"""
def __init__(self, torrent_id):
"""
:param torrent_id: the torrent_id
@ -186,6 +197,7 @@ class TorrentFileCompletedEvent(DelugeEvent):
"""
Emitted when a file completes.
"""
def __init__(self, torrent_id, index):
"""
:param torrent_id: the torrent_id
@ -200,6 +212,7 @@ class TorrentStorageMovedEvent(DelugeEvent):
"""
Emitted when the storage location for a torrent has been moved.
"""
def __init__(self, torrent_id, path):
"""
:param torrent_id: the torrent_id
@ -214,6 +227,7 @@ class CreateTorrentProgressEvent(DelugeEvent):
"""
Emitted when creating a torrent file remotely.
"""
def __init__(self, piece_count, num_pieces):
self._args = [piece_count, num_pieces]
@ -222,6 +236,7 @@ class NewVersionAvailableEvent(DelugeEvent):
"""
Emitted when a more recent version of Deluge is available.
"""
def __init__(self, new_release):
"""
:param new_release: the new version that is available
@ -235,6 +250,7 @@ class SessionStartedEvent(DelugeEvent):
Emitted when a session has started. This typically only happens once when
the daemon is initially started.
"""
pass
@ -242,6 +258,7 @@ class SessionPausedEvent(DelugeEvent):
"""
Emitted when the session has been paused.
"""
pass
@ -249,6 +266,7 @@ class SessionResumedEvent(DelugeEvent):
"""
Emitted when the session has been resumed.
"""
pass
@ -256,6 +274,7 @@ class ConfigValueChangedEvent(DelugeEvent):
"""
Emitted when a config value changes in the Core.
"""
def __init__(self, key, value):
"""
:param key: the key that changed
@ -269,6 +288,7 @@ class PluginEnabledEvent(DelugeEvent):
"""
Emitted when a plugin is enabled in the Core.
"""
def __init__(self, plugin_name):
self._args = [plugin_name]
@ -277,6 +297,7 @@ class PluginDisabledEvent(DelugeEvent):
"""
Emitted when a plugin is disabled in the Core.
"""
def __init__(self, plugin_name):
self._args = [plugin_name]
@ -285,6 +306,7 @@ class ClientDisconnectedEvent(DelugeEvent):
"""
Emitted when a client disconnects.
"""
def __init__(self, session_id):
self._args = [session_id]
@ -293,6 +315,7 @@ class ExternalIPEvent(DelugeEvent):
"""
Emitted when the external ip address is received from libtorrent.
"""
def __init__(self, external_ip):
"""
Args:

View File

@ -31,13 +31,14 @@ log = logging.getLogger(__name__)
class CompressionDecoder(client.GzipDecoder):
"""A compression decoder for gzip, x-gzip and deflate."""
def deliverBody(self, protocol): # NOQA: N802
self.original.deliverBody(
CompressionDecoderProtocol(protocol, self.original))
self.original.deliverBody(CompressionDecoderProtocol(protocol, self.original))
class CompressionDecoderProtocol(client._GzipProtocol):
"""A compression decoder protocol for CompressionDecoder."""
def __init__(self, protocol, response):
super(CompressionDecoderProtocol, self).__init__(protocol, response)
self._zlibDecompress = zlib.decompressobj(32 + zlib.MAX_WBITS)
@ -45,6 +46,7 @@ class CompressionDecoderProtocol(client._GzipProtocol):
class BodyHandler(HTTPClientParser, object):
"""An HTTP parser that saves the response to a file."""
def __init__(self, request, finished, length, agent, encoding=None):
"""BodyHandler init.
@ -66,8 +68,7 @@ class BodyHandler(HTTPClientParser, object):
self.current_length += len(data)
self.data += data
if self.agent.part_callback:
self.agent.part_callback(
data, self.current_length, self.total_length)
self.agent.part_callback(data, self.current_length, self.total_length)
def connectionLost(self, reason): # NOQA: N802
if self.encoding:
@ -82,6 +83,7 @@ class BodyHandler(HTTPClientParser, object):
@implementer(IAgent)
class HTTPDownloaderAgent(object):
"""A File Downloader Agent."""
def __init__(
self,
agent,
@ -125,21 +127,19 @@ class HTTPDownloaderAgent(object):
finished.errback(Failure(error))
else:
headers = response.headers
body_length = int(
headers.getRawHeaders(b'content-length', default=[0])[0])
body_length = int(headers.getRawHeaders(b'content-length', default=[0])[0])
if (
headers.hasHeader(b'content-disposition')
and not self.force_filename
):
content_disp = headers.getRawHeaders(
b'content-disposition')[0].decode('utf-8')
if headers.hasHeader(b'content-disposition') and not self.force_filename:
content_disp = headers.getRawHeaders(b'content-disposition')[0].decode(
'utf-8'
)
content_disp_params = cgi.parse_header(content_disp)[1]
if 'filename' in content_disp_params:
new_file_name = content_disp_params['filename']
new_file_name = sanitise_filename(new_file_name)
new_file_name = os.path.join(
os.path.split(self.filename)[0], new_file_name)
os.path.split(self.filename)[0], new_file_name
)
count = 1
fileroot = os.path.splitext(new_file_name)[0]
@ -155,13 +155,8 @@ class HTTPDownloaderAgent(object):
params = cgi.parse_header(cont_type)[1]
encoding = params.get('charset', None)
response.deliverBody(
BodyHandler(
response.request,
finished,
body_length,
self,
encoding,
))
BodyHandler(response.request, finished, body_length, self, encoding)
)
return finished
@ -186,10 +181,7 @@ class HTTPDownloaderAgent(object):
headers.addRawHeader('User-Agent', user_agent)
d = self.agent.request(
method=method,
uri=uri,
headers=headers,
bodyProducer=body_producer,
method=method, uri=uri, headers=headers, bodyProducer=body_producer
)
d.addCallback(self.request_callback)
return d
@ -212,8 +204,7 @@ def sanitise_filename(filename):
if os.path.basename(filename) != filename:
# Dodgy server, log it
log.warning(
'Potentially malicious server: trying to write to file: %s',
filename,
'Potentially malicious server: trying to write to file: %s', filename
)
# Only use the basename
filename = os.path.basename(filename)
@ -222,15 +213,15 @@ def sanitise_filename(filename):
if filename.startswith('.') or ';' in filename or '|' in filename:
# Dodgy server, log it
log.warning(
'Potentially malicious server: trying to write to file: %s',
filename,
'Potentially malicious server: trying to write to file: %s', filename
)
return filename
def _download_file(
url, filename,
url,
filename,
callback=None,
headers=None,
force_filename=False,
@ -269,12 +260,7 @@ def _download_file(
agent = client.RedirectAgent(agent)
agent = HTTPDownloaderAgent(
agent,
filename,
callback,
force_filename,
allow_compression,
handle_redirects,
agent, filename, callback, force_filename, allow_compression, handle_redirects
)
# The Headers init expects dict values to be a list.
@ -317,6 +303,7 @@ def download_file(
t.w.e.PageRedirect: If handle_redirects is False.
t.w.e.Error: For all other HTTP response errors.
"""
def on_download_success(result):
log.debug('Download success!')
return result
@ -324,14 +311,19 @@ def download_file(
def on_download_fail(failure):
log.warning(
'Error occurred downloading file from "%s": %s',
url, failure.getErrorMessage(),
url,
failure.getErrorMessage(),
)
result = failure
return result
d = _download_file(
url, filename, callback=callback, headers=headers,
force_filename=force_filename, allow_compression=allow_compression,
url,
filename,
callback=callback,
headers=headers,
force_filename=force_filename,
allow_compression=allow_compression,
handle_redirects=handle_redirects,
)
d.addCallbacks(on_download_success, on_download_fail)

View File

@ -29,7 +29,9 @@ LoggingLoggerClass = logging.getLoggerClass()
if 'dev' in common.get_version():
DEFAULT_LOGGING_FORMAT = '%%(asctime)s.%%(msecs)03.0f [%%(levelname)-8s][%%(name)-%ds:%%(lineno)-4d] %%(message)s'
else:
DEFAULT_LOGGING_FORMAT = '%%(asctime)s [%%(levelname)-8s][%%(name)-%ds:%%(lineno)-4d] %%(message)s'
DEFAULT_LOGGING_FORMAT = (
'%%(asctime)s [%%(levelname)-8s][%%(name)-%ds:%%(lineno)-4d] %%(message)s'
)
MAX_LOGGER_NAME_LENGTH = 10
@ -43,10 +45,12 @@ class Logging(LoggingLoggerClass):
if len(logger_name) > MAX_LOGGER_NAME_LENGTH:
MAX_LOGGER_NAME_LENGTH = len(logger_name)
for handler in logging.getLogger().handlers:
handler.setFormatter(logging.Formatter(
DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH,
datefmt='%H:%M:%S',
))
handler.setFormatter(
logging.Formatter(
DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH,
datefmt='%H:%M:%S',
)
)
@defer.inlineCallbacks
def garbage(self, msg, *args, **kwargs):
@ -112,8 +116,12 @@ levels = {
def setup_logger(
level='error', filename=None, filemode='w', logrotate=None,
output_stream=sys.stdout, twisted_observer=True,
level='error',
filename=None,
filemode='w',
logrotate=None,
output_stream=sys.stdout,
twisted_observer=True,
):
"""
Sets up the basic logger and if `:param:filename` is set, then it will log
@ -140,13 +148,14 @@ def setup_logger(
if filename and logrotate:
handler = logging.handlers.RotatingFileHandler(
filename, maxBytes=logrotate,
backupCount=5, encoding='utf-8',
filename, maxBytes=logrotate, backupCount=5, encoding='utf-8'
)
elif filename and filemode == 'w':
handler_cls = logging.FileHandler
if not common.windows_check():
handler_cls = getattr(logging.handlers, 'WatchedFileHandler', logging.FileHandler)
handler_cls = getattr(
logging.handlers, 'WatchedFileHandler', logging.FileHandler
)
handler = handler_cls(filename, mode=filemode, encoding='utf-8')
else:
handler = logging.StreamHandler(stream=output_stream)
@ -154,8 +163,7 @@ def setup_logger(
handler.setLevel(level)
formatter = logging.Formatter(
DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH,
datefmt='%H:%M:%S',
DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH, datefmt='%H:%M:%S'
)
handler.setFormatter(formatter)
@ -190,7 +198,9 @@ class TwistedLoggingObserver(PythonLoggingObserver):
log = logging.getLogger(__name__)
if 'log_failure' in event_dict:
fmt = '%(log_namespace)s \n%(log_failure)s'
getattr(LoggingLoggerClass, event_dict['log_level'].name)(log, fmt % (event_dict))
getattr(LoggingLoggerClass, event_dict['log_level'].name)(
log, fmt % (event_dict)
)
else:
PythonLoggingObserver.emit(self, event_dict)
@ -214,13 +224,13 @@ def tweak_logging_levels():
the command line.
"""
from deluge import configmanager
logging_config_file = os.path.join(configmanager.get_config_dir(), 'logging.conf')
if not os.path.isfile(logging_config_file):
return
log = logging.getLogger(__name__)
log.warning(
'logging.conf found! tweaking logging levels from %s',
logging_config_file,
'logging.conf found! tweaking logging levels from %s', logging_config_file
)
with open(logging_config_file, 'r') as _file:
for line in _file:
@ -249,15 +259,18 @@ def set_logger_level(level, logger_name=None):
def get_plugin_logger(logger_name):
import warnings
stack = inspect.stack()
stack.pop(0) # The logging call from this module
stack.pop(0) # The logging call from this module
module_stack = stack.pop(0) # The module that called the log function
caller_module = inspect.getmodule(module_stack[0])
# In some weird cases caller_module might be None, try to continue
caller_module_name = getattr(caller_module, '__name__', '')
warnings.warn_explicit(
DEPRECATION_WARNING, DeprecationWarning,
module_stack[1], module_stack[2],
DEPRECATION_WARNING,
DeprecationWarning,
module_stack[1],
module_stack[2],
caller_module_name,
)
@ -292,16 +305,19 @@ Triggering code:"""
class _BackwardsCompatibleLOG(object):
def __getattribute__(self, name):
import warnings
logger_name = 'deluge'
stack = inspect.stack()
stack.pop(0) # The logging call from this module
stack.pop(0) # The logging call from this module
module_stack = stack.pop(0) # The module that called the log function
caller_module = inspect.getmodule(module_stack[0])
# In some weird cases caller_module might be None, try to continue
caller_module_name = getattr(caller_module, '__name__', '')
warnings.warn_explicit(
DEPRECATION_WARNING, DeprecationWarning,
module_stack[1], module_stack[2],
DEPRECATION_WARNING,
DeprecationWarning,
module_stack[1],
module_stack[2],
caller_module_name,
)
if caller_module:
@ -320,7 +336,7 @@ class _BackwardsCompatibleLOG(object):
else:
logging.getLogger(logger_name).warning(
"Unable to monkey-patch the calling module's `log` attribute! "
'You should really update and rebuild your plugins...',
'You should really update and rebuild your plugins...'
)
return getattr(logging.getLogger(logger_name), name)

View File

@ -18,6 +18,7 @@ from deluge.common import get_path_size, utf8_encode_structure
class InvalidPath(Exception):
"""Raised when an invalid path is supplied."""
pass
@ -27,6 +28,7 @@ class InvalidPieceSize(Exception):
Note:
Piece sizes must be multiples of 16KiB.
"""
pass
@ -42,6 +44,7 @@ class TorrentMetadata(object):
>>> t.save('/tmp/test.torrent')
"""
def __init__(self):
self.__data_path = None
self.__piece_size = 0
@ -66,9 +69,7 @@ class TorrentMetadata(object):
if not self.data_path:
raise InvalidPath('Need to set a data_path!')
torrent = {
'info': {},
}
torrent = {'info': {}}
if self.comment:
torrent['comment'] = self.comment
@ -121,8 +122,10 @@ class TorrentMetadata(object):
# Collect a list of file paths and add padding files if necessary
for (dirpath, dirnames, filenames) in os.walk(self.data_path):
for index, filename in enumerate(filenames):
size = get_path_size(os.path.join(self.data_path, dirpath, filename))
p = dirpath[len(self.data_path):]
size = get_path_size(
os.path.join(self.data_path, dirpath, filename)
)
p = dirpath[len(self.data_path) :]
p = p.lstrip('/')
p = p.split('/')
if p[0]:
@ -156,7 +159,9 @@ class TorrentMetadata(object):
buf = b''
fs[-1][b'attr'] = b'p'
else:
with open(os.path.join(self.data_path.encode('utf8'), *path), 'rb') as _file:
with open(
os.path.join(self.data_path.encode('utf8'), *path), 'rb'
) as _file:
r = _file.read(piece_size - len(buf))
while r:
buf += r

View File

@ -50,14 +50,25 @@ class RemoteFileProgress(object):
def __call__(self, piece_count, num_pieces):
component.get('RPCServer').emit_event_for_session_id(
self.session_id, CreateTorrentProgressEvent(piece_count, num_pieces),
self.session_id, CreateTorrentProgressEvent(piece_count, num_pieces)
)
def make_meta_file(
path, url, piece_length, progress=None, title=None, comment=None,
safe=None, content_type=None, target=None, webseeds=None, name=None,
private=False, created_by=None, trackers=None,
path,
url,
piece_length,
progress=None,
title=None,
comment=None,
safe=None,
content_type=None,
target=None,
webseeds=None,
name=None,
private=False,
created_by=None,
trackers=None,
):
data = {'creation date': int(gmtime())}
if url:
@ -140,6 +151,7 @@ def makeinfo(path, piece_length, progress, name=None, content_type=None, private
totalsize += os.path.getsize(f)
if totalsize >= piece_length:
import math
num_pieces = math.ceil(totalsize / piece_length)
else:
num_pieces = 1
@ -149,10 +161,9 @@ def makeinfo(path, piece_length, progress, name=None, content_type=None, private
size = os.path.getsize(f)
p2 = [n.encode('utf8') for n in p]
if content_type:
fs.append({
'length': size, 'path': p2,
'content_type': content_type,
}) # HEREDAVE. bad for batch!
fs.append(
{'length': size, 'path': p2, 'content_type': content_type}
) # HEREDAVE. bad for batch!
else:
fs.append({'length': size, 'path': p2})
with open(f, 'rb') as file_:
@ -206,14 +217,16 @@ def makeinfo(path, piece_length, progress, name=None, content_type=None, private
if content_type is not None:
return {
'pieces': b''.join(pieces),
'piece length': piece_length, 'length': size,
'piece length': piece_length,
'length': size,
'name': name,
'content_type': content_type,
'private': private,
}
return {
'pieces': b''.join(pieces),
'piece length': piece_length, 'length': size,
'piece length': piece_length,
'length': size,
'name': name,
'private': private,
}

View File

@ -17,9 +17,12 @@ def is_hidden(filepath):
def has_hidden_attribute(filepath):
import win32api
import win32con
try:
attribute = win32api.GetFileAttributes(filepath)
return attribute & (win32con.FILE_ATTRIBUTE_HIDDEN | win32con.FILE_ATTRIBUTE_SYSTEM)
return attribute & (
win32con.FILE_ATTRIBUTE_HIDDEN | win32con.FILE_ATTRIBUTE_SYSTEM
)
except (AttributeError, AssertionError):
return False

View File

@ -56,7 +56,9 @@ class PluginManagerBase(object):
self.config = deluge.configmanager.ConfigManager(config_file)
# Create the plugins folder if it doesn't exist
if not os.path.exists(os.path.join(deluge.configmanager.get_config_dir(), 'plugins')):
if not os.path.exists(
os.path.join(deluge.configmanager.get_config_dir(), 'plugins')
):
os.mkdir(os.path.join(deluge.configmanager.get_config_dir(), 'plugins'))
# This is the entry we want to load..
@ -149,7 +151,9 @@ class PluginManagerBase(object):
log.error(ex)
return defer.succeed(False)
except Exception as ex:
log.error('Unable to instantiate plugin %r from %r!', name, egg.location)
log.error(
'Unable to instantiate plugin %r from %r!', name, egg.location
)
log.exception(ex)
continue
try:
@ -161,35 +165,47 @@ class PluginManagerBase(object):
if not instance.__module__.startswith('deluge.plugins.'):
import warnings
warnings.warn_explicit(
DEPRECATION_WARNING % name,
DeprecationWarning,
instance.__module__, 0,
instance.__module__,
0,
)
if self._component_state == 'Started':
def on_enabled(result, instance):
return component.start([instance.plugin._component_name])
return_d.addCallback(on_enabled, instance)
def on_started(result, instance):
plugin_name_space = plugin_name.replace('-', ' ')
self.plugins[plugin_name_space] = instance
if plugin_name_space not in self.config['enabled_plugins']:
log.debug('Adding %s to enabled_plugins list in config', plugin_name_space)
log.debug(
'Adding %s to enabled_plugins list in config', plugin_name_space
)
self.config['enabled_plugins'].append(plugin_name_space)
log.info('Plugin %s enabled...', plugin_name_space)
return True
def on_started_error(result, instance):
log.error(
'Failed to start plugin: %s\n%s', plugin_name,
'Failed to start plugin: %s\n%s',
plugin_name,
result.getTraceback(elideFrameworkCode=1, detail='brief'),
)
self.plugins[plugin_name.replace('-', ' ')] = instance
self.disable_plugin(plugin_name)
return False
return_d.addCallbacks(on_started, on_started_error, callbackArgs=[instance], errbackArgs=[instance])
return_d.addCallbacks(
on_started,
on_started_error,
callbackArgs=[instance],
errbackArgs=[instance],
)
return return_d
return defer.succeed(False)
@ -219,7 +235,9 @@ class PluginManagerBase(object):
def on_disabled(result):
ret = True
if isinstance(result, Failure):
log.debug('Error when disabling plugin %s: %s', name, result.getTraceback())
log.debug(
'Error when disabling plugin %s: %s', name, result.getTraceback()
)
ret = False
try:
component.deregister(self.plugins[name].plugin)
@ -250,7 +268,9 @@ class PluginManagerBase(object):
for line in self.pkg_env[name][0].get_metadata('PKG-INFO').splitlines():
if not line:
continue
if line[0] in ' \t' and (len(line.split(':', 1)) == 1 or line.split(':', 1)[0] not in info):
if line[0] in ' \t' and (
len(line.split(':', 1)) == 1 or line.split(':', 1)[0] not in info
):
# This is a continuation
cont_lines.append(line.strip())
else:

View File

@ -20,6 +20,7 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
super(CorePlugin, self).__init__(plugin_name)
@ -27,6 +28,7 @@ class CorePlugin(PluginInitBase):
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
super(GtkUIPlugin, self).__init__(plugin_name)
@ -34,5 +36,6 @@ class GtkUIPlugin(PluginInitBase):
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
super(WebUIPlugin, self).__init__(plugin_name)

View File

@ -35,10 +35,7 @@ from deluge.plugins.pluginbase import CorePluginBase
log = logging.getLogger(__name__)
DEFAULT_PREFS = {
'watchdirs': {},
'next_id': 1,
}
DEFAULT_PREFS = {'watchdirs': {}, 'next_id': 1}
OPTIONS_AVAILABLE = { # option: builtin
@ -72,6 +69,7 @@ MAX_NUM_ATTEMPTS = 10
class AutoaddOptionsChangedEvent(DelugeEvent):
"""Emitted when the options for the plugin are changed."""
def __init__(self):
pass
@ -92,7 +90,7 @@ class Core(CorePluginBase):
self.rpcserver = component.get('RPCServer')
component.get('EventManager').register_event_handler(
'PreTorrentRemovedEvent', self.__on_pre_torrent_removed,
'PreTorrentRemovedEvent', self.__on_pre_torrent_removed
)
# Dict of Filename:Attempts
@ -110,7 +108,7 @@ class Core(CorePluginBase):
def disable(self):
# disable all running looping calls
component.get('EventManager').deregister_event_handler(
'PreTorrentRemovedEvent', self.__on_pre_torrent_removed,
'PreTorrentRemovedEvent', self.__on_pre_torrent_removed
)
for loopingcall in self.update_timers.values():
loopingcall.stop()
@ -124,14 +122,10 @@ class Core(CorePluginBase):
"""Update the options for a watch folder."""
watchdir_id = str(watchdir_id)
options = self._make_unicode(options)
check_input(
watchdir_id in self.watchdirs, _('Watch folder does not exist.'),
)
check_input(watchdir_id in self.watchdirs, _('Watch folder does not exist.'))
if 'path' in options:
options['abspath'] = os.path.abspath(options['path'])
check_input(
os.path.isdir(options['abspath']), _('Path does not exist.'),
)
check_input(os.path.isdir(options['abspath']), _('Path does not exist.'))
for w_id, w in self.watchdirs.items():
if options['abspath'] == w['abspath'] and watchdir_id != w_id:
raise Exception('Path is already being watched.')
@ -211,10 +205,7 @@ class Core(CorePluginBase):
watchdir = self.watchdirs[watchdir_id]
if not watchdir['enabled']:
# We shouldn't be updating because this watchdir is not enabled
log.debug(
'Watchdir id %s is not enabled. Disabling it.',
watchdir_id,
)
log.debug('Watchdir id %s is not enabled. Disabling it.', watchdir_id)
self.disable_watchdir(watchdir_id)
return
@ -231,7 +222,10 @@ class Core(CorePluginBase):
# without them is valid, and applies all its settings.
for option, value in watchdir.items():
if OPTIONS_AVAILABLE.get(option):
if watchdir.get(option + '_toggle', True) or option in ['owner', 'seed_mode']:
if watchdir.get(option + '_toggle', True) or option in [
'owner',
'seed_mode',
]:
options[option] = value
# Check for .magnet files containing multiple magnet links and
@ -240,19 +234,27 @@ class Core(CorePluginBase):
try:
filepath = os.path.join(watchdir['abspath'], filename)
except UnicodeDecodeError as ex:
log.error('Unable to auto add torrent due to improper filename encoding: %s', ex)
log.error(
'Unable to auto add torrent due to improper filename encoding: %s',
ex,
)
continue
if os.path.isdir(filepath):
# Skip directories
continue
elif os.path.splitext(filename)[1] == '.magnet' and self.split_magnets(filepath):
elif os.path.splitext(filename)[1] == '.magnet' and self.split_magnets(
filepath
):
os.remove(filepath)
for filename in os.listdir(watchdir['abspath']):
try:
filepath = os.path.join(watchdir['abspath'], filename)
except UnicodeDecodeError as ex:
log.error('Unable to auto add torrent due to improper filename encoding: %s', ex)
log.error(
'Unable to auto add torrent due to improper filename encoding: %s',
ex,
)
continue
if os.path.isdir(filepath):
@ -276,7 +278,8 @@ class Core(CorePluginBase):
if self.invalid_torrents[filename] >= MAX_NUM_ATTEMPTS:
log.warning(
'Maximum attempts reached while trying to add the '
'torrent file with the path %s', filepath,
'torrent file with the path %s',
filepath,
)
os.rename(filepath, filepath + '.invalid')
del self.invalid_torrents[filename]
@ -296,7 +299,10 @@ class Core(CorePluginBase):
except Exception as ex:
log.error('Unable to set label: %s', ex)
if watchdir.get('queue_to_top_toggle', True) and 'queue_to_top' in watchdir:
if (
watchdir.get('queue_to_top_toggle', True)
and 'queue_to_top' in watchdir
):
if watchdir['queue_to_top']:
component.get('TorrentManager').queue_top(torrent_id)
else:
@ -312,7 +318,8 @@ class Core(CorePluginBase):
copy_torrent_file = os.path.join(copy_torrent_path, filename)
log.debug(
'Moving added torrent file "%s" to "%s"',
os.path.basename(filepath), copy_torrent_path,
os.path.basename(filepath),
copy_torrent_path,
)
shutil.move(filepath, copy_torrent_file)
else:
@ -331,10 +338,12 @@ class Core(CorePluginBase):
try:
# The torrent looks good, so lets add it to the session.
if magnet:
d = component.get('Core').add_torrent_magnet(filedump.strip(), options)
d = component.get('Core').add_torrent_magnet(
filedump.strip(), options
)
else:
d = component.get('Core').add_torrent_file_async(
filename, b64encode(filedump), options,
filename, b64encode(filedump), options
)
d.addCallback(on_torrent_added, filename, filepath)
d.addErrback(fail_torrent_add, filepath, magnet)
@ -346,7 +355,8 @@ class Core(CorePluginBase):
self.disable_watchdir(watchdir_id)
log.error(
'Disabling "%s", error during update: %s',
self.watchdirs[watchdir_id]['path'], failure,
self.watchdirs[watchdir_id]['path'],
failure,
)
@export
@ -356,7 +366,7 @@ class Core(CorePluginBase):
if w_id not in self.update_timers or not self.update_timers[w_id].running:
self.update_timers[w_id] = LoopingCall(self.update_watchdir, w_id)
self.update_timers[w_id].start(5).addErrback(
self.on_update_watchdir_error, w_id,
self.on_update_watchdir_error, w_id
)
# Update the config
if not self.watchdirs[w_id]['enabled']:
@ -398,8 +408,8 @@ class Core(CorePluginBase):
session_auth_level = self.rpcserver.get_session_auth_level()
if session_auth_level == AUTH_LEVEL_ADMIN:
log.debug(
'Current logged in user %s is an ADMIN, send all '
'watchdirs', session_user,
'Current logged in user %s is an ADMIN, send all ' 'watchdirs',
session_user,
)
return self.watchdirs
@ -410,7 +420,9 @@ class Core(CorePluginBase):
log.debug(
'Current logged in user %s is not an ADMIN, send only '
'their watchdirs: %s', session_user, list(watchdirs),
'their watchdirs: %s',
session_user,
list(watchdirs),
)
return watchdirs
@ -451,7 +463,9 @@ class Core(CorePluginBase):
def remove(self, watchdir_id):
"""Remove a watch folder."""
watchdir_id = str(watchdir_id)
check_input(watchdir_id in self.watchdirs, 'Unknown Watchdir: %s' % self.watchdirs)
check_input(
watchdir_id in self.watchdirs, 'Unknown Watchdir: %s' % self.watchdirs
)
if self.watchdirs[watchdir_id]['enabled']:
self.disable_watchdir(watchdir_id)
del self.watchdirs[watchdir_id]
@ -488,13 +502,16 @@ class Core(CorePluginBase):
os.remove(torrent_fname_path)
log.info(
'Removed torrent file "%s" from "%s"',
torrent_fname, copy_torrent_path,
torrent_fname,
copy_torrent_path,
)
break
except OSError as ex:
log.info(
'Failed to removed torrent file "%s" from "%s": %s',
torrent_fname, copy_torrent_path, ex,
torrent_fname,
copy_torrent_path,
ex,
)
@export

View File

@ -38,8 +38,12 @@ class OptionsDialog(object):
spin_ids = ['max_download_speed', 'max_upload_speed', 'stop_ratio']
spin_int_ids = ['max_upload_slots', 'max_connections']
chk_ids = [
'stop_at_ratio', 'remove_at_ratio', 'move_completed',
'add_paused', 'auto_managed', 'queue_to_top',
'stop_at_ratio',
'remove_at_ratio',
'move_completed',
'add_paused',
'auto_managed',
'queue_to_top',
]
def __init__(self):
@ -52,13 +56,15 @@ class OptionsDialog(object):
options = {}
self.builder = gtk.Builder()
self.builder.add_from_file(get_resource('autoadd_options.ui'))
self.builder.connect_signals({
'on_opts_add': self.on_add,
'on_opts_apply': self.on_apply,
'on_opts_cancel': self.on_cancel,
'on_options_dialog_close': self.on_cancel,
'on_toggle_toggled': self.on_toggle_toggled,
})
self.builder.connect_signals(
{
'on_opts_add': self.on_add,
'on_opts_apply': self.on_apply,
'on_opts_cancel': self.on_cancel,
'on_options_dialog_close': self.on_cancel,
'on_toggle_toggled': self.on_toggle_toggled,
}
)
self.dialog = self.builder.get_object('options_dialog')
self.dialog.set_transient_for(component.get('Preferences').pref_dialog)
@ -79,23 +85,21 @@ class OptionsDialog(object):
def load_options(self, options):
self.builder.get_object('enabled').set_active(options.get('enabled', True))
self.builder.get_object('append_extension_toggle').set_active(
options.get('append_extension_toggle', False),
options.get('append_extension_toggle', False)
)
self.builder.get_object('append_extension').set_text(
options.get('append_extension', '.added'),
options.get('append_extension', '.added')
)
self.builder.get_object('download_location_toggle').set_active(
options.get('download_location_toggle', False),
options.get('download_location_toggle', False)
)
self.builder.get_object('copy_torrent_toggle').set_active(
options.get('copy_torrent_toggle', False),
options.get('copy_torrent_toggle', False)
)
self.builder.get_object('delete_copy_torrent_toggle').set_active(
options.get('delete_copy_torrent_toggle', False),
)
self.builder.get_object('seed_mode').set_active(
options.get('seed_mode', False),
options.get('delete_copy_torrent_toggle', False)
)
self.builder.get_object('seed_mode').set_active(options.get('seed_mode', False))
self.accounts.clear()
self.labels.clear()
combobox = self.builder.get_object('OwnerCombobox')
@ -108,14 +112,20 @@ class OptionsDialog(object):
label_widget.child.set_text(options.get('label', ''))
label_widget.set_model(self.labels)
label_widget.set_entry_text_column(0)
self.builder.get_object('label_toggle').set_active(options.get('label_toggle', False))
self.builder.get_object('label_toggle').set_active(
options.get('label_toggle', False)
)
for spin_id in self.spin_ids + self.spin_int_ids:
self.builder.get_object(spin_id).set_value(options.get(spin_id, 0))
self.builder.get_object(spin_id + '_toggle').set_active(options.get(spin_id + '_toggle', False))
self.builder.get_object(spin_id + '_toggle').set_active(
options.get(spin_id + '_toggle', False)
)
for chk_id in self.chk_ids:
self.builder.get_object(chk_id).set_active(bool(options.get(chk_id, True)))
self.builder.get_object(chk_id + '_toggle').set_active(options.get(chk_id + '_toggle', False))
self.builder.get_object(chk_id + '_toggle').set_active(
options.get(chk_id + '_toggle', False)
)
if not options.get('add_paused', True):
self.builder.get_object('isnt_add_paused').set_active(True)
if not options.get('queue_to_top', True):
@ -123,18 +133,20 @@ class OptionsDialog(object):
if not options.get('auto_managed', True):
self.builder.get_object('isnt_auto_managed').set_active(True)
for field in [
'move_completed_path', 'path', 'download_location',
'move_completed_path',
'path',
'download_location',
'copy_torrent',
]:
if client.is_localhost():
self.builder.get_object(field + '_chooser').set_current_folder(
options.get(field, os.path.expanduser('~')),
options.get(field, os.path.expanduser('~'))
)
self.builder.get_object(field + '_chooser').show()
self.builder.get_object(field + '_entry').hide()
else:
self.builder.get_object(field + '_entry').set_text(
options.get(field, ''),
options.get(field, '')
)
self.builder.get_object(field + '_entry').show()
self.builder.get_object(field + '_chooser').hide()
@ -143,36 +155,44 @@ class OptionsDialog(object):
def on_core_config(config):
if client.is_localhost():
self.builder.get_object('download_location_chooser').set_current_folder(
options.get('download_location', config['download_location']),
options.get('download_location', config['download_location'])
)
if options.get('move_completed_toggle', config['move_completed']):
self.builder.get_object('move_completed_toggle').set_active(True)
self.builder.get_object('move_completed_path_chooser').set_current_folder(
options.get('move_completed_path', config['move_completed_path']),
self.builder.get_object(
'move_completed_path_chooser'
).set_current_folder(
options.get(
'move_completed_path', config['move_completed_path']
)
)
if options.get('copy_torrent_toggle', config['copy_torrent_file']):
self.builder.get_object('copy_torrent_toggle').set_active(True)
self.builder.get_object('copy_torrent_chooser').set_current_folder(
options.get('copy_torrent', config['torrentfiles_location']),
options.get('copy_torrent', config['torrentfiles_location'])
)
else:
self.builder.get_object('download_location_entry').set_text(
options.get('download_location', config['download_location']),
options.get('download_location', config['download_location'])
)
if options.get('move_completed_toggle', config['move_completed']):
self.builder.get_object('move_completed_toggle').set_active(
options.get('move_completed_toggle', False),
options.get('move_completed_toggle', False)
)
self.builder.get_object('move_completed_path_entry').set_text(
options.get('move_completed_path', config['move_completed_path']),
options.get(
'move_completed_path', config['move_completed_path']
)
)
if options.get('copy_torrent_toggle', config['copy_torrent_file']):
self.builder.get_object('copy_torrent_toggle').set_active(True)
self.builder.get_object('copy_torrent_entry').set_text(
options.get('copy_torrent', config['torrentfiles_location']),
options.get('copy_torrent', config['torrentfiles_location'])
)
if options.get('delete_copy_torrent_toggle', config['del_copy_torrent_file']):
if options.get(
'delete_copy_torrent_toggle', config['del_copy_torrent_file']
):
self.builder.get_object('delete_copy_torrent_toggle').set_active(True)
if not options:
@ -183,9 +203,7 @@ class OptionsDialog(object):
selected_iter = None
for account in accounts:
acc_iter = self.accounts.append()
self.accounts.set_value(
acc_iter, 0, account['username'],
)
self.accounts.set_value(acc_iter, 0, account['username'])
if account['username'] == owner:
selected_iter = acc_iter
self.builder.get_object('OwnerCombobox').set_active_iter(selected_iter)
@ -219,7 +237,7 @@ class OptionsDialog(object):
client.core.get_enabled_plugins().addCallback(on_get_enabled_plugins)
if client.get_auth_level() == deluge.common.AUTH_LEVEL_ADMIN:
client.core.get_known_accounts().addCallback(
on_accounts, options.get('owner', client.get_auth_user()),
on_accounts, options.get('owner', client.get_auth_user())
).addErrback(on_accounts_failure)
else:
acc_iter = self.accounts.append()
@ -229,11 +247,19 @@ class OptionsDialog(object):
def set_sensitive(self):
maintoggles = [
'download_location', 'append_extension',
'move_completed', 'label', 'max_download_speed',
'max_upload_speed', 'max_connections',
'max_upload_slots', 'add_paused', 'auto_managed',
'stop_at_ratio', 'queue_to_top', 'copy_torrent',
'download_location',
'append_extension',
'move_completed',
'label',
'max_download_speed',
'max_upload_speed',
'max_connections',
'max_upload_slots',
'add_paused',
'auto_managed',
'stop_at_ratio',
'queue_to_top',
'copy_torrent',
]
for maintoggle in maintoggles:
self.on_toggle_toggled(self.builder.get_object(maintoggle + '_toggle'))
@ -249,9 +275,13 @@ class OptionsDialog(object):
elif toggle == 'copy_torrent':
self.builder.get_object('copy_torrent_entry').set_sensitive(isactive)
self.builder.get_object('copy_torrent_chooser').set_sensitive(isactive)
self.builder.get_object('delete_copy_torrent_toggle').set_sensitive(isactive)
self.builder.get_object('delete_copy_torrent_toggle').set_sensitive(
isactive
)
elif toggle == 'move_completed':
self.builder.get_object('move_completed_path_chooser').set_sensitive(isactive)
self.builder.get_object('move_completed_path_chooser').set_sensitive(
isactive
)
self.builder.get_object('move_completed_path_entry').set_sensitive(isactive)
self.builder.get_object('move_completed').set_active(isactive)
elif toggle == 'label':
@ -283,9 +313,9 @@ class OptionsDialog(object):
def on_apply(self, event=None):
try:
options = self.generate_opts()
client.autoadd.set_options(
str(self.watchdir_id), options,
).addCallbacks(self.on_added, self.on_error_show)
client.autoadd.set_options(str(self.watchdir_id), options).addCallbacks(
self.on_added, self.on_error_show
)
except IncompatibleOption as ex:
dialogs.ErrorDialog(_('Incompatible Option'), str(ex), self.dialog).run()
@ -314,54 +344,72 @@ class OptionsDialog(object):
if client.is_localhost():
options['path'] = self.builder.get_object('path_chooser').get_filename()
options['download_location'] = self.builder.get_object(
'download_location_chooser',
'download_location_chooser'
).get_filename()
options['move_completed_path'] = self.builder.get_object(
'move_completed_path_chooser',
'move_completed_path_chooser'
).get_filename()
options['copy_torrent'] = self.builder.get_object(
'copy_torrent_chooser',
'copy_torrent_chooser'
).get_filename()
else:
options['path'] = self.builder.get_object('path_entry').get_text()
options['download_location'] = self.builder.get_object(
'download_location_entry',
'download_location_entry'
).get_text()
options['move_completed_path'] = self.builder.get_object(
'move_completed_path_entry',
'move_completed_path_entry'
).get_text()
options['copy_torrent'] = self.builder.get_object(
'copy_torrent_entry',
'copy_torrent_entry'
).get_text()
options['label'] = self.builder.get_object('label').child.get_text().lower()
options['append_extension'] = self.builder.get_object('append_extension').get_text()
options['append_extension'] = self.builder.get_object(
'append_extension'
).get_text()
options['owner'] = self.accounts[
self.builder.get_object('OwnerCombobox').get_active()
][0]
for key in [
'append_extension_toggle', 'download_location_toggle',
'label_toggle', 'copy_torrent_toggle',
'delete_copy_torrent_toggle', 'seed_mode',
'append_extension_toggle',
'download_location_toggle',
'label_toggle',
'copy_torrent_toggle',
'delete_copy_torrent_toggle',
'seed_mode',
]:
options[key] = self.builder.get_object(key).get_active()
for spin_id in self.spin_ids:
options[spin_id] = self.builder.get_object(spin_id).get_value()
options[spin_id + '_toggle'] = self.builder.get_object(spin_id + '_toggle').get_active()
options[spin_id + '_toggle'] = self.builder.get_object(
spin_id + '_toggle'
).get_active()
for spin_int_id in self.spin_int_ids:
options[spin_int_id] = self.builder.get_object(spin_int_id).get_value_as_int()
options[spin_int_id + '_toggle'] = self.builder.get_object(spin_int_id + '_toggle').get_active()
options[spin_int_id] = self.builder.get_object(
spin_int_id
).get_value_as_int()
options[spin_int_id + '_toggle'] = self.builder.get_object(
spin_int_id + '_toggle'
).get_active()
for chk_id in self.chk_ids:
options[chk_id] = self.builder.get_object(chk_id).get_active()
options[chk_id + '_toggle'] = self.builder.get_object(chk_id + '_toggle').get_active()
options[chk_id + '_toggle'] = self.builder.get_object(
chk_id + '_toggle'
).get_active()
if options['copy_torrent_toggle'] and options['path'] == options['copy_torrent']:
raise IncompatibleOption(_(
'"Watch Folder" directory and "Copy of .torrent'
' files to" directory cannot be the same!',
))
if (
options['copy_torrent_toggle']
and options['path'] == options['copy_torrent']
):
raise IncompatibleOption(
_(
'"Watch Folder" directory and "Copy of .torrent'
' files to" directory cannot be the same!'
)
)
return options
@ -374,13 +422,13 @@ class GtkUI(GtkPluginBase):
self.opts_dialog = OptionsDialog()
component.get('PluginManager').register_hook(
'on_apply_prefs', self.on_apply_prefs,
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').register_hook(
'on_show_prefs', self.on_show_prefs,
'on_show_prefs', self.on_show_prefs
)
client.register_event_handler(
'AutoaddOptionsChangedEvent', self.on_options_changed_event,
'AutoaddOptionsChangedEvent', self.on_options_changed_event
)
self.watchdirs = {}
@ -403,31 +451,35 @@ class GtkUI(GtkPluginBase):
sw.add(self.treeView)
sw.show_all()
component.get('Preferences').add_page(
_('AutoAdd'), self.builder.get_object('prefs_box'),
_('AutoAdd'), self.builder.get_object('prefs_box')
)
def disable(self):
component.get('Preferences').remove_page(_('AutoAdd'))
component.get('PluginManager').deregister_hook(
'on_apply_prefs', self.on_apply_prefs,
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').deregister_hook(
'on_show_prefs', self.on_show_prefs,
'on_show_prefs', self.on_show_prefs
)
def create_model(self):
store = gtk.ListStore(str, bool, str, str)
for watchdir_id, watchdir in self.watchdirs.items():
store.append([
watchdir_id, watchdir['enabled'],
watchdir.get('owner', 'localclient'), watchdir['path'],
])
store.append(
[
watchdir_id,
watchdir['enabled'],
watchdir.get('owner', 'localclient'),
watchdir['path'],
]
)
return store
def create_columns(self, treeview):
renderer_toggle = gtk.CellRendererToggle()
column = gtk.TreeViewColumn(
_('Active'), renderer_toggle, activatable=1, active=1,
_('Active'), renderer_toggle, activatable=1, active=1
)
column.set_sort_column_id(1)
treeview.append_column(column)
@ -505,10 +557,14 @@ class GtkUI(GtkPluginBase):
self.watchdirs = watchdirs or {}
self.store.clear()
for watchdir_id, watchdir in self.watchdirs.items():
self.store.append([
watchdir_id, watchdir['enabled'],
watchdir.get('owner', 'localclient'), watchdir['path'],
])
self.store.append(
[
watchdir_id,
watchdir['enabled'],
watchdir.get('owner', 'localclient'),
watchdir['path'],
]
)
# Workaround for cached glade signal appearing when re-enabling plugin in same session
if self.builder.get_object('edit_button'):
# Disable the remove and edit buttons, because nothing in the store is selected

View File

@ -37,7 +37,6 @@ setup(
packages=find_packages(),
namespace_packages=['deluge', 'deluge.plugins'],
package_data=__pkg_data__,
entry_points="""
[deluge.plugin.core]
%s = deluge.plugins.%s:CorePlugin
@ -45,5 +44,6 @@ setup(
%s = deluge.plugins.%s:GtkUIPlugin
[deluge.plugin.web]
%s = deluge.plugins.%s:WebUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower()) * 3),
"""
% ((__plugin_name__, __plugin_name__.lower()) * 3),
)

View File

@ -15,6 +15,7 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
super(CorePlugin, self).__init__(plugin_name)
@ -22,6 +23,7 @@ class CorePlugin(PluginInitBase):
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
super(GtkUIPlugin, self).__init__(plugin_name)
@ -29,5 +31,6 @@ class GtkUIPlugin(PluginInitBase):
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
super(WebUIPlugin, self).__init__(plugin_name)

View File

@ -31,8 +31,10 @@ def raises_errors_as(error):
function to raise all exceptions as the specified error type.
"""
def decorator(func):
"""Returns a function which wraps the given func to raise all exceptions as error."""
@wraps(func)
def wrapper(self, *args, **kwargs):
"""Wraps the function in a try..except block and calls it with the specified args.
@ -46,7 +48,9 @@ def raises_errors_as(error):
except Exception:
(value, tb) = exc_info()[1:]
six.reraise(error, value, tb)
return wrapper
return decorator
@ -117,37 +121,37 @@ class IP(object):
def quadrants(self):
return (self.q1, self.q2, self.q3, self.q4)
# def next_ip(self):
# (q1, q2, q3, q4) = self.quadrants()
# if q4 >= 255:
# if q3 >= 255:
# if q2 >= 255:
# if q1 >= 255:
# raise BadIP(_('There is not a next IP address'))
# q1 += 1
# else:
# q2 += 1
# else:
# q3 += 1
# else:
# q4 += 1
# return IP(q1, q2, q3, q4)
#
# def previous_ip(self):
# (q1, q2, q3, q4) = self.quadrants()
# if q4 <= 1:
# if q3 <= 1:
# if q2 <= 1:
# if q1 <= 1:
# raise BadIP(_('There is not a previous IP address'))
# q1 -= 1
# else:
# q2 -= 1
# else:
# q3 -= 1
# else:
# q4 -= 1
# return IP(q1, q2, q3, q4)
# def next_ip(self):
# (q1, q2, q3, q4) = self.quadrants()
# if q4 >= 255:
# if q3 >= 255:
# if q2 >= 255:
# if q1 >= 255:
# raise BadIP(_('There is not a next IP address'))
# q1 += 1
# else:
# q2 += 1
# else:
# q3 += 1
# else:
# q4 += 1
# return IP(q1, q2, q3, q4)
#
# def previous_ip(self):
# (q1, q2, q3, q4) = self.quadrants()
# if q4 <= 1:
# if q3 <= 1:
# if q2 <= 1:
# if q1 <= 1:
# raise BadIP(_('There is not a previous IP address'))
# q1 -= 1
# else:
# q2 -= 1
# else:
# q3 -= 1
# else:
# q4 -= 1
# return IP(q1, q2, q3, q4)
def __lt__(self, other):
if isinstance(other, ''.__class__):
@ -166,5 +170,7 @@ class IP(object):
def __repr__(self):
return '<%s long=%s address="%s">' % (
self.__class__.__name__, self.long, self.address,
self.__class__.__name__,
self.long,
self.address,
)

View File

@ -76,11 +76,15 @@ class Core(CorePluginBase):
self.file_progress = 0.0
self.core = component.get('Core')
self.config = deluge.configmanager.ConfigManager('blocklist.conf', DEFAULT_PREFS)
self.config = deluge.configmanager.ConfigManager(
'blocklist.conf', DEFAULT_PREFS
)
if 'whitelisted' not in self.config:
self.config['whitelisted'] = []
self.reader = create_reader(self.config['list_type'], self.config['list_compression'])
self.reader = create_reader(
self.config['list_type'], self.config['list_compression']
)
if not isinstance(self.config['last_update'], float):
self.config.config['last_update'] = 0.0
@ -91,10 +95,15 @@ class Core(CorePluginBase):
if self.config['last_update']:
last_update = datetime.fromtimestamp(self.config['last_update'])
check_period = timedelta(days=self.config['check_after_days'])
if not self.config['last_update'] or last_update + check_period < datetime.now():
if (
not self.config['last_update']
or last_update + check_period < datetime.now()
):
update_now = True
else:
d = self.import_list(deluge.configmanager.get_config_dir('blocklist.cache'))
d = self.import_list(
deluge.configmanager.get_config_dir('blocklist.cache')
)
d.addCallbacks(self.on_import_complete, self.on_import_error)
if self.need_to_resume_session:
d.addBoth(self.resume_session)
@ -104,14 +113,14 @@ class Core(CorePluginBase):
self.update_timer = LoopingCall(self.check_import)
if self.config['check_after_days'] > 0:
self.update_timer.start(
self.config['check_after_days'] * 24 * 60 * 60, update_now,
self.config['check_after_days'] * 24 * 60 * 60, update_now
)
def disable(self):
self.config.save()
log.debug('Reset IP filter')
self.core.session.get_ip_filter().add_rule(
'0.0.0.0', '255.255.255.255', ALLOW_RANGE,
'0.0.0.0', '255.255.255.255', ALLOW_RANGE
)
log.debug('Blocklist: Plugin disabled')
@ -189,7 +198,7 @@ class Core(CorePluginBase):
try:
ip = IP.parse(ip)
self.blocklist.add_rule(
ip.address, ip.address, ALLOW_RANGE,
ip.address, ip.address, ALLOW_RANGE
)
saved.add(ip.address)
log.debug('Added %s to whitelisted', ip)
@ -217,13 +226,16 @@ class Core(CorePluginBase):
if self.config['last_update']:
last_update = datetime.fromtimestamp(self.config['last_update'])
check_period = timedelta(days=self.config['check_after_days'])
if not self.config['last_update'] or last_update + check_period < datetime.now():
if (
not self.config['last_update']
or last_update + check_period < datetime.now()
):
update_now = True
if self.update_timer.running:
self.update_timer.stop()
if self.config['check_after_days'] > 0:
self.update_timer.start(
self.config['check_after_days'] * 24 * 60 * 60, update_now,
self.config['check_after_days'] * 24 * 60 * 60, update_now
)
continue
self.config[key] = config[key]
@ -232,7 +244,7 @@ class Core(CorePluginBase):
log.debug(
'IP addresses were removed from the whitelist. Since we '
'do not know if they were blocked before. Re-import '
'current blocklist and re-add whitelisted.',
'current blocklist and re-add whitelisted.'
)
self.has_imported = False
d = self.import_list(deluge.configmanager.get_config_dir('blocklist.cache'))
@ -295,6 +307,7 @@ class Core(CorePluginBase):
Deferred: a Deferred which fires once the blocklist has been downloaded.
"""
def on_retrieve_data(data, current_length, total_length):
if total_length:
fp = current_length / total_length
@ -306,6 +319,7 @@ class Core(CorePluginBase):
self.file_progress = fp
import socket
socket.setdefaulttimeout(self.config['timeout'])
if not url:
@ -313,14 +327,18 @@ class Core(CorePluginBase):
headers = {}
if self.config['last_update'] and not self.force_download:
headers['If-Modified-Since'] = formatdate(self.config['last_update'], usegmt=True)
headers['If-Modified-Since'] = formatdate(
self.config['last_update'], usegmt=True
)
log.debug('Attempting to download blocklist %s', url)
log.debug('Sending headers: %s', headers)
self.is_downloading = True
return download_file(
url, deluge.configmanager.get_config_dir('blocklist.download'),
on_retrieve_data, headers,
url,
deluge.configmanager.get_config_dir('blocklist.download'),
on_retrieve_data,
headers,
)
def on_download_complete(self, blocklist):
@ -369,7 +387,8 @@ class Core(CorePluginBase):
if self.failed_attempts < self.config['try_times']:
log.debug(
'Try downloading blocklist again... (%s/%s)',
self.failed_attempts, self.config['try_times'],
self.failed_attempts,
self.config['try_times'],
)
self.failed_attempts += 1
d = self.download_list()
@ -430,7 +449,11 @@ class Core(CorePluginBase):
log.exception(failure)
log.debug('Importing using reader: %s', self.reader)
log.debug('Reader type: %s compression: %s', self.config['list_type'], self.config['list_compression'])
log.debug(
'Reader type: %s compression: %s',
self.config['list_type'],
self.config['list_compression'],
)
log.debug('Clearing current ip filtering')
# self.blocklist.add_rule('0.0.0.0', '255.255.255.255', ALLOW_RANGE)
d = threads.deferToThread(self.reader(blocklist).read, on_read_ip_range)
@ -508,13 +531,21 @@ class Core(CorePluginBase):
"""
self.config['list_compression'] = detect_compression(blocklist)
self.config['list_type'] = detect_format(blocklist, self.config['list_compression'])
log.debug('Auto-detected type: %s compression: %s', self.config['list_type'], self.config['list_compression'])
self.config['list_type'] = detect_format(
blocklist, self.config['list_compression']
)
log.debug(
'Auto-detected type: %s compression: %s',
self.config['list_type'],
self.config['list_compression'],
)
if not self.config['list_type']:
self.config['list_compression'] = ''
raise UnknownFormatError
else:
self.reader = create_reader(self.config['list_type'], self.config['list_compression'])
self.reader = create_reader(
self.config['list_type'], self.config['list_compression']
)
def pause_session(self):
self.need_to_resume_session = not self.core.session.is_paused()

View File

@ -17,25 +17,31 @@ import zipfile
def Zipped(reader): # NOQA: N802
"""Blocklist reader for zipped blocklists"""
def _open(self):
z = zipfile.ZipFile(self.file)
f = z.open(z.namelist()[0])
return f
reader.open = _open
return reader
def GZipped(reader): # NOQA: N802
"""Blocklist reader for gzipped blocklists"""
def _open(self):
return gzip.open(self.file)
reader.open = _open
return reader
def BZipped2(reader): # NOQA: N802
"""Blocklist reader for bzipped2 blocklists"""
def _open(self):
return bz2.BZ2File(self.file)
reader.open = _open
return reader

View File

@ -12,17 +12,9 @@ from __future__ import unicode_literals
from .decompressers import BZipped2, GZipped, Zipped
from .readers import EmuleReader, PeerGuardianReader, SafePeerReader
COMPRESSION_TYPES = {
'PK': 'Zip',
'\x1f\x8b': 'GZip',
'BZ': 'BZip2',
}
COMPRESSION_TYPES = {'PK': 'Zip', '\x1f\x8b': 'GZip', 'BZ': 'BZip2'}
DECOMPRESSERS = {
'Zip': Zipped,
'GZip': GZipped,
'BZip2': BZipped2,
}
DECOMPRESSERS = {'Zip': Zipped, 'GZip': GZipped, 'BZip2': BZipped2}
READERS = {
'Emule': EmuleReader,

View File

@ -67,9 +67,11 @@ class GtkUI(GtkPluginBase):
self.builder.get_object('image_up_to_date').hide()
self.status_item.set_text(
'Downloading %.2f%%' % (status['file_progress'] * 100),
'Downloading %.2f%%' % (status['file_progress'] * 100)
)
self.progress_bar.set_text(
'Downloading %.2f%%' % (status['file_progress'] * 100)
)
self.progress_bar.set_text('Downloading %.2f%%' % (status['file_progress'] * 100))
self.progress_bar.set_fraction(status['file_progress'])
self.progress_bar.show()
@ -79,9 +81,7 @@ class GtkUI(GtkPluginBase):
self.builder.get_object('button_force_download').set_sensitive(False)
self.builder.get_object('image_up_to_date').hide()
self.status_item.set_text(
'Importing ' + str(status['num_blocked']),
)
self.status_item.set_text('Importing ' + str(status['num_blocked']))
self.progress_bar.set_text('Importing %s' % (status['num_blocked']))
self.progress_bar.pulse()
self.progress_bar.show()
@ -99,15 +99,13 @@ class GtkUI(GtkPluginBase):
self.status_item.set_text('%(num_blocked)s/%(num_whited)s' % status)
self.builder.get_object('label_filesize').set_text(
deluge.common.fsize(status['file_size']),
deluge.common.fsize(status['file_size'])
)
self.builder.get_object('label_modified').set_text(
datetime.fromtimestamp(status['file_date']).strftime('%c'),
datetime.fromtimestamp(status['file_date']).strftime('%c')
)
self.builder.get_object('label_type').set_text(status['file_type'])
self.builder.get_object('label_url').set_text(
status['file_url'],
)
self.builder.get_object('label_url').set_text(status['file_url'])
client.blocklist.get_status().addCallback(_on_get_status)
@ -115,8 +113,12 @@ class GtkUI(GtkPluginBase):
def _on_get_config(config):
log.trace('Loaded config: %s', config)
self.builder.get_object('entry_url').set_text(config['url'])
self.builder.get_object('spin_check_days').set_value(config['check_after_days'])
self.builder.get_object('chk_import_on_start').set_active(config['load_on_start'])
self.builder.get_object('spin_check_days').set_value(
config['check_after_days']
)
self.builder.get_object('chk_import_on_start').set_active(
config['load_on_start']
)
self.populate_whitelist(config['whitelisted'])
client.blocklist.get_config().addCallback(_on_get_config)
@ -124,9 +126,15 @@ class GtkUI(GtkPluginBase):
def _on_apply_prefs(self):
config = {}
config['url'] = self.builder.get_object('entry_url').get_text().strip()
config['check_after_days'] = self.builder.get_object('spin_check_days').get_value_as_int()
config['load_on_start'] = self.builder.get_object('chk_import_on_start').get_active()
config['whitelisted'] = [ip[0] for ip in self.whitelist_model if ip[0] != 'IP HERE']
config['check_after_days'] = self.builder.get_object(
'spin_check_days'
).get_value_as_int()
config['load_on_start'] = self.builder.get_object(
'chk_import_on_start'
).get_active()
config['whitelisted'] = [
ip[0] for ip in self.whitelist_model if ip[0] != 'IP HERE'
]
client.blocklist.set_config(config)
def _on_button_check_download_clicked(self, widget):
@ -157,26 +165,28 @@ class GtkUI(GtkPluginBase):
# Create the whitelisted model
self.build_whitelist_model_treeview()
self.builder.connect_signals({
'on_button_check_download_clicked': self._on_button_check_download_clicked,
'on_button_force_download_clicked': self._on_button_force_download_clicked,
'on_whitelist_add_clicked': (
self.on_add_button_clicked,
self.whitelist_treeview,
),
'on_whitelist_remove_clicked': (
self.on_delete_button_clicked,
self.whitelist_treeview,
),
})
self.builder.connect_signals(
{
'on_button_check_download_clicked': self._on_button_check_download_clicked,
'on_button_force_download_clicked': self._on_button_force_download_clicked,
'on_whitelist_add_clicked': (
self.on_add_button_clicked,
self.whitelist_treeview,
),
'on_whitelist_remove_clicked': (
self.on_delete_button_clicked,
self.whitelist_treeview,
),
}
)
# Set button icons
self.builder.get_object('image_download').set_from_file(
common.get_resource('blocklist_download24.png'),
common.get_resource('blocklist_download24.png')
)
self.builder.get_object('image_import').set_from_file(
common.get_resource('blocklist_import24.png'),
common.get_resource('blocklist_import24.png')
)
# Update the preferences page with config values from the core
@ -184,15 +194,14 @@ class GtkUI(GtkPluginBase):
# Add the page to the preferences dialog
self.plugin.add_preferences_page(
_('Blocklist'),
self.builder.get_object('blocklist_prefs_box'),
_('Blocklist'), self.builder.get_object('blocklist_prefs_box')
)
def build_whitelist_model_treeview(self):
self.whitelist_treeview = self.builder.get_object('whitelist_treeview')
treeview_selection = self.whitelist_treeview.get_selection()
treeview_selection.connect(
'changed', self.on_whitelist_treeview_selection_changed,
'changed', self.on_whitelist_treeview_selection_changed
)
self.whitelist_model = gtk.ListStore(str, bool)
renderer = gtk.CellRendererText()
@ -213,21 +222,16 @@ class GtkUI(GtkPluginBase):
except common.BadIP as ex:
model.remove(model.get_iter_from_string(path_string))
from deluge.ui.gtkui import dialogs
d = dialogs.ErrorDialog(_('Bad IP address'), ex.message)
d.run()
def on_whitelist_treeview_selection_changed(self, selection):
model, selected_connection_iter = selection.get_selected()
if selected_connection_iter:
self.builder.get_object('whitelist_delete').set_property(
'sensitive',
True,
)
self.builder.get_object('whitelist_delete').set_property('sensitive', True)
else:
self.builder.get_object('whitelist_delete').set_property(
'sensitive',
False,
)
self.builder.get_object('whitelist_delete').set_property('sensitive', False)
def on_add_button_clicked(self, widget, treeview):
model = treeview.get_model()
@ -243,6 +247,4 @@ class GtkUI(GtkPluginBase):
def populate_whitelist(self, whitelist):
self.whitelist_model.clear()
for ip in whitelist:
self.whitelist_model.set(
self.whitelist_model.append(), 0, ip, 1, True,
)
self.whitelist_model.set(self.whitelist_model.append(), 0, ip, 1, True)

View File

@ -24,7 +24,6 @@ class PGException(Exception):
# Incrementally reads PeerGuardian blocklists v1 and v2.
# See http://wiki.phoenixlabs.org/wiki/P2B_Format
class PGReader(object):
def __init__(self, filename):
log.debug('PGReader loading: %s', filename)

View File

@ -25,6 +25,7 @@ class ReaderParseError(Exception):
class BaseReader(object):
"""Base reader for blocklist files"""
def __init__(self, _file):
"""Creates a new BaseReader given a file"""
self.file = _file
@ -60,8 +61,9 @@ class BaseReader(object):
if not self.is_ignored(line):
try:
(start, end) = self.parse(line)
if not re.match(r'^(\d{1,3}\.){4}$', start + '.') or \
not re.match(r'^(\d{1,3}\.){4}$', end + '.'):
if not re.match(r'^(\d{1,3}\.){4}$', start + '.') or not re.match(
r'^(\d{1,3}\.){4}$', end + '.'
):
valid = False
except Exception:
valid = False
@ -82,16 +84,19 @@ class BaseReader(object):
class EmuleReader(BaseReader):
"""Blocklist reader for emule style blocklists"""
def parse(self, line):
return line.strip().split(' , ')[0].split(' - ')
class SafePeerReader(BaseReader):
"""Blocklist reader for SafePeer style blocklists"""
def parse(self, line):
return line.strip().split(':')[-1].split('-')
class PeerGuardianReader(SafePeerReader):
"""Blocklist reader for PeerGuardian style blocklists"""
pass

View File

@ -39,5 +39,6 @@ setup(
%s = deluge.plugins.%s:GtkUIPlugin
[deluge.plugin.web]
%s = deluge.plugins.%s:WebUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower()) * 3),
"""
% ((__plugin_name__, __plugin_name__.lower()) * 3),
)

View File

@ -15,6 +15,7 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
super(CorePlugin, self).__init__(plugin_name)
@ -22,6 +23,7 @@ class CorePlugin(PluginInitBase):
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
super(GtkUIPlugin, self).__init__(plugin_name)
@ -29,5 +31,6 @@ class GtkUIPlugin(PluginInitBase):
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
super(WebUIPlugin, self).__init__(plugin_name)

View File

@ -25,9 +25,7 @@ from deluge.plugins.pluginbase import CorePluginBase
log = logging.getLogger(__name__)
DEFAULT_CONFIG = {
'commands': [],
}
DEFAULT_CONFIG = {'commands': []}
EXECUTE_ID = 0
EXECUTE_EVENT = 1
@ -44,6 +42,7 @@ class ExecuteCommandAddedEvent(DelugeEvent):
"""
Emitted when a new command is added.
"""
def __init__(self, command_id, event, command):
self._args = [command_id, event, command]
@ -52,6 +51,7 @@ class ExecuteCommandRemovedEvent(DelugeEvent):
"""
Emitted when a command is removed.
"""
def __init__(self, command_id):
self._args = [command_id]
@ -72,11 +72,15 @@ class Core(CorePluginBase):
def create_event_handler(event):
def event_handler(torrent_id, *arg):
self.execute_commands(torrent_id, event, *arg)
return event_handler
event_handler = create_event_handler(event)
event_manager.register_event_handler(EVENT_MAP[event], event_handler)
if event == 'removed':
event_manager.register_event_handler('PreTorrentRemovedEvent', self.on_preremoved)
event_manager.register_event_handler(
'PreTorrentRemovedEvent', self.on_preremoved
)
self.registered_events[event] = event_handler
log.debug('Execute core plugin enabled!')
@ -85,14 +89,20 @@ class Core(CorePluginBase):
# Get and store the torrent info before it is removed
torrent = component.get('TorrentManager').torrents[torrent_id]
info = torrent.get_status(['name', 'download_location'])
self.preremoved_cache[torrent_id] = [torrent_id, info['name'], info['download_location']]
self.preremoved_cache[torrent_id] = [
torrent_id,
info['name'],
info['download_location'],
]
def execute_commands(self, torrent_id, event, *arg):
if event == 'added' and arg[0]:
# No futher action as from_state (arg[0]) is True
return
elif event == 'removed':
torrent_id, torrent_name, download_location = self.preremoved_cache.pop(torrent_id)
torrent_id, torrent_name, download_location = self.preremoved_cache.pop(
torrent_id
)
else:
torrent = component.get('TorrentManager').torrents[torrent_id]
info = torrent.get_status(['name', 'download_location'])
@ -119,7 +129,8 @@ class Core(CorePluginBase):
command = os.path.expanduser(command)
cmd_args = [
torrent_id.encode('utf8'), torrent_name.encode('utf8'),
torrent_id.encode('utf8'),
torrent_name.encode('utf8'),
download_location.encode('utf8'),
]
if windows_check():
@ -146,7 +157,9 @@ class Core(CorePluginBase):
command_id = hashlib.sha1(str(time.time())).hexdigest()
self.config['commands'].append((command_id, event, command))
self.config.save()
component.get('EventManager').emit(ExecuteCommandAddedEvent(command_id, event, command))
component.get('EventManager').emit(
ExecuteCommandAddedEvent(command_id, event, command)
)
@export
def get_commands(self):
@ -157,7 +170,9 @@ class Core(CorePluginBase):
for command in self.config['commands']:
if command[EXECUTE_ID] == command_id:
self.config['commands'].remove(command)
component.get('EventManager').emit(ExecuteCommandRemovedEvent(command_id))
component.get('EventManager').emit(
ExecuteCommandRemovedEvent(command_id)
)
break
self.config.save()

View File

@ -55,14 +55,20 @@ class ExecutePreferences(object):
events.set_model(store)
events.set_active(0)
self.plugin.add_preferences_page(_('Execute'), self.builder.get_object('execute_box'))
self.plugin.add_preferences_page(
_('Execute'), self.builder.get_object('execute_box')
)
self.plugin.register_hook('on_show_prefs', self.load_commands)
self.plugin.register_hook('on_apply_prefs', self.on_apply_prefs)
self.load_commands()
client.register_event_handler('ExecuteCommandAddedEvent', self.on_command_added_event)
client.register_event_handler('ExecuteCommandRemovedEvent', self.on_command_removed_event)
client.register_event_handler(
'ExecuteCommandAddedEvent', self.on_command_added_event
)
client.register_event_handler(
'ExecuteCommandRemovedEvent', self.on_command_removed_event
)
def unload(self):
self.plugin.remove_preferences_page(_('Execute'))
@ -145,7 +151,6 @@ class ExecutePreferences(object):
class GtkUI(GtkPluginBase):
def enable(self):
self.plugin = component.get('PluginManager')
self.preferences = ExecutePreferences(self.plugin)

View File

@ -28,11 +28,9 @@ setup(
url=__url__,
license=__license__,
long_description=__long_description__,
packages=find_packages(),
namespace_packages=['deluge', 'deluge.plugins'],
package_data=__pkg_data__,
entry_points="""
[deluge.plugin.core]
%s = deluge.plugins.%s:CorePlugin
@ -40,5 +38,6 @@ setup(
%s = deluge.plugins.%s:GtkUIPlugin
[deluge.plugin.web]
%s = deluge.plugins.%s:WebUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower()) * 3),
"""
% ((__plugin_name__, __plugin_name__.lower()) * 3),
)

View File

@ -19,6 +19,7 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
super(CorePlugin, self).__init__(plugin_name)
@ -26,6 +27,7 @@ class CorePlugin(PluginInitBase):
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
super(GtkUIPlugin, self).__init__(plugin_name)
@ -33,5 +35,6 @@ class GtkUIPlugin(PluginInitBase):
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
super(WebUIPlugin, self).__init__(plugin_name)

View File

@ -28,10 +28,7 @@ from deluge.plugins.pluginbase import CorePluginBase
log = logging.getLogger(__name__)
DEFAULT_PREFS = {
'extract_path': '',
'use_name_folder': True,
}
DEFAULT_PREFS = {'extract_path': '', 'use_name_folder': True}
if windows_check():
win_7z_exes = [
@ -61,10 +58,7 @@ if windows_check():
# ".tar.bz2", ".tbz",
# ".tar.lzma", ".tlz",
# ".tar.xz", ".txz",
exts_7z = [
'.rar', '.zip', '.tar',
'.7z', '.xz', '.lzma',
]
exts_7z = ['.rar', '.zip', '.tar', '.7z', '.xz', '.lzma']
for win_7z_exe in win_7z_exes:
if which(win_7z_exe):
EXTRACT_COMMANDS = dict.fromkeys(exts_7z, [win_7z_exe, switch_7z])
@ -82,10 +76,14 @@ else:
'.rar': ['unrar', 'x -o+ -y'],
'.tar': ['tar', '-xf'],
'.zip': ['unzip', ''],
'.tar.gz': ['tar', '-xzf'], '.tgz': ['tar', '-xzf'],
'.tar.bz2': ['tar', '-xjf'], '.tbz': ['tar', '-xjf'],
'.tar.lzma': ['tar', '--lzma -xf'], '.tlz': ['tar', '--lzma -xf'],
'.tar.xz': ['tar', '--xz -xf'], '.txz': ['tar', '--xz -xf'],
'.tar.gz': ['tar', '-xzf'],
'.tgz': ['tar', '-xzf'],
'.tar.bz2': ['tar', '-xjf'],
'.tbz': ['tar', '-xjf'],
'.tar.lzma': ['tar', '--lzma -xf'],
'.tlz': ['tar', '--lzma -xf'],
'.tar.xz': ['tar', '--xz -xf'],
'.txz': ['tar', '--xz -xf'],
'.7z': ['7zr', 'x'],
}
# Test command exists and if not, remove.
@ -102,13 +100,21 @@ if not EXTRACT_COMMANDS:
class Core(CorePluginBase):
def enable(self):
self.config = deluge.configmanager.ConfigManager('extractor.conf', DEFAULT_PREFS)
self.config = deluge.configmanager.ConfigManager(
'extractor.conf', DEFAULT_PREFS
)
if not self.config['extract_path']:
self.config['extract_path'] = deluge.configmanager.ConfigManager('core.conf')['download_location']
component.get('EventManager').register_event_handler('TorrentFinishedEvent', self._on_torrent_finished)
self.config['extract_path'] = deluge.configmanager.ConfigManager(
'core.conf'
)['download_location']
component.get('EventManager').register_event_handler(
'TorrentFinishedEvent', self._on_torrent_finished
)
def disable(self):
component.get('EventManager').deregister_event_handler('TorrentFinishedEvent', self._on_torrent_finished)
component.get('EventManager').deregister_event_handler(
'TorrentFinishedEvent', self._on_torrent_finished
)
def update(self):
pass
@ -136,7 +142,9 @@ class Core(CorePluginBase):
continue
cmd = EXTRACT_COMMANDS[file_ext]
fpath = os.path.join(tid_status['download_location'], os.path.normpath(f['path']))
fpath = os.path.join(
tid_status['download_location'], os.path.normpath(f['path'])
)
dest = os.path.normpath(self.config['extract_path'])
if self.config['use_name_folder']:
dest = os.path.join(dest, tid_status['name'])
@ -153,11 +161,22 @@ class Core(CorePluginBase):
if not result[2]:
log.info('Extract successful: %s (%s)', fpath, torrent_id)
else:
log.error('Extract failed: %s (%s) %s', fpath, torrent_id, result[1])
log.error(
'Extract failed: %s (%s) %s', fpath, torrent_id, result[1]
)
# Run the command and add callback.
log.debug('Extracting %s from %s with %s %s to %s', fpath, torrent_id, cmd[0], cmd[1], dest)
d = getProcessOutputAndValue(cmd[0], cmd[1].split() + [str(fpath)], os.environ, str(dest))
log.debug(
'Extracting %s from %s with %s %s to %s',
fpath,
torrent_id,
cmd[0],
cmd[1],
dest,
)
d = getProcessOutputAndValue(
cmd[0], cmd[1].split() + [str(fpath)], os.environ, str(dest)
)
d.addCallback(on_extract, torrent_id, fpath)
@export

View File

@ -31,15 +31,25 @@ class GtkUI(GtkPluginBase):
self.builder = gtk.Builder()
self.builder.add_from_file(get_resource('extractor_prefs.ui'))
component.get('Preferences').add_page(_('Extractor'), self.builder.get_object('extractor_prefs_box'))
component.get('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs)
component.get('PluginManager').register_hook('on_show_prefs', self.on_show_prefs)
component.get('Preferences').add_page(
_('Extractor'), self.builder.get_object('extractor_prefs_box')
)
component.get('PluginManager').register_hook(
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').register_hook(
'on_show_prefs', self.on_show_prefs
)
self.on_show_prefs()
def disable(self):
component.get('Preferences').remove_page(_('Extractor'))
component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs)
component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs)
component.get('PluginManager').deregister_hook(
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').deregister_hook(
'on_show_prefs', self.on_show_prefs
)
del self.builder
def on_apply_prefs(self):
@ -66,10 +76,14 @@ class GtkUI(GtkPluginBase):
def on_get_config(config):
if client.is_localhost():
self.builder.get_object('folderchooser_path').set_current_folder(config['extract_path'])
self.builder.get_object('folderchooser_path').set_current_folder(
config['extract_path']
)
else:
self.builder.get_object('entry_path').set_text(config['extract_path'])
self.builder.get_object('chk_use_name').set_active(config['use_name_folder'])
self.builder.get_object('chk_use_name').set_active(
config['use_name_folder']
)
client.extractor.get_config().addCallback(on_get_config)

View File

@ -41,11 +41,9 @@ setup(
url=__url__,
license=__license__,
long_description=__long_description__ if __long_description__ else __description__,
packages=find_packages(),
namespace_packages=['deluge', 'deluge.plugins'],
package_data=__pkg_data__,
entry_points="""
[deluge.plugin.core]
%s = deluge.plugins.%s:CorePlugin
@ -53,5 +51,6 @@ setup(
%s = deluge.plugins.%s:GtkUIPlugin
[deluge.plugin.web]
%s = deluge.plugins.%s:WebUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower()) * 3),
"""
% ((__plugin_name__, __plugin_name__.lower()) * 3),
)

View File

@ -19,6 +19,7 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
super(CorePlugin, self).__init__(plugin_name)
@ -26,6 +27,7 @@ class CorePlugin(PluginInitBase):
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
super(GtkUIPlugin, self).__init__(plugin_name)
@ -33,5 +35,6 @@ class GtkUIPlugin(PluginInitBase):
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
super(WebUIPlugin, self).__init__(plugin_name)

View File

@ -73,6 +73,7 @@ class Core(CorePluginBase):
self.labels = {label_id:label_options_dict}
self.torrent_labels = {torrent_id:label_id}
"""
def enable(self):
log.info('*** Start Label plugin ***')
self.plugin = component.get('CorePluginManager')
@ -90,19 +91,29 @@ class Core(CorePluginBase):
self.clean_initial_config()
component.get('EventManager').register_event_handler('TorrentAddedEvent', self.post_torrent_add)
component.get('EventManager').register_event_handler('TorrentRemovedEvent', self.post_torrent_remove)
component.get('EventManager').register_event_handler(
'TorrentAddedEvent', self.post_torrent_add
)
component.get('EventManager').register_event_handler(
'TorrentRemovedEvent', self.post_torrent_remove
)
# register tree:
component.get('FilterManager').register_tree_field('label', self.init_filter_dict)
component.get('FilterManager').register_tree_field(
'label', self.init_filter_dict
)
log.debug('Label plugin enabled..')
def disable(self):
self.plugin.deregister_status_field('label')
component.get('FilterManager').deregister_tree_field('label')
component.get('EventManager').deregister_event_handler('TorrentAddedEvent', self.post_torrent_add)
component.get('EventManager').deregister_event_handler('TorrentRemovedEvent', self.post_torrent_remove)
component.get('EventManager').deregister_event_handler(
'TorrentAddedEvent', self.post_torrent_add
)
component.get('EventManager').deregister_event_handler(
'TorrentRemovedEvent', self.post_torrent_remove
)
def update(self):
pass
@ -169,7 +180,9 @@ class Core(CorePluginBase):
see label_set_options for more options.
"""
label_id = label_id.lower()
check_input(RE_VALID.match(label_id), _('Invalid label, valid characters:[a-z0-9_-]'))
check_input(
RE_VALID.match(label_id), _('Invalid label, valid characters:[a-z0-9_-]')
)
check_input(label_id, _('Empty Label'))
check_input(not (label_id in self.labels), _('Label already exists'))
@ -209,7 +222,7 @@ class Core(CorePluginBase):
{
'move_completed': options['move_completed'],
'move_completed_path': options['move_completed_path'],
},
}
)
def _unset_torrent_options(self, torrent_id, label_id):
@ -217,11 +230,21 @@ class Core(CorePluginBase):
torrent = self.torrents[torrent_id]
if options['apply_max']:
torrent.set_max_download_speed(self.core_cfg.config['max_download_speed_per_torrent'])
torrent.set_max_upload_speed(self.core_cfg.config['max_upload_speed_per_torrent'])
torrent.set_max_connections(self.core_cfg.config['max_connections_per_torrent'])
torrent.set_max_upload_slots(self.core_cfg.config['max_upload_slots_per_torrent'])
torrent.set_prioritize_first_last_pieces(self.core_cfg.config['prioritize_first_last_pieces'])
torrent.set_max_download_speed(
self.core_cfg.config['max_download_speed_per_torrent']
)
torrent.set_max_upload_speed(
self.core_cfg.config['max_upload_speed_per_torrent']
)
torrent.set_max_connections(
self.core_cfg.config['max_connections_per_torrent']
)
torrent.set_max_upload_slots(
self.core_cfg.config['max_upload_slots_per_torrent']
)
torrent.set_prioritize_first_last_pieces(
self.core_cfg.config['prioritize_first_last_pieces']
)
if options['apply_queue']:
torrent.set_auto_managed(self.core_cfg.config['auto_managed'])
@ -234,7 +257,7 @@ class Core(CorePluginBase):
{
'move_completed': self.core_cfg.config['move_completed'],
'move_completed_path': self.core_cfg.config['move_completed_path'],
},
}
)
def _has_auto_match(self, torrent, label_options):
@ -311,8 +334,7 @@ class Core(CorePluginBase):
def get_config(self):
"""see : label_set_config"""
return {
key: self.config[key]
for key in CORE_OPTIONS if key in self.config.config
key: self.config[key] for key in CORE_OPTIONS if key in self.config.config
}
@export

View File

@ -33,7 +33,9 @@ class LabelConfig(object):
builder = Builder()
builder.add_from_file(get_resource('label_pref.ui'))
self.plugin.add_preferences_page(_('Label'), builder.get_object('label_prefs_box'))
self.plugin.add_preferences_page(
_('Label'), builder.get_object('label_prefs_box')
)
self.plugin.register_hook('on_show_prefs', self.load_settings)
self.plugin.register_hook('on_apply_prefs', self.on_apply_prefs)

View File

@ -80,7 +80,7 @@ class LabelSidebarMenu(object):
for item in self.items:
item.show()
# default items
sensitive = ((label not in (NO_LABEL, None, '', 'All')) and (cat != 'cat'))
sensitive = (label not in (NO_LABEL, None, '', 'All')) and (cat != 'cat')
for item in self.items:
item.set_sensitive(sensitive)
@ -127,13 +127,28 @@ class OptionsDialog(object):
spin_ids = ['max_download_speed', 'max_upload_speed', 'stop_ratio']
spin_int_ids = ['max_upload_slots', 'max_connections']
chk_ids = [
'apply_max', 'apply_queue', 'stop_at_ratio', 'apply_queue', 'remove_at_ratio',
'apply_move_completed', 'move_completed', 'is_auto_managed', 'auto_add',
'apply_max',
'apply_queue',
'stop_at_ratio',
'apply_queue',
'remove_at_ratio',
'apply_move_completed',
'move_completed',
'is_auto_managed',
'auto_add',
]
# list of tuples, because order matters when nesting.
sensitive_groups = [
('apply_max', ['max_download_speed', 'max_upload_speed', 'max_upload_slots', 'max_connections']),
(
'apply_max',
[
'max_download_speed',
'max_upload_speed',
'max_upload_slots',
'max_connections',
],
),
('apply_queue', ['is_auto_managed', 'stop_at_ratio']),
('stop_at_ratio', ['remove_at_ratio', 'stop_ratio']), # nested
('apply_move_completed', ['move_completed']),
@ -152,7 +167,9 @@ class OptionsDialog(object):
self.dialog.set_transient_for(component.get('MainWindow').window)
self.builder.connect_signals(self)
# Show the label name in the header label
self.builder.get_object('label_header').set_markup('<b>%s:</b> %s' % (_('Label Options'), self.label))
self.builder.get_object('label_header').set_markup(
'<b>%s:</b> %s' % (_('Label Options'), self.label)
)
for chk_id, group in self.sensitive_groups:
chk = self.builder.get_object(chk_id)
@ -171,15 +188,21 @@ class OptionsDialog(object):
self.builder.get_object(chk_id).set_active(bool(options[chk_id]))
if client.is_localhost():
self.builder.get_object('move_completed_path').set_filename(options['move_completed_path'])
self.builder.get_object('move_completed_path').set_filename(
options['move_completed_path']
)
self.builder.get_object('move_completed_path').show()
self.builder.get_object('move_completed_path_entry').hide()
else:
self.builder.get_object('move_completed_path_entry').set_text(options['move_completed_path'])
self.builder.get_object('move_completed_path_entry').set_text(
options['move_completed_path']
)
self.builder.get_object('move_completed_path_entry').show()
self.builder.get_object('move_completed_path').hide()
self.builder.get_object('auto_add_trackers').get_buffer().set_text('\n'.join(options['auto_add_trackers']))
self.builder.get_object('auto_add_trackers').get_buffer().set_text(
'\n'.join(options['auto_add_trackers'])
)
self.apply_sensitivity()
@ -190,18 +213,32 @@ class OptionsDialog(object):
for spin_id in self.spin_ids:
options[spin_id] = self.builder.get_object(spin_id).get_value()
for spin_int_id in self.spin_int_ids:
options[spin_int_id] = self.builder.get_object(spin_int_id).get_value_as_int()
options[spin_int_id] = self.builder.get_object(
spin_int_id
).get_value_as_int()
for chk_id in self.chk_ids:
options[chk_id] = self.builder.get_object(chk_id).get_active()
if client.is_localhost():
options['move_completed_path'] = self.builder.get_object('move_completed_path').get_filename()
options['move_completed_path'] = self.builder.get_object(
'move_completed_path'
).get_filename()
else:
options['move_completed_path'] = self.builder.get_object('move_completed_path_entry').get_text()
options['move_completed_path'] = self.builder.get_object(
'move_completed_path_entry'
).get_text()
buff = self.builder.get_object('auto_add_trackers').get_buffer() # sometimes I hate gtk...
tracker_lst = buff.get_text(buff.get_start_iter(), buff.get_end_iter()).strip().split('\n')
options['auto_add_trackers'] = [x for x in tracker_lst if x] # filter out empty lines.
buff = self.builder.get_object(
'auto_add_trackers'
).get_buffer() # sometimes I hate gtk...
tracker_lst = (
buff.get_text(buff.get_start_iter(), buff.get_end_iter())
.strip()
.split('\n')
)
options['auto_add_trackers'] = [
x for x in tracker_lst if x
] # filter out empty lines.
log.debug(options)
client.label.set_options(self.label, options)

View File

@ -32,11 +32,9 @@ setup(
url=__url__,
license=__license__,
long_description=__long_description__,
packages=find_packages(),
namespace_packages=['deluge', 'deluge.plugins'],
package_data=__pkg_data__,
entry_points="""
[deluge.plugin.core]
%s = deluge.plugins.%s:CorePlugin
@ -44,5 +42,6 @@ setup(
%s = deluge.plugins.%s:GtkUIPlugin
[deluge.plugin.web]
%s = deluge.plugins.%s:WebUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower()) * 3),
"""
% ((__plugin_name__, __plugin_name__.lower()) * 3),
)

View File

@ -20,6 +20,7 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
super(CorePlugin, self).__init__(plugin_name)
@ -27,6 +28,7 @@ class CorePlugin(PluginInitBase):
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
super(GtkUIPlugin, self).__init__(plugin_name)
@ -34,5 +36,6 @@ class GtkUIPlugin(PluginInitBase):
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
super(WebUIPlugin, self).__init__(plugin_name)

View File

@ -27,18 +27,14 @@ log = logging.getLogger(__name__)
def get_resource(filename):
return resource_filename('deluge.plugins.notifications', os.path.join('data', filename))
return resource_filename(
'deluge.plugins.notifications', os.path.join('data', filename)
)
class CustomNotifications(object):
def __init__(self, plugin_name=None):
self.custom_notifications = {
'email': {},
'popup': {},
'blink': {},
'sound': {},
}
self.custom_notifications = {'email': {}, 'popup': {}, 'blink': {}, 'sound': {}}
def enable(self):
pass
@ -50,7 +46,13 @@ class CustomNotifications(object):
self._deregister_custom_provider(kind, eventtype)
def _handle_custom_providers(self, kind, eventtype, *args, **kwargs):
log.debug('Calling CORE custom %s providers for %s: %s %s', kind, eventtype, args, kwargs)
log.debug(
'Calling CORE custom %s providers for %s: %s %s',
kind,
eventtype,
args,
kwargs,
)
if eventtype in self.config['subscriptions'][kind]:
wrapper, handler = self.custom_notifications[kind][eventtype]
log.debug('Found handler for kind %s: %s', kind, handler)
@ -65,17 +67,18 @@ class CustomNotifications(object):
if not self._handled_eventtype(eventtype, handler):
return defer.succeed('Event not handled')
if eventtype not in self.custom_notifications:
def wrapper(*args, **kwargs):
return self._handle_custom_providers(kind, eventtype, *args, **kwargs)
self.custom_notifications[kind][eventtype] = (wrapper, handler)
else:
wrapper, handler = self.custom_notifications[kind][eventtype]
try:
component.get('EventManager').register_event_handler(
eventtype, wrapper,
)
component.get('EventManager').register_event_handler(eventtype, wrapper)
except KeyError:
from deluge.ui.client import client
client.register_event_handler(eventtype, wrapper)
def _deregister_custom_provider(self, kind, eventtype):
@ -83,10 +86,11 @@ class CustomNotifications(object):
wrapper, handler = self.custom_notifications[kind][eventtype]
try:
component.get('EventManager').deregister_event_handler(
eventtype, wrapper,
eventtype, wrapper
)
except KeyError:
from deluge.ui.client import client
client.deregister_event_handler(eventtype, wrapper)
self.custom_notifications[kind].pop(eventtype)
except KeyError:
@ -101,7 +105,7 @@ class CustomNotifications(object):
return True
log.error(
'You cannot register custom notification providers '
'for built-in event types.',
'for built-in event types.'
)
return False
return True

View File

@ -40,22 +40,18 @@ DEFAULT_PREFS = {
'smtp_tls': False, # SSL or TLS
'smtp_recipients': [],
# Subscriptions
'subscriptions': {
'email': [],
},
'subscriptions': {'email': []},
}
class CoreNotifications(CustomNotifications):
def __init__(self, plugin_name=None):
CustomNotifications.__init__(self, plugin_name)
def enable(self):
CustomNotifications.enable(self)
self.register_custom_email_notification(
'TorrentFinishedEvent',
self._on_torrent_finished_event,
'TorrentFinishedEvent', self._on_torrent_finished_event
)
def disable(self):
@ -80,8 +76,7 @@ class CoreNotifications(CustomNotifications):
return defer.succeed('SMTP notification not enabled.')
subject, message = result
log.debug(
'Spawning new thread to send email with subject: %s: %s',
subject, message,
'Spawning new thread to send email with subject: %s: %s', subject, message
)
# Spawn thread because we don't want Deluge to lock up while we send the
# email.
@ -109,20 +104,25 @@ class CoreNotifications(CustomNotifications):
'smtp_recipients': to_addrs_str,
'date': formatdate(),
}
headers = """\
headers = (
"""\
From: %(smtp_from)s
To: %(smtp_recipients)s
Subject: %(subject)s
Date: %(date)s
""" % headers_dict
"""
% headers_dict
)
message = '\r\n'.join((headers + message).splitlines())
try:
# Python 2.6
server = smtplib.SMTP(self.config['smtp_host'], self.config['smtp_port'], timeout=60)
server = smtplib.SMTP(
self.config['smtp_host'], self.config['smtp_port'], timeout=60
)
except Exception as ex:
err_msg = _('There was an error sending the notification email: %s') % ex
log.error(err_msg)
@ -154,7 +154,9 @@ Date: %(date)s
try:
server.sendmail(self.config['smtp_from'], to_addrs, message)
except smtplib.SMTPException as ex:
err_msg = _('There was an error sending the notification email: %s') % ex
err_msg = (
_('There was an error sending the notification email: %s') % ex
)
log.error(err_msg)
return ex
finally:
@ -162,6 +164,7 @@ Date: %(date)s
# avoid false failure detection when the server closes
# the SMTP connection with TLS enabled
import socket
try:
server.quit()
except socket.sslerror:
@ -176,13 +179,16 @@ Date: %(date)s
torrent_status = torrent.get_status({})
# Email
subject = _('Finished Torrent "%(name)s"') % torrent_status
message = _(
'This email is to inform you that Deluge has finished '
'downloading "%(name)s", which includes %(num_files)i files.'
'\nTo stop receiving these alerts, simply turn off email '
"notification in Deluge's preferences.\n\n"
'Thank you,\nDeluge.',
) % torrent_status
message = (
_(
'This email is to inform you that Deluge has finished '
'downloading "%(name)s", which includes %(num_files)i files.'
'\nTo stop receiving these alerts, simply turn off email '
"notification in Deluge's preferences.\n\n"
'Thank you,\nDeluge.'
)
% torrent_status
)
return subject, message
# d = defer.maybeDeferred(self.handle_custom_email_notification,
@ -201,7 +207,7 @@ class Core(CorePluginBase, CoreNotifications):
def enable(self):
CoreNotifications.enable(self)
self.config = deluge.configmanager.ConfigManager(
'notifications-core.conf', DEFAULT_PREFS,
'notifications-core.conf', DEFAULT_PREFS
)
log.debug('ENABLING CORE NOTIFICATIONS')

View File

@ -34,12 +34,14 @@ log = logging.getLogger(__name__)
try:
import pygame
SOUND_AVAILABLE = True
except ImportError:
SOUND_AVAILABLE = False
try:
import pynotify
POPUP_AVAILABLE = True
if deluge.common.windows_check():
POPUP_AVAILABLE = False
@ -59,36 +61,35 @@ DEFAULT_PREFS = {
'sound_path': '',
'custom_sounds': {},
# Subscriptions
'subscriptions': {
'popup': [],
'blink': [],
'sound': [],
},
'subscriptions': {'popup': [], 'blink': [], 'sound': []},
}
RECIPIENT_FIELD, RECIPIENT_EDIT = list(range(2))
(
SUB_EVENT, SUB_EVENT_DOC, SUB_NOT_EMAIL, SUB_NOT_POPUP, SUB_NOT_BLINK,
SUB_EVENT,
SUB_EVENT_DOC,
SUB_NOT_EMAIL,
SUB_NOT_POPUP,
SUB_NOT_BLINK,
SUB_NOT_SOUND,
) = list(range(6))
SND_EVENT, SND_EVENT_DOC, SND_NAME, SND_PATH = list(range(4))
class GtkUiNotifications(CustomNotifications):
def __init__(self, plugin_name=None):
CustomNotifications.__init__(self, plugin_name)
def enable(self):
CustomNotifications.enable(self)
self.register_custom_blink_notification(
'TorrentFinishedEvent', self._on_torrent_finished_event_blink,
'TorrentFinishedEvent', self._on_torrent_finished_event_blink
)
self.register_custom_sound_notification(
'TorrentFinishedEvent', self._on_torrent_finished_event_sound,
'TorrentFinishedEvent', self._on_torrent_finished_event_sound
)
self.register_custom_popup_notification(
'TorrentFinishedEvent', self._on_torrent_finished_event_popup,
'TorrentFinishedEvent', self._on_torrent_finished_event_popup
)
def disable(self):
@ -150,19 +151,19 @@ class GtkUiNotifications(CustomNotifications):
return defer.maybeDeferred(self.__blink)
return defer.succeed(
'Will not blink. The returned value from the custom '
'handler was: %s' % result,
'handler was: %s' % result
)
def handle_custom_sound_notification(self, result, eventtype):
if isinstance(result, ''.__class__):
if not result and eventtype in self.config['custom_sounds']:
return defer.maybeDeferred(
self.__play_sound, self.config['custom_sounds'][eventtype],
self.__play_sound, self.config['custom_sounds'][eventtype]
)
return defer.maybeDeferred(self.__play_sound, result)
return defer.succeed(
'Will not play sound. The returned value from the '
'custom handler was: %s' % result,
'custom handler was: %s' % result
)
def __blink(self):
@ -176,7 +177,9 @@ class GtkUiNotifications(CustomNotifications):
return defer.fail(_('pynotify is not installed'))
if pynotify.init('Deluge'):
icon = gtk.gdk.pixbuf_new_from_file_at_size(deluge.common.get_pixmap('deluge.svg'), 48, 48)
icon = gtk.gdk.pixbuf_new_from_file_at_size(
deluge.common.get_pixmap('deluge.svg'), 48, 48
)
self.note = pynotify.Notification(title, message)
self.note.set_icon_from_pixbuf(icon)
if not self.note.show():
@ -227,15 +230,17 @@ class GtkUiNotifications(CustomNotifications):
def _on_torrent_finished_event_got_torrent_status(self, torrent_status):
log.debug(
'Handler for TorrentFinishedEvent GTKUI called. '
'Got Torrent Status',
'Handler for TorrentFinishedEvent GTKUI called. ' 'Got Torrent Status'
)
title = _('Finished Torrent')
torrent_status['num_files'] = torrent_status['file_progress'].count(1.0)
message = _(
'The torrent "%(name)s" including %(num_files)i file(s) '
'has finished downloading.',
) % torrent_status
message = (
_(
'The torrent "%(name)s" including %(num_files)i file(s) '
'has finished downloading.'
)
% torrent_status
)
return title, message
@ -246,7 +251,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
def enable(self):
self.config = deluge.configmanager.ConfigManager(
'notifications-gtk.conf', DEFAULT_PREFS,
'notifications-gtk.conf', DEFAULT_PREFS
)
self.builder = gtk.Builder()
self.builder.add_from_file(get_resource('config.ui'))
@ -259,65 +264,52 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
self.build_notifications_model_populate_treeview()
client.notifications.get_handled_events().addCallback(
self.popuplate_what_needs_handled_events,
self.popuplate_what_needs_handled_events
)
self.builder.connect_signals({
'on_add_button_clicked': (
self.on_add_button_clicked,
self.recipients_treeview,
),
'on_delete_button_clicked': (
self.on_delete_button_clicked,
self.recipients_treeview,
),
'on_enabled_toggled': self.on_enabled_toggled,
'on_sound_enabled_toggled': self.on_sound_enabled_toggled,
'on_sounds_edit_button_clicked': self.on_sounds_edit_button_clicked,
'on_sounds_revert_button_clicked': self.on_sounds_revert_button_clicked,
'on_sound_path_update_preview': self.on_sound_path_update_preview,
})
self.builder.connect_signals(
{
'on_add_button_clicked': (
self.on_add_button_clicked,
self.recipients_treeview,
),
'on_delete_button_clicked': (
self.on_delete_button_clicked,
self.recipients_treeview,
),
'on_enabled_toggled': self.on_enabled_toggled,
'on_sound_enabled_toggled': self.on_sound_enabled_toggled,
'on_sounds_edit_button_clicked': self.on_sounds_edit_button_clicked,
'on_sounds_revert_button_clicked': self.on_sounds_revert_button_clicked,
'on_sound_path_update_preview': self.on_sound_path_update_preview,
}
)
component.get('Preferences').add_page(_('Notifications'), self.prefs)
component.get('PluginManager').register_hook(
'on_apply_prefs',
self.on_apply_prefs,
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').register_hook(
'on_show_prefs',
self.on_show_prefs,
'on_show_prefs', self.on_show_prefs
)
if not POPUP_AVAILABLE:
self.builder.get_object('popup_enabled').set_property(
'sensitive',
False,
)
self.builder.get_object('popup_enabled').set_property('sensitive', False)
if not SOUND_AVAILABLE:
# for widget_name in ('sound_enabled', 'sound_path', 'sounds_page', 'sounds_page_label'):
# self.builder.get_object(widget_name).set_property('sensitive', False)
self.builder.get_object('sound_enabled').set_property(
'sensitive',
False,
)
self.builder.get_object('sound_enabled').set_property('sensitive', False)
self.builder.get_object('sound_path').set_property('sensitive', False)
self.builder.get_object('sounds_page').set_property(
'sensitive',
False,
)
self.builder.get_object('sounds_page').set_property('sensitive', False)
self.builder.get_object('sounds_page_label').set_property(
'sensitive',
False,
'sensitive', False
)
self.systray = component.get('SystemTray')
if not hasattr(self.systray, 'tray'):
# Tray is not beeing used
self.builder.get_object('blink_enabled').set_property(
'sensitive',
False,
)
self.builder.get_object('blink_enabled').set_property('sensitive', False)
GtkUiNotifications.enable(self)
@ -325,12 +317,10 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
GtkUiNotifications.disable(self)
component.get('Preferences').remove_page(_('Notifications'))
component.get('PluginManager').deregister_hook(
'on_apply_prefs',
self.on_apply_prefs,
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').deregister_hook(
'on_show_prefs',
self.on_show_prefs,
'on_show_prefs', self.on_show_prefs
)
def build_recipients_model_populate_treeview(self):
@ -338,7 +328,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
self.recipients_treeview = self.builder.get_object('smtp_recipients')
treeview_selection = self.recipients_treeview.get_selection()
treeview_selection.connect(
'changed', self.on_recipients_treeview_selection_changed,
'changed', self.on_recipients_treeview_selection_changed
)
self.recipients_model = gtk.ListStore(str, bool)
@ -346,9 +336,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
renderer.connect('edited', self.on_cell_edited, self.recipients_model)
renderer.set_data('recipient', RECIPIENT_FIELD)
column = gtk.TreeViewColumn(
'Recipients', renderer,
text=RECIPIENT_FIELD,
editable=RECIPIENT_EDIT,
'Recipients', renderer, text=RECIPIENT_FIELD, editable=RECIPIENT_EDIT
)
column.set_expand(True)
self.recipients_treeview.append_column(column)
@ -358,9 +346,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
# Sound customisation treeview/model
self.sounds_treeview = self.builder.get_object('sounds_treeview')
sounds_selection = self.sounds_treeview.get_selection()
sounds_selection.connect(
'changed', self.on_sounds_treeview_selection_changed,
)
sounds_selection.connect('changed', self.on_sounds_treeview_selection_changed)
self.sounds_treeview.set_tooltip_column(SND_EVENT_DOC)
self.sounds_model = gtk.ListStore(str, str, str, str)
@ -395,7 +381,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
self.subscriptions_treeview = self.builder.get_object('subscriptions_treeview')
subscriptions_selection = self.subscriptions_treeview.get_selection()
subscriptions_selection.connect(
'changed', self.on_subscriptions_treeview_selection_changed,
'changed', self.on_subscriptions_treeview_selection_changed
)
self.subscriptions_treeview.set_tooltip_column(SUB_EVENT_DOC)
self.subscriptions_model = gtk.ListStore(str, str, bool, bool, bool, bool)
@ -441,7 +427,9 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
self.subscriptions_treeview.append_column(column)
self.subscriptions_treeview.set_model(self.subscriptions_model)
def popuplate_what_needs_handled_events(self, handled_events, email_subscriptions=None):
def popuplate_what_needs_handled_events(
self, handled_events, email_subscriptions=None
):
if email_subscriptions is None:
email_subscriptions = []
self.populate_subscriptions(handled_events, email_subscriptions)
@ -458,10 +446,14 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
if snd_path:
self.sounds_model.set(
self.sounds_model.append(),
SND_EVENT, event_name,
SND_EVENT_DOC, event_doc,
SND_NAME, basename(snd_path),
SND_PATH, snd_path,
SND_EVENT,
event_name,
SND_EVENT_DOC,
event_doc,
SND_NAME,
basename(snd_path),
SND_PATH,
snd_path,
)
def populate_subscriptions(self, handled_events, email_subscriptions=None):
@ -469,16 +461,22 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
email_subscriptions = []
subscriptions_dict = self.config['subscriptions']
self.subscriptions_model.clear()
# self.handled_events = handled_events
# self.handled_events = handled_events
for event_name, event_doc in handled_events:
self.subscriptions_model.set(
self.subscriptions_model.append(),
SUB_EVENT, event_name,
SUB_EVENT_DOC, event_doc,
SUB_NOT_EMAIL, event_name in email_subscriptions,
SUB_NOT_POPUP, event_name in subscriptions_dict['popup'],
SUB_NOT_BLINK, event_name in subscriptions_dict['blink'],
SUB_NOT_SOUND, event_name in subscriptions_dict['sound'],
SUB_EVENT,
event_name,
SUB_EVENT_DOC,
event_doc,
SUB_NOT_EMAIL,
event_name in email_subscriptions,
SUB_NOT_POPUP,
event_name in subscriptions_dict['popup'],
SUB_NOT_BLINK,
event_name in subscriptions_dict['blink'],
SUB_NOT_SOUND,
event_name in subscriptions_dict['sound'],
)
def on_apply_prefs(self):
@ -501,8 +499,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
old_sound_file = self.config['sound_path']
new_sound_file = self.builder.get_object('sound_path').get_filename()
log.debug(
'Old Default sound file: %s New one: %s',
old_sound_file, new_sound_file,
'Old Default sound file: %s New one: %s', old_sound_file, new_sound_file
)
custom_sounds = {}
for event_name, event_doc, filename, filepath in self.sounds_model:
@ -511,18 +508,20 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
continue
custom_sounds[event_name] = filepath
self.config.config.update({
'popup_enabled': self.builder.get_object('popup_enabled').get_active(),
'blink_enabled': self.builder.get_object('blink_enabled').get_active(),
'sound_enabled': self.builder.get_object('sound_enabled').get_active(),
'sound_path': new_sound_file,
'subscriptions': {
'popup': current_popup_subscriptions,
'blink': current_blink_subscriptions,
'sound': current_sound_subscriptions,
},
'custom_sounds': custom_sounds,
})
self.config.config.update(
{
'popup_enabled': self.builder.get_object('popup_enabled').get_active(),
'blink_enabled': self.builder.get_object('blink_enabled').get_active(),
'sound_enabled': self.builder.get_object('sound_enabled').get_active(),
'sound_path': new_sound_file,
'subscriptions': {
'popup': current_popup_subscriptions,
'blink': current_blink_subscriptions,
'sound': current_sound_subscriptions,
},
'custom_sounds': custom_sounds,
}
)
self.config.save()
core_config = {
@ -534,8 +533,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
'smtp_from': self.builder.get_object('smtp_from').get_text(),
'smtp_tls': self.builder.get_object('smtp_tls').get_active(),
'smtp_recipients': [
dest[0] for dest in self.recipients_model if
dest[0] != 'USER@HOST'
dest[0] for dest in self.recipients_model if dest[0] != 'USER@HOST'
],
'subscriptions': {'email': current_email_subscriptions},
}
@ -558,20 +556,20 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
for recipient in core_config['smtp_recipients']:
self.recipients_model.set(
self.recipients_model.append(),
RECIPIENT_FIELD, recipient,
RECIPIENT_EDIT, False,
RECIPIENT_FIELD,
recipient,
RECIPIENT_EDIT,
False,
)
self.builder.get_object('smtp_enabled').set_active(
core_config['smtp_enabled'],
)
self.builder.get_object('smtp_enabled').set_active(core_config['smtp_enabled'])
self.builder.get_object('sound_enabled').set_active(
self.config['sound_enabled'],
self.config['sound_enabled']
)
self.builder.get_object('popup_enabled').set_active(
self.config['popup_enabled'],
self.config['popup_enabled']
)
self.builder.get_object('blink_enabled').set_active(
self.config['blink_enabled'],
self.config['blink_enabled']
)
if self.config['sound_path']:
sound_path = self.config['sound_path']
@ -588,17 +586,11 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
)
def on_sound_path_update_preview(self, filechooser):
client.notifications.get_handled_events().addCallback(
self.populate_sounds,
)
client.notifications.get_handled_events().addCallback(self.populate_sounds)
def on_add_button_clicked(self, widget, treeview):
model = treeview.get_model()
model.set(
model.append(),
RECIPIENT_FIELD, 'USER@HOST',
RECIPIENT_EDIT, True,
)
model.set(model.append(), RECIPIENT_FIELD, 'USER@HOST', RECIPIENT_EDIT, True)
def on_delete_button_clicked(self, widget, treeview):
selection = treeview.get_selection()
@ -613,42 +605,40 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
def on_recipients_treeview_selection_changed(self, selection):
model, selected_connection_iter = selection.get_selected()
if selected_connection_iter:
self.builder.get_object('delete_button').set_property(
'sensitive',
True,
)
self.builder.get_object('delete_button').set_property('sensitive', True)
else:
self.builder.get_object('delete_button').set_property(
'sensitive',
False,
)
self.builder.get_object('delete_button').set_property('sensitive', False)
def on_subscriptions_treeview_selection_changed(self, selection):
model, selected_connection_iter = selection.get_selected()
if selected_connection_iter:
self.builder.get_object('delete_button').set_property(
'sensitive',
True,
)
self.builder.get_object('delete_button').set_property('sensitive', True)
else:
self.builder.get_object('delete_button').set_property(
'sensitive',
False,
)
self.builder.get_object('delete_button').set_property('sensitive', False)
def on_sounds_treeview_selection_changed(self, selection):
model, selected_iter = selection.get_selected()
if selected_iter:
self.builder.get_object('sounds_edit_button').set_property('sensitive', True)
self.builder.get_object('sounds_edit_button').set_property(
'sensitive', True
)
path = model.get(selected_iter, SND_PATH)[0]
log.debug('Sound selection changed: %s', path)
if path != self.config['sound_path']:
self.builder.get_object('sounds_revert_button').set_property('sensitive', True)
self.builder.get_object('sounds_revert_button').set_property(
'sensitive', True
)
else:
self.builder.get_object('sounds_revert_button').set_property('sensitive', False)
self.builder.get_object('sounds_revert_button').set_property(
'sensitive', False
)
else:
self.builder.get_object('sounds_edit_button').set_property('sensitive', False)
self.builder.get_object('sounds_revert_button').set_property('sensitive', False)
self.builder.get_object('sounds_edit_button').set_property(
'sensitive', False
)
self.builder.get_object('sounds_revert_button').set_property(
'sensitive', False
)
def on_sounds_revert_button_clicked(self, widget):
log.debug('on_sounds_revert_button_clicked')
@ -658,8 +648,10 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
log.debug('on_sounds_revert_button_clicked: got iter')
model.set(
selected_iter,
SND_PATH, self.config['sound_path'],
SND_NAME, basename(self.config['sound_path']),
SND_PATH,
self.config['sound_path'],
SND_NAME,
basename(self.config['sound_path']),
)
def on_sounds_edit_button_clicked(self, widget):
@ -686,9 +678,12 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
log.debug(new_filename)
model.set(
selected_iter,
SND_PATH, new_filename,
SND_NAME, basename(new_filename),
SND_PATH,
new_filename,
SND_NAME,
basename(new_filename),
)
d = defer.maybeDeferred(dialog.run)
d.addCallback(update_model)
@ -696,57 +691,55 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
def on_enabled_toggled(self, widget):
for widget_name in (
'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass',
'smtp_pass', 'smtp_tls', 'smtp_from',
'smtp_host',
'smtp_port',
'smtp_user',
'smtp_pass',
'smtp_pass',
'smtp_tls',
'smtp_from',
'smtp_recipients',
):
self.builder.get_object(widget_name).set_property(
'sensitive',
widget.get_active(),
'sensitive', widget.get_active()
)
def on_sound_enabled_toggled(self, widget):
if widget.get_active():
self.builder.get_object('sound_path').set_property('sensitive', True)
self.builder.get_object('sounds_page').set_property(
'sensitive',
True,
)
self.builder.get_object('sounds_page_label').set_property(
'sensitive',
True,
)
self.builder.get_object('sounds_page').set_property('sensitive', True)
self.builder.get_object('sounds_page_label').set_property('sensitive', True)
else:
self.builder.get_object('sound_path').set_property('sensitive', False)
self.builder.get_object('sounds_page').set_property(
'sensitive',
False,
)
self.builder.get_object('sounds_page').set_property('sensitive', False)
self.builder.get_object('sounds_page_label').set_property(
'sensitive',
False,
'sensitive', False
)
# for widget_name in ('sounds_path', 'sounds_page', 'sounds_page_label'):
# self.builder.get_object(widget_name).set_property('sensitive',
# widget.get_active())
# for widget_name in ('sounds_path', 'sounds_page', 'sounds_page_label'):
# self.builder.get_object(widget_name).set_property('sensitive',
# widget.get_active())
def _on_email_col_toggled(self, cell, path):
self.subscriptions_model[path][SUB_NOT_EMAIL] = \
not self.subscriptions_model[path][SUB_NOT_EMAIL]
self.subscriptions_model[path][SUB_NOT_EMAIL] = not self.subscriptions_model[
path
][SUB_NOT_EMAIL]
return
def _on_popup_col_toggled(self, cell, path):
self.subscriptions_model[path][SUB_NOT_POPUP] = \
not self.subscriptions_model[path][SUB_NOT_POPUP]
self.subscriptions_model[path][SUB_NOT_POPUP] = not self.subscriptions_model[
path
][SUB_NOT_POPUP]
return
def _on_blink_col_toggled(self, cell, path):
self.subscriptions_model[path][SUB_NOT_BLINK] = \
not self.subscriptions_model[path][SUB_NOT_BLINK]
self.subscriptions_model[path][SUB_NOT_BLINK] = not self.subscriptions_model[
path
][SUB_NOT_BLINK]
return
def _on_sound_col_toggled(self, cell, path):
self.subscriptions_model[path][SUB_NOT_SOUND] = \
not self.subscriptions_model[path][SUB_NOT_SOUND]
self.subscriptions_model[path][SUB_NOT_SOUND] = not self.subscriptions_model[
path
][SUB_NOT_SOUND]
return

View File

@ -32,10 +32,7 @@ class TestEmailNotifications(component.Component):
self.__imp = imp
self.lc = task.LoopingCall(self.update)
self.n = 1
self.events = [
FooEvent(),
CustomEvent(),
]
self.events = [FooEvent(), CustomEvent()]
self.events_classes = []
def enable(self):
@ -44,22 +41,18 @@ class TestEmailNotifications(component.Component):
if self.__imp == 'core':
# component.get('CorePlugin.Notifications').register_custom_email_notification(
component.get('Notifications').register_custom_email_notification(
event.__class__.__name__,
self.custom_email_message_provider,
event.__class__.__name__, self.custom_email_message_provider
)
elif self.__imp == 'gtk':
notifications_component = component.get('Notifications')
notifications_component.register_custom_popup_notification(
event.__class__.__name__,
self.custom_popup_message_provider,
event.__class__.__name__, self.custom_popup_message_provider
)
notifications_component.register_custom_blink_notification(
event.__class__.__name__,
self.custom_blink_message_provider,
event.__class__.__name__, self.custom_blink_message_provider
)
notifications_component.register_custom_sound_notification(
event.__class__.__name__,
self.custom_sound_message_provider,
event.__class__.__name__, self.custom_sound_message_provider
)
self.lc.start(60, False)

View File

@ -40,11 +40,9 @@ setup(
url=__url__,
license=__license__,
long_description=__long_description__ if __long_description__ else __description__,
packages=find_packages(exclude=['**/test.py']),
namespace_packages=['deluge', 'deluge.plugins'],
package_data=__pkg_data__,
entry_points="""
[deluge.plugin.core]
%s = deluge.plugins.%s:CorePlugin
@ -52,5 +50,6 @@ setup(
%s = deluge.plugins.%s:GtkUIPlugin
[deluge.plugin.web]
%s = deluge.plugins.%s:WebUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower()) * 3),
"""
% ((__plugin_name__, __plugin_name__.lower()) * 3),
)

View File

@ -19,6 +19,7 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
super(CorePlugin, self).__init__(plugin_name)
@ -26,6 +27,7 @@ class CorePlugin(PluginInitBase):
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
super(GtkUIPlugin, self).__init__(plugin_name)
@ -33,5 +35,6 @@ class GtkUIPlugin(PluginInitBase):
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
super(WebUIPlugin, self).__init__(plugin_name)

View File

@ -35,11 +35,7 @@ DEFAULT_PREFS = {
'button_state': [[0] * 7 for dummy in range(24)],
}
STATES = {
0: 'Green',
1: 'Yellow',
2: 'Red',
}
STATES = {0: 'Green', 1: 'Yellow', 2: 'Red'}
CONTROLLED_SETTINGS = [
'max_download_speed',
@ -54,6 +50,7 @@ class SchedulerEvent(DelugeEvent):
"""
Emitted when a schedule state changes.
"""
def __init__(self, colour):
"""
:param colour: str, the current scheduler state
@ -71,7 +68,9 @@ class Core(CorePluginBase):
DEFAULT_PREFS['low_active_down'] = core_config['max_active_downloading']
DEFAULT_PREFS['low_active_up'] = core_config['max_active_seeding']
self.config = deluge.configmanager.ConfigManager('scheduler.conf', DEFAULT_PREFS)
self.config = deluge.configmanager.ConfigManager(
'scheduler.conf', DEFAULT_PREFS
)
self.state = self.get_state()
@ -84,12 +83,16 @@ class Core(CorePluginBase):
self.timer = reactor.callLater(secs_to_next_hour, self.do_schedule)
# Register for config changes so state isn't overridden
component.get('EventManager').register_event_handler('ConfigValueChangedEvent', self.on_config_value_changed)
component.get('EventManager').register_event_handler(
'ConfigValueChangedEvent', self.on_config_value_changed
)
def disable(self):
if self.timer.active():
self.timer.cancel()
component.get('EventManager').deregister_event_handler('ConfigValueChangedEvent', self.on_config_value_changed)
component.get('EventManager').deregister_event_handler(
'ConfigValueChangedEvent', self.on_config_value_changed
)
self.__apply_set_functions()
def update(self):
@ -105,7 +108,9 @@ class Core(CorePluginBase):
"""
core_config = deluge.configmanager.ConfigManager('core.conf')
for setting in CONTROLLED_SETTINGS:
component.get('PreferencesManager').do_config_set_func(setting, core_config[setting])
component.get('PreferencesManager').do_config_set_func(
setting, core_config[setting]
)
# Resume the session if necessary
component.get('Core').resume_session()

View File

@ -32,8 +32,10 @@ class SchedulerSelectWidget(gtk.DrawingArea):
def __init__(self, hover):
super(SchedulerSelectWidget, self).__init__()
self.set_events(
gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK |
gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.LEAVE_NOTIFY_MASK,
gtk.gdk.BUTTON_PRESS_MASK
| gtk.gdk.BUTTON_RELEASE_MASK
| gtk.gdk.POINTER_MOTION_MASK
| gtk.gdk.LEAVE_NOTIFY_MASK
)
self.connect('expose_event', self.expose)
@ -65,7 +67,9 @@ class SchedulerSelectWidget(gtk.DrawingArea):
# redraw the whole thing
def expose(self, widget, event):
context = self.window.cairo_create()
context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
context.rectangle(
event.area.x, event.area.y, event.area.width, event.area.height
)
context.clip()
width = self.window.get_size()[0]
@ -76,11 +80,14 @@ class SchedulerSelectWidget(gtk.DrawingArea):
context.set_source_rgba(
self.colors[self.button_state[x][y]][0],
self.colors[self.button_state[x][y]][1],
self.colors[self.button_state[x][y]][2], 0.7,
self.colors[self.button_state[x][y]][2],
0.7,
)
context.rectangle(
width * (6 * x / 145 + 1 / 145), height * (6 * y / 43 + 1 / 43),
5 * width / 145, 5 * height / 43,
width * (6 * x / 145 + 1 / 145),
height * (6 * y / 43 + 1 / 43),
5 * width / 145,
5 * height / 43,
)
context.fill_preserve()
context.set_source_rgba(0.5, 0.5, 0.5, 0.5)
@ -132,17 +139,25 @@ class SchedulerSelectWidget(gtk.DrawingArea):
self.hover_point = self.get_point(event)
self.hover_label.set_text(
self.hover_days[self.hover_point[1]] +
' ' + str(self.hover_point[0]) +
':00 - ' + str(self.hover_point[0]) + ':59',
self.hover_days[self.hover_point[1]]
+ ' '
+ str(self.hover_point[0])
+ ':00 - '
+ str(self.hover_point[0])
+ ':59'
)
if self.mouse_press:
points = [[self.hover_point[0], self.start_point[0]], [self.hover_point[1], self.start_point[1]]]
points = [
[self.hover_point[0], self.start_point[0]],
[self.hover_point[1], self.start_point[1]],
]
for x in range(min(points[0]), max(points[0]) + 1):
for y in range(min(points[1]), max(points[1]) + 1):
self.button_state[x][y] = self.button_state[self.start_point[0]][self.start_point[1]]
self.button_state[x][y] = self.button_state[
self.start_point[0]
][self.start_point[1]]
self.queue_draw()
@ -156,8 +171,12 @@ class GtkUI(GtkPluginBase):
def enable(self):
self.create_prefs_page()
component.get('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs)
component.get('PluginManager').register_hook('on_show_prefs', self.on_show_prefs)
component.get('PluginManager').register_hook(
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').register_hook(
'on_show_prefs', self.on_show_prefs
)
self.statusbar = component.get('StatusBar')
self.status_item = self.statusbar.add_item(
image=get_resource('green.png'),
@ -169,20 +188,29 @@ class GtkUI(GtkPluginBase):
def on_state_deferred(state):
self.state = state
self.on_scheduler_event(state)
client.scheduler.get_state().addCallback(on_state_deferred)
client.register_event_handler('SchedulerEvent', self.on_scheduler_event)
def disable(self):
component.get('Preferences').remove_page(_('Scheduler'))
# Reset statusbar dict.
self.statusbar.config_value_changed_dict['max_download_speed'] = self.statusbar._on_max_download_speed
self.statusbar.config_value_changed_dict['max_upload_speed'] = self.statusbar._on_max_upload_speed
self.statusbar.config_value_changed_dict[
'max_download_speed'
] = self.statusbar._on_max_download_speed
self.statusbar.config_value_changed_dict[
'max_upload_speed'
] = self.statusbar._on_max_upload_speed
# Remove statusbar item.
self.statusbar.remove_item(self.status_item)
del self.status_item
component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs)
component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs)
component.get('PluginManager').deregister_hook(
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').deregister_hook(
'on_show_prefs', self.on_show_prefs
)
def on_apply_prefs(self):
log.debug('applying prefs for Scheduler')
@ -221,8 +249,12 @@ class GtkUI(GtkPluginBase):
# Skip error due to Plugin being enabled before statusbar items created on startup.
pass
else:
self.statusbar.config_value_changed_dict['max_download_speed'] = self.statusbar._on_max_download_speed
self.statusbar.config_value_changed_dict['max_upload_speed'] = self.statusbar._on_max_upload_speed
self.statusbar.config_value_changed_dict[
'max_download_speed'
] = self.statusbar._on_max_download_speed
self.statusbar.config_value_changed_dict[
'max_upload_speed'
] = self.statusbar._on_max_upload_speed
def update_config_values(config):
try:
@ -231,7 +263,10 @@ class GtkUI(GtkPluginBase):
except AttributeError:
# Skip error due to Plugin being enabled before statusbar items created on startup.
pass
client.core.get_config_values(['max_download_speed', 'max_upload_speed']).addCallback(update_config_values)
client.core.get_config_values(
['max_download_speed', 'max_upload_speed']
).addCallback(update_config_values)
def on_status_item_clicked(self, widget, event):
component.get('Preferences').show('Scheduler')

View File

@ -32,11 +32,9 @@ setup(
url=__url__,
license=__license__,
long_description=__long_description__ if __long_description__ else __description__,
packages=find_packages(),
namespace_packages=['deluge', 'deluge.plugins'],
package_data=__pkg_data__,
entry_points="""
[deluge.plugin.core]
%s = deluge.plugins.%s:CorePlugin
@ -44,5 +42,6 @@ setup(
%s = deluge.plugins.%s:GtkUIPlugin
[deluge.plugin.web]
%s = deluge.plugins.%s:WebUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower()) * 3),
"""
% ((__plugin_name__, __plugin_name__.lower()) * 3),
)

View File

@ -19,6 +19,7 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
super(CorePlugin, self).__init__(plugin_name)
@ -26,6 +27,7 @@ class CorePlugin(PluginInitBase):
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
super(GtkUIPlugin, self).__init__(plugin_name)
@ -33,5 +35,6 @@ class GtkUIPlugin(PluginInitBase):
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
super(WebUIPlugin, self).__init__(plugin_name)

View File

@ -119,13 +119,15 @@ class Core(CorePluginBase):
stats.update(self.core.get_session_status([key]))
except AttributeError:
pass
stats['num_connections'] = stats['num_peers'] + stats['peer.num_peers_half_open']
stats['num_connections'] = (
stats['num_peers'] + stats['peer.num_peers_half_open']
)
stats['dht_cache_nodes'] = stats['dht.dht_node_cache']
stats.update(self.core.get_config_values([
'max_download',
'max_upload',
'max_num_connections',
]))
stats.update(
self.core.get_config_values(
['max_download', 'max_upload', 'max_num_connections']
)
)
# status = self.core.session.status()
# for stat in dir(status):
# if not stat.startswith('_') and stat not in stats:
@ -195,7 +197,12 @@ class Core(CorePluginBase):
@export
def get_session_totals(self):
return self.core.get_session_status(
['total_upload', 'total_download', 'total_payload_upload', 'total_payload_download'],
[
'total_upload',
'total_download',
'total_payload_upload',
'total_payload_download',
]
)
@export

View File

@ -136,7 +136,9 @@ class Graph(object):
seconds_to_step = math.ceil(start / x_step) * x_step - start
for i in range(0, duration // x_step + 1):
text = time.strftime('%H:%M', time.localtime(start + seconds_to_step + i * x_step))
text = time.strftime(
'%H:%M', time.localtime(start + seconds_to_step + i * x_step)
)
# + 0.5 to allign x to nearest pixel
x = int(ratio * (seconds_to_step + i * x_step) + left) + 0.5
self.draw_x_text(text, x, bottom)
@ -171,6 +173,7 @@ class Graph(object):
def space_required(text):
te = self.ctx.text_extents(text)
return math.ceil(te[4] - te[0])
y_tick_width = max((space_required(text) for text in y_tick_text))
top = font_extents[2] / 2
@ -220,7 +223,9 @@ class Graph(object):
else:
interval = interval * 2
intervals = [i * interval * scale for i in range(1 + int(math.ceil(x / interval)))]
intervals = [
i * interval * scale for i in range(1 + int(math.ceil(x / interval)))
]
return intervals
def draw_left_axis(self, bounds, y_ticks, y_tick_text):
@ -247,7 +252,9 @@ class Graph(object):
for stat, info in stats.items():
if len(info['values']) > 0:
self.draw_value_poly(info['values'], info['color'], max_value, bounds)
self.draw_value_poly(info['values'], info['fill_color'], max_value, bounds, info['fill'])
self.draw_value_poly(
info['values'], info['fill_color'], max_value, bounds, info['fill']
)
def draw_legend(self):
pass
@ -271,10 +278,7 @@ class Graph(object):
self.ctx.line_to(x, int(bottom - value * ratio))
x -= step
self.ctx.line_to(
int(right - (len(values) - 1) * step),
bottom,
)
self.ctx.line_to(int(right - (len(values) - 1) * step), bottom)
self.ctx.close_path()
def draw_value_poly(self, values, color, max_value, bounds, fill=False):
@ -305,7 +309,9 @@ class Graph(object):
ascent = fe[0]
x_bearing = te[0]
width = te[4]
self.ctx.move_to(int(x - width - x_bearing - 2), int(y + (ascent - descent) / 2))
self.ctx.move_to(
int(x - width - x_bearing - 2), int(y + (ascent - descent) / 2)
)
self.ctx.set_source_rgba(*self.black)
self.ctx.show_text(text)

View File

@ -43,9 +43,7 @@ DEFAULT_CONF = {
'dht_torrents': str(gtk.gdk.Color('green')),
'num_connections': str(gtk.gdk.Color('darkred')),
},
'seeds_graph': {
'num_peers': str(gtk.gdk.Color('blue')),
},
'seeds_graph': {'num_peers': str(gtk.gdk.Color('blue'))},
},
}
@ -126,7 +124,9 @@ class GraphsTab(Tab):
def graph_expose(self, widget, event):
context = self.graph_widget.window.cairo_create()
# set a clip region
context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
context.rectangle(
event.area.x, event.area.y, event.area.width, event.area.height
)
context.clip()
self.graph.draw_to_context(
context,
@ -143,6 +143,7 @@ class GraphsTab(Tab):
def _update_complete(result):
self.graph_widget.queue_draw()
return result
d1.addCallback(_update_complete)
return d1
@ -158,16 +159,17 @@ class GraphsTab(Tab):
self.graph = Graph()
colors = self.colors['bandwidth_graph']
self.graph.add_stat(
'download_rate', label='Download Rate',
'download_rate',
label='Download Rate',
color=gtk_to_graph_color(colors['download_rate']),
)
self.graph.add_stat(
'upload_rate', label='Upload Rate',
'upload_rate',
label='Upload Rate',
color=gtk_to_graph_color(colors['upload_rate']),
)
self.graph.set_left_axis(
formatter=fspeed_shortform, min=10240,
formatter_scale=size_formatter_scale,
formatter=fspeed_shortform, min=10240, formatter_scale=size_formatter_scale
)
def select_connections_graph(self):
@ -177,9 +179,13 @@ class GraphsTab(Tab):
self.graph = g
colors = self.colors['connections_graph']
g.add_stat('dht_nodes', color=gtk_to_graph_color(colors['dht_nodes']))
g.add_stat('dht_cache_nodes', color=gtk_to_graph_color(colors['dht_cache_nodes']))
g.add_stat(
'dht_cache_nodes', color=gtk_to_graph_color(colors['dht_cache_nodes'])
)
g.add_stat('dht_torrents', color=gtk_to_graph_color(colors['dht_torrents']))
g.add_stat('num_connections', color=gtk_to_graph_color(colors['num_connections']))
g.add_stat(
'num_connections', color=gtk_to_graph_color(colors['num_connections'])
)
g.set_left_axis(formatter=int_str, min=10)
def select_seeds_graph(self):
@ -194,9 +200,7 @@ class GraphsTab(Tab):
self.colors = colors
# Fake switch page to update the graph colors (HACKY)
self._on_notebook_switch_page(
self.notebook,
None, # This is unused
self.notebook.get_current_page(),
self.notebook, None, self.notebook.get_current_page() # This is unused
)
def _on_intervals_changed(self, intervals):
@ -233,17 +237,24 @@ class GraphsTab(Tab):
class GtkUI(GtkPluginBase):
def enable(self):
log.debug('Stats plugin enable called')
self.config = deluge.configmanager.ConfigManager('stats.gtkui.conf', DEFAULT_CONF)
self.config = deluge.configmanager.ConfigManager(
'stats.gtkui.conf', DEFAULT_CONF
)
self.builder = gtk.Builder()
self.builder.add_from_file(get_resource('config.ui'))
component.get('Preferences').add_page('Stats', self.builder.get_object('prefs_box'))
component.get('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs)
component.get('PluginManager').register_hook('on_show_prefs', self.on_show_prefs)
component.get('Preferences').add_page(
'Stats', self.builder.get_object('prefs_box')
)
component.get('PluginManager').register_hook(
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').register_hook(
'on_show_prefs', self.on_show_prefs
)
self.on_show_prefs()
self.graphs_tab = GraphsTab(self.config['colors'])
@ -252,8 +263,12 @@ class GtkUI(GtkPluginBase):
def disable(self):
component.get('Preferences').remove_page('Stats')
component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs)
component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs)
component.get('PluginManager').deregister_hook(
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').deregister_hook(
'on_show_prefs', self.on_show_prefs
)
self.torrent_details.remove_tab(self.graphs_tab.get_name())
def on_apply_prefs(self):

View File

@ -27,7 +27,6 @@ def print_totals(totals):
class StatsTestCase(BaseTestCase):
def set_up(self):
defer.setDebugging(True)
tests_common.set_tmp_config_dir()

View File

@ -36,11 +36,9 @@ setup(
url=__url__,
license=__license__,
long_description=__long_description__,
packages=find_packages(),
namespace_packages=['deluge', 'deluge.plugins'],
package_data=__pkg_data__,
entry_points="""
[deluge.plugin.core]
%s = deluge.plugins.%s:CorePlugin
@ -48,5 +46,6 @@ setup(
%s = deluge.plugins.%s:GtkUIPlugin
[deluge.plugin.web]
%s = deluge.plugins.%s:WebUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower()) * 3),
"""
% ((__plugin_name__, __plugin_name__.lower()) * 3),
)

View File

@ -20,6 +20,7 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
super(CorePlugin, self).__init__(plugin_name)
@ -27,6 +28,7 @@ class CorePlugin(PluginInitBase):
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
super(GtkUIPlugin, self).__init__(plugin_name)
@ -34,5 +36,6 @@ class GtkUIPlugin(PluginInitBase):
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
super(WebUIPlugin, self).__init__(plugin_name)

View File

@ -22,8 +22,7 @@ from deluge.plugins.pluginbase import CorePluginBase
log = logging.getLogger(__name__)
DEFAULT_PREFS = {
}
DEFAULT_PREFS = {}
class Core(CorePluginBase):

View File

@ -29,8 +29,10 @@ class GtkUI(GtkPluginBase):
self.plugin = component.get('PluginManager')
self.separator = self.plugin.add_toolbar_separator()
self.button = self.plugin.add_toolbar_button(
self._on_button_clicked, label='Pause Session',
stock='gtk-media-pause', tooltip='Pause the session',
self._on_button_clicked,
label='Pause Session',
stock='gtk-media-pause',
tooltip='Pause the session',
)
def disable(self):
@ -47,6 +49,7 @@ class GtkUI(GtkPluginBase):
self.button.set_label('Pause Session')
self.button.set_tooltip_text('Pause the session')
self.button.set_stock_id('gtk-media-pause')
self.core.get_status().addCallback(_on_get_status)
def _on_button_clicked(self, widget):

View File

@ -33,11 +33,9 @@ setup(
url=__url__,
license=__license__,
long_description=__long_description__ if __long_description__ else __description__,
packages=find_packages(),
namespace_packages=['deluge', 'deluge.plugins'],
package_data=__pkg_data__,
entry_points="""
[deluge.plugin.core]
%s = deluge.plugins.%s:CorePlugin
@ -45,5 +43,6 @@ setup(
%s = deluge.plugins.%s:GtkUIPlugin
[deluge.plugin.web]
%s = deluge.plugins.%s:WebUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower()) * 3),
"""
% ((__plugin_name__, __plugin_name__.lower()) * 3),
)

View File

@ -19,6 +19,7 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
super(CorePlugin, self).__init__(plugin_name)
@ -26,6 +27,7 @@ class CorePlugin(PluginInitBase):
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
super(GtkUIPlugin, self).__init__(plugin_name)
@ -33,5 +35,6 @@ class GtkUIPlugin(PluginInitBase):
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
super(WebUIPlugin, self).__init__(plugin_name)

View File

@ -30,11 +30,7 @@ except ImportError:
log = logging.getLogger(__name__)
DEFAULT_PREFS = {
'enabled': False,
'ssl': False,
'port': 8112,
}
DEFAULT_PREFS = {'enabled': False, 'ssl': False, 'port': 8112}
class Core(CorePluginBase):

View File

@ -31,16 +31,26 @@ class GtkUI(GtkPluginBase):
self.builder = gtk.Builder()
self.builder.add_from_file(get_resource('config.ui'))
component.get('Preferences').add_page(_('WebUi'), self.builder.get_object('prefs_box'))
component.get('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs)
component.get('PluginManager').register_hook('on_show_prefs', self.on_show_prefs)
component.get('Preferences').add_page(
_('WebUi'), self.builder.get_object('prefs_box')
)
component.get('PluginManager').register_hook(
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').register_hook(
'on_show_prefs', self.on_show_prefs
)
client.webui.get_config().addCallback(self.cb_get_config)
client.webui.got_deluge_web().addCallback(self.cb_chk_deluge_web)
def disable(self):
component.get('Preferences').remove_page(_('WebUi'))
component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs)
component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs)
component.get('PluginManager').deregister_hook(
'on_apply_prefs', self.on_apply_prefs
)
component.get('PluginManager').deregister_hook(
'on_show_prefs', self.on_show_prefs
)
def on_apply_prefs(self):
if not self.have_web:
@ -71,14 +81,18 @@ class GtkUI(GtkPluginBase):
vbox = self.builder.get_object('prefs_box')
hbox = gtk.HBox()
icon = gtk.image_new_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_SMALL_TOOLBAR)
icon = gtk.image_new_from_stock(
gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_SMALL_TOOLBAR
)
icon.set_padding(5, 5)
hbox.pack_start(icon, False, False)
label = gtk.Label(_(
'The Deluge web interface is not installed, '
'please install the\ninterface and try again',
))
label = gtk.Label(
_(
'The Deluge web interface is not installed, '
'please install the\ninterface and try again'
)
)
label.set_alignment(0, 0.5)
label.set_padding(5, 5)
hbox.pack_start(label)

View File

@ -21,7 +21,6 @@ common.disable_new_release_check()
class WebUIPluginTestCase(BaseTestCase):
def set_up(self):
common.set_tmp_config_dir()
self.rpcserver = RPCServer(listen=False)
@ -29,10 +28,10 @@ class WebUIPluginTestCase(BaseTestCase):
return component.start()
def tear_down(self):
def on_shutdown(result):
del self.rpcserver
del self.core
return component.shutdown().addCallback(on_shutdown)
def test_enable_webui(self):

View File

@ -32,15 +32,14 @@ setup(
url=__url__,
license=__license__,
long_description=__long_description__ if __long_description__ else __description__,
packages=find_packages(),
namespace_packages=['deluge', 'deluge.plugins'],
package_data=__pkg_data__,
entry_points="""
[deluge.plugin.core]
%s = deluge.plugins.%s:CorePlugin
[deluge.plugin.gtkui]
%s = deluge.plugins.%s:GtkUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower()) * 2),
"""
% ((__plugin_name__, __plugin_name__.lower()) * 2),
)

View File

@ -31,7 +31,6 @@ class PluginBase(component.Component):
class CorePluginBase(PluginBase):
def __init__(self, plugin_name):
super(CorePluginBase, self).__init__('CorePlugin.' + plugin_name)
# Register RPC methods
@ -49,7 +48,6 @@ class CorePluginBase(PluginBase):
class GtkPluginBase(PluginBase):
def __init__(self, plugin_name):
super(GtkPluginBase, self).__init__('GtkPlugin.' + plugin_name)
log.debug('GtkPlugin initialized..')

View File

@ -78,10 +78,14 @@ if py3:
def int2byte(c):
return bytes([c])
else:
def int2byte(c):
return chr(c)
# Default number of bits for serialized floats, either 32 or 64 (also a parameter for dumps()).
DEFAULT_FLOAT_BITS = 32
@ -137,44 +141,44 @@ def decode_int(x, f):
n = int(x[f:newf])
except (OverflowError, ValueError):
n = long(x[f:newf])
if x[f:f + 1] == '-':
if x[f + 1:f + 2] == '0':
if x[f : f + 1] == '-':
if x[f + 1 : f + 2] == '0':
raise ValueError
elif x[f:f + 1] == '0' and newf != f + 1:
elif x[f : f + 1] == '0' and newf != f + 1:
raise ValueError
return (n, newf + 1)
def decode_intb(x, f):
f += 1
return (struct.unpack('!b', x[f:f + 1])[0], f + 1)
return (struct.unpack('!b', x[f : f + 1])[0], f + 1)
def decode_inth(x, f):
f += 1
return (struct.unpack('!h', x[f:f + 2])[0], f + 2)
return (struct.unpack('!h', x[f : f + 2])[0], f + 2)
def decode_intl(x, f):
f += 1
return (struct.unpack('!l', x[f:f + 4])[0], f + 4)
return (struct.unpack('!l', x[f : f + 4])[0], f + 4)
def decode_intq(x, f):
f += 1
return (struct.unpack('!q', x[f:f + 8])[0], f + 8)
return (struct.unpack('!q', x[f : f + 8])[0], f + 8)
def decode_float32(x, f):
f += 1
n = struct.unpack('!f', x[f:f + 4])[0]
n = struct.unpack('!f', x[f : f + 4])[0]
return (n, f + 4)
def decode_float64(x, f):
f += 1
n = struct.unpack('!d', x[f:f + 8])[0]
n = struct.unpack('!d', x[f : f + 8])[0]
return (n, f + 8)
@ -187,7 +191,7 @@ def decode_string(x, f):
if x[f] == '0' and colon != f + 1:
raise ValueError
colon += 1
s = x[colon:colon + n]
s = x[colon : colon + n]
if _decode_utf8:
s = s.decode('utf8')
return (s, colon + n)
@ -195,17 +199,17 @@ def decode_string(x, f):
def decode_list(x, f):
r, f = [], f + 1
while x[f:f + 1] != CHR_TERM:
v, f = decode_func[x[f:f + 1]](x, f)
while x[f : f + 1] != CHR_TERM:
v, f = decode_func[x[f : f + 1]](x, f)
r.append(v)
return (tuple(r), f + 1)
def decode_dict(x, f):
r, f = {}, f + 1
while x[f:f + 1] != CHR_TERM:
k, f = decode_func[x[f:f + 1]](x, f)
r[k], f = decode_func[x[f:f + 1]](x, f)
while x[f : f + 1] != CHR_TERM:
k, f = decode_func[x[f : f + 1]](x, f)
r[k], f = decode_func[x[f : f + 1]](x, f)
return (r, f + 1)
@ -249,11 +253,13 @@ decode_func[CHR_NONE] = decode_none
def make_fixed_length_string_decoders():
def make_decoder(slen):
def f(x, f):
s = x[f + 1:f + 1 + slen]
s = x[f + 1 : f + 1 + slen]
if _decode_utf8:
s = s.decode('utf8')
return (s, f + 1 + slen)
return f
for i in range(STR_FIXED_COUNT):
decode_func[int2byte(STR_FIXED_START + i)] = make_decoder(i)
@ -266,10 +272,12 @@ def make_fixed_length_list_decoders():
def f(x, f):
r, f = [], f + 1
for _ in range(slen):
v, f = decode_func[x[f:f + 1]](x, f)
v, f = decode_func[x[f : f + 1]](x, f)
r.append(v)
return (tuple(r), f)
return f
for i in range(LIST_FIXED_COUNT):
decode_func[int2byte(LIST_FIXED_START + i)] = make_decoder(i)
@ -281,7 +289,9 @@ def make_fixed_length_int_decoders():
def make_decoder(j):
def f(x, f):
return (j, f + 1)
return f
for i in range(INT_POS_FIXED_COUNT):
decode_func[int2byte(INT_POS_FIXED_START + i)] = make_decoder(i)
for i in range(INT_NEG_FIXED_COUNT):
@ -296,10 +306,12 @@ def make_fixed_length_dict_decoders():
def f(x, f):
r, f = {}, f + 1
for _ in range(slen):
k, f = decode_func[x[f:f + 1]](x, f)
r[k], f = decode_func[x[f:f + 1]](x, f)
k, f = decode_func[x[f : f + 1]](x, f)
r[k], f = decode_func[x[f : f + 1]](x, f)
return (r, f)
return f
for i in range(DICT_FIXED_COUNT):
decode_func[int2byte(DICT_FIXED_START + i)] = make_decoder(i)
@ -436,17 +448,45 @@ def test():
f3 = struct.unpack('!f', struct.pack('!f', -0.6))[0]
ld = (
(
{b'a': 15, b'bb': f1, b'ccc': f2, b'': (f3, (), False, True, b'')}, (b'a', 10**20),
tuple(range(-100000, 100000)), b'b' * 31, b'b' * 62, b'b' * 64, 2**30, 2**33, 2**62,
2**64, 2**30, 2**33, 2**62, 2**64, False, False, True, -1, 2, 0,
{b'a': 15, b'bb': f1, b'ccc': f2, b'': (f3, (), False, True, b'')},
(b'a', 10 ** 20),
tuple(range(-100000, 100000)),
b'b' * 31,
b'b' * 62,
b'b' * 64,
2 ** 30,
2 ** 33,
2 ** 62,
2 ** 64,
2 ** 30,
2 ** 33,
2 ** 62,
2 ** 64,
False,
False,
True,
-1,
2,
0,
),
)
assert loads(dumps(ld)) == ld
d = dict(zip(range(-100000, 100000), range(-100000, 100000)))
d.update({b'a': 20, 20: 40, 40: 41, f1: f2, f2: f3, f3: False, False: True, True: False})
d.update(
{b'a': 20, 20: 40, 40: 41, f1: f2, f2: f3, f3: False, False: True, True: False}
)
ld = (d, {}, {5: 6}, {7: 7, True: 8}, {9: 10, 22: 39, 49: 50, 44: b''})
assert loads(dumps(ld)) == ld
ld = (b'', b'a' * 10, b'a' * 100, b'a' * 1000, b'a' * 10000, b'a' * 100000, b'a' * 1000000, b'a' * 10000000)
ld = (
b'',
b'a' * 10,
b'a' * 100,
b'a' * 1000,
b'a' * 10000,
b'a' * 100000,
b'a' * 1000000,
b'a' * 10000000,
)
assert loads(dumps(ld)) == ld
ld = tuple(dict(zip(range(n), range(n))) for n in range(100)) + (b'b',)
assert loads(dumps(ld)) == ld
@ -468,6 +508,7 @@ def test():
try:
import psyco
psyco.bind(dumps)
psyco.bind(loads)
except ImportError:

View File

@ -17,19 +17,35 @@ from datetime import datetime
import deluge.common
parser = ArgumentParser()
parser.add_argument('-n', '--name', metavar='<plugin name>', required=True, help='Plugin name')
parser.add_argument('-m', '--module-name', metavar='<module name>', help='Module name')
parser.add_argument('-p', '--basepath', metavar='<path>', required=True, help='Base path')
parser.add_argument(
'-a', '--author-name', metavar='<author name>', required=True,
'-n', '--name', metavar='<plugin name>', required=True, help='Plugin name'
)
parser.add_argument('-m', '--module-name', metavar='<module name>', help='Module name')
parser.add_argument(
'-p', '--basepath', metavar='<path>', required=True, help='Base path'
)
parser.add_argument(
'-a',
'--author-name',
metavar='<author name>',
required=True,
help='Author name,for the GPL header',
)
parser.add_argument(
'-e', '--author-email', metavar='<author email>', required=True,
'-e',
'--author-email',
metavar='<author email>',
required=True,
help='Author email,for the GPL header',
)
parser.add_argument('-u', '--url', metavar='<URL>', help='Homepage URL')
parser.add_argument('-c', '--config', metavar='<Config dir>', dest='configdir', help='Location of deluge configuration')
parser.add_argument(
'-c',
'--config',
metavar='<Config dir>',
dest='configdir',
help='Location of deluge configuration',
)
options = parser.parse_args()

View File

@ -32,33 +32,48 @@ def is_float_digit(string):
# set up command-line options
parser = OptionParser()
parser.add_option('--port', help='port for deluge backend host (default: 58846)', default='58846', dest='port')
parser.add_option(
'--host', help='hostname of deluge backend to connect to (default: localhost)',
default='localhost', dest='host',
'--port',
help='port for deluge backend host (default: 58846)',
default='58846',
dest='port',
)
parser.add_option(
'--max_active_limit', dest='max_active_limit',
'--host',
help='hostname of deluge backend to connect to (default: localhost)',
default='localhost',
dest='host',
)
parser.add_option(
'--max_active_limit',
dest='max_active_limit',
help='sets the absolute maximum number of active torrents on the deluge backend',
)
parser.add_option(
'--max_active_downloading', dest='max_active_downloading',
'--max_active_downloading',
dest='max_active_downloading',
help='sets the maximum number of active downloading torrents on the deluge backend',
)
parser.add_option(
'--max_active_seeding', dest='max_active_seeding',
'--max_active_seeding',
dest='max_active_seeding',
help='sets the maximum number of active seeding torrents on the deluge backend',
)
parser.add_option(
'--max_download_speed', help='sets the maximum global download speed on the deluge backend',
'--max_download_speed',
help='sets the maximum global download speed on the deluge backend',
dest='max_download_speed',
)
parser.add_option(
'--max_upload_speed', help='sets the maximum global upload speed on the deluge backend',
'--max_upload_speed',
help='sets the maximum global upload speed on the deluge backend',
dest='max_upload_speed',
)
parser.add_option(
'--debug', help='outputs debug information to the console', default=False, action='store_true',
'--debug',
help='outputs debug information to the console',
default=False,
action='store_true',
dest='debug',
)
@ -79,7 +94,10 @@ if options.max_active_limit:
sys.exit(-1)
if options.max_active_downloading:
if options.max_active_downloading.isdigit() and int(options.max_active_downloading) >= 0:
if (
options.max_active_downloading.isdigit()
and int(options.max_active_downloading) >= 0
):
settings['max_active_downloading'] = int(options.max_active_downloading)
else:
sys.stderr.write('ERROR: Invalid max_active_downloading parameter!\n')
@ -94,7 +112,8 @@ if options.max_active_seeding:
if options.max_download_speed:
if is_float_digit(options.max_download_speed) and (
float(options.max_download_speed) >= 0.0 or float(options.max_download_speed) == -1.0
float(options.max_download_speed) >= 0.0
or float(options.max_download_speed) == -1.0
):
settings['max_download_speed'] = float(options.max_download_speed)
else:
@ -103,7 +122,8 @@ if options.max_download_speed:
if options.max_upload_speed:
if is_float_digit(options.max_upload_speed) and (
float(options.max_upload_speed) >= 0.0 or float(options.max_upload_speed) == -1.0
float(options.max_upload_speed) >= 0.0
or float(options.max_upload_speed) == -1.0
):
settings['max_upload_speed'] = float(options.max_upload_speed)
else:
@ -114,6 +134,7 @@ if options.max_upload_speed:
if settings:
# create connection to daemon
from deluge.ui.client import sclient as client
client.set_core_uri('http://' + options.host + ':' + options.port)
# commit configurations changes

View File

@ -22,13 +22,14 @@ class BaseTestCase(unittest.TestCase):
have finished.
"""
def setUp(self): # NOQA: N803
if len(component._ComponentRegistry.components) != 0:
warnings.warn(
'The component._ComponentRegistry.components is not empty on test setup.\n'
'This is probably caused by another test that did not clean up after finishing!: %s' %
component._ComponentRegistry.components,
'This is probably caused by another test that did not clean up after finishing!: %s'
% component._ComponentRegistry.components
)
d = maybeDeferred(self.set_up)

View File

@ -60,7 +60,6 @@ def todo_test(caller):
def add_watchdog(deferred, timeout=0.05, message=None):
def callback(value):
if not watchdog.called and not watchdog.cancelled:
watchdog.cancel()
@ -102,8 +101,9 @@ class ReactorOverride(object):
class ProcessOutputHandler(protocol.ProcessProtocol):
def __init__(self, script, callbacks, logfile=None, print_stdout=True, print_stderr=True):
def __init__(
self, script, callbacks, logfile=None, print_stdout=True, print_stderr=True
):
"""Executes a script and handle the process' output to stdout and stderr.
Args:
@ -210,8 +210,14 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
def start_core(
listen_port=58846, logfile=None, timeout=10, timeout_msg=None,
custom_script='', print_stdout=True, print_stderr=True, extra_callbacks=None,
listen_port=58846,
logfile=None,
timeout=10,
timeout_msg=None,
custom_script='',
print_stdout=True,
print_stderr=True,
extra_callbacks=None,
):
"""Start the deluge core as a daemon.
@ -252,7 +258,11 @@ try:
except Exception:
import traceback
sys.stderr.write('Exception raised:\\n %%s' %% traceback.format_exc())
""" % {'dir': config_directory, 'port': listen_port, 'script': custom_script}
""" % {
'dir': config_directory,
'port': listen_port,
'script': custom_script,
}
callbacks = []
default_core_cb = {'deferred': Deferred(), 'types': 'stdout'}
@ -263,15 +273,20 @@ except Exception:
default_core_cb['triggers'] = [
{'expr': 'Finished loading ', 'value': lambda reader, data, data_all: reader},
{
'expr': 'Could not listen on localhost:%d' % (listen_port), 'type': 'errback', # Error from libtorrent
'expr': 'Could not listen on localhost:%d' % (listen_port),
'type': 'errback', # Error from libtorrent
'value': lambda reader, data, data_all: CannotListenError(
'localhost', listen_port,
'localhost',
listen_port,
'Could not start deluge test client!\n%s' % data,
),
},
{
'expr': 'Traceback', 'type': 'errback',
'value': lambda reader, data, data_all: DelugeError('Traceback found when starting daemon:\n%s' % data),
'expr': 'Traceback',
'type': 'errback',
'value': lambda reader, data, data_all: DelugeError(
'Traceback found when starting daemon:\n%s' % data
),
},
]
@ -279,11 +294,15 @@ except Exception:
if extra_callbacks:
callbacks.extend(extra_callbacks)
process_protocol = start_process(daemon_script, callbacks, logfile, print_stdout, print_stderr)
process_protocol = start_process(
daemon_script, callbacks, logfile, print_stdout, print_stderr
)
return default_core_cb['deferred'], process_protocol
def start_process(script, callbacks, logfile=None, print_stdout=True, print_stderr=True):
def start_process(
script, callbacks, logfile=None, print_stdout=True, print_stderr=True
):
"""
Starts an external python process which executes the given script.
@ -309,13 +328,19 @@ def start_process(script, callbacks, logfile=None, print_stdout=True, print_stde
"""
cwd = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
process_protocol = ProcessOutputHandler(script.encode('utf8'), callbacks, logfile, print_stdout, print_stderr)
process_protocol = ProcessOutputHandler(
script.encode('utf8'), callbacks, logfile, print_stdout, print_stderr
)
# Add timeouts to deferreds
for c in callbacks:
if 'timeout' in c:
w = add_watchdog(c['deferred'], timeout=c['timeout'], message=c.get('timeout_msg', None))
w = add_watchdog(
c['deferred'], timeout=c['timeout'], message=c.get('timeout_msg', None)
)
process_protocol.watchdogs.append(w)
reactor.spawnProcess(process_protocol, sys.executable, args=[sys.executable], path=cwd)
reactor.spawnProcess(
process_protocol, sys.executable, args=[sys.executable], path=cwd
)
return process_protocol

View File

@ -26,6 +26,7 @@ class WebServerTestBase(BaseTestCase, DaemonBase):
Base class for tests that need a running webapi
"""
def set_up(self):
self.host_id = None
deluge.ui.web.server.reactor = ReactorOverride()
@ -60,16 +61,16 @@ class WebServerMockBase(object):
Class with utility functions for mocking with tests using the webserver
"""
def mock_authentication_ignore(self, auth):
def mock_authentication_ignore(self, auth):
def check_request(request, method=None, level=None):
pass
self.patch(auth, 'check_request', check_request)
def mock_compress_body(self):
def compress(contents, request):
return contents
# Patch compress to avoid having to decompress output with zlib
self.patch(deluge.ui.web.json_api, 'compress', compress)

View File

@ -41,8 +41,15 @@ class DaemonBase(object):
@defer.inlineCallbacks
def start_core(
self, arg, custom_script='', logfile='', print_stdout=True, print_stderr=True, timeout=5,
port_range=10, extra_callbacks=None,
self,
arg,
custom_script='',
logfile='',
print_stdout=True,
print_stderr=True,
timeout=5,
port_range=10,
extra_callbacks=None,
):
if logfile == '':
logfile = 'daemon_%s.log' % self.id()
@ -59,8 +66,10 @@ class DaemonBase(object):
for dummy in range(port_range):
try:
d, self.core = common.start_core(
listen_port=self.listen_port, logfile=logfile,
timeout=timeout, timeout_msg='Timeout!',
listen_port=self.listen_port,
logfile=logfile,
timeout=timeout,
timeout_msg='Timeout!',
custom_script=custom_script,
print_stdout=print_stdout,
print_stderr=print_stderr,

View File

@ -14,7 +14,6 @@ from .basetest import BaseTestCase
class AlertManagerTestCase(BaseTestCase):
def set_up(self):
self.core = Core()
self.core.config.config['lsd'] = False

View File

@ -24,7 +24,4 @@ class AuthManagerTestCase(BaseTestCase):
return component.shutdown()
def test_authorize(self):
self.assertEqual(
self.auth.authorize(*get_localhost_auth()),
AUTH_LEVEL_ADMIN,
)
self.assertEqual(self.auth.authorize(*get_localhost_auth()), AUTH_LEVEL_ADMIN)

View File

@ -14,7 +14,6 @@ from . import common
class BencodeTestCase(unittest.TestCase):
def test_bencode_unicode_metainfo(self):
filename = common.get_test_data_file('test.torrent')
with open(filename, 'rb') as _file:

View File

@ -35,9 +35,12 @@ class NoVersionSendingDaemonSSLProxy(DaemonSSLProxy):
class NoVersionSendingClient(Client):
def connect(
self, host='127.0.0.1', port=58846, username='', password='',
self,
host='127.0.0.1',
port=58846,
username='',
password='',
skip_authentication=False,
):
self._daemon_proxy = NoVersionSendingDaemonSSLProxy()
@ -104,7 +107,9 @@ class ClientTestCase(BaseTestCase, DaemonBase):
def test_connect_localclient(self):
username, password = get_localhost_auth()
d = client.connect('localhost', self.listen_port, username=username, password=password)
d = client.connect(
'localhost', self.listen_port, username=username, password=password
)
def on_connect(result):
self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
@ -116,13 +121,12 @@ class ClientTestCase(BaseTestCase, DaemonBase):
def test_connect_bad_password(self):
username, password = get_localhost_auth()
d = client.connect('localhost', self.listen_port, username=username, password=password + '1')
d = client.connect(
'localhost', self.listen_port, username=username, password=password + '1'
)
def on_failure(failure):
self.assertEqual(
failure.trap(error.BadLoginError),
error.BadLoginError,
)
self.assertEqual(failure.trap(error.BadLoginError), error.BadLoginError)
self.assertEqual(failure.value.message, 'Password does not match')
self.addCleanup(client.disconnect)
@ -134,10 +138,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
d = client.connect('localhost', self.listen_port, username='invalid-user')
def on_failure(failure):
self.assertEqual(
failure.trap(error.BadLoginError),
error.BadLoginError,
)
self.assertEqual(failure.trap(error.BadLoginError), error.BadLoginError)
self.assertEqual(failure.value.message, 'Username does not exist')
self.addCleanup(client.disconnect)
@ -150,8 +151,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
def on_failure(failure):
self.assertEqual(
failure.trap(error.AuthenticationRequired),
error.AuthenticationRequired,
failure.trap(error.AuthenticationRequired), error.AuthenticationRequired
)
self.assertEqual(failure.value.username, username)
self.addCleanup(client.disconnect)
@ -162,10 +162,14 @@ class ClientTestCase(BaseTestCase, DaemonBase):
@defer.inlineCallbacks
def test_connect_with_password(self):
username, password = get_localhost_auth()
yield client.connect('localhost', self.listen_port, username=username, password=password)
yield client.connect(
'localhost', self.listen_port, username=username, password=password
)
yield client.core.create_account('testuser', 'testpw', 'DEFAULT')
yield client.disconnect()
ret = yield client.connect('localhost', self.listen_port, username='testuser', password='testpw')
ret = yield client.connect(
'localhost', self.listen_port, username='testuser', password='testpw'
)
self.assertEqual(ret, AUTH_LEVEL_NORMAL)
yield
@ -175,8 +179,11 @@ class ClientTestCase(BaseTestCase, DaemonBase):
d = client.core.invalid_method()
def on_failure(failure):
self.assertEqual(failure.trap(error.WrappedException), error.WrappedException)
self.assertEqual(
failure.trap(error.WrappedException), error.WrappedException
)
self.addCleanup(client.disconnect)
d.addCallbacks(self.fail, on_failure)
yield d
@ -184,13 +191,12 @@ class ClientTestCase(BaseTestCase, DaemonBase):
username, password = get_localhost_auth()
no_version_sending_client = NoVersionSendingClient()
d = no_version_sending_client.connect(
'localhost', self.listen_port, username=username, password=password,
'localhost', self.listen_port, username=username, password=password
)
def on_failure(failure):
self.assertEqual(
failure.trap(error.IncompatibleClient),
error.IncompatibleClient,
failure.trap(error.IncompatibleClient), error.IncompatibleClient
)
self.addCleanup(no_version_sending_client.disconnect)

View File

@ -12,8 +12,24 @@ import tarfile
from twisted.trial import unittest
from deluge.common import (VersionSplit, archive_files, fdate, fpcnt, fpeer, fsize, fspeed, ftime, get_path_size,
is_infohash, is_ip, is_ipv4, is_ipv6, is_magnet, is_url, windows_check)
from deluge.common import (
VersionSplit,
archive_files,
fdate,
fpcnt,
fpeer,
fsize,
fspeed,
ftime,
get_path_size,
is_infohash,
is_ip,
is_ipv4,
is_ipv6,
is_magnet,
is_url,
windows_check,
)
from deluge.ui.translations_util import setup_translations
from .common import get_test_data_file, set_tmp_config_dir
@ -73,7 +89,9 @@ class CommonTestCase(unittest.TestCase):
self.assertFalse(is_url('file://test.torrent'))
def test_is_magnet(self):
self.assertTrue(is_magnet('magnet:?xt=urn:btih:SU5225URMTUEQLDXQWRB2EQWN6KLTYKN'))
self.assertTrue(
is_magnet('magnet:?xt=urn:btih:SU5225URMTUEQLDXQWRB2EQWN6KLTYKN')
)
self.assertFalse(is_magnet(None))
def test_is_infohash(self):
@ -121,23 +139,26 @@ class CommonTestCase(unittest.TestCase):
def test_parse_human_size(self):
from deluge.common import parse_human_size
sizes = [
('1', 1),
('10 bytes', 10),
('2048 bytes', 2048),
('1MiB', 2**(10 * 2)),
('1 MiB', 2**(10 * 2)),
('1 GiB', 2**(10 * 3)),
('1 GiB', 2**(10 * 3)),
('1M', 10**6),
('1MB', 10**6),
('1 GB', 10**9),
('1 TB', 10**12),
('1MiB', 2 ** (10 * 2)),
('1 MiB', 2 ** (10 * 2)),
('1 GiB', 2 ** (10 * 3)),
('1 GiB', 2 ** (10 * 3)),
('1M', 10 ** 6),
('1MB', 10 ** 6),
('1 GB', 10 ** 9),
('1 TB', 10 ** 12),
]
for human_size, byte_size in sizes:
parsed = parse_human_size(human_size)
self.assertEqual(parsed, byte_size, 'Mismatch when converting: %s' % human_size)
self.assertEqual(
parsed, byte_size, 'Mismatch when converting: %s' % human_size
)
def test_archive_files(self):
arc_filelist = [
@ -149,4 +170,6 @@ class CommonTestCase(unittest.TestCase):
with tarfile.open(arc_filepath, 'r') as tar:
for tar_info in tar:
self.assertTrue(tar_info.isfile())
self.assertTrue(tar_info.name in [os.path.basename(arcf) for arcf in arc_filelist])
self.assertTrue(
tar_info.name in [os.path.basename(arcf) for arcf in arc_filelist]
)

View File

@ -16,7 +16,6 @@ from .basetest import BaseTestCase
class ComponentTester(component.Component):
def __init__(self, name, depend=None):
component.Component.__init__(self, name, depend=depend)
self.start_count = 0
@ -30,20 +29,21 @@ class ComponentTester(component.Component):
class ComponentTesterDelayStart(ComponentTester):
def start(self):
def do_sleep():
import time
time.sleep(1)
d = threads.deferToThread(do_sleep)
def on_done(result):
self.start_count += 1
return d.addCallback(on_done)
class ComponentTesterUpdate(component.Component):
def __init__(self, name):
component.Component.__init__(self, name)
self.counter = 0
@ -58,7 +58,6 @@ class ComponentTesterUpdate(component.Component):
class ComponentTesterShutdown(component.Component):
def __init__(self, name):
component.Component.__init__(self, name)
self.shutdowned = False
@ -72,7 +71,6 @@ class ComponentTesterShutdown(component.Component):
class ComponentTestClass(BaseTestCase):
def tear_down(self):
return component.shutdown()
@ -98,7 +96,9 @@ class ComponentTestClass(BaseTestCase):
self.assertEqual(c2._component_state, 'Started')
self.assertEqual(c1.start_count, 1)
self.assertEqual(c2.start_count, 1)
return component.stop(['test_start_depends_c1']).addCallback(on_stop, c1, c2)
return component.stop(['test_start_depends_c1']).addCallback(
on_stop, c1, c2
)
c1 = ComponentTester('test_start_depends_c1')
c2 = ComponentTester('test_start_depends_c2', depend=['test_start_depends_c1'])
@ -110,7 +110,9 @@ class ComponentTestClass(BaseTestCase):
def start_with_depends(self):
c1 = ComponentTesterDelayStart('test_start_all_c1')
c2 = ComponentTester('test_start_all_c2', depend=['test_start_all_c4'])
c3 = ComponentTesterDelayStart('test_start_all_c3', depend=['test_start_all_c5', 'test_start_all_c1'])
c3 = ComponentTesterDelayStart(
'test_start_all_c3', depend=['test_start_all_c5', 'test_start_all_c1']
)
c4 = ComponentTester('test_start_all_c4', depend=['test_start_all_c3'])
c5 = ComponentTester('test_start_all_c5')
@ -214,8 +216,12 @@ class ComponentTestClass(BaseTestCase):
try:
result = self.failureResultOf(test_comp._component_start())
except AttributeError:
raise SkipTest('This test requires trial failureResultOf() in Twisted version >= 13')
self.assertEqual(result.check(component.ComponentException), component.ComponentException)
raise SkipTest(
'This test requires trial failureResultOf() in Twisted version >= 13'
)
self.assertEqual(
result.check(component.ComponentException), component.ComponentException
)
@defer.inlineCallbacks
def test_start_paused_error(self):
@ -231,14 +237,17 @@ class ComponentTestClass(BaseTestCase):
result = yield component.start()
self.assertEqual(
[(result[0][0], result[0][1].value)],
[(
defer.FAILURE,
component.ComponentException(
'Trying to start component "%s" but it is '
'not in a stopped state. Current state: %s' %
('test_pause_c1', 'Paused'), '',
),
)],
[
(
defer.FAILURE,
component.ComponentException(
'Trying to start component "%s" but it is '
'not in a stopped state. Current state: %s'
% ('test_pause_c1', 'Paused'),
'',
),
)
],
)
def test_shutdown(self):

View File

@ -19,7 +19,13 @@ from deluge.config import Config
from .common import set_tmp_config_dir
DEFAULTS = {'string': 'foobar', 'int': 1, 'float': 0.435, 'bool': True, 'unicode': 'foobar'}
DEFAULTS = {
'string': 'foobar',
'int': 1,
'float': 0.435,
'bool': True,
'unicode': 'foobar',
}
class ConfigTestCase(unittest.TestCase):
@ -92,6 +98,7 @@ class ConfigTestCase(unittest.TestCase):
# Test opening a previous 1.2 config file of just a json object
import json
with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file:
json.dump(DEFAULTS, getwriter('utf8')(_file), **JSON_FORMAT)

View File

@ -35,22 +35,24 @@ common.disable_new_release_check()
class CookieResource(Resource):
def render(self, request):
if request.getCookie(b'password') != b'deluge':
request.setResponseCode(FORBIDDEN)
return
request.setHeader(b'Content-Type', b'application/x-bittorrent')
with open(common.get_test_data_file('ubuntu-9.04-desktop-i386.iso.torrent'), 'rb') as _file:
with open(
common.get_test_data_file('ubuntu-9.04-desktop-i386.iso.torrent'), 'rb'
) as _file:
data = _file.read()
return data
class PartialDownload(Resource):
def render(self, request):
with open(common.get_test_data_file('ubuntu-9.04-desktop-i386.iso.torrent'), 'rb') as _file:
with open(
common.get_test_data_file('ubuntu-9.04-desktop-i386.iso.torrent'), 'rb'
) as _file:
data = _file.read()
request.setHeader(b'Content-Type', len(data))
request.setHeader(b'Content-Type', b'application/x-bittorrent')
@ -60,7 +62,6 @@ class PartialDownload(Resource):
class RedirectResource(Resource):
def render(self, request):
request.redirect(b'/ubuntu-9.04-desktop-i386.iso.torrent')
return b''
@ -82,7 +83,6 @@ class TopLevelResource(Resource):
class CoreTestCase(BaseTestCase):
def set_up(self):
common.set_tmp_config_dir()
self.rpcserver = RPCServer(listen=False)
@ -109,7 +109,6 @@ class CoreTestCase(BaseTestCase):
return result
def tear_down(self):
def on_shutdown(result):
del self.rpcserver
del self.core
@ -120,11 +119,7 @@ class CoreTestCase(BaseTestCase):
def add_torrent(self, filename, paused=False):
if not paused:
# Patch libtorrent flags starting torrents paused
self.patch(
deluge.core.torrentmanager,
'LT_DEFAULT_ADD_TORRENT_FLAGS',
592,
)
self.patch(deluge.core.torrentmanager, 'LT_DEFAULT_ADD_TORRENT_FLAGS', 592)
options = {'add_paused': paused, 'auto_managed': False}
filepath = common.get_test_data_file(filename)
with open(filepath, 'rb') as _file:
@ -169,6 +164,7 @@ class CoreTestCase(BaseTestCase):
# Get the info hash from the test.torrent
from deluge.bencode import bdecode, bencode
with open(filename, 'rb') as _file:
info_hash = sha(bencode(bdecode(_file.read())[b'info'])).hexdigest()
self.assertEqual(torrent_id, info_hash)
@ -176,11 +172,16 @@ class CoreTestCase(BaseTestCase):
def test_add_torrent_file_invalid_filedump(self):
options = {}
filename = common.get_test_data_file('test.torrent')
self.assertRaises(AddTorrentError, self.core.add_torrent_file, filename, False, options)
self.assertRaises(
AddTorrentError, self.core.add_torrent_file, filename, False, options
)
@defer.inlineCallbacks
def test_add_torrent_url(self):
url = 'http://localhost:%d/ubuntu-9.04-desktop-i386.iso.torrent' % self.listen_port
url = (
'http://localhost:%d/ubuntu-9.04-desktop-i386.iso.torrent'
% self.listen_port
)
options = {}
info_hash = '60d5d82328b4547511fdeac9bf4d0112daa0ce00'
@ -340,7 +341,12 @@ class CoreTestCase(BaseTestCase):
self.assertEqual(len(self.core.get_session_state()), 0)
def test_remove_torrent_invalid(self):
self.assertRaises(InvalidTorrentError, self.core.remove_torrent, 'torrentidthatdoesntexist', True)
self.assertRaises(
InvalidTorrentError,
self.core.remove_torrent,
'torrentidthatdoesntexist',
True,
)
@defer.inlineCallbacks
def test_remove_torrents(self):
@ -353,15 +359,19 @@ class CoreTestCase(BaseTestCase):
filename2 = common.get_test_data_file('unicode_filenames.torrent')
with open(filename2, 'rb') as _file:
filedump = b64encode(_file.read())
torrent_id2 = yield self.core.add_torrent_file_async(filename2, filedump, options)
torrent_id2 = yield self.core.add_torrent_file_async(
filename2, filedump, options
)
d = self.core.remove_torrents([torrent_id, torrent_id2], True)
def test_ret(val):
self.assertTrue(val == [])
d.addCallback(test_ret)
def test_session_state(val):
self.assertEqual(len(self.core.get_session_state()), 0)
d.addCallback(test_session_state)
yield d
@ -371,15 +381,24 @@ class CoreTestCase(BaseTestCase):
filename = common.get_test_data_file('test.torrent')
with open(filename, 'rb') as _file:
filedump = b64encode(_file.read())
torrent_id = yield self.core.add_torrent_file_async(filename, filedump, options)
val = yield self.core.remove_torrents(['invalidid1', 'invalidid2', torrent_id], False)
torrent_id = yield self.core.add_torrent_file_async(
filename, filedump, options
)
val = yield self.core.remove_torrents(
['invalidid1', 'invalidid2', torrent_id], False
)
self.assertEqual(len(val), 2)
self.assertEqual(val[0], ('invalidid1', 'torrent_id invalidid1 not in session.'))
self.assertEqual(val[1], ('invalidid2', 'torrent_id invalidid2 not in session.'))
self.assertEqual(
val[0], ('invalidid1', 'torrent_id invalidid1 not in session.')
)
self.assertEqual(
val[1], ('invalidid2', 'torrent_id invalidid2 not in session.')
)
def test_get_session_status(self):
status = self.core.get_session_status(
['net.recv_tracker_bytes', 'net.sent_tracker_bytes'])
['net.recv_tracker_bytes', 'net.sent_tracker_bytes']
)
self.assertIsInstance(status, dict)
self.assertEqual(status['net.recv_tracker_bytes'], 0)
self.assertEqual(status['net.sent_tracker_bytes'], 0)
@ -402,8 +421,7 @@ class CoreTestCase(BaseTestCase):
self.assertEqual(status['upload_rate'], 0)
def test_get_session_status_ratio(self):
status = self.core.get_session_status([
'write_hit_ratio', 'read_hit_ratio'])
status = self.core.get_session_status(['write_hit_ratio', 'read_hit_ratio'])
self.assertIsInstance(status, dict)
self.assertEqual(status['write_hit_ratio'], 0.0)
self.assertEqual(status['read_hit_ratio'], 0.0)
@ -438,14 +456,23 @@ class CoreTestCase(BaseTestCase):
}
for key in pathlist:
self.assertEqual(deluge.core.torrent.sanitize_filepath(key, folder=False), pathlist[key])
self.assertEqual(deluge.core.torrent.sanitize_filepath(key, folder=True), pathlist[key] + '/')
self.assertEqual(
deluge.core.torrent.sanitize_filepath(key, folder=False), pathlist[key]
)
self.assertEqual(
deluge.core.torrent.sanitize_filepath(key, folder=True),
pathlist[key] + '/',
)
def test_get_set_config_values(self):
self.assertEqual(self.core.get_config_values(['abc', 'foo']), {'foo': None, 'abc': None})
self.assertEqual(
self.core.get_config_values(['abc', 'foo']), {'foo': None, 'abc': None}
)
self.assertEqual(self.core.get_config_value('foobar'), None)
self.core.set_config({'abc': 'def', 'foo': 10, 'foobar': 'barfoo'})
self.assertEqual(self.core.get_config_values(['foo', 'abc']), {'foo': 10, 'abc': 'def'})
self.assertEqual(
self.core.get_config_values(['foo', 'abc']), {'foo': 10, 'abc': 'def'}
)
self.assertEqual(self.core.get_config_value('foobar'), 'barfoo')
def test_read_only_config_keys(self):

View File

@ -24,6 +24,7 @@ class ErrorTestCase(unittest.TestCase):
e = deluge.error.DelugeError(msg)
self.assertEqual(str(e), msg)
from twisted.internet.defer import DebugInfo
del DebugInfo.__del__ # Hides all errors
self.assertEqual(e._args, (msg,))
self.assertEqual(e._kwargs, {})
@ -32,15 +33,19 @@ class ErrorTestCase(unittest.TestCase):
version = '1.3.6'
e = deluge.error.IncompatibleClient(version)
self.assertEqual(
str(e), 'Your deluge client is not compatible with the daemon. \
Please upgrade your client to %s' % version,
str(e),
'Your deluge client is not compatible with the daemon. \
Please upgrade your client to %s'
% version,
)
def test_not_authorized_error(self):
current_level = 5
required_level = 10
e = deluge.error.NotAuthorizedError(current_level, required_level)
self.assertEqual(str(e), 'Auth level too low: %d < %d' % (current_level, required_level))
self.assertEqual(
str(e), 'Auth level too low: %d < %d' % (current_level, required_level)
)
def test_bad_login_error(self):
message = 'Login failed'

View File

@ -28,6 +28,7 @@ try:
except ImportError as err:
libs_available = False
import traceback
traceback.print_exc()
setup_translations()
@ -35,7 +36,6 @@ setup_translations()
@pytest.mark.gtkui
class FilesTabTestCase(BaseTestCase):
def set_up(self):
if libs_available is False:
raise unittest.SkipTest('GTKUI dependencies not available')
@ -70,7 +70,6 @@ class FilesTabTestCase(BaseTestCase):
print('')
def verify_treestore(self, treestore, tree):
def _verify_treestore(itr, tree_values):
i = 0
while itr:
@ -83,6 +82,7 @@ class FilesTabTestCase(BaseTestCase):
itr = treestore.iter_next(itr)
i += 1
return True
return _verify_treestore(treestore.get_iter_root(), tree)
def test_files_tab(self):
@ -91,9 +91,14 @@ class FilesTabTestCase(BaseTestCase):
{'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
)
self.filestab.update_files()
self.filestab._on_torrentfilerenamed_event(self.t_id, self.index, '2/test_100.txt')
self.filestab._on_torrentfilerenamed_event(
self.t_id, self.index, '2/test_100.txt'
)
ret = self.verify_treestore(self.filestab.treestore, [['1/', [['test_10.txt']]], ['2/', [['test_100.txt']]]])
ret = self.verify_treestore(
self.filestab.treestore,
[['1/', [['test_10.txt']]], ['2/', [['test_100.txt']]]],
)
if not ret:
self.print_treestore('Treestore not expected:', self.filestab.treestore)
self.assertTrue(ret)
@ -106,9 +111,14 @@ class FilesTabTestCase(BaseTestCase):
{'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
)
self.filestab.update_files()
self.filestab._on_torrentfilerenamed_event(self.t_id, self.index, '1/1/test_100.txt')
self.filestab._on_torrentfilerenamed_event(
self.t_id, self.index, '1/1/test_100.txt'
)
ret = self.verify_treestore(self.filestab.treestore, [['1/', [['1/', [['test_100.txt'], ['test_10.txt']]]]]])
ret = self.verify_treestore(
self.filestab.treestore,
[['1/', [['1/', [['test_100.txt'], ['test_10.txt']]]]]],
)
if not ret:
self.print_treestore('Treestore not expected:', self.filestab.treestore)
self.assertTrue(ret)
@ -121,9 +131,13 @@ class FilesTabTestCase(BaseTestCase):
{'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
)
self.filestab.update_files()
self.filestab._on_torrentfilerenamed_event(self.t_id, self.index, '1/test_100.txt')
self.filestab._on_torrentfilerenamed_event(
self.t_id, self.index, '1/test_100.txt'
)
ret = self.verify_treestore(self.filestab.treestore, [['1/', [['test_100.txt'], ['test_10.txt']]]])
ret = self.verify_treestore(
self.filestab.treestore, [['1/', [['test_100.txt'], ['test_10.txt']]]]
)
if not ret:
self.print_treestore('Treestore not expected:', self.filestab.treestore)
self.assertTrue(ret)
@ -134,15 +148,13 @@ class FilesTabTestCase(BaseTestCase):
{'index': 1, 'path': '1/test_100.txt', 'offset': 13, 'size': 14},
)
self.filestab.update_files()
self.filestab._on_torrentfilerenamed_event(self.t_id, self.index, '1/2/test_100.txt')
self.filestab._on_torrentfilerenamed_event(
self.t_id, self.index, '1/2/test_100.txt'
)
ret = self.verify_treestore(
self.filestab.treestore, [[
'1/', [
['2/', [['test_100.txt']]],
['test_10.txt'],
],
]],
self.filestab.treestore,
[['1/', [['2/', [['test_100.txt']]], ['test_10.txt']]]],
)
if not ret:
self.print_treestore('Treestore not expected:', self.filestab.treestore)
@ -156,9 +168,13 @@ class FilesTabTestCase(BaseTestCase):
{'index': 1, 'path': '2/test_100.txt', 'offset': 13, 'size': 14},
)
self.filestab.update_files()
self.filestab._on_torrentfilerenamed_event(self.t_id, self.index, '1/test_100.txt')
self.filestab._on_torrentfilerenamed_event(
self.t_id, self.index, '1/test_100.txt'
)
ret = self.verify_treestore(self.filestab.treestore, [['1/', [['test_100.txt'], ['test_10.txt']]]])
ret = self.verify_treestore(
self.filestab.treestore, [['1/', [['test_100.txt'], ['test_10.txt']]]]
)
if not ret:
self.print_treestore('Treestore not expected:', self.filestab.treestore)
self.assertTrue(ret)

View File

@ -33,26 +33,20 @@ def fname(name):
class RedirectResource(Resource):
def render(self, request):
url = self.get_url().encode('utf8')
return redirectTo(url, request)
class RenameResource(Resource):
def render(self, request):
filename = request.args.get(b'filename', [b'renamed_file'])[0]
request.setHeader(b'Content-Type', b'text/plain')
request.setHeader(
b'Content-Disposition', b'attachment; filename=' +
filename,
)
request.setHeader(b'Content-Disposition', b'attachment; filename=' + filename)
return b'This file should be called ' + filename
class AttachmentResource(Resource):
def render(self, request):
request.setHeader(b'Content-Type', b'text/plain')
request.setHeader(b'Content-Disposition', b'attachment')
@ -60,7 +54,6 @@ class AttachmentResource(Resource):
class CookieResource(Resource):
def render(self, request):
request.setHeader(b'Content-Type', b'text/plain')
if request.getCookie(b'password') is None:
@ -73,7 +66,6 @@ class CookieResource(Resource):
class GzipResource(Resource):
def render(self, request):
message = request.args.get(b'msg', [b'EFFICIENCY!'])[0]
request.setHeader(b'Content-Type', b'text/plain')
@ -81,7 +73,6 @@ class GzipResource(Resource):
class PartialDownloadResource(Resource):
def __init__(self, *args, **kwargs):
Resource.__init__(self)
self.render_count = 0
@ -126,7 +117,6 @@ class TopLevelResource(Resource):
class DownloadFileTestCase(unittest.TestCase):
def get_url(self, path=''):
return 'http://localhost:%d/%s' % (self.listen_port, path)
@ -245,6 +235,7 @@ class DownloadFileTestCase(unittest.TestCase):
def cb(result):
print(result)
d.addCallback(self.assertNotContains, b'fail', file_mode='rb')
return d
@ -255,6 +246,7 @@ class DownloadFileTestCase(unittest.TestCase):
def on_redirect(failure):
self.assertTrue(type(failure), PageRedirect)
d.addErrback(on_redirect)
return d

View File

@ -34,10 +34,11 @@ common.disable_new_release_check()
class JSONBase(BaseTestCase, DaemonBase):
def connect_client(self, *args, **kwargs):
return client.connect(
'localhost', self.listen_port, username=kwargs.get('user', ''),
'localhost',
self.listen_port,
username=kwargs.get('user', ''),
password=kwargs.get('password', ''),
)
@ -52,7 +53,6 @@ class JSONBase(BaseTestCase, DaemonBase):
class JSONTestCase(JSONBase):
def set_up(self):
d = self.common_set_up()
d.addCallback(self.start_core)
@ -81,6 +81,7 @@ class JSONTestCase(JSONBase):
def compress(contents, request):
return contents
self.patch(deluge.ui.web.json_api, 'compress', compress)
def write(response_str):
@ -88,7 +89,9 @@ class JSONTestCase(JSONBase):
response = json_lib.loads(response_str)
self.assertEqual(response['result'], None)
self.assertEqual(response['id'], None)
self.assertEqual(response['error']['message'], 'JSONException: JSON not decodable')
self.assertEqual(
response['error']['message'], 'JSONException: JSON not decodable'
)
self.assertEqual(response['error']['code'], 5)
request.write = write
@ -127,7 +130,6 @@ class JSONTestCase(JSONBase):
class JSONCustomUserTestCase(JSONBase):
def set_up(self):
d = self.common_set_up()
d.addCallback(self.start_core)
@ -152,7 +154,6 @@ class JSONCustomUserTestCase(JSONBase):
class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
def set_up(self):
d = self.common_set_up()
custom_script = """
@ -176,6 +177,7 @@ class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
def get_session_id(s_id):
return s_id
self.patch(deluge.ui.web.auth, 'get_session_id', get_session_id)
auth_conf = {'session_timeout': 10, 'sessions': {}}
auth = Auth(auth_conf)
@ -195,12 +197,12 @@ class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
def on_error(error):
self.assertEqual(error.type, DelugeError)
result.addErrback(on_error)
yield result
class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
def set_up(self):
d = self.common_set_up()
custom_script = """
@ -219,13 +221,17 @@ class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
daemon.rpcserver.register_object(test)
"""
from twisted.internet.defer import Deferred
extra_callback = {
'deferred': Deferred(), 'types': ['stderr'],
'deferred': Deferred(),
'types': ['stderr'],
'timeout': 10,
'triggers': [{
'expr': 'in test_raise_error',
'value': lambda reader, data, data_all: 'Test',
}],
'triggers': [
{
'expr': 'in test_raise_error',
'value': lambda reader, data, data_all: 'Test',
}
],
}
def on_test_raise(*args):
@ -234,8 +240,12 @@ class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
extra_callback['deferred'].addCallback(on_test_raise)
d.addCallback(
self.start_core, custom_script=custom_script, print_stdout=False, print_stderr=False,
timeout=5, extra_callbacks=[extra_callback],
self.start_core,
custom_script=custom_script,
print_stdout=False,
print_stderr=False,
timeout=5,
extra_callbacks=[extra_callback],
)
d.addCallbacks(self.connect_client, self.terminate_core)
return d
@ -278,5 +288,6 @@ class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
def on_success(arg):
self.assertEqual(arg, server.NOT_DONE_YET)
return True
d.addCallbacks(on_success, self.fail)
yield d

Some files were not shown because too many files have changed in this diff Show More