[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:
parent
bcca07443c
commit
b1cdc32f73
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
294
deluge/common.py
294
deluge/common.py
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -27,7 +27,6 @@ def print_totals(totals):
|
|||
|
||||
|
||||
class StatsTestCase(BaseTestCase):
|
||||
|
||||
def set_up(self):
|
||||
defer.setDebugging(True)
|
||||
tests_common.set_tmp_config_dir()
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -22,8 +22,7 @@ from deluge.plugins.pluginbase import CorePluginBase
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
}
|
||||
DEFAULT_PREFS = {}
|
||||
|
||||
|
||||
class Core(CorePluginBase):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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..')
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -14,7 +14,6 @@ from .basetest import BaseTestCase
|
|||
|
||||
|
||||
class AlertManagerTestCase(BaseTestCase):
|
||||
|
||||
def set_up(self):
|
||||
self.core = Core()
|
||||
self.core.config.config['lsd'] = False
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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]
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue