[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):
|
def scan_for_methods(obj):
|
||||||
methods = {
|
methods = {'__doc__': 'Methods available in %s' % obj.__name__.lower()}
|
||||||
'__doc__': 'Methods available in %s' % obj.__name__.lower(),
|
|
||||||
}
|
|
||||||
for d in dir(obj):
|
for d in dir(obj):
|
||||||
if not hasattr(getattr(obj, d), '_rpcserver_export'):
|
if not hasattr(getattr(obj, d), '_rpcserver_export'):
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -28,4 +28,6 @@ REQUIRED_VERSION = '1.1.2.0'
|
|||||||
LT_VERSION = lt.__version__
|
LT_VERSION = lt.__version__
|
||||||
|
|
||||||
if VersionSplit(LT_VERSION) < VersionSplit(REQUIRED_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)
|
||||||
|
)
|
||||||
|
|||||||
150
deluge/common.py
150
deluge/common.py
@ -44,6 +44,7 @@ except ImportError:
|
|||||||
# see: https://twistedmatrix.com/trac/ticket/9209
|
# see: https://twistedmatrix.com/trac/ticket/9209
|
||||||
if platform.system() in ('Windows', 'Microsoft'):
|
if platform.system() in ('Windows', 'Microsoft'):
|
||||||
from certifi import where
|
from certifi import where
|
||||||
|
|
||||||
os.environ['SSL_CERT_FILE'] = where()
|
os.environ['SSL_CERT_FILE'] = where()
|
||||||
|
|
||||||
DBUS_FILEMAN = None
|
DBUS_FILEMAN = None
|
||||||
@ -56,7 +57,9 @@ if platform.system() not in ('Windows', 'Microsoft', 'Darwin'):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
bus = dbus.SessionBus()
|
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:
|
except dbus.DBusException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -101,6 +104,7 @@ def get_default_config_dir(filename=None):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if windows_check():
|
if windows_check():
|
||||||
|
|
||||||
def save_config_path(resource):
|
def save_config_path(resource):
|
||||||
app_data_path = os.environ.get('APPDATA')
|
app_data_path = os.environ.get('APPDATA')
|
||||||
if not app_data_path:
|
if not app_data_path:
|
||||||
@ -116,6 +120,7 @@ def get_default_config_dir(filename=None):
|
|||||||
app_data_path = app_data_reg[0]
|
app_data_path = app_data_reg[0]
|
||||||
winreg.CloseKey(hkey)
|
winreg.CloseKey(hkey)
|
||||||
return os.path.join(app_data_path, resource)
|
return os.path.join(app_data_path, resource)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
from xdg.BaseDirectory import save_config_path
|
from xdg.BaseDirectory import save_config_path
|
||||||
if not filename:
|
if not filename:
|
||||||
@ -136,11 +141,14 @@ def get_default_download_dir():
|
|||||||
download_dir = ''
|
download_dir = ''
|
||||||
if not windows_check():
|
if not windows_check():
|
||||||
from xdg.BaseDirectory import xdg_config_home
|
from xdg.BaseDirectory import xdg_config_home
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(xdg_config_home, 'user-dirs.dirs'), 'r') as _file:
|
with open(os.path.join(xdg_config_home, 'user-dirs.dirs'), 'r') as _file:
|
||||||
for line in _file:
|
for line in _file:
|
||||||
if not line.startswith('#') and line.startswith('XDG_DOWNLOAD_DIR'):
|
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
|
break
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
@ -170,8 +178,12 @@ def archive_files(arc_name, filepaths):
|
|||||||
arc_comp = 'xz' if not PY2 else 'bz2'
|
arc_comp = 'xz' if not PY2 else 'bz2'
|
||||||
|
|
||||||
archive_dir = os.path.join(get_config_dir(), 'archive')
|
archive_dir = os.path.join(get_config_dir(), 'archive')
|
||||||
timestamp = datetime.datetime.now().replace(microsecond=0).isoformat().replace(':', '-')
|
timestamp = (
|
||||||
arc_filepath = os.path.join(archive_dir, arc_name + '-' + timestamp + '.tar.' + arc_comp)
|
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
|
max_num_arcs = 20
|
||||||
|
|
||||||
if not os.path.exists(archive_dir):
|
if not os.path.exists(archive_dir):
|
||||||
@ -281,7 +293,7 @@ def resource_filename(module, path):
|
|||||||
This is a work-around that.
|
This is a work-around that.
|
||||||
"""
|
"""
|
||||||
return pkg_resources.require('Deluge>=%s' % get_version())[0].get_resource_filename(
|
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:
|
if timestamp is None:
|
||||||
timestamp = int(time.time())
|
timestamp = int(time.time())
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env['DESKTOP_STARTUP_ID'] = '%s-%u-%s-xdg_open_TIME%d' % \
|
env['DESKTOP_STARTUP_ID'] = '%s-%u-%s-xdg_open_TIME%d' % (
|
||||||
(os.path.basename(sys.argv[0]), os.getpid(), os.uname()[1], timestamp)
|
os.path.basename(sys.argv[0]),
|
||||||
|
os.getpid(),
|
||||||
|
os.uname()[1],
|
||||||
|
timestamp,
|
||||||
|
)
|
||||||
subprocess.Popen(['xdg-open', '%s' % path], env=env)
|
subprocess.Popen(['xdg-open', '%s' % path], env=env)
|
||||||
|
|
||||||
|
|
||||||
@ -321,10 +337,17 @@ def show_file(path, timestamp=None):
|
|||||||
else:
|
else:
|
||||||
if timestamp is None:
|
if timestamp is None:
|
||||||
timestamp = int(time.time())
|
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:
|
if DBUS_FILEMAN:
|
||||||
paths = [urljoin('file:', pathname2url(path))]
|
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:
|
else:
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env['DESKTOP_STARTUP_ID'] = startup_id.replace('dbus', 'xdg-open')
|
env['DESKTOP_STARTUP_ID'] = startup_id.replace('dbus', 'xdg-open')
|
||||||
@ -341,6 +364,7 @@ def open_url_in_browser(url):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
webbrowser.open(url)
|
webbrowser.open(url)
|
||||||
|
|
||||||
|
|
||||||
@ -396,13 +420,29 @@ def fsize(fsize_b, precision=1, shortform=False):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if fsize_b >= 1024 ** 4:
|
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:
|
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:
|
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:
|
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:
|
else:
|
||||||
return '%d %s' % (fsize_b, byte_txt)
|
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:
|
if pcnt == 0 or pcnt == 100:
|
||||||
precision = 0
|
precision = 0
|
||||||
return '%.*f%%' % (precision, pcnt)
|
return '%.*f%%' % (precision, pcnt)
|
||||||
@ -447,13 +487,29 @@ def fspeed(bps, precision=1, shortform=False):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if bps < 1024 ** 2:
|
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:
|
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:
|
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:
|
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):
|
def fpeer(num_peers, total_peers):
|
||||||
@ -736,7 +792,12 @@ def get_magnet_info(uri):
|
|||||||
if info_hash:
|
if info_hash:
|
||||||
if not name:
|
if not name:
|
||||||
name = info_hash
|
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:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@ -758,11 +819,7 @@ def create_magnet_uri(infohash, name=None, trackers=None):
|
|||||||
except TypeError:
|
except TypeError:
|
||||||
infohash.encode('utf-8')
|
infohash.encode('utf-8')
|
||||||
|
|
||||||
uri = [
|
uri = [MAGNET_SCHEME, XT_BTIH_PARAM, base64.b32encode(infohash).decode('utf-8')]
|
||||||
MAGNET_SCHEME,
|
|
||||||
XT_BTIH_PARAM,
|
|
||||||
base64.b32encode(infohash).decode('utf-8'),
|
|
||||||
]
|
|
||||||
if name:
|
if name:
|
||||||
uri.extend(['&', DN_PARAM, name])
|
uri.extend(['&', DN_PARAM, name])
|
||||||
if trackers:
|
if trackers:
|
||||||
@ -817,6 +874,7 @@ def free_space(path):
|
|||||||
|
|
||||||
if windows_check():
|
if windows_check():
|
||||||
from win32file import GetDiskFreeSpaceEx
|
from win32file import GetDiskFreeSpaceEx
|
||||||
|
|
||||||
return GetDiskFreeSpaceEx(path)[0]
|
return GetDiskFreeSpaceEx(path)[0]
|
||||||
else:
|
else:
|
||||||
disk_data = os.statvfs(path.encode('utf8'))
|
disk_data = os.statvfs(path.encode('utf8'))
|
||||||
@ -860,6 +918,7 @@ def is_ipv4(ip):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if windows_check():
|
if windows_check():
|
||||||
return socket.inet_aton(ip)
|
return socket.inet_aton(ip)
|
||||||
@ -888,6 +947,7 @@ def is_ipv6(ip):
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return socket.inet_pton(socket.AF_INET6, ip)
|
return socket.inet_pton(socket.AF_INET6, ip)
|
||||||
except (socket.error, AttributeError):
|
except (socket.error, AttributeError):
|
||||||
@ -969,8 +1029,7 @@ def utf8_encode_structure(data):
|
|||||||
return type(data)([utf8_encode_structure(d) for d in data])
|
return type(data)([utf8_encode_structure(d) for d in data])
|
||||||
elif isinstance(data, dict):
|
elif isinstance(data, dict):
|
||||||
return {
|
return {
|
||||||
utf8_encode_structure(k): utf8_encode_structure(v)
|
utf8_encode_structure(k): utf8_encode_structure(v) for k, v in data.items()
|
||||||
for k, v in data.items()
|
|
||||||
}
|
}
|
||||||
elif not isinstance(data, bytes):
|
elif not isinstance(data, bytes):
|
||||||
try:
|
try:
|
||||||
@ -989,6 +1048,7 @@ class VersionSplit(object):
|
|||||||
:type ver: string
|
:type ver: string
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ver):
|
def __init__(self, ver):
|
||||||
version_re = re.compile(
|
version_re = re.compile(
|
||||||
r"""
|
r"""
|
||||||
@ -1001,7 +1061,8 @@ class VersionSplit(object):
|
|||||||
(?P<prerelversion>\d+(?:\.\d+)*)
|
(?P<prerelversion>\d+(?:\.\d+)*)
|
||||||
)?
|
)?
|
||||||
(?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?
|
(?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?
|
||||||
$""", re.VERBOSE,
|
$""",
|
||||||
|
re.VERBOSE,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check for PEP 386 compliant version
|
# Check for PEP 386 compliant version
|
||||||
@ -1086,11 +1147,14 @@ def create_localclient_account(append=False):
|
|||||||
|
|
||||||
with open(auth_file, 'a' if append else 'w') as _file:
|
with open(auth_file, 'a' if append else 'w') as _file:
|
||||||
_file.write(
|
_file.write(
|
||||||
':'.join([
|
':'.join(
|
||||||
|
[
|
||||||
'localclient',
|
'localclient',
|
||||||
sha(str(random.random()).encode('utf8')).hexdigest(),
|
sha(str(random.random()).encode('utf8')).hexdigest(),
|
||||||
str(AUTH_LEVEL_ADMIN),
|
str(AUTH_LEVEL_ADMIN),
|
||||||
]) + '\n',
|
]
|
||||||
|
)
|
||||||
|
+ '\n'
|
||||||
)
|
)
|
||||||
_file.flush()
|
_file.flush()
|
||||||
os.fsync(_file.fileno())
|
os.fsync(_file.fileno())
|
||||||
@ -1101,12 +1165,13 @@ def get_localhost_auth():
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: With the username and password to login as.
|
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')
|
auth_file = get_config_dir('auth')
|
||||||
if not os.path.exists(auth_file):
|
if not os.path.exists(auth_file):
|
||||||
from deluge.common import create_localclient_account
|
from deluge.common import create_localclient_account
|
||||||
|
|
||||||
create_localclient_account()
|
create_localclient_account()
|
||||||
|
|
||||||
with open(auth_file) as auth:
|
with open(auth_file) as auth:
|
||||||
@ -1169,9 +1234,16 @@ def set_env_variable(name, value):
|
|||||||
if result == 0:
|
if result == 0:
|
||||||
raise Warning
|
raise Warning
|
||||||
except Exception:
|
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:
|
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)
|
# Update the copy maintained by msvcrt (used by gtk+ runtime)
|
||||||
try:
|
try:
|
||||||
@ -1191,9 +1263,13 @@ def set_env_variable(name, value):
|
|||||||
if result != 0:
|
if result != 0:
|
||||||
raise Warning
|
raise Warning
|
||||||
except Exception:
|
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:
|
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():
|
def unicode_argv():
|
||||||
@ -1219,8 +1295,7 @@ def unicode_argv():
|
|||||||
if argc.value > 0:
|
if argc.value > 0:
|
||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in range(start, argc.value)]
|
||||||
range(start, argc.value)]
|
|
||||||
else:
|
else:
|
||||||
# On other platforms, we have to find the likely encoding of the args and decode
|
# 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
|
# 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:
|
if kwargs.get('do_profile', True) is not False:
|
||||||
import cProfile
|
import cProfile
|
||||||
|
|
||||||
profiler = cProfile.Profile()
|
profiler = cProfile.Profile()
|
||||||
|
|
||||||
def on_shutdown():
|
def on_shutdown():
|
||||||
@ -1264,6 +1340,7 @@ def run_profiled(func, *args, **kwargs):
|
|||||||
else:
|
else:
|
||||||
import pstats
|
import pstats
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
strio = StringIO()
|
strio = StringIO()
|
||||||
ps = pstats.Stats(profiler, stream=strio).sort_stats('cumulative')
|
ps = pstats.Stats(profiler, stream=strio).sort_stats('cumulative')
|
||||||
ps.print_stats()
|
ps.print_stats()
|
||||||
@ -1291,6 +1368,7 @@ def is_process_running(pid):
|
|||||||
|
|
||||||
if windows_check():
|
if windows_check():
|
||||||
from win32process import EnumProcesses
|
from win32process import EnumProcesses
|
||||||
|
|
||||||
return pid in EnumProcesses()
|
return pid in EnumProcesses()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -27,7 +27,6 @@ class ComponentAlreadyRegistered(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class ComponentException(Exception):
|
class ComponentException(Exception):
|
||||||
|
|
||||||
def __init__(self, message, tb):
|
def __init__(self, message, tb):
|
||||||
super(ComponentException, self).__init__(message)
|
super(ComponentException, self).__init__(message)
|
||||||
self.message = message
|
self.message = message
|
||||||
@ -93,6 +92,7 @@ class Component(object):
|
|||||||
still be considered in a Started state.
|
still be considered in a Started state.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, interval=1, depend=None):
|
def __init__(self, name, interval=1, depend=None):
|
||||||
"""Initialize component.
|
"""Initialize component.
|
||||||
|
|
||||||
@ -146,12 +146,14 @@ class Component(object):
|
|||||||
elif self._component_state == 'Started':
|
elif self._component_state == 'Started':
|
||||||
d = succeed(True)
|
d = succeed(True)
|
||||||
else:
|
else:
|
||||||
d = fail(ComponentException(
|
d = fail(
|
||||||
|
ComponentException(
|
||||||
'Trying to start component "%s" but it is '
|
'Trying to start component "%s" but it is '
|
||||||
'not in a stopped state. Current state: %s' %
|
'not in a stopped state. Current state: %s'
|
||||||
(self._component_name, self._component_state),
|
% (self._component_name, self._component_state),
|
||||||
traceback.format_stack(limit=4),
|
traceback.format_stack(limit=4),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def _component_stop(self):
|
def _component_stop(self):
|
||||||
@ -195,12 +197,14 @@ class Component(object):
|
|||||||
elif self._component_state == 'Paused':
|
elif self._component_state == 'Paused':
|
||||||
d = succeed(None)
|
d = succeed(None)
|
||||||
else:
|
else:
|
||||||
d = fail(ComponentException(
|
d = fail(
|
||||||
|
ComponentException(
|
||||||
'Trying to pause component "%s" but it is '
|
'Trying to pause component "%s" but it is '
|
||||||
'not in a started state. Current state: %s' %
|
'not in a started state. Current state: %s'
|
||||||
(self._component_name, self._component_state),
|
% (self._component_name, self._component_state),
|
||||||
traceback.format_stack(limit=4),
|
traceback.format_stack(limit=4),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def _component_resume(self):
|
def _component_resume(self):
|
||||||
@ -211,12 +215,14 @@ class Component(object):
|
|||||||
d = maybeDeferred(self._component_start_timer)
|
d = maybeDeferred(self._component_start_timer)
|
||||||
d.addCallback(on_resume)
|
d.addCallback(on_resume)
|
||||||
else:
|
else:
|
||||||
d = fail(ComponentException(
|
d = fail(
|
||||||
|
ComponentException(
|
||||||
'Trying to resume component "%s" but it is '
|
'Trying to resume component "%s" but it is '
|
||||||
'not in a paused state. Current state: %s' %
|
'not in a paused state. Current state: %s'
|
||||||
(self._component_name, self._component_state),
|
% (self._component_name, self._component_state),
|
||||||
traceback.format_stack(limit=4),
|
traceback.format_stack(limit=4),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def _component_shutdown(self):
|
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.
|
It is used to manage the Components by starting, stopping, pausing and shutting them down.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.components = {}
|
self.components = {}
|
||||||
# Stores all of the components that are dependent on a particular component
|
# Stores all of the components that are dependent on a particular component
|
||||||
@ -270,7 +277,9 @@ class ComponentRegistry(object):
|
|||||||
"""
|
"""
|
||||||
name = obj._component_name
|
name = obj._component_name
|
||||||
if name in self.components:
|
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
|
self.components[obj._component_name] = obj
|
||||||
if obj._component_depend:
|
if obj._component_depend:
|
||||||
@ -295,6 +304,7 @@ class ComponentRegistry(object):
|
|||||||
def on_stop(result, name):
|
def on_stop(result, name):
|
||||||
# Component may have been removed, so pop to ensure it doesn't fail
|
# Component may have been removed, so pop to ensure it doesn't fail
|
||||||
self.components.pop(name, None)
|
self.components.pop(name, None)
|
||||||
|
|
||||||
return d.addCallback(on_stop, obj._component_name)
|
return d.addCallback(on_stop, obj._component_name)
|
||||||
else:
|
else:
|
||||||
return succeed(None)
|
return succeed(None)
|
||||||
@ -364,7 +374,9 @@ class ComponentRegistry(object):
|
|||||||
if name in self.components:
|
if name in self.components:
|
||||||
if name in self.dependents:
|
if name in self.dependents:
|
||||||
# If other components depend on this component, stop them first
|
# 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)
|
deferreds.append(d)
|
||||||
stopped_in_deferred.update(self.dependents[name])
|
stopped_in_deferred.update(self.dependents[name])
|
||||||
else:
|
else:
|
||||||
@ -434,8 +446,11 @@ class ComponentRegistry(object):
|
|||||||
Deferred: Fired once all Components have been successfully shut down.
|
Deferred: Fired once all Components have been successfully shut down.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_stopped(result):
|
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)
|
return self.stop(list(self.components)).addCallback(on_stopped)
|
||||||
|
|
||||||
|
|||||||
@ -121,16 +121,14 @@ class Config(object):
|
|||||||
setup to convert old config files. (default: 1)
|
setup to convert old config files. (default: 1)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, filename, defaults=None, config_dir=None, file_version=1):
|
def __init__(self, filename, defaults=None, config_dir=None, file_version=1):
|
||||||
self.__config = {}
|
self.__config = {}
|
||||||
self.__set_functions = {}
|
self.__set_functions = {}
|
||||||
self.__change_callbacks = []
|
self.__change_callbacks = []
|
||||||
|
|
||||||
# These hold the version numbers and they will be set when loaded
|
# These hold the version numbers and they will be set when loaded
|
||||||
self.__version = {
|
self.__version = {'format': 1, 'file': file_version}
|
||||||
'format': 1,
|
|
||||||
'file': file_version,
|
|
||||||
}
|
|
||||||
|
|
||||||
# This will get set with a reactor.callLater whenever a config option
|
# This will get set with a reactor.callLater whenever a config option
|
||||||
# is set.
|
# is set.
|
||||||
@ -210,7 +208,9 @@ class Config(object):
|
|||||||
global callLater
|
global callLater
|
||||||
if callLater is None:
|
if callLater is None:
|
||||||
# Must import here and not at the top or it will throw ReactorAlreadyInstalledError
|
# 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
|
# Run the set_function for this key if any
|
||||||
try:
|
try:
|
||||||
for func in self.__set_functions[key]:
|
for func in self.__set_functions[key]:
|
||||||
@ -218,9 +218,11 @@ class Config(object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
|
|
||||||
def do_change_callbacks(key, value):
|
def do_change_callbacks(key, value):
|
||||||
for func in self.__change_callbacks:
|
for func in self.__change_callbacks:
|
||||||
func(key, value)
|
func(key, value)
|
||||||
|
|
||||||
callLater(0, do_change_callbacks, key, value)
|
callLater(0, do_change_callbacks, key, value)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@ -306,7 +308,9 @@ class Config(object):
|
|||||||
global callLater
|
global callLater
|
||||||
if callLater is None:
|
if callLater is None:
|
||||||
# Must import here and not at the top or it will throw ReactorAlreadyInstalledError
|
# 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
|
# We set the save_timer for 5 seconds if not already set
|
||||||
if not self._save_timer or not self._save_timer.active():
|
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.warning('Unable to load config file: %s', filename)
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
'Config %s version: %s.%s loaded: %s', filename,
|
'Config %s version: %s.%s loaded: %s',
|
||||||
self.__version['format'], self.__version['file'], self.__config,
|
filename,
|
||||||
|
self.__version['format'],
|
||||||
|
self.__version['file'],
|
||||||
|
self.__config,
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self, filename=None):
|
def save(self, filename=None):
|
||||||
@ -518,7 +525,8 @@ class Config(object):
|
|||||||
if self.__version['file'] not in input_range:
|
if self.__version['file'] not in input_range:
|
||||||
log.debug(
|
log.debug(
|
||||||
'File version %s is not in input_range %s, ignoring converter function..',
|
'File version %s is not in input_range %s, ignoring converter function..',
|
||||||
self.__version['file'], input_range,
|
self.__version['file'],
|
||||||
|
input_range,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -528,7 +536,9 @@ class Config(object):
|
|||||||
log.exception(ex)
|
log.exception(ex)
|
||||||
log.error(
|
log.error(
|
||||||
'There was an exception try to convert config file %s %s to %s',
|
'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
|
raise ex
|
||||||
else:
|
else:
|
||||||
@ -542,9 +552,11 @@ class Config(object):
|
|||||||
@prop
|
@prop
|
||||||
def config(): # pylint: disable=no-method-argument
|
def config(): # pylint: disable=no-method-argument
|
||||||
"""The config dictionary"""
|
"""The config dictionary"""
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
return self.__config
|
return self.__config
|
||||||
|
|
||||||
def fdel(self):
|
def fdel(self):
|
||||||
return self.save()
|
return self.save()
|
||||||
|
|
||||||
return locals()
|
return locals()
|
||||||
|
|||||||
@ -95,7 +95,8 @@ class _ConfigManager(object):
|
|||||||
# Create the config object if not already created
|
# Create the config object if not already created
|
||||||
if config_file not in self.config_files:
|
if config_file not in self.config_files:
|
||||||
self.config_files[config_file] = Config(
|
self.config_files[config_file] = Config(
|
||||||
config_file, defaults,
|
config_file,
|
||||||
|
defaults,
|
||||||
config_dir=self.config_directory,
|
config_dir=self.config_directory,
|
||||||
file_version=file_version,
|
file_version=file_version,
|
||||||
)
|
)
|
||||||
@ -108,7 +109,9 @@ _configmanager = _ConfigManager()
|
|||||||
|
|
||||||
|
|
||||||
def ConfigManager(config, defaults=None, file_version=1): # NOQA: N802
|
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):
|
def set_config_dir(directory):
|
||||||
|
|||||||
@ -30,6 +30,7 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class AlertManager(component.Component):
|
class AlertManager(component.Component):
|
||||||
"""AlertManager fetches and processes libtorrent alerts"""
|
"""AlertManager fetches and processes libtorrent alerts"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
log.debug('AlertManager init...')
|
log.debug('AlertManager init...')
|
||||||
component.Component.__init__(self, 'AlertManager', interval=0.3)
|
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)
|
self.set_alert_queue_size(self.alert_queue_size)
|
||||||
|
|
||||||
alert_mask = (
|
alert_mask = (
|
||||||
lt.alert.category_t.error_notification |
|
lt.alert.category_t.error_notification
|
||||||
lt.alert.category_t.port_mapping_notification |
|
| lt.alert.category_t.port_mapping_notification
|
||||||
lt.alert.category_t.storage_notification |
|
| lt.alert.category_t.storage_notification
|
||||||
lt.alert.category_t.tracker_notification |
|
| lt.alert.category_t.tracker_notification
|
||||||
lt.alert.category_t.status_notification |
|
| lt.alert.category_t.status_notification
|
||||||
lt.alert.category_t.ip_block_notification |
|
| lt.alert.category_t.ip_block_notification
|
||||||
lt.alert.category_t.performance_warning
|
| lt.alert.category_t.performance_warning
|
||||||
)
|
)
|
||||||
|
|
||||||
self.session.apply_settings({'alert_mask': alert_mask})
|
self.session.apply_settings({'alert_mask': alert_mask})
|
||||||
@ -107,7 +108,10 @@ class AlertManager(component.Component):
|
|||||||
if log.isEnabledFor(logging.DEBUG):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
log.debug('Alerts queued: %s', num_alerts)
|
log.debug('Alerts queued: %s', num_alerts)
|
||||||
if num_alerts > 0.9 * self.alert_queue_size:
|
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
|
# Loop through all alerts in the queue
|
||||||
for alert in alerts:
|
for alert in alerts:
|
||||||
@ -126,4 +130,6 @@ class AlertManager(component.Component):
|
|||||||
"""Sets the maximum size of the libtorrent alert queue"""
|
"""Sets the maximum size of the libtorrent alert queue"""
|
||||||
log.info('Alert Queue Size set to %s', queue_size)
|
log.info('Alert Queue Size set to %s', queue_size)
|
||||||
self.alert_queue_size = 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.component as component
|
||||||
import deluge.configmanager as configmanager
|
import deluge.configmanager as configmanager
|
||||||
from deluge.common import (AUTH_LEVEL_ADMIN, AUTH_LEVEL_DEFAULT, AUTH_LEVEL_NONE, AUTH_LEVEL_NORMAL,
|
from deluge.common import (
|
||||||
AUTH_LEVEL_READONLY, create_localclient_account)
|
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
|
from deluge.error import AuthenticationRequired, AuthManagerError, BadLoginError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -50,8 +56,10 @@ class Account(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ('<Account username="%(username)s" authlevel=%(authlevel)s>' %
|
return '<Account username="%(username)s" authlevel=%(authlevel)s>' % {
|
||||||
{'username': self.username, 'authlevel': self.authlevel})
|
'username': self.username,
|
||||||
|
'authlevel': self.authlevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AuthManager(component.Component):
|
class AuthManager(component.Component):
|
||||||
@ -99,7 +107,7 @@ class AuthManager(component.Component):
|
|||||||
"""
|
"""
|
||||||
if not username:
|
if not username:
|
||||||
raise AuthenticationRequired(
|
raise AuthenticationRequired(
|
||||||
'Username and Password are required.', username,
|
'Username and Password are required.', username
|
||||||
)
|
)
|
||||||
|
|
||||||
if username not in self.__auth:
|
if username not in self.__auth:
|
||||||
@ -131,8 +139,7 @@ class AuthManager(component.Component):
|
|||||||
raise AuthManagerError('Invalid auth level: %s' % authlevel)
|
raise AuthManagerError('Invalid auth level: %s' % authlevel)
|
||||||
try:
|
try:
|
||||||
self.__auth[username] = Account(
|
self.__auth[username] = Account(
|
||||||
username, password,
|
username, password, AUTH_LEVELS_MAPPING[authlevel]
|
||||||
AUTH_LEVELS_MAPPING[authlevel],
|
|
||||||
)
|
)
|
||||||
self.write_auth_file()
|
self.write_auth_file()
|
||||||
return True
|
return True
|
||||||
@ -160,7 +167,7 @@ class AuthManager(component.Component):
|
|||||||
raise AuthManagerError('Username not known', username)
|
raise AuthManagerError('Username not known', username)
|
||||||
elif username == component.get('RPCServer').get_session_user():
|
elif username == component.get('RPCServer').get_session_user():
|
||||||
raise AuthManagerError(
|
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]
|
del self.__auth[username]
|
||||||
@ -184,7 +191,10 @@ class AuthManager(component.Component):
|
|||||||
try:
|
try:
|
||||||
with open(filepath_tmp, 'w', encoding='utf8') as _file:
|
with open(filepath_tmp, 'w', encoding='utf8') as _file:
|
||||||
for account in self.__auth.values():
|
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()
|
_file.flush()
|
||||||
os.fsync(_file.fileno())
|
os.fsync(_file.fileno())
|
||||||
shutil.move(filepath_tmp, filepath)
|
shutil.move(filepath_tmp, filepath)
|
||||||
@ -237,7 +247,9 @@ class AuthManager(component.Component):
|
|||||||
username, password = lsplit
|
username, password = lsplit
|
||||||
log.warning(
|
log.warning(
|
||||||
'Your auth entry for %s contains no auth level, '
|
'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':
|
if username == 'localclient':
|
||||||
authlevel = AUTH_LEVEL_ADMIN
|
authlevel = AUTH_LEVEL_ADMIN
|
||||||
@ -259,7 +271,10 @@ class AuthManager(component.Component):
|
|||||||
try:
|
try:
|
||||||
authlevel = AUTH_LEVELS_MAPPING[authlevel]
|
authlevel = AUTH_LEVELS_MAPPING[authlevel]
|
||||||
except KeyError:
|
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
|
continue
|
||||||
|
|
||||||
self.__auth[username] = Account(username, password, authlevel)
|
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.common import PY2
|
||||||
from deluge.configmanager import ConfigManager, get_config_dir
|
from deluge.configmanager import ConfigManager, get_config_dir
|
||||||
from deluge.core.alertmanager import AlertManager
|
from deluge.core.alertmanager import AlertManager
|
||||||
from deluge.core.authmanager import (AUTH_LEVEL_ADMIN, AUTH_LEVEL_NONE, AUTH_LEVELS_MAPPING,
|
from deluge.core.authmanager import (
|
||||||
AUTH_LEVELS_MAPPING_REVERSE, AuthManager)
|
AUTH_LEVEL_ADMIN,
|
||||||
|
AUTH_LEVEL_NONE,
|
||||||
|
AUTH_LEVELS_MAPPING,
|
||||||
|
AUTH_LEVELS_MAPPING_REVERSE,
|
||||||
|
AuthManager,
|
||||||
|
)
|
||||||
from deluge.core.eventmanager import EventManager
|
from deluge.core.eventmanager import EventManager
|
||||||
from deluge.core.filtermanager import FilterManager
|
from deluge.core.filtermanager import FilterManager
|
||||||
from deluge.core.pluginmanager import PluginManager
|
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.rpcserver import export
|
||||||
from deluge.core.torrentmanager import TorrentManager
|
from deluge.core.torrentmanager import TorrentManager
|
||||||
from deluge.decorators import deprecated
|
from deluge.decorators import deprecated
|
||||||
from deluge.error import AddTorrentError, DelugeError, InvalidPathError, InvalidTorrentError
|
from deluge.error import (
|
||||||
from deluge.event import NewVersionAvailableEvent, SessionPausedEvent, SessionResumedEvent, TorrentQueueChangedEvent
|
AddTorrentError,
|
||||||
|
DelugeError,
|
||||||
|
InvalidPathError,
|
||||||
|
InvalidTorrentError,
|
||||||
|
)
|
||||||
|
from deluge.event import (
|
||||||
|
NewVersionAvailableEvent,
|
||||||
|
SessionPausedEvent,
|
||||||
|
SessionResumedEvent,
|
||||||
|
TorrentQueueChangedEvent,
|
||||||
|
)
|
||||||
from deluge.httpdownloader import download_file
|
from deluge.httpdownloader import download_file
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -99,16 +114,15 @@ DELUGE_VER = deluge.common.get_version()
|
|||||||
|
|
||||||
|
|
||||||
class Core(component.Component):
|
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')
|
component.Component.__init__(self, 'Core')
|
||||||
|
|
||||||
# Start the libtorrent session.
|
# Start the libtorrent session.
|
||||||
user_agent = 'Deluge/{} libtorrent/{}'.format(DELUGE_VER, LT_VERSION)
|
user_agent = 'Deluge/{} libtorrent/{}'.format(DELUGE_VER, LT_VERSION)
|
||||||
peer_id = self._create_peer_id(DELUGE_VER)
|
peer_id = self._create_peer_id(DELUGE_VER)
|
||||||
log.debug(
|
log.debug('Starting session (peer_id: %s, user_agent: %s)', peer_id, user_agent)
|
||||||
'Starting session (peer_id: %s, user_agent: %s)',
|
|
||||||
peer_id, user_agent,
|
|
||||||
)
|
|
||||||
settings_pack = {
|
settings_pack = {
|
||||||
'peer_fingerprint': peer_id,
|
'peer_fingerprint': peer_id,
|
||||||
'user_agent': user_agent,
|
'user_agent': user_agent,
|
||||||
@ -139,7 +153,9 @@ class Core(component.Component):
|
|||||||
|
|
||||||
# External IP Address from libtorrent
|
# External IP Address from libtorrent
|
||||||
self.external_ip = None
|
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
|
# GeoIP instance with db loaded
|
||||||
self.geoip_instance = None
|
self.geoip_instance = None
|
||||||
@ -161,7 +177,10 @@ class Core(component.Component):
|
|||||||
self._old_listen_interface = self.config['listen_interface']
|
self._old_listen_interface = self.config['listen_interface']
|
||||||
self.config['listen_interface'] = listen_interface
|
self.config['listen_interface'] = listen_interface
|
||||||
else:
|
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
|
self._old_outgoing_interface = None
|
||||||
if outgoing_interface:
|
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.update({k: 0.0 for k in hit_ratio_keys})
|
||||||
|
|
||||||
self.session_status_timer_interval = 0.5
|
self.session_status_timer_interval = 0.5
|
||||||
self.session_status_timer = task.LoopingCall(
|
self.session_status_timer = task.LoopingCall(self.session.post_session_stats)
|
||||||
self.session.post_session_stats)
|
|
||||||
self.alertmanager.register_handler(
|
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_interval = 2
|
||||||
self.session_rates_timer = task.LoopingCall(self._update_session_rates)
|
self.session_rates_timer = task.LoopingCall(self._update_session_rates)
|
||||||
|
|
||||||
@ -358,16 +377,20 @@ class Core(component.Component):
|
|||||||
for rate_key, prev_bytes in list(self._session_prev_bytes.items()):
|
for rate_key, prev_bytes in list(self._session_prev_bytes.items()):
|
||||||
new_bytes = self.session_status[SESSION_RATES_MAPPING[rate_key]]
|
new_bytes = self.session_status[SESSION_RATES_MAPPING[rate_key]]
|
||||||
self.session_status[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.
|
# Store current value for next update.
|
||||||
self._session_prev_bytes[rate_key] = new_bytes
|
self._session_prev_bytes[rate_key] = new_bytes
|
||||||
|
|
||||||
def get_new_release(self):
|
def get_new_release(self):
|
||||||
log.debug('get_new_release')
|
log.debug('get_new_release')
|
||||||
try:
|
try:
|
||||||
self.new_release = urlopen(
|
self.new_release = (
|
||||||
'http://download.deluge-torrent.org/version-2.0',
|
urlopen('http://download.deluge-torrent.org/version-2.0')
|
||||||
).read().decode().strip()
|
.read()
|
||||||
|
.decode()
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
except URLError as ex:
|
except URLError as ex:
|
||||||
log.debug('Unable to get release info from website: %s', ex)
|
log.debug('Unable to get release info from website: %s', ex)
|
||||||
else:
|
else:
|
||||||
@ -376,8 +399,12 @@ class Core(component.Component):
|
|||||||
def check_new_release(self):
|
def check_new_release(self):
|
||||||
if self.new_release:
|
if self.new_release:
|
||||||
log.debug('new_release: %s', 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()):
|
if deluge.common.VersionSplit(
|
||||||
component.get('EventManager').emit(NewVersionAvailableEvent(self.new_release))
|
self.new_release
|
||||||
|
) > deluge.common.VersionSplit(deluge.common.get_version()):
|
||||||
|
component.get('EventManager').emit(
|
||||||
|
NewVersionAvailableEvent(self.new_release)
|
||||||
|
)
|
||||||
return self.new_release
|
return self.new_release
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -403,7 +430,10 @@ class Core(component.Component):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
d = self.torrentmanager.add_async(
|
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:
|
except RuntimeError as ex:
|
||||||
log.error('There was an error adding the torrent file %s: %s', filename, 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.
|
The metadata is base64 encoded.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_metadata(result, result_d):
|
def on_metadata(result, result_d):
|
||||||
torrent_id, metadata = result
|
torrent_id, metadata = result
|
||||||
result_d.callback((torrent_id, b64encode(metadata)))
|
result_d.callback((torrent_id, b64encode(metadata)))
|
||||||
@ -456,7 +487,7 @@ class Core(component.Component):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
return self.torrentmanager.add(
|
return self.torrentmanager.add(
|
||||||
filedump=filedump, options=options, filename=filename,
|
filedump=filedump, options=options, filename=filename
|
||||||
)
|
)
|
||||||
except RuntimeError as ex:
|
except RuntimeError as ex:
|
||||||
log.error('There was an error adding the torrent file %s: %s', filename, ex)
|
log.error('There was an error adding the torrent file %s: %s', filename, ex)
|
||||||
@ -473,6 +504,7 @@ class Core(component.Component):
|
|||||||
Deferred
|
Deferred
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def add_torrents():
|
def add_torrents():
|
||||||
errors = []
|
errors = []
|
||||||
@ -480,12 +512,13 @@ class Core(component.Component):
|
|||||||
for idx, torrent in enumerate(torrent_files):
|
for idx, torrent in enumerate(torrent_files):
|
||||||
try:
|
try:
|
||||||
yield self.add_torrent_file_async(
|
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:
|
except AddTorrentError as ex:
|
||||||
log.warning('Error when adding torrent: %s', ex)
|
log.warning('Error when adding torrent: %s', ex)
|
||||||
errors.append(ex)
|
errors.append(ex)
|
||||||
defer.returnValue(errors)
|
defer.returnValue(errors)
|
||||||
|
|
||||||
return task.deferLater(reactor, 0, add_torrents)
|
return task.deferLater(reactor, 0, add_torrents)
|
||||||
|
|
||||||
@export
|
@export
|
||||||
@ -583,14 +616,19 @@ class Core(component.Component):
|
|||||||
errors = []
|
errors = []
|
||||||
for torrent_id in torrent_ids:
|
for torrent_id in torrent_ids:
|
||||||
try:
|
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:
|
except InvalidTorrentError as ex:
|
||||||
errors.append((torrent_id, str(ex)))
|
errors.append((torrent_id, str(ex)))
|
||||||
# Save the session state
|
# Save the session state
|
||||||
self.torrentmanager.save_state()
|
self.torrentmanager.save_state()
|
||||||
if errors:
|
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 errors
|
||||||
|
|
||||||
return task.deferLater(reactor, 0, do_remove_torrents)
|
return task.deferLater(reactor, 0, do_remove_torrents)
|
||||||
|
|
||||||
@export
|
@export
|
||||||
@ -617,9 +655,7 @@ class Core(component.Component):
|
|||||||
if key in DEPR_SESSION_STATUS_KEYS:
|
if key in DEPR_SESSION_STATUS_KEYS:
|
||||||
new_key = DEPR_SESSION_STATUS_KEYS[key]
|
new_key = DEPR_SESSION_STATUS_KEYS[key]
|
||||||
log.debug(
|
log.debug(
|
||||||
'Deprecated session status key %s, please use %s',
|
'Deprecated session status key %s, please use %s', key, new_key
|
||||||
key,
|
|
||||||
new_key,
|
|
||||||
)
|
)
|
||||||
status[key] = self.session_status[new_key]
|
status[key] = self.session_status[new_key]
|
||||||
else:
|
else:
|
||||||
@ -700,11 +736,22 @@ class Core(component.Component):
|
|||||||
for torrent_id in torrent_ids:
|
for torrent_id in torrent_ids:
|
||||||
self.resume_torrent(torrent_id)
|
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:
|
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:
|
except KeyError:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
# Torrent was probaly removed meanwhile
|
# Torrent was probaly removed meanwhile
|
||||||
return {}
|
return {}
|
||||||
@ -716,9 +763,15 @@ class Core(component.Component):
|
|||||||
|
|
||||||
@export
|
@export
|
||||||
def get_torrent_status(self, torrent_id, keys, diff=False):
|
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(
|
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,
|
all_keys=not keys,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -735,8 +788,11 @@ class Core(component.Component):
|
|||||||
# Ask the plugin manager to fill in the plugin keys
|
# Ask the plugin manager to fill in the plugin keys
|
||||||
if len(plugin_keys) > 0:
|
if len(plugin_keys) > 0:
|
||||||
for key in status_dict:
|
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
|
return status_dict
|
||||||
|
|
||||||
d.addCallback(add_plugin_fields)
|
d.addCallback(add_plugin_fields)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -798,7 +854,9 @@ class Core(component.Component):
|
|||||||
|
|
||||||
settings = self.session.get_settings()
|
settings = self.session.get_settings()
|
||||||
proxy_type = settings['proxy_type']
|
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_port = settings['i2p_port'] if proxy_type == 6 else settings['proxy_port']
|
||||||
proxy_dict = {
|
proxy_dict = {
|
||||||
'type': proxy_type,
|
'type': proxy_type,
|
||||||
@ -939,8 +997,17 @@ class Core(component.Component):
|
|||||||
|
|
||||||
@export
|
@export
|
||||||
def create_torrent(
|
def create_torrent(
|
||||||
self, path, tracker, piece_length, comment, target,
|
self,
|
||||||
webseeds, private, created_by, trackers, add_to_session,
|
path,
|
||||||
|
tracker,
|
||||||
|
piece_length,
|
||||||
|
comment,
|
||||||
|
target,
|
||||||
|
webseeds,
|
||||||
|
private,
|
||||||
|
created_by,
|
||||||
|
trackers,
|
||||||
|
add_to_session,
|
||||||
):
|
):
|
||||||
|
|
||||||
log.debug('creating torrent..')
|
log.debug('creating torrent..')
|
||||||
@ -961,10 +1028,20 @@ class Core(component.Component):
|
|||||||
).start()
|
).start()
|
||||||
|
|
||||||
def _create_torrent_thread(
|
def _create_torrent_thread(
|
||||||
self, path, tracker, piece_length, comment, target,
|
self,
|
||||||
webseeds, private, created_by, trackers, add_to_session,
|
path,
|
||||||
|
tracker,
|
||||||
|
piece_length,
|
||||||
|
comment,
|
||||||
|
target,
|
||||||
|
webseeds,
|
||||||
|
private,
|
||||||
|
created_by,
|
||||||
|
trackers,
|
||||||
|
add_to_session,
|
||||||
):
|
):
|
||||||
from deluge import metafile
|
from deluge import metafile
|
||||||
|
|
||||||
metafile.make_meta_file(
|
metafile.make_meta_file(
|
||||||
path,
|
path,
|
||||||
tracker,
|
tracker,
|
||||||
@ -1058,7 +1135,9 @@ class Core(component.Component):
|
|||||||
def queue_top(self, torrent_ids):
|
def queue_top(self, torrent_ids):
|
||||||
log.debug('Attempting to queue %s to top', torrent_ids)
|
log.debug('Attempting to queue %s to top', torrent_ids)
|
||||||
# torrent_ids must be sorted in reverse before moving to preserve order
|
# 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:
|
try:
|
||||||
# If the queue method returns True, then we should emit a signal
|
# If the queue method returns True, then we should emit a signal
|
||||||
if self.torrentmanager.queue_top(torrent_id):
|
if self.torrentmanager.queue_top(torrent_id):
|
||||||
@ -1069,7 +1148,10 @@ class Core(component.Component):
|
|||||||
@export
|
@export
|
||||||
def queue_up(self, torrent_ids):
|
def queue_up(self, torrent_ids):
|
||||||
log.debug('Attempting to queue %s to up', 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
|
torrent_moved = True
|
||||||
prev_queue_position = None
|
prev_queue_position = None
|
||||||
# torrent_ids must be sorted before moving.
|
# torrent_ids must be sorted before moving.
|
||||||
@ -1079,7 +1161,9 @@ class Core(component.Component):
|
|||||||
try:
|
try:
|
||||||
torrent_moved = self.torrentmanager.queue_up(torrent_id)
|
torrent_moved = self.torrentmanager.queue_up(torrent_id)
|
||||||
except KeyError:
|
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 the torrent moved, then we should emit a signal
|
||||||
if torrent_moved:
|
if torrent_moved:
|
||||||
component.get('EventManager').emit(TorrentQueueChangedEvent())
|
component.get('EventManager').emit(TorrentQueueChangedEvent())
|
||||||
@ -1089,7 +1173,10 @@ class Core(component.Component):
|
|||||||
@export
|
@export
|
||||||
def queue_down(self, torrent_ids):
|
def queue_down(self, torrent_ids):
|
||||||
log.debug('Attempting to queue %s to down', 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
|
torrent_moved = True
|
||||||
prev_queue_position = None
|
prev_queue_position = None
|
||||||
# torrent_ids must be sorted before moving.
|
# torrent_ids must be sorted before moving.
|
||||||
@ -1099,7 +1186,9 @@ class Core(component.Component):
|
|||||||
try:
|
try:
|
||||||
torrent_moved = self.torrentmanager.queue_down(torrent_id)
|
torrent_moved = self.torrentmanager.queue_down(torrent_id)
|
||||||
except KeyError:
|
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 the torrent moved, then we should emit a signal
|
||||||
if torrent_moved:
|
if torrent_moved:
|
||||||
component.get('EventManager').emit(TorrentQueueChangedEvent())
|
component.get('EventManager').emit(TorrentQueueChangedEvent())
|
||||||
@ -1110,7 +1199,9 @@ class Core(component.Component):
|
|||||||
def queue_bottom(self, torrent_ids):
|
def queue_bottom(self, torrent_ids):
|
||||||
log.debug('Attempting to queue %s to bottom', torrent_ids)
|
log.debug('Attempting to queue %s to bottom', torrent_ids)
|
||||||
# torrent_ids must be sorted before moving to preserve order
|
# 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:
|
try:
|
||||||
# If the queue method returns True, then we should emit a signal
|
# If the queue method returns True, then we should emit a signal
|
||||||
if self.torrentmanager.queue_bottom(torrent_id):
|
if self.torrentmanager.queue_bottom(torrent_id):
|
||||||
|
|||||||
@ -66,7 +66,12 @@ class Daemon(object):
|
|||||||
"""The Deluge Daemon class"""
|
"""The Deluge Daemon class"""
|
||||||
|
|
||||||
def __init__(
|
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,
|
read_only_config_keys=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@ -84,19 +89,23 @@ class Daemon(object):
|
|||||||
self.pid_file = get_config_dir('deluged.pid')
|
self.pid_file = get_config_dir('deluged.pid')
|
||||||
log.info('Deluge daemon %s', get_version())
|
log.info('Deluge daemon %s', get_version())
|
||||||
if is_daemon_running(self.pid_file):
|
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.
|
# Twisted catches signals to terminate, so just have it call the shutdown method.
|
||||||
reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown)
|
reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown)
|
||||||
|
|
||||||
# Catch some Windows specific signals
|
# Catch some Windows specific signals
|
||||||
if windows_check():
|
if windows_check():
|
||||||
|
|
||||||
def win_handler(ctrl_type):
|
def win_handler(ctrl_type):
|
||||||
"""Handle the Windows shutdown or close events."""
|
"""Handle the Windows shutdown or close events."""
|
||||||
log.debug('windows handler ctrl_type: %s', ctrl_type)
|
log.debug('windows handler ctrl_type: %s', ctrl_type)
|
||||||
if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT:
|
if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT:
|
||||||
self._shutdown()
|
self._shutdown()
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
SetConsoleCtrlHandler(win_handler)
|
SetConsoleCtrlHandler(win_handler)
|
||||||
|
|
||||||
# Start the core as a thread and join it until it's done
|
# Start the core as a thread and join it until it's done
|
||||||
@ -123,7 +132,10 @@ class Daemon(object):
|
|||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
'Listening to UI on: %s:%s and bittorrent on: %s Making connections out on: %s',
|
'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):
|
def start(self):
|
||||||
@ -179,4 +191,6 @@ class Daemon(object):
|
|||||||
if rpc not in self.get_method_list():
|
if rpc not in self.get_method_list():
|
||||||
return False
|
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):
|
def add_daemon_options(parser):
|
||||||
group = parser.add_argument_group(_('Daemon Options'))
|
group = parser.add_argument_group(_('Daemon Options'))
|
||||||
group.add_argument(
|
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'),
|
help=_('IP address to listen for UI connections'),
|
||||||
)
|
)
|
||||||
group.add_argument(
|
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'),
|
help=_('Port to listen for UI connections on'),
|
||||||
)
|
)
|
||||||
group.add_argument(
|
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'),
|
help=_('IP address to listen for BitTorrent connections'),
|
||||||
)
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
@ -44,8 +55,12 @@ def add_daemon_options(parser):
|
|||||||
help=_('The interface adapter name for outgoing BitTorrent connections.'),
|
help=_('The interface adapter name for outgoing BitTorrent connections.'),
|
||||||
)
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
'--read-only-config-keys', metavar='<comma-separated-keys>', action='store',
|
'--read-only-config-keys',
|
||||||
help=_('Config keys to be unmodified by `set_config` RPC'), type=str, default='',
|
metavar='<comma-separated-keys>',
|
||||||
|
action='store',
|
||||||
|
help=_('Config keys to be unmodified by `set_config` RPC'),
|
||||||
|
type=str,
|
||||||
|
default='',
|
||||||
)
|
)
|
||||||
parser.add_process_arg_group()
|
parser.add_process_arg_group()
|
||||||
|
|
||||||
@ -71,11 +86,12 @@ def start_daemon(skip_start=False):
|
|||||||
|
|
||||||
# Check for any daemons running with this same config
|
# Check for any daemons running with this same config
|
||||||
from deluge.core.daemon import is_daemon_running
|
from deluge.core.daemon import is_daemon_running
|
||||||
|
|
||||||
pid_file = get_config_dir('deluged.pid')
|
pid_file = get_config_dir('deluged.pid')
|
||||||
if is_daemon_running(pid_file):
|
if is_daemon_running(pid_file):
|
||||||
print(
|
print(
|
||||||
'Cannot run multiple daemons with same config directory.\n'
|
'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)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -90,6 +106,7 @@ def start_daemon(skip_start=False):
|
|||||||
def run_daemon(options):
|
def run_daemon(options):
|
||||||
try:
|
try:
|
||||||
from deluge.core.daemon import Daemon
|
from deluge.core.daemon import Daemon
|
||||||
|
|
||||||
daemon = Daemon(
|
daemon = Daemon(
|
||||||
listen_interface=options.listen_interface,
|
listen_interface=options.listen_interface,
|
||||||
outgoing_interface=options.outgoing_interface,
|
outgoing_interface=options.outgoing_interface,
|
||||||
@ -105,7 +122,8 @@ def start_daemon(skip_start=False):
|
|||||||
log.error(
|
log.error(
|
||||||
'Cannot start deluged, listen port in use.\n'
|
'Cannot start deluged, listen port in use.\n'
|
||||||
' Check for other running daemons or services using this port: %s:%s',
|
' Check for other running daemons or services using this port: %s:%s',
|
||||||
ex.interface, ex.port,
|
ex.interface,
|
||||||
|
ex.port,
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@ -118,4 +136,6 @@ def start_daemon(skip_start=False):
|
|||||||
if options.pidfile:
|
if options.pidfile:
|
||||||
os.remove(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:
|
try:
|
||||||
handler(*event.args)
|
handler(*event.args)
|
||||||
except Exception as ex:
|
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):
|
def register_event_handler(self, event, handler):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -101,6 +101,7 @@ class FilterManager(component.Component):
|
|||||||
"""FilterManager
|
"""FilterManager
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, core):
|
def __init__(self, core):
|
||||||
component.Component.__init__(self, 'FilterManager')
|
component.Component.__init__(self, 'FilterManager')
|
||||||
log.debug('FilterManager init..')
|
log.debug('FilterManager init..')
|
||||||
@ -115,12 +116,14 @@ class FilterManager(component.Component):
|
|||||||
|
|
||||||
def _init_tracker_tree():
|
def _init_tracker_tree():
|
||||||
return {'Error': 0}
|
return {'Error': 0}
|
||||||
|
|
||||||
self.register_tree_field('tracker_host', _init_tracker_tree)
|
self.register_tree_field('tracker_host', _init_tracker_tree)
|
||||||
|
|
||||||
self.register_filter('tracker_host', tracker_error_filter)
|
self.register_filter('tracker_host', tracker_error_filter)
|
||||||
|
|
||||||
def _init_users_tree():
|
def _init_users_tree():
|
||||||
return {'': 0}
|
return {'': 0}
|
||||||
|
|
||||||
self.register_tree_field('owner', _init_users_tree)
|
self.register_tree_field('owner', _init_users_tree)
|
||||||
|
|
||||||
def filter_torrent_ids(self, filter_dict):
|
def filter_torrent_ids(self, filter_dict):
|
||||||
@ -165,16 +168,22 @@ class FilterManager(component.Component):
|
|||||||
for field, values in list(filter_dict.items()):
|
for field, values in list(filter_dict.items()):
|
||||||
if field in self.registered_filters:
|
if field in self.registered_filters:
|
||||||
# Filters out doubles
|
# 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]
|
del filter_dict[field]
|
||||||
|
|
||||||
if not filter_dict:
|
if not filter_dict:
|
||||||
return torrent_ids
|
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.
|
# Leftover filter arguments, default filter on status fields.
|
||||||
for torrent_id in list(torrent_ids):
|
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():
|
for field, values in filter_dict.items():
|
||||||
if field in status and status[field] in values:
|
if field in status and status[field] in values:
|
||||||
continue
|
continue
|
||||||
@ -197,14 +206,18 @@ class FilterManager(component.Component):
|
|||||||
items = {field: self.tree_fields[field]() for field in tree_keys}
|
items = {field: self.tree_fields[field]() for field in tree_keys}
|
||||||
|
|
||||||
for torrent_id in list(torrent_ids):
|
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:
|
for field in tree_keys:
|
||||||
value = status[field]
|
value = status[field]
|
||||||
items[field][value] = items[field].get(value, 0) + 1
|
items[field][value] = items[field].get(value, 0) + 1
|
||||||
|
|
||||||
if 'tracker_host' in items:
|
if 'tracker_host' in items:
|
||||||
items['tracker_host']['All'] = len(torrent_ids)
|
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:
|
if not show_zero_hits:
|
||||||
for cat in ['state', 'owner', 'tracker_host']:
|
for cat in ['state', 'owner', 'tracker_host']:
|
||||||
@ -224,7 +237,9 @@ class FilterManager(component.Component):
|
|||||||
init_state['All'] = len(self.torrents.get_torrent_list())
|
init_state['All'] = len(self.torrents.get_torrent_list())
|
||||||
for state in TORRENT_STATE:
|
for state in TORRENT_STATE:
|
||||||
init_state[state] = 0
|
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
|
return init_state
|
||||||
|
|
||||||
def register_filter(self, filter_id, filter_func, filter_value=None):
|
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):
|
def filter_state_active(self, torrent_ids):
|
||||||
for torrent_id in list(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']:
|
if status['download_payload_rate'] or status['upload_payload_rate']:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -33,7 +33,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, component.Compon
|
|||||||
|
|
||||||
# Call the PluginManagerBase constructor
|
# Call the PluginManagerBase constructor
|
||||||
deluge.pluginmanagerbase.PluginManagerBase.__init__(
|
deluge.pluginmanagerbase.PluginManagerBase.__init__(
|
||||||
self, 'core.conf', 'deluge.plugin.core',
|
self, 'core.conf', 'deluge.plugin.core'
|
||||||
)
|
)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@ -77,6 +77,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, component.Compon
|
|||||||
if name not in self.plugins:
|
if name not in self.plugins:
|
||||||
component.get('EventManager').emit(PluginDisabledEvent(name))
|
component.get('EventManager').emit(PluginDisabledEvent(name))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
d.addBoth(on_disable_plugin)
|
d.addBoth(on_disable_plugin)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|||||||
@ -73,8 +73,9 @@ DEFAULT_PREFS = {
|
|||||||
'max_download_speed': -1.0,
|
'max_download_speed': -1.0,
|
||||||
'max_upload_slots_global': 4,
|
'max_upload_slots_global': 4,
|
||||||
'max_half_open_connections': (
|
'max_half_open_connections': (
|
||||||
lambda: deluge.common.windows_check() and
|
lambda: deluge.common.windows_check()
|
||||||
(lambda: deluge.common.vista_check() and 4 or 8)() or 50
|
and (lambda: deluge.common.vista_check() and 4 or 8)()
|
||||||
|
or 50
|
||||||
)(),
|
)(),
|
||||||
'max_connections_per_second': 20,
|
'max_connections_per_second': 20,
|
||||||
'ignore_limits_on_local_network': True,
|
'ignore_limits_on_local_network': True,
|
||||||
@ -134,7 +135,9 @@ class PreferencesManager(component.Component):
|
|||||||
component.Component.__init__(self, 'PreferencesManager')
|
component.Component.__init__(self, 'PreferencesManager')
|
||||||
self.config = deluge.configmanager.ConfigManager('core.conf', DEFAULT_PREFS)
|
self.config = deluge.configmanager.ConfigManager('core.conf', DEFAULT_PREFS)
|
||||||
if 'proxies' in self.config:
|
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'])
|
self.config['proxy'].update(self.config['proxies']['peer'])
|
||||||
log.warning('New proxy config is: %s', self.config['proxy'])
|
log.warning('New proxy config is: %s', self.config['proxy'])
|
||||||
del self.config['proxies']
|
del self.config['proxies']
|
||||||
@ -205,7 +208,9 @@ class PreferencesManager(component.Component):
|
|||||||
if self.config['random_port']:
|
if self.config['random_port']:
|
||||||
if not self.config['listen_random_port']:
|
if not self.config['listen_random_port']:
|
||||||
self.config['listen_random_port'] = random.randrange(49152, 65525)
|
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:
|
else:
|
||||||
self.config['listen_random_port'] = None
|
self.config['listen_random_port'] = None
|
||||||
listen_ports = self.config['listen_ports']
|
listen_ports = self.config['listen_ports']
|
||||||
@ -217,7 +222,9 @@ class PreferencesManager(component.Component):
|
|||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
'Listen Interface: %s, Ports: %s with use_sys_port: %s',
|
'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 = [
|
interfaces = [
|
||||||
'%s:%s' % (interface, port)
|
'%s:%s' % (interface, port)
|
||||||
@ -227,7 +234,7 @@ class PreferencesManager(component.Component):
|
|||||||
{
|
{
|
||||||
'listen_system_port_fallback': self.config['listen_use_sys_port'],
|
'listen_system_port_fallback': self.config['listen_use_sys_port'],
|
||||||
'listen_interfaces': ''.join(interfaces),
|
'listen_interfaces': ''.join(interfaces),
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _on_set_outgoing_ports(self, key, value):
|
def _on_set_outgoing_ports(self, key, value):
|
||||||
@ -237,14 +244,22 @@ class PreferencesManager(component.Component):
|
|||||||
self.__set_outgoing_ports()
|
self.__set_outgoing_ports()
|
||||||
|
|
||||||
def __set_outgoing_ports(self):
|
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:
|
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
|
num_ports = num_ports if num_ports > 1 else 5
|
||||||
else:
|
else:
|
||||||
num_ports = 0
|
num_ports = 0
|
||||||
log.debug('Outgoing port set to %s with range: %s', port, num_ports)
|
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):
|
def _on_set_peer_tos(self, key, value):
|
||||||
try:
|
try:
|
||||||
@ -263,12 +278,11 @@ class PreferencesManager(component.Component):
|
|||||||
'router.bitcomet.com:6881',
|
'router.bitcomet.com:6881',
|
||||||
'dht.transmissionbt.com:6881',
|
'dht.transmissionbt.com:6881',
|
||||||
'dht.aelitis.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):
|
def _on_set_upnp(self, key, value):
|
||||||
self.core.apply_session_setting('enable_upnp', value)
|
self.core.apply_session_setting('enable_upnp', value)
|
||||||
@ -294,14 +308,20 @@ class PreferencesManager(component.Component):
|
|||||||
|
|
||||||
def _on_set_encryption(self, key, value):
|
def _on_set_encryption(self, key, value):
|
||||||
# Convert Deluge enc_level values to libtorrent enc_level values.
|
# 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(
|
self.core.apply_session_settings(
|
||||||
{
|
{
|
||||||
'out_enc_policy': lt.enc_policy(self.config['enc_out_policy']),
|
'out_enc_policy': lt.enc_policy(self.config['enc_out_policy']),
|
||||||
'in_enc_policy': lt.enc_policy(self.config['enc_in_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,
|
'prefer_rc4': True,
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _on_set_max_connections_global(self, key, value):
|
def _on_set_max_connections_global(self, key, value):
|
||||||
@ -364,20 +384,29 @@ class PreferencesManager(component.Component):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
import time
|
import time
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
# check if we've done this within the last week or never
|
# check if we've done this within the last week or never
|
||||||
if (now - self.config['info_sent']) >= (60 * 60 * 24 * 7):
|
if (now - self.config['info_sent']) >= (60 * 60 * 24 * 7):
|
||||||
try:
|
try:
|
||||||
url = 'http://deluge-torrent.org/stats_get.php?processor=' + \
|
url = (
|
||||||
platform.machine() + '&python=' + platform.python_version() \
|
'http://deluge-torrent.org/stats_get.php?processor='
|
||||||
+ '&deluge=' + deluge.common.get_version() \
|
+ platform.machine()
|
||||||
+ '&os=' + platform.system() \
|
+ '&python='
|
||||||
+ '&plugins=' + quote_plus(':'.join(self.config['enabled_plugins']))
|
+ platform.python_version()
|
||||||
|
+ '&deluge='
|
||||||
|
+ deluge.common.get_version()
|
||||||
|
+ '&os='
|
||||||
|
+ platform.system()
|
||||||
|
+ '&plugins='
|
||||||
|
+ quote_plus(':'.join(self.config['enabled_plugins']))
|
||||||
|
)
|
||||||
urlopen(url)
|
urlopen(url)
|
||||||
except IOError as ex:
|
except IOError as ex:
|
||||||
log.debug('Network error while trying to send info: %s', ex)
|
log.debug('Network error while trying to send info: %s', ex)
|
||||||
else:
|
else:
|
||||||
self.config['info_sent'] = now
|
self.config['info_sent'] = now
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
SendInfoThread(self.config).start()
|
SendInfoThread(self.config).start()
|
||||||
|
|
||||||
@ -389,7 +418,7 @@ class PreferencesManager(component.Component):
|
|||||||
self.new_release_timer.stop()
|
self.new_release_timer.stop()
|
||||||
# Set a timer to check for a new release every 3 days
|
# Set a timer to check for a new release every 3 days
|
||||||
self.new_release_timer = LoopingCall(
|
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)
|
self.new_release_timer.start(72 * 60 * 60, False)
|
||||||
else:
|
else:
|
||||||
@ -410,20 +439,23 @@ class PreferencesManager(component.Component):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if value['type'] == lt.proxy_type.i2p_proxy:
|
if value['type'] == lt.proxy_type.i2p_proxy:
|
||||||
proxy_settings.update({
|
proxy_settings.update(
|
||||||
|
{
|
||||||
'proxy_type': lt.proxy_type.i2p_proxy,
|
'proxy_type': lt.proxy_type.i2p_proxy,
|
||||||
'i2p_hostname': value['hostname'],
|
'i2p_hostname': value['hostname'],
|
||||||
'i2p_port': value['port'],
|
'i2p_port': value['port'],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
elif value['type'] != lt.proxy_type.none:
|
elif value['type'] != lt.proxy_type.none:
|
||||||
proxy_settings.update({
|
proxy_settings.update(
|
||||||
|
{
|
||||||
'proxy_type': value['type'],
|
'proxy_type': value['type'],
|
||||||
'proxy_hostname': value['hostname'],
|
'proxy_hostname': value['hostname'],
|
||||||
'proxy_port': value['port'],
|
'proxy_port': value['port'],
|
||||||
'proxy_username': value['username'],
|
'proxy_username': value['username'],
|
||||||
'proxy_password': value['password'],
|
'proxy_password': value['password'],
|
||||||
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
self.core.apply_session_settings(proxy_settings)
|
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
|
# Load the GeoIP DB for country look-ups if available
|
||||||
if os.path.exists(geoipdb_path):
|
if os.path.exists(geoipdb_path):
|
||||||
try:
|
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:
|
except AttributeError:
|
||||||
log.warning('GeoIP Unavailable')
|
log.warning('GeoIP Unavailable')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -24,9 +24,19 @@ from twisted.internet.protocol import Factory, connectionDone
|
|||||||
|
|
||||||
import deluge.component as component
|
import deluge.component as component
|
||||||
import deluge.configmanager
|
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.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.event import ClientDisconnectedEvent
|
||||||
from deluge.transfer import DelugeTransferProtocol
|
from deluge.transfer import DelugeTransferProtocol
|
||||||
|
|
||||||
@ -48,6 +58,7 @@ def export(auth_level=AUTH_LEVEL_DEFAULT):
|
|||||||
:type auth_level: int
|
:type auth_level: int
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def wrap(func, *args, **kwargs):
|
def wrap(func, *args, **kwargs):
|
||||||
func._rpcserver_export = True
|
func._rpcserver_export = True
|
||||||
func._rpcserver_auth_level = auth_level
|
func._rpcserver_auth_level = auth_level
|
||||||
@ -120,8 +131,8 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
|
|||||||
for call in request:
|
for call in request:
|
||||||
if len(call) != 4:
|
if len(call) != 4:
|
||||||
log.debug(
|
log.debug(
|
||||||
'Received invalid rpc request: number of items '
|
'Received invalid rpc request: number of items ' 'in request is %s',
|
||||||
'in request is %s', len(call),
|
len(call),
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
# log.debug('RPCRequest: %s', format_request(call))
|
# log.debug('RPCRequest: %s', format_request(call))
|
||||||
@ -148,14 +159,11 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
|
|||||||
This method is called when a new client connects.
|
This method is called when a new client connects.
|
||||||
"""
|
"""
|
||||||
peer = self.transport.getPeer()
|
peer = self.transport.getPeer()
|
||||||
log.info(
|
log.info('Deluge Client connection made from: %s:%s', peer.host, peer.port)
|
||||||
'Deluge Client connection made from: %s:%s',
|
|
||||||
peer.host, peer.port,
|
|
||||||
)
|
|
||||||
# Set the initial auth level of this session to AUTH_LEVEL_NONE
|
# Set the initial auth level of this session to AUTH_LEVEL_NONE
|
||||||
self.factory.authorized_sessions[
|
self.factory.authorized_sessions[self.transport.sessionno] = self.AuthLevel(
|
||||||
self.transport.sessionno
|
AUTH_LEVEL_NONE, ''
|
||||||
] = self.AuthLevel(AUTH_LEVEL_NONE, '')
|
)
|
||||||
|
|
||||||
def connectionLost(self, reason=connectionDone): # NOQA: N802
|
def connectionLost(self, reason=connectionDone): # NOQA: N802
|
||||||
"""
|
"""
|
||||||
@ -174,7 +182,9 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
|
|||||||
del self.factory.interested_events[self.transport.sessionno]
|
del self.factory.interested_events[self.transport.sessionno]
|
||||||
|
|
||||||
if self.factory.state == 'running':
|
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)
|
log.info('Deluge client disconnected: %s', reason.value)
|
||||||
|
|
||||||
def valid_session(self):
|
def valid_session(self):
|
||||||
@ -196,6 +206,7 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
|
|||||||
:type kwargs: dict
|
:type kwargs: dict
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def send_error():
|
def send_error():
|
||||||
"""
|
"""
|
||||||
Sends an error response with the contents of the exception that was raised.
|
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()
|
exc_type, exc_value, dummy_exc_trace = sys.exc_info()
|
||||||
formated_tb = traceback.format_exc()
|
formated_tb = traceback.format_exc()
|
||||||
try:
|
try:
|
||||||
self.sendData((
|
self.sendData(
|
||||||
|
(
|
||||||
RPC_ERROR,
|
RPC_ERROR,
|
||||||
request_id,
|
request_id,
|
||||||
exc_type.__name__,
|
exc_type.__name__,
|
||||||
exc_value._args,
|
exc_value._args,
|
||||||
exc_value._kwargs,
|
exc_value._kwargs,
|
||||||
formated_tb,
|
formated_tb,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# This is not a deluge exception (object has no attribute '_args), let's wrap it
|
# This is not a deluge exception (object has no attribute '_args), let's wrap it
|
||||||
log.warning(
|
log.warning(
|
||||||
'An exception occurred while sending RPC_ERROR to '
|
'An exception occurred while sending RPC_ERROR to '
|
||||||
'client. Wrapping it and resending. 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:
|
try:
|
||||||
raise WrappedException(
|
raise WrappedException(
|
||||||
str(exc_value), exc_type.__name__, formated_tb,
|
str(exc_value), exc_type.__name__, formated_tb
|
||||||
)
|
)
|
||||||
except WrappedException:
|
except WrappedException:
|
||||||
send_error()
|
send_error()
|
||||||
except Exception as ex:
|
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':
|
if method == 'daemon.info':
|
||||||
# This is a special case and used in the initial connection process
|
# 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)
|
log.debug('RPC dispatch %s', method)
|
||||||
try:
|
try:
|
||||||
method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level
|
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:
|
if auth_level < method_auth_requirement:
|
||||||
# This session is not allowed to call this method
|
# This session is not allowed to call this method
|
||||||
log.debug(
|
log.debug(
|
||||||
@ -307,6 +325,7 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
|
|||||||
# Check if the return value is a deferred, since we'll need to
|
# Check if the return value is a deferred, since we'll need to
|
||||||
# wait for it to fire before sending the RPC_RESPONSE
|
# wait for it to fire before sending the RPC_RESPONSE
|
||||||
if isinstance(ret, defer.Deferred):
|
if isinstance(ret, defer.Deferred):
|
||||||
|
|
||||||
def on_success(result):
|
def on_success(result):
|
||||||
try:
|
try:
|
||||||
self.sendData((RPC_RESPONSE, request_id, result))
|
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')
|
pkey = os.path.join(deluge.configmanager.get_config_dir('ssl'), 'daemon.pkey')
|
||||||
|
|
||||||
try:
|
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:
|
except Exception as ex:
|
||||||
log.debug('Daemon already running or port not available.: %s', ex)
|
log.debug('Daemon already running or port not available.: %s', ex)
|
||||||
raise
|
raise
|
||||||
@ -513,7 +534,7 @@ class RPCServer(component.Component):
|
|||||||
log.debug('Emit Event: %s %s', event.name, event.args)
|
log.debug('Emit Event: %s %s', event.name, event.args)
|
||||||
# This session is interested so send a RPC_EVENT
|
# This session is interested so send a RPC_EVENT
|
||||||
self.factory.session_protocols[session_id].sendData(
|
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):
|
def emit_event_for_session_id(self, session_id, event):
|
||||||
@ -526,22 +547,35 @@ class RPCServer(component.Component):
|
|||||||
:type event: :class:`deluge.event.DelugeEvent`
|
:type event: :class:`deluge.event.DelugeEvent`
|
||||||
"""
|
"""
|
||||||
if not self.is_session_valid(session_id):
|
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
|
return
|
||||||
if session_id not in self.factory.interested_events:
|
if session_id not in self.factory.interested_events:
|
||||||
log.debug(
|
log.debug(
|
||||||
'Session ID %s is not interested in any events. Not sending event "%s".',
|
'Session ID %s is not interested in any events. Not sending event "%s".',
|
||||||
session_id, event.name,
|
session_id,
|
||||||
|
event.name,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if event.name not in self.factory.interested_events[session_id]:
|
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
|
return
|
||||||
log.debug(
|
log.debug(
|
||||||
'Sending event "%s" with args "%s" to session id "%s".',
|
'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):
|
def stop(self):
|
||||||
self.factory.state = 'stopping'
|
self.factory.state = 'stopping'
|
||||||
@ -568,6 +602,7 @@ def generate_ssl_keys():
|
|||||||
This method generates a new SSL key/cert.
|
This method generates a new SSL key/cert.
|
||||||
"""
|
"""
|
||||||
from deluge.common import PY2
|
from deluge.common import PY2
|
||||||
|
|
||||||
digest = 'sha256' if not PY2 else b'sha256'
|
digest = 'sha256' if not PY2 else b'sha256'
|
||||||
|
|
||||||
# Generate key pair
|
# Generate key pair
|
||||||
|
|||||||
@ -28,7 +28,11 @@ from deluge.common import decode_bytes
|
|||||||
from deluge.configmanager import ConfigManager, get_config_dir
|
from deluge.configmanager import ConfigManager, get_config_dir
|
||||||
from deluge.core.authmanager import AUTH_LEVEL_ADMIN
|
from deluge.core.authmanager import AUTH_LEVEL_ADMIN
|
||||||
from deluge.decorators import deprecated
|
from deluge.decorators import deprecated
|
||||||
from deluge.event import TorrentFolderRenamedEvent, TorrentStateChangedEvent, TorrentTrackerStatusEvent
|
from deluge.event import (
|
||||||
|
TorrentFolderRenamedEvent,
|
||||||
|
TorrentStateChangedEvent,
|
||||||
|
TorrentTrackerStatusEvent,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
@ -65,6 +69,7 @@ def sanitize_filepath(filepath, folder=False):
|
|||||||
Args:
|
Args:
|
||||||
folder (bool): A trailing slash is appended to the returned filepath.
|
folder (bool): A trailing slash is appended to the returned filepath.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def clean_filename(filename):
|
def clean_filename(filename):
|
||||||
"""Strips whitespace and discards dotted filenames"""
|
"""Strips whitespace and discards dotted filenames"""
|
||||||
filename = filename.strip()
|
filename = filename.strip()
|
||||||
@ -110,12 +115,14 @@ def convert_lt_files(files):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
file_path = _file.path
|
file_path = _file.path
|
||||||
|
|
||||||
filelist.append({
|
filelist.append(
|
||||||
|
{
|
||||||
'index': index,
|
'index': index,
|
||||||
'path': file_path.replace('\\', '/'),
|
'path': file_path.replace('\\', '/'),
|
||||||
'size': _file.size,
|
'size': _file.size,
|
||||||
'offset': _file.offset,
|
'offset': _file.offset,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return filelist
|
return filelist
|
||||||
|
|
||||||
@ -152,6 +159,7 @@ class TorrentOptions(dict):
|
|||||||
stop_ratio (float): The seeding ratio to stop (or remove) the torrent at.
|
stop_ratio (float): The seeding ratio to stop (or remove) the torrent at.
|
||||||
super_seeding (bool): Enable super seeding/initial seeding.
|
super_seeding (bool): Enable super seeding/initial seeding.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(TorrentOptions, self).__init__()
|
super(TorrentOptions, self).__init__()
|
||||||
config = ConfigManager('core.conf').config
|
config = ConfigManager('core.conf').config
|
||||||
@ -227,6 +235,7 @@ class Torrent(object):
|
|||||||
we can re-pause it after its done if necessary
|
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.
|
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):
|
def __init__(self, handle, options, state=None, filename=None, magnet=None):
|
||||||
self.torrent_id = str(handle.info_hash())
|
self.torrent_id = str(handle.info_hash())
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
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.
|
# 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:
|
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():
|
for key, value in options.items():
|
||||||
if key in self.options:
|
if key in self.options:
|
||||||
@ -407,8 +418,12 @@ class Torrent(object):
|
|||||||
|
|
||||||
# Set the pieces in first and last ranges to priority 7
|
# Set the pieces in first and last ranges to priority 7
|
||||||
# if they are not marked as do not download
|
# 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[first_start:first_end] = [
|
||||||
priorities[last_start:last_end] = [p and 7 for p in priorities[last_start:last_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
|
# Setting the priorites for all the pieces of this torrent
|
||||||
self.handle.prioritize_pieces(priorities)
|
self.handle.prioritize_pieces(priorities)
|
||||||
@ -494,10 +509,15 @@ class Torrent(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
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
|
if (
|
||||||
len(file_priorities) == len(self.get_files())):
|
self.handle.has_metadata()
|
||||||
|
and file_priorities
|
||||||
|
and len(file_priorities) == len(self.get_files())
|
||||||
|
):
|
||||||
self.handle.prioritize_files(file_priorities)
|
self.handle.prioritize_files(file_priorities)
|
||||||
else:
|
else:
|
||||||
log.debug('Unable to set new file priorities.')
|
log.debug('Unable to set new file priorities.')
|
||||||
@ -516,7 +536,9 @@ class Torrent(object):
|
|||||||
|
|
||||||
# Set the first/last priorities if needed.
|
# Set the first/last priorities if needed.
|
||||||
if self.options['prioritize_first_last_pieces']:
|
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
|
@deprecated
|
||||||
def set_save_path(self, download_location):
|
def set_save_path(self, download_location):
|
||||||
@ -592,11 +614,16 @@ class Torrent(object):
|
|||||||
|
|
||||||
if self.tracker_status != status:
|
if self.tracker_status != status:
|
||||||
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):
|
def merge_trackers(self, torrent_info):
|
||||||
"""Merges new trackers in torrent_info into torrent"""
|
"""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:
|
if not torrent_info:
|
||||||
return
|
return
|
||||||
# Don't merge trackers if either torrent has private flag set.
|
# 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))
|
self.state = LT_TORRENT_STATE_MAP.get(str(status.state), str(status.state))
|
||||||
|
|
||||||
if self.state != old_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):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
log.debug(
|
log.debug(
|
||||||
'State from lt was: %s | Session is paused: %s\nTorrent state set from "%s" to "%s" (%s)',
|
'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:
|
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):
|
def set_status_message(self, message=None):
|
||||||
"""Sets the torrent status message.
|
"""Sets the torrent status message.
|
||||||
@ -783,16 +818,21 @@ class Torrent(object):
|
|||||||
client = decode_bytes(peer.client)
|
client = decode_bytes(peer.client)
|
||||||
|
|
||||||
try:
|
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:
|
except AttributeError:
|
||||||
country = ''
|
country = ''
|
||||||
else:
|
else:
|
||||||
try:
|
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:
|
except TypeError:
|
||||||
country = ''
|
country = ''
|
||||||
|
|
||||||
ret.append({
|
ret.append(
|
||||||
|
{
|
||||||
'client': client,
|
'client': client,
|
||||||
'country': country,
|
'country': country,
|
||||||
'down_speed': peer.payload_down_speed,
|
'down_speed': peer.payload_down_speed,
|
||||||
@ -800,7 +840,8 @@ class Torrent(object):
|
|||||||
'progress': peer.progress,
|
'progress': peer.progress,
|
||||||
'seed': peer.flags & peer.seed,
|
'seed': peer.flags & peer.seed,
|
||||||
'up_speed': peer.payload_up_speed,
|
'up_speed': peer.payload_up_speed,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -832,8 +873,10 @@ class Torrent(object):
|
|||||||
if not self.has_metadata:
|
if not self.has_metadata:
|
||||||
return []
|
return []
|
||||||
return [
|
return [
|
||||||
progress / _file.size if _file.size else 0.0 for progress, _file in
|
progress / _file.size if _file.size else 0.0
|
||||||
zip(self.handle.file_progress(), self.torrent_info.files())
|
for progress, _file in zip(
|
||||||
|
self.handle.file_progress(), self.torrent_info.files()
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_tracker_host(self):
|
def get_tracker_host(self):
|
||||||
@ -854,7 +897,7 @@ class Torrent(object):
|
|||||||
if tracker:
|
if tracker:
|
||||||
url = urlparse(tracker.replace('udp://', 'http://'))
|
url = urlparse(tracker.replace('udp://', 'http://'))
|
||||||
if hasattr(url, 'hostname'):
|
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
|
# Check if hostname is an IP address and just return it if that's the case
|
||||||
try:
|
try:
|
||||||
socket.inet_aton(host)
|
socket.inet_aton(host)
|
||||||
@ -995,7 +1038,9 @@ class Torrent(object):
|
|||||||
'seeding_time': lambda: self.status.seeding_time,
|
'seeding_time': lambda: self.status.seeding_time,
|
||||||
'finished_time': lambda: self.status.finished_time,
|
'finished_time': lambda: self.status.finished_time,
|
||||||
'all_time_download': lambda: self.status.all_time_download,
|
'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),
|
'distributed_copies': lambda: max(0.0, self.status.distributed_copies),
|
||||||
'download_payload_rate': lambda: self.status.download_payload_rate,
|
'download_payload_rate': lambda: self.status.download_payload_rate,
|
||||||
'file_priorities': self.get_file_priorities,
|
'file_priorities': self.get_file_priorities,
|
||||||
@ -1008,8 +1053,12 @@ class Torrent(object):
|
|||||||
'max_upload_slots': lambda: self.options['max_upload_slots'],
|
'max_upload_slots': lambda: self.options['max_upload_slots'],
|
||||||
'max_upload_speed': lambda: self.options['max_upload_speed'],
|
'max_upload_speed': lambda: self.options['max_upload_speed'],
|
||||||
'message': lambda: self.statusmsg,
|
'message': lambda: self.statusmsg,
|
||||||
'move_on_completed_path': lambda: self.options['move_completed_path'], # Deprecated: move_completed_path
|
'move_on_completed_path': lambda: self.options[
|
||||||
'move_on_completed': lambda: self.options['move_completed'], # Deprecated: Use move_completed
|
'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_path': lambda: self.options['move_completed_path'],
|
||||||
'move_completed': lambda: self.options['move_completed'],
|
'move_completed': lambda: self.options['move_completed'],
|
||||||
'next_announce': lambda: self.status.next_announce.seconds,
|
'next_announce': lambda: self.status.next_announce.seconds,
|
||||||
@ -1017,16 +1066,24 @@ class Torrent(object):
|
|||||||
'num_seeds': lambda: self.status.num_seeds,
|
'num_seeds': lambda: self.status.num_seeds,
|
||||||
'owner': lambda: self.options['owner'],
|
'owner': lambda: self.options['owner'],
|
||||||
'paused': lambda: self.status.paused,
|
'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
|
# 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'],
|
'sequential_download': lambda: self.options['sequential_download'],
|
||||||
'progress': self.get_progress,
|
'progress': self.get_progress,
|
||||||
'shared': lambda: self.options['shared'],
|
'shared': lambda: self.options['shared'],
|
||||||
'remove_at_ratio': lambda: self.options['remove_at_ratio'],
|
'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'],
|
'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
|
self.status.num_complete / self.status.num_incomplete
|
||||||
),
|
),
|
||||||
'seed_rank': lambda: self.status.seed_rank,
|
'seed_rank': lambda: self.status.seed_rank,
|
||||||
@ -1041,19 +1098,32 @@ class Torrent(object):
|
|||||||
'total_seeds': lambda: self.status.num_complete,
|
'total_seeds': lambda: self.status.num_complete,
|
||||||
'total_uploaded': lambda: self.status.all_time_upload,
|
'total_uploaded': lambda: self.status.all_time_upload,
|
||||||
'total_wanted': lambda: self.status.total_wanted,
|
'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': lambda: self.status.current_tracker,
|
||||||
'tracker_host': self.get_tracker_host,
|
'tracker_host': self.get_tracker_host,
|
||||||
'trackers': lambda: self.trackers,
|
'trackers': lambda: self.trackers,
|
||||||
'tracker_status': lambda: self.tracker_status,
|
'tracker_status': lambda: self.tracker_status,
|
||||||
'upload_payload_rate': lambda: self.status.upload_payload_rate,
|
'upload_payload_rate': lambda: self.status.upload_payload_rate,
|
||||||
'comment': lambda: decode_bytes(self.torrent_info.comment()) if self.has_metadata else '',
|
'comment': lambda: decode_bytes(self.torrent_info.comment())
|
||||||
'creator': lambda: decode_bytes(self.torrent_info.creator()) if self.has_metadata else '',
|
if self.has_metadata
|
||||||
'num_files': lambda: self.torrent_info.num_files() if self.has_metadata else 0,
|
else '',
|
||||||
'num_pieces': lambda: self.torrent_info.num_pieces() if self.has_metadata else 0,
|
'creator': lambda: decode_bytes(self.torrent_info.creator())
|
||||||
'piece_length': lambda: self.torrent_info.piece_length() if self.has_metadata else 0,
|
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,
|
'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,
|
'eta': self.get_eta,
|
||||||
'file_progress': self.get_file_progress,
|
'file_progress': self.get_file_progress,
|
||||||
'files': self.get_files,
|
'files': self.get_files,
|
||||||
@ -1090,7 +1160,9 @@ class Torrent(object):
|
|||||||
# show it as 'Paused'. We need to emit a torrent_paused signal because
|
# show it as 'Paused'. We need to emit a torrent_paused signal because
|
||||||
# the torrent_paused alert from libtorrent will not be generated.
|
# the torrent_paused alert from libtorrent will not be generated.
|
||||||
self.update_state()
|
self.update_state()
|
||||||
component.get('EventManager').emit(TorrentStateChangedEvent(self.torrent_id, 'Paused'))
|
component.get('EventManager').emit(
|
||||||
|
TorrentStateChangedEvent(self.torrent_id, 'Paused')
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.handle.pause()
|
self.handle.pause()
|
||||||
@ -1102,9 +1174,14 @@ class Torrent(object):
|
|||||||
if self.status.paused and self.status.auto_managed:
|
if self.status.paused and self.status.auto_managed:
|
||||||
log.debug('Resume not possible for auto-managed torrent!')
|
log.debug('Resume not possible for auto-managed torrent!')
|
||||||
elif self.forced_error and self.forced_error.was_paused:
|
elif self.forced_error and self.forced_error.was_paused:
|
||||||
log.debug('Resume skipped for forced_error torrent as it was originally paused.')
|
log.debug(
|
||||||
elif (self.status.is_finished and self.options['stop_at_ratio'] and
|
'Resume skipped for forced_error torrent as it was originally paused.'
|
||||||
self.get_ratio() >= self.options['stop_ratio']):
|
)
|
||||||
|
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".')
|
log.debug('Resume skipped for torrent as it has reached "stop_seed_ratio".')
|
||||||
else:
|
else:
|
||||||
# Check if torrent was originally being auto-managed.
|
# Check if torrent was originally being auto-managed.
|
||||||
@ -1157,7 +1234,9 @@ class Torrent(object):
|
|||||||
log.error(
|
log.error(
|
||||||
'Could not move storage for torrent %s since %s does '
|
'Could not move storage for torrent %s since %s does '
|
||||||
'not exist and could not create the directory: %s',
|
'not exist and could not create the directory: %s',
|
||||||
self.torrent_id, dest, ex,
|
self.torrent_id,
|
||||||
|
dest,
|
||||||
|
ex,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -1191,9 +1270,9 @@ class Torrent(object):
|
|||||||
flags = lt.save_resume_flags_t.flush_disk_cache if flush_disk_cache else 0
|
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.
|
# Don't generate fastresume data if torrent is in a Deluge Error state.
|
||||||
if self.forced_error:
|
if self.forced_error:
|
||||||
component.get('TorrentManager').waiting_on_resume_data[self.torrent_id].errback(
|
component.get('TorrentManager').waiting_on_resume_data[
|
||||||
UserWarning('Skipped creating resume_data while in Error state'),
|
self.torrent_id
|
||||||
)
|
].errback(UserWarning('Skipped creating resume_data while in Error state'))
|
||||||
else:
|
else:
|
||||||
self.handle.save_resume_data(flags)
|
self.handle.save_resume_data(flags)
|
||||||
|
|
||||||
@ -1232,9 +1311,13 @@ class Torrent(object):
|
|||||||
|
|
||||||
def delete_torrentfile(self, delete_copies=False):
|
def delete_torrentfile(self, delete_copies=False):
|
||||||
"""Deletes the .torrent file in the state directory in config"""
|
"""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:
|
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:
|
for torrent_file in torrent_files:
|
||||||
log.debug('Deleting torrent file: %s', torrent_file)
|
log.debug('Deleting torrent file: %s', torrent_file)
|
||||||
@ -1325,7 +1408,7 @@ class Torrent(object):
|
|||||||
if _file['path'].startswith(folder):
|
if _file['path'].startswith(folder):
|
||||||
# Keep track of filerenames we're waiting on
|
# Keep track of filerenames we're waiting on
|
||||||
wait_on_folder[_file['index']] = Deferred().addBoth(
|
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)
|
new_path = _file['path'].replace(folder, new_folder, 1)
|
||||||
try:
|
try:
|
||||||
@ -1335,10 +1418,14 @@ class Torrent(object):
|
|||||||
|
|
||||||
def on_folder_rename_complete(dummy_result, torrent, folder, new_folder):
|
def on_folder_rename_complete(dummy_result, torrent, folder, new_folder):
|
||||||
"""Folder rename complete"""
|
"""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
|
# Empty folders are removed after libtorrent folder renames
|
||||||
self.remove_empty_folders(folder)
|
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,))
|
component.get('TorrentManager').save_resume_data((self.torrent_id,))
|
||||||
|
|
||||||
d = DeferredList(list(wait_on_folder.values()))
|
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
|
# Removes leading slashes that can cause join to ignore download_location
|
||||||
download_location = self.options['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:
|
try:
|
||||||
if not os.listdir(folder_full_path):
|
if not os.listdir(folder_full_path):
|
||||||
@ -1366,7 +1455,9 @@ class Torrent(object):
|
|||||||
for name in dirs:
|
for name in dirs:
|
||||||
try:
|
try:
|
||||||
os.removedirs(os.path.join(root, name))
|
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:
|
except OSError as ex:
|
||||||
log.debug(ex)
|
log.debug(ex)
|
||||||
|
|
||||||
@ -1389,16 +1480,24 @@ class Torrent(object):
|
|||||||
pieces = None
|
pieces = None
|
||||||
else:
|
else:
|
||||||
pieces = []
|
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:
|
if piece:
|
||||||
pieces.append(3) # Completed.
|
pieces.append(3) # Completed.
|
||||||
elif avail_piece:
|
elif avail_piece:
|
||||||
pieces.append(1) # Available, just not downloaded nor being downloaded.
|
pieces.append(
|
||||||
|
1
|
||||||
|
) # Available, just not downloaded nor being downloaded.
|
||||||
else:
|
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():
|
for peer_info in self.handle.get_peer_info():
|
||||||
if peer_info.downloading_piece_index >= 0:
|
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
|
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.authmanager import AUTH_LEVEL_ADMIN
|
||||||
from deluge.core.torrent import Torrent, TorrentOptions, sanitize_filepath
|
from deluge.core.torrent import Torrent, TorrentOptions, sanitize_filepath
|
||||||
from deluge.error import AddTorrentError, InvalidTorrentError
|
from deluge.error import AddTorrentError, InvalidTorrentError
|
||||||
from deluge.event import (ExternalIPEvent, PreTorrentRemovedEvent, SessionStartedEvent, TorrentAddedEvent,
|
from deluge.event import (
|
||||||
TorrentFileCompletedEvent, TorrentFileRenamedEvent, TorrentFinishedEvent, TorrentRemovedEvent,
|
ExternalIPEvent,
|
||||||
TorrentResumedEvent)
|
PreTorrentRemovedEvent,
|
||||||
|
SessionStartedEvent,
|
||||||
|
TorrentAddedEvent,
|
||||||
|
TorrentFileCompletedEvent,
|
||||||
|
TorrentFileRenamedEvent,
|
||||||
|
TorrentFinishedEvent,
|
||||||
|
TorrentRemovedEvent,
|
||||||
|
TorrentResumedEvent,
|
||||||
|
)
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
LT_DEFAULT_ADD_TORRENT_FLAGS = (
|
LT_DEFAULT_ADD_TORRENT_FLAGS = (
|
||||||
lt.add_torrent_params_flags_t.flag_paused |
|
lt.add_torrent_params_flags_t.flag_paused
|
||||||
lt.add_torrent_params_flags_t.flag_auto_managed |
|
| 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_update_subscribe
|
||||||
lt.add_torrent_params_flags_t.flag_apply_ip_filter
|
| 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.
|
This must be old style class to avoid breaking torrent.state file.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
torrent_id=None,
|
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.
|
This must be old style class to avoid breaking torrent.state file.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.torrents = []
|
self.torrents = []
|
||||||
|
|
||||||
def __eq__(self, other):
|
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):
|
def __ne__(self, other):
|
||||||
return not 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.
|
This object is also responsible for saving the state of the session for use on restart.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
callLater = reactor.callLater
|
callLater = reactor.callLater
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
component.Component.__init__(
|
component.Component.__init__(
|
||||||
self, 'TorrentManager', interval=5,
|
self,
|
||||||
|
'TorrentManager',
|
||||||
|
interval=5,
|
||||||
depend=['CorePluginManager', 'AlertManager'],
|
depend=['CorePluginManager', 'AlertManager'],
|
||||||
)
|
)
|
||||||
log.debug('TorrentManager init...')
|
log.debug('TorrentManager init...')
|
||||||
@ -163,8 +178,10 @@ class TorrentManager(component.Component):
|
|||||||
|
|
||||||
# Register set functions
|
# Register set functions
|
||||||
set_config_keys = [
|
set_config_keys = [
|
||||||
'max_connections_per_torrent', 'max_upload_slots_per_torrent',
|
'max_connections_per_torrent',
|
||||||
'max_upload_speed_per_torrent', 'max_download_speed_per_torrent',
|
'max_upload_slots_per_torrent',
|
||||||
|
'max_upload_speed_per_torrent',
|
||||||
|
'max_download_speed_per_torrent',
|
||||||
]
|
]
|
||||||
|
|
||||||
for config_key in set_config_keys:
|
for config_key in set_config_keys:
|
||||||
@ -173,18 +190,34 @@ class TorrentManager(component.Component):
|
|||||||
|
|
||||||
# Register alert functions
|
# Register alert functions
|
||||||
alert_handles = [
|
alert_handles = [
|
||||||
'external_ip_alert', 'performance_alert', 'add_torrent_alert',
|
'external_ip_alert',
|
||||||
'metadata_received_alert', 'torrent_finished_alert', 'torrent_paused_alert',
|
'performance_alert',
|
||||||
'torrent_checked_alert', 'torrent_resumed_alert', 'tracker_reply_alert',
|
'add_torrent_alert',
|
||||||
'tracker_announce_alert', 'tracker_warning_alert', 'tracker_error_alert',
|
'metadata_received_alert',
|
||||||
'file_renamed_alert', 'file_error_alert', 'file_completed_alert',
|
'torrent_finished_alert',
|
||||||
'storage_moved_alert', 'storage_moved_failed_alert', 'state_update_alert',
|
'torrent_paused_alert',
|
||||||
'state_changed_alert', 'save_resume_data_alert', 'save_resume_data_failed_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',
|
'fastresume_rejected_alert',
|
||||||
]
|
]
|
||||||
|
|
||||||
for alert_handle in alert_handles:
|
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)
|
self.alerts.register_handler(alert_handle, on_alert_func)
|
||||||
|
|
||||||
# Define timers
|
# Define timers
|
||||||
@ -195,7 +228,9 @@ class TorrentManager(component.Component):
|
|||||||
def start(self):
|
def start(self):
|
||||||
# Check for old temp file to verify safe shutdown
|
# Check for old temp file to verify safe shutdown
|
||||||
if os.path.isfile(self.temp_file):
|
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 = []
|
arc_filepaths = []
|
||||||
for filename in ('torrents.fastresume', 'torrents.state'):
|
for filename in ('torrents.fastresume', 'torrents.state'):
|
||||||
filepath = os.path.join(self.state_dir, filename)
|
filepath = os.path.join(self.state_dir, filename)
|
||||||
@ -240,14 +275,20 @@ class TorrentManager(component.Component):
|
|||||||
for torrent_id, torrent in self.torrents.items():
|
for torrent_id, torrent in self.torrents.items():
|
||||||
# XXX: Should the state check be those that _can_ be stopped at ratio
|
# XXX: Should the state check be those that _can_ be stopped at ratio
|
||||||
if torrent.options['stop_at_ratio'] and torrent.state not in (
|
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...
|
# If the global setting is set, but the per-torrent isn't...
|
||||||
# Just skip to the next torrent.
|
# 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
|
# 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']:
|
if not torrent.options['stop_at_ratio']:
|
||||||
continue
|
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']:
|
if torrent.options['remove_at_ratio']:
|
||||||
self.remove(torrent_id)
|
self.remove(torrent_id)
|
||||||
break
|
break
|
||||||
@ -324,7 +365,8 @@ class TorrentManager(component.Component):
|
|||||||
add_torrent_params = {}
|
add_torrent_params = {}
|
||||||
add_torrent_params['save_path'] = gettempdir()
|
add_torrent_params['save_path'] = gettempdir()
|
||||||
add_torrent_params['url'] = magnet.strip().encode('utf8')
|
add_torrent_params['url'] = magnet.strip().encode('utf8')
|
||||||
add_torrent_params['flags'] = ((
|
add_torrent_params['flags'] = (
|
||||||
|
(
|
||||||
LT_DEFAULT_ADD_TORRENT_FLAGS
|
LT_DEFAULT_ADD_TORRENT_FLAGS
|
||||||
| lt.add_torrent_params_flags_t.flag_duplicate_is_error
|
| 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_upload_mode
|
||||||
@ -379,7 +421,7 @@ class TorrentManager(component.Component):
|
|||||||
return options
|
return options
|
||||||
|
|
||||||
def _build_torrent_params(
|
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."""
|
"""Create the add_torrent_params dict for adding torrent to libtorrent."""
|
||||||
add_torrent_params = {}
|
add_torrent_params = {}
|
||||||
@ -387,7 +429,9 @@ class TorrentManager(component.Component):
|
|||||||
add_torrent_params['ti'] = torrent_info
|
add_torrent_params['ti'] = torrent_info
|
||||||
name = torrent_info.name()
|
name = torrent_info.name()
|
||||||
if not 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
|
add_torrent_params['name'] = name
|
||||||
torrent_id = str(torrent_info.info_hash())
|
torrent_id = str(torrent_info.info_hash())
|
||||||
elif magnet:
|
elif magnet:
|
||||||
@ -397,7 +441,9 @@ class TorrentManager(component.Component):
|
|||||||
add_torrent_params['name'] = magnet_info['name']
|
add_torrent_params['name'] = magnet_info['name']
|
||||||
torrent_id = magnet_info['info_hash']
|
torrent_id = magnet_info['info_hash']
|
||||||
else:
|
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.
|
# Check for existing torrent in session.
|
||||||
if torrent_id in self.get_torrent_list():
|
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.
|
# Set flags: enable duplicate_is_error & override_resume_data, disable auto_managed.
|
||||||
add_torrent_params['flags'] = (
|
add_torrent_params['flags'] = (
|
||||||
(
|
LT_DEFAULT_ADD_TORRENT_FLAGS
|
||||||
LT_DEFAULT_ADD_TORRENT_FLAGS |
|
| lt.add_torrent_params_flags_t.flag_duplicate_is_error
|
||||||
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_override_resume_data
|
) ^ lt.add_torrent_params_flags_t.flag_auto_managed
|
||||||
) ^
|
|
||||||
lt.add_torrent_params_flags_t.flag_auto_managed
|
|
||||||
)
|
|
||||||
if options['seed_mode']:
|
if options['seed_mode']:
|
||||||
add_torrent_params['flags'] |= lt.add_torrent_params_flags_t.flag_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:
|
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:
|
if filedump:
|
||||||
try:
|
try:
|
||||||
torrent_info = lt.torrent_info(lt.bdecode(filedump))
|
torrent_info = lt.torrent_info(lt.bdecode(filedump))
|
||||||
except RuntimeError as ex:
|
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)
|
options = self._build_torrent_options(options)
|
||||||
__, add_torrent_params = self._build_torrent_params(
|
__, 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
|
# 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)
|
raise AddTorrentError('Unable to add torrent to session: %s' % ex)
|
||||||
|
|
||||||
torrent = self._add_torrent_obj(
|
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
|
return torrent.torrent_id
|
||||||
|
|
||||||
@ -541,28 +588,51 @@ class TorrentManager(component.Component):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not torrent_info and not filedump and not magnet:
|
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:
|
if filedump:
|
||||||
try:
|
try:
|
||||||
torrent_info = lt.torrent_info(lt.bdecode(filedump))
|
torrent_info = lt.torrent_info(lt.bdecode(filedump))
|
||||||
except RuntimeError as ex:
|
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)
|
options = self._build_torrent_options(options)
|
||||||
torrent_id, add_torrent_params = self._build_torrent_params(
|
torrent_id, add_torrent_params = self._build_torrent_params(
|
||||||
torrent_info, magnet, options, resume_data,
|
torrent_info, magnet, options, resume_data
|
||||||
)
|
)
|
||||||
|
|
||||||
d = Deferred()
|
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:
|
try:
|
||||||
self.session.async_add_torrent(add_torrent_params)
|
self.session.async_add_torrent(add_torrent_params)
|
||||||
except RuntimeError as ex:
|
except RuntimeError as ex:
|
||||||
raise AddTorrentError('Unable to add torrent to session: %s' % ex)
|
raise AddTorrentError('Unable to add torrent to session: %s' % ex)
|
||||||
return d
|
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.
|
# For magnets added with metadata, filename is used so set as magnet.
|
||||||
if not magnet and is_magnet(filename):
|
if not magnet and is_magnet(filename):
|
||||||
magnet = filename
|
magnet = filename
|
||||||
@ -590,7 +660,9 @@ class TorrentManager(component.Component):
|
|||||||
|
|
||||||
# Emit torrent_added signal.
|
# Emit torrent_added signal.
|
||||||
from_state = state is not None
|
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):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
log.debug('Torrent added: %s', str(handle.info_hash()))
|
log.debug('Torrent added: %s', str(handle.info_hash()))
|
||||||
@ -614,10 +686,19 @@ class TorrentManager(component.Component):
|
|||||||
return torrent
|
return torrent
|
||||||
|
|
||||||
def add_async_callback(
|
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(
|
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)
|
d.callback(torrent.torrent_id)
|
||||||
@ -661,7 +742,9 @@ class TorrentManager(component.Component):
|
|||||||
self.resume_data.pop(torrent_id, None)
|
self.resume_data.pop(torrent_id, None)
|
||||||
|
|
||||||
# Remove the .torrent file in the state and copy location, if user requested.
|
# 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)
|
torrent.delete_torrentfile(delete_copies)
|
||||||
|
|
||||||
# Remove from set if it wasn't finished
|
# Remove from set if it wasn't finished
|
||||||
@ -670,7 +753,9 @@ class TorrentManager(component.Component):
|
|||||||
self.queued_torrents.remove(torrent_id)
|
self.queued_torrents.remove(torrent_id)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
log.debug('%s is not in queued torrents set.', torrent_id)
|
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
|
# Remove the torrent from deluge's session
|
||||||
del self.torrents[torrent_id]
|
del self.torrents[torrent_id]
|
||||||
@ -680,7 +765,11 @@ class TorrentManager(component.Component):
|
|||||||
|
|
||||||
# Emit the signal to the clients
|
# Emit the signal to the clients
|
||||||
component.get('EventManager').emit(TorrentRemovedEvent(torrent_id))
|
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
|
return True
|
||||||
|
|
||||||
def fixup_state(self, state):
|
def fixup_state(self, state):
|
||||||
@ -701,7 +790,9 @@ class TorrentManager(component.Component):
|
|||||||
for t_state in state.torrents:
|
for t_state in state.torrents:
|
||||||
setattr(t_state, attr, getattr(t_state_tmp, attr, None))
|
setattr(t_state, attr, getattr(t_state_tmp, attr, None))
|
||||||
except AttributeError as ex:
|
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
|
return state
|
||||||
|
|
||||||
def open_state(self):
|
def open_state(self):
|
||||||
@ -740,7 +831,9 @@ class TorrentManager(component.Component):
|
|||||||
state = self.fixup_state(state)
|
state = self.fixup_state(state)
|
||||||
|
|
||||||
# Reorder the state.torrents list to add torrents in the correct queue order.
|
# 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()
|
resume_data = self.load_resume_data_file()
|
||||||
|
|
||||||
deferreds = []
|
deferreds = []
|
||||||
@ -760,7 +853,7 @@ class TorrentManager(component.Component):
|
|||||||
|
|
||||||
magnet = t_state.magnet
|
magnet = t_state.magnet
|
||||||
torrent_info = self.get_torrent_info_from_file(
|
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:
|
try:
|
||||||
@ -773,15 +866,24 @@ class TorrentManager(component.Component):
|
|||||||
resume_data=resume_data.get(t_state.torrent_id),
|
resume_data=resume_data.get(t_state.torrent_id),
|
||||||
)
|
)
|
||||||
except AddTorrentError as ex:
|
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:
|
else:
|
||||||
deferreds.append(d)
|
deferreds.append(d)
|
||||||
|
|
||||||
deferred_list = DeferredList(deferreds, consumeErrors=False)
|
deferred_list = DeferredList(deferreds, consumeErrors=False)
|
||||||
|
|
||||||
def on_complete(result):
|
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())
|
component.get('EventManager').emit(SessionStartedEvent())
|
||||||
|
|
||||||
deferred_list.addCallback(on_complete)
|
deferred_list.addCallback(on_complete)
|
||||||
|
|
||||||
def create_state(self):
|
def create_state(self):
|
||||||
@ -850,6 +952,7 @@ class TorrentManager(component.Component):
|
|||||||
self.is_saving_state = False
|
self.is_saving_state = False
|
||||||
if self.save_state_timer.running:
|
if self.save_state_timer.running:
|
||||||
self.save_state_timer.reset()
|
self.save_state_timer.reset()
|
||||||
|
|
||||||
d.addBoth(on_state_saved)
|
d.addBoth(on_state_saved)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -910,7 +1013,11 @@ class TorrentManager(component.Component):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if torrent_ids is None:
|
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):
|
def on_torrent_resume_save(dummy_result, torrent_id):
|
||||||
"""Recieved torrent resume_data alert so remove from waiting list"""
|
"""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:
|
if self.save_resume_data_timer.running:
|
||||||
self.save_resume_data_timer.reset()
|
self.save_resume_data_timer.reset()
|
||||||
return arg
|
return arg
|
||||||
|
|
||||||
d.addBoth(on_resume_data_file_saved)
|
d.addBoth(on_resume_data_file_saved)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
return self.save_resume_data_file_lock.run(on_lock_aquired)
|
return self.save_resume_data_file_lock.run(on_lock_aquired)
|
||||||
|
|
||||||
def _save_resume_data_file(self):
|
def _save_resume_data_file(self):
|
||||||
@ -1066,7 +1175,9 @@ class TorrentManager(component.Component):
|
|||||||
|
|
||||||
def queue_down(self, torrent_id):
|
def queue_down(self, torrent_id):
|
||||||
"""Queue torrent down one position"""
|
"""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
|
return False
|
||||||
|
|
||||||
self.torrents[torrent_id].handle.queue_position_down()
|
self.torrents[torrent_id].handle.queue_position_down()
|
||||||
@ -1074,7 +1185,9 @@ class TorrentManager(component.Component):
|
|||||||
|
|
||||||
def queue_bottom(self, torrent_id):
|
def queue_bottom(self, torrent_id):
|
||||||
"""Queue torrent to bottom"""
|
"""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
|
return False
|
||||||
|
|
||||||
self.torrents[torrent_id].handle.queue_position_bottom()
|
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.
|
# 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
|
# 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):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
log.debug('Finished %s ', torrent_id)
|
log.debug('Finished %s ', torrent_id)
|
||||||
log.debug(
|
log.debug(
|
||||||
'Torrent settings: is_finished: %s, total_download: %s, move_completed: %s, move_path: %s',
|
'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.options['move_completed_path'],
|
||||||
)
|
)
|
||||||
|
|
||||||
torrent.update_state()
|
torrent.update_state()
|
||||||
if not torrent.is_finished and total_download:
|
if not torrent.is_finished and total_download:
|
||||||
# Move completed download to completed folder if needed
|
# Move completed download to completed folder if needed
|
||||||
if torrent.options['move_completed'] and \
|
if (
|
||||||
torrent.options['download_location'] != torrent.options['move_completed_path']:
|
torrent.options['move_completed']
|
||||||
|
and torrent.options['download_location']
|
||||||
|
!= torrent.options['move_completed_path']
|
||||||
|
):
|
||||||
self.waiting_on_finish_moving.append(torrent_id)
|
self.waiting_on_finish_moving.append(torrent_id)
|
||||||
torrent.move_storage(torrent.options['move_completed_path'])
|
torrent.move_storage(torrent.options['move_completed_path'])
|
||||||
else:
|
else:
|
||||||
@ -1217,7 +1337,10 @@ class TorrentManager(component.Component):
|
|||||||
torrent.set_tracker_status('Announce OK')
|
torrent.set_tracker_status('Announce OK')
|
||||||
|
|
||||||
# Check for peer information from the tracker, if none then send a scrape request.
|
# 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()
|
torrent.scrape_tracker()
|
||||||
|
|
||||||
def on_alert_tracker_announce(self, alert):
|
def on_alert_tracker_announce(self, alert):
|
||||||
@ -1249,7 +1372,9 @@ class TorrentManager(component.Component):
|
|||||||
error_message = decode_bytes(alert.error_message())
|
error_message = decode_bytes(alert.error_message())
|
||||||
if not error_message:
|
if not error_message:
|
||||||
error_message = decode_bytes(alert.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)
|
torrent.set_tracker_status('Error: ' + error_message)
|
||||||
|
|
||||||
def on_alert_storage_moved(self, alert):
|
def on_alert_storage_moved(self, alert):
|
||||||
@ -1337,7 +1462,9 @@ class TorrentManager(component.Component):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if torrent_id in self.waiting_on_resume_data:
|
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):
|
def on_alert_fastresume_rejected(self, alert):
|
||||||
"""Alert handler for libtorrent fastresume_rejected_alert"""
|
"""Alert handler for libtorrent fastresume_rejected_alert"""
|
||||||
@ -1355,7 +1482,9 @@ class TorrentManager(component.Component):
|
|||||||
else:
|
else:
|
||||||
error_msg = 'Missing or invalid torrent data!'
|
error_msg = 'Missing or invalid torrent data!'
|
||||||
else:
|
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)
|
torrent.force_error_state(error_msg, restart_to_resume=True)
|
||||||
|
|
||||||
def on_alert_file_renamed(self, alert):
|
def on_alert_file_renamed(self, alert):
|
||||||
@ -1381,7 +1510,9 @@ class TorrentManager(component.Component):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# This is just a regular file rename so send the signal
|
# 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,))
|
self.save_resume_data((torrent_id,))
|
||||||
|
|
||||||
def on_alert_metadata_received(self, alert):
|
def on_alert_metadata_received(self, alert):
|
||||||
@ -1427,7 +1558,9 @@ class TorrentManager(component.Component):
|
|||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
return
|
return
|
||||||
if torrent_id in self.torrents:
|
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):
|
def on_alert_state_update(self, alert):
|
||||||
"""Alert handler for libtorrent state_update_alert
|
"""Alert handler for libtorrent state_update_alert
|
||||||
@ -1464,7 +1597,11 @@ class TorrentManager(component.Component):
|
|||||||
|
|
||||||
def on_alert_performance(self, alert):
|
def on_alert_performance(self, alert):
|
||||||
"""Alert handler for libtorrent performance_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:
|
if alert.warning_code == lt.performance_warning_t.send_buffer_watermark_too_low:
|
||||||
max_send_buffer_watermark = 3 * 1024 * 1024 # 3MiB
|
max_send_buffer_watermark = 3 * 1024 * 1024 # 3MiB
|
||||||
settings = self.session.get_settings()
|
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 is too small, try increasing its size by 512KiB (up to max_send_buffer_watermark)
|
||||||
if send_buffer_watermark < max_send_buffer_watermark:
|
if send_buffer_watermark < max_send_buffer_watermark:
|
||||||
value = send_buffer_watermark + (500 * 1024)
|
value = send_buffer_watermark + (500 * 1024)
|
||||||
log.info('Increasing send_buffer_watermark from %s to %s Bytes', send_buffer_watermark, value)
|
log.info(
|
||||||
component.get('Core').apply_session_setting('send_buffer_watermark', value)
|
'Increasing send_buffer_watermark from %s to %s Bytes',
|
||||||
|
send_buffer_watermark,
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
component.get('Core').apply_session_setting(
|
||||||
|
'send_buffer_watermark', value
|
||||||
|
)
|
||||||
else:
|
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):
|
def separate_keys(self, keys, torrent_ids):
|
||||||
"""Separates the input keys into torrent class keys and plugins keys"""
|
"""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.
|
# Could be the clients cache (sessionproxy) isn't up to speed.
|
||||||
del status_dict[torrent_id]
|
del status_dict[torrent_id]
|
||||||
else:
|
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
|
self.status_dict = status_dict
|
||||||
d.callback((status_dict, plugin_keys))
|
d.callback((status_dict, plugin_keys))
|
||||||
|
|
||||||
@ -1527,7 +1675,9 @@ class TorrentManager(component.Component):
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
# If last update was recent, use cached data instead of request updates from libtorrent
|
# If last update was recent, use cached data instead of request updates from libtorrent
|
||||||
if (now - self.last_state_update_alert_ts) < 1.5:
|
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:
|
else:
|
||||||
# Ask libtorrent for status update
|
# Ask libtorrent for status update
|
||||||
self.torrents_status_requests.insert(0, (d, torrent_ids, keys, diff))
|
self.torrents_status_requests.insert(0, (d, torrent_ids, keys, diff))
|
||||||
|
|||||||
@ -10,7 +10,13 @@
|
|||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
|
|
||||||
from OpenSSL.crypto import FILETYPE_PEM
|
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.
|
# A TLS ciphers list.
|
||||||
# Sources for more information on TLS ciphers:
|
# 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 cipher suites that offer perfect forward secrecy (ECDHE),
|
||||||
# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common,
|
# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common,
|
||||||
# - disable NULL authentication, MD5 MACs and DSS for security reasons.
|
# - disable NULL authentication, MD5 MACs and DSS for security reasons.
|
||||||
TLS_CIPHERS = ':'.join([
|
TLS_CIPHERS = ':'.join(
|
||||||
|
[
|
||||||
'ECDH+AESGCM',
|
'ECDH+AESGCM',
|
||||||
'ECDH+CHACHA20',
|
'ECDH+CHACHA20',
|
||||||
'AES256-GCM-SHA384',
|
'AES256-GCM-SHA384',
|
||||||
'AES128-GCM-SHA256',
|
'AES128-GCM-SHA256',
|
||||||
'!DSS'
|
'!DSS' '!aNULL',
|
||||||
'!aNULL',
|
|
||||||
'!eNULL',
|
'!eNULL',
|
||||||
'!MD5'
|
'!MD5',
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# This value tells OpenSSL to disable all SSL/TLS renegotiation.
|
# This value tells OpenSSL to disable all SSL/TLS renegotiation.
|
||||||
SSL_OP_NO_RENEGOTIATION = 0x40000000
|
SSL_OP_NO_RENEGOTIATION = 0x40000000
|
||||||
|
|||||||
@ -23,11 +23,14 @@ def proxy(proxy_func):
|
|||||||
:param proxy_func: the proxy function
|
:param proxy_func: the proxy function
|
||||||
:type proxy_func: function
|
:type proxy_func: function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
return proxy_func(func, *args, **kwargs)
|
return proxy_func(func, *args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
@ -57,6 +60,7 @@ def overrides(*args):
|
|||||||
# called with the real function as argument
|
# called with the real function as argument
|
||||||
def ret_func(func, **kwargs):
|
def ret_func(func, **kwargs):
|
||||||
return _overrides(stack, func, explicit_base_classes=args)
|
return _overrides(stack, func, explicit_base_classes=args)
|
||||||
|
|
||||||
return ret_func
|
return ret_func
|
||||||
|
|
||||||
|
|
||||||
@ -75,7 +79,10 @@ def _overrides(stack, method, explicit_base_classes=None):
|
|||||||
check_classes = base_classes
|
check_classes = base_classes
|
||||||
|
|
||||||
if not 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):
|
def get_class(cls_name):
|
||||||
if '.' not in cls_name:
|
if '.' not in cls_name:
|
||||||
@ -91,7 +98,9 @@ def _overrides(stack, method, explicit_base_classes=None):
|
|||||||
|
|
||||||
if explicit_base_classes:
|
if explicit_base_classes:
|
||||||
# One or more base classes are explicitly given, check only those 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(',')]
|
override_classes = [c.strip() for c in override_classes.split(',')]
|
||||||
check_classes = override_classes
|
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
|
# Verify that the excplicit override class is one of base classes
|
||||||
if explicit_base_classes:
|
if explicit_base_classes:
|
||||||
from itertools import product
|
from itertools import product
|
||||||
|
|
||||||
for bc, cc in product(base_classes, check_classes):
|
for bc, cc in product(base_classes, check_classes):
|
||||||
if issubclass(classes[bc], classes[cc]):
|
if issubclass(classes[bc], classes[cc]):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise Exception('Excplicit override class "%s" is not a super class of: %s'
|
raise Exception(
|
||||||
% (explicit_base_classes, class_name))
|
'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):
|
if not all(hasattr(classes[cls], method.__name__) for cls in check_classes):
|
||||||
for cls in check_classes:
|
for cls in check_classes:
|
||||||
if not hasattr(classes[cls], method.__name__):
|
if not hasattr(classes[cls], method.__name__):
|
||||||
raise Exception('Function override "%s" not found in superclass: %s\n%s'
|
raise Exception(
|
||||||
% (method.__name__, cls, 'File: %s:%s' % (stack[1][1], stack[1][2])))
|
'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):
|
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'
|
raise Exception(
|
||||||
% (method.__name__, check_classes, 'File: %s:%s' % (stack[1][1], stack[1][2])))
|
'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
|
return method
|
||||||
|
|
||||||
|
|
||||||
@ -131,7 +155,8 @@ def deprecated(func):
|
|||||||
warnings.simplefilter('always', DeprecationWarning) # Turn off filter
|
warnings.simplefilter('always', DeprecationWarning) # Turn off filter
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'Call to deprecated function {}.'.format(func.__name__),
|
'Call to deprecated function {}.'.format(func.__name__),
|
||||||
category=DeprecationWarning, stacklevel=2,
|
category=DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
warnings.simplefilter('default', DeprecationWarning) # Reset filter
|
warnings.simplefilter('default', DeprecationWarning) # Reset filter
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|||||||
@ -13,7 +13,6 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
|
|
||||||
class DelugeError(Exception):
|
class DelugeError(Exception):
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
inst = super(DelugeError, cls).__new__(cls, *args, **kwargs)
|
inst = super(DelugeError, cls).__new__(cls, *args, **kwargs)
|
||||||
inst._args = args
|
inst._args = args
|
||||||
@ -45,7 +44,6 @@ class InvalidPathError(DelugeError):
|
|||||||
|
|
||||||
|
|
||||||
class WrappedException(DelugeError):
|
class WrappedException(DelugeError):
|
||||||
|
|
||||||
def __init__(self, message, exception_type, traceback):
|
def __init__(self, message, exception_type, traceback):
|
||||||
super(WrappedException, self).__init__(message)
|
super(WrappedException, self).__init__(message)
|
||||||
self.type = exception_type
|
self.type = exception_type
|
||||||
@ -60,7 +58,6 @@ class _ClientSideRecreateError(DelugeError):
|
|||||||
|
|
||||||
|
|
||||||
class IncompatibleClient(_ClientSideRecreateError):
|
class IncompatibleClient(_ClientSideRecreateError):
|
||||||
|
|
||||||
def __init__(self, daemon_version):
|
def __init__(self, daemon_version):
|
||||||
self.daemon_version = daemon_version
|
self.daemon_version = daemon_version
|
||||||
msg = (
|
msg = (
|
||||||
@ -71,11 +68,8 @@ class IncompatibleClient(_ClientSideRecreateError):
|
|||||||
|
|
||||||
|
|
||||||
class NotAuthorizedError(_ClientSideRecreateError):
|
class NotAuthorizedError(_ClientSideRecreateError):
|
||||||
|
|
||||||
def __init__(self, current_level, required_level):
|
def __init__(self, current_level, required_level):
|
||||||
msg = (
|
msg = ('Auth level too low: %(current_level)s < %(required_level)s') % {
|
||||||
'Auth level too low: %(current_level)s < %(required_level)s'
|
|
||||||
) % {
|
|
||||||
'current_level': current_level,
|
'current_level': current_level,
|
||||||
'required_level': required_level,
|
'required_level': required_level,
|
||||||
}
|
}
|
||||||
@ -85,7 +79,6 @@ class NotAuthorizedError(_ClientSideRecreateError):
|
|||||||
|
|
||||||
|
|
||||||
class _UsernameBasedPasstroughError(_ClientSideRecreateError):
|
class _UsernameBasedPasstroughError(_ClientSideRecreateError):
|
||||||
|
|
||||||
def __init__(self, message, username):
|
def __init__(self, message, username):
|
||||||
super(_UsernameBasedPasstroughError, self).__init__(message)
|
super(_UsernameBasedPasstroughError, self).__init__(message)
|
||||||
self.username = username
|
self.username = username
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class DelugeEventMetaClass(type):
|
|||||||
"""
|
"""
|
||||||
This metaclass simply keeps a list of all events classes created.
|
This metaclass simply keeps a list of all events classes created.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(cls, name, bases, dct): # pylint: disable=bad-mcs-method-argument
|
def __init__(cls, name, bases, dct): # pylint: disable=bad-mcs-method-argument
|
||||||
super(DelugeEventMetaClass, cls).__init__(name, bases, dct)
|
super(DelugeEventMetaClass, cls).__init__(name, bases, dct)
|
||||||
if name != 'DelugeEvent':
|
if name != 'DelugeEvent':
|
||||||
@ -58,6 +59,7 @@ class TorrentAddedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a new torrent is successfully added to the session.
|
Emitted when a new torrent is successfully added to the session.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id, from_state):
|
def __init__(self, torrent_id, from_state):
|
||||||
"""
|
"""
|
||||||
:param torrent_id: the torrent_id of the torrent that was added
|
: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.
|
Emitted when a torrent has been removed from the session.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id):
|
def __init__(self, torrent_id):
|
||||||
"""
|
"""
|
||||||
:param torrent_id: the 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.
|
Emitted when a torrent is about to be removed from the session.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id):
|
def __init__(self, torrent_id):
|
||||||
"""
|
"""
|
||||||
:param torrent_id: the torrent_id
|
:param torrent_id: the torrent_id
|
||||||
@ -96,6 +100,7 @@ class TorrentStateChangedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a torrent changes state.
|
Emitted when a torrent changes state.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id, state):
|
def __init__(self, torrent_id, state):
|
||||||
"""
|
"""
|
||||||
:param torrent_id: the torrent_id
|
:param torrent_id: the torrent_id
|
||||||
@ -110,6 +115,7 @@ class TorrentTrackerStatusEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a torrents tracker status changes.
|
Emitted when a torrents tracker status changes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id, status):
|
def __init__(self, torrent_id, status):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
@ -123,6 +129,7 @@ class TorrentQueueChangedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when the queue order has changed.
|
Emitted when the queue order has changed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -130,6 +137,7 @@ class TorrentFolderRenamedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a folder within a torrent has been renamed.
|
Emitted when a folder within a torrent has been renamed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id, old, new):
|
def __init__(self, torrent_id, old, new):
|
||||||
"""
|
"""
|
||||||
:param torrent_id: the torrent_id
|
:param torrent_id: the torrent_id
|
||||||
@ -146,6 +154,7 @@ class TorrentFileRenamedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a file within a torrent has been renamed.
|
Emitted when a file within a torrent has been renamed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id, index, name):
|
def __init__(self, torrent_id, index, name):
|
||||||
"""
|
"""
|
||||||
:param torrent_id: the torrent_id
|
:param torrent_id: the torrent_id
|
||||||
@ -162,6 +171,7 @@ class TorrentFinishedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a torrent finishes downloading.
|
Emitted when a torrent finishes downloading.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id):
|
def __init__(self, torrent_id):
|
||||||
"""
|
"""
|
||||||
:param torrent_id: the torrent_id
|
:param torrent_id: the torrent_id
|
||||||
@ -174,6 +184,7 @@ class TorrentResumedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a torrent resumes from a paused state.
|
Emitted when a torrent resumes from a paused state.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id):
|
def __init__(self, torrent_id):
|
||||||
"""
|
"""
|
||||||
:param torrent_id: the torrent_id
|
:param torrent_id: the torrent_id
|
||||||
@ -186,6 +197,7 @@ class TorrentFileCompletedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a file completes.
|
Emitted when a file completes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id, index):
|
def __init__(self, torrent_id, index):
|
||||||
"""
|
"""
|
||||||
:param torrent_id: the torrent_id
|
:param torrent_id: the torrent_id
|
||||||
@ -200,6 +212,7 @@ class TorrentStorageMovedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when the storage location for a torrent has been moved.
|
Emitted when the storage location for a torrent has been moved.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, torrent_id, path):
|
def __init__(self, torrent_id, path):
|
||||||
"""
|
"""
|
||||||
:param torrent_id: the torrent_id
|
:param torrent_id: the torrent_id
|
||||||
@ -214,6 +227,7 @@ class CreateTorrentProgressEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when creating a torrent file remotely.
|
Emitted when creating a torrent file remotely.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, piece_count, num_pieces):
|
def __init__(self, piece_count, num_pieces):
|
||||||
self._args = [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.
|
Emitted when a more recent version of Deluge is available.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, new_release):
|
def __init__(self, new_release):
|
||||||
"""
|
"""
|
||||||
:param new_release: the new version that is available
|
: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
|
Emitted when a session has started. This typically only happens once when
|
||||||
the daemon is initially started.
|
the daemon is initially started.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -242,6 +258,7 @@ class SessionPausedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when the session has been paused.
|
Emitted when the session has been paused.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -249,6 +266,7 @@ class SessionResumedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when the session has been resumed.
|
Emitted when the session has been resumed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -256,6 +274,7 @@ class ConfigValueChangedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a config value changes in the Core.
|
Emitted when a config value changes in the Core.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, key, value):
|
def __init__(self, key, value):
|
||||||
"""
|
"""
|
||||||
:param key: the key that changed
|
:param key: the key that changed
|
||||||
@ -269,6 +288,7 @@ class PluginEnabledEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a plugin is enabled in the Core.
|
Emitted when a plugin is enabled in the Core.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
self._args = [plugin_name]
|
self._args = [plugin_name]
|
||||||
|
|
||||||
@ -277,6 +297,7 @@ class PluginDisabledEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a plugin is disabled in the Core.
|
Emitted when a plugin is disabled in the Core.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
self._args = [plugin_name]
|
self._args = [plugin_name]
|
||||||
|
|
||||||
@ -285,6 +306,7 @@ class ClientDisconnectedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a client disconnects.
|
Emitted when a client disconnects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session_id):
|
def __init__(self, session_id):
|
||||||
self._args = [session_id]
|
self._args = [session_id]
|
||||||
|
|
||||||
@ -293,6 +315,7 @@ class ExternalIPEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when the external ip address is received from libtorrent.
|
Emitted when the external ip address is received from libtorrent.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, external_ip):
|
def __init__(self, external_ip):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@ -31,13 +31,14 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class CompressionDecoder(client.GzipDecoder):
|
class CompressionDecoder(client.GzipDecoder):
|
||||||
"""A compression decoder for gzip, x-gzip and deflate."""
|
"""A compression decoder for gzip, x-gzip and deflate."""
|
||||||
|
|
||||||
def deliverBody(self, protocol): # NOQA: N802
|
def deliverBody(self, protocol): # NOQA: N802
|
||||||
self.original.deliverBody(
|
self.original.deliverBody(CompressionDecoderProtocol(protocol, self.original))
|
||||||
CompressionDecoderProtocol(protocol, self.original))
|
|
||||||
|
|
||||||
|
|
||||||
class CompressionDecoderProtocol(client._GzipProtocol):
|
class CompressionDecoderProtocol(client._GzipProtocol):
|
||||||
"""A compression decoder protocol for CompressionDecoder."""
|
"""A compression decoder protocol for CompressionDecoder."""
|
||||||
|
|
||||||
def __init__(self, protocol, response):
|
def __init__(self, protocol, response):
|
||||||
super(CompressionDecoderProtocol, self).__init__(protocol, response)
|
super(CompressionDecoderProtocol, self).__init__(protocol, response)
|
||||||
self._zlibDecompress = zlib.decompressobj(32 + zlib.MAX_WBITS)
|
self._zlibDecompress = zlib.decompressobj(32 + zlib.MAX_WBITS)
|
||||||
@ -45,6 +46,7 @@ class CompressionDecoderProtocol(client._GzipProtocol):
|
|||||||
|
|
||||||
class BodyHandler(HTTPClientParser, object):
|
class BodyHandler(HTTPClientParser, object):
|
||||||
"""An HTTP parser that saves the response to a file."""
|
"""An HTTP parser that saves the response to a file."""
|
||||||
|
|
||||||
def __init__(self, request, finished, length, agent, encoding=None):
|
def __init__(self, request, finished, length, agent, encoding=None):
|
||||||
"""BodyHandler init.
|
"""BodyHandler init.
|
||||||
|
|
||||||
@ -66,8 +68,7 @@ class BodyHandler(HTTPClientParser, object):
|
|||||||
self.current_length += len(data)
|
self.current_length += len(data)
|
||||||
self.data += data
|
self.data += data
|
||||||
if self.agent.part_callback:
|
if self.agent.part_callback:
|
||||||
self.agent.part_callback(
|
self.agent.part_callback(data, self.current_length, self.total_length)
|
||||||
data, self.current_length, self.total_length)
|
|
||||||
|
|
||||||
def connectionLost(self, reason): # NOQA: N802
|
def connectionLost(self, reason): # NOQA: N802
|
||||||
if self.encoding:
|
if self.encoding:
|
||||||
@ -82,6 +83,7 @@ class BodyHandler(HTTPClientParser, object):
|
|||||||
@implementer(IAgent)
|
@implementer(IAgent)
|
||||||
class HTTPDownloaderAgent(object):
|
class HTTPDownloaderAgent(object):
|
||||||
"""A File Downloader Agent."""
|
"""A File Downloader Agent."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
agent,
|
agent,
|
||||||
@ -125,21 +127,19 @@ class HTTPDownloaderAgent(object):
|
|||||||
finished.errback(Failure(error))
|
finished.errback(Failure(error))
|
||||||
else:
|
else:
|
||||||
headers = response.headers
|
headers = response.headers
|
||||||
body_length = int(
|
body_length = int(headers.getRawHeaders(b'content-length', default=[0])[0])
|
||||||
headers.getRawHeaders(b'content-length', default=[0])[0])
|
|
||||||
|
|
||||||
if (
|
if headers.hasHeader(b'content-disposition') and not self.force_filename:
|
||||||
headers.hasHeader(b'content-disposition')
|
content_disp = headers.getRawHeaders(b'content-disposition')[0].decode(
|
||||||
and not self.force_filename
|
'utf-8'
|
||||||
):
|
)
|
||||||
content_disp = headers.getRawHeaders(
|
|
||||||
b'content-disposition')[0].decode('utf-8')
|
|
||||||
content_disp_params = cgi.parse_header(content_disp)[1]
|
content_disp_params = cgi.parse_header(content_disp)[1]
|
||||||
if 'filename' in content_disp_params:
|
if 'filename' in content_disp_params:
|
||||||
new_file_name = content_disp_params['filename']
|
new_file_name = content_disp_params['filename']
|
||||||
new_file_name = sanitise_filename(new_file_name)
|
new_file_name = sanitise_filename(new_file_name)
|
||||||
new_file_name = os.path.join(
|
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
|
count = 1
|
||||||
fileroot = os.path.splitext(new_file_name)[0]
|
fileroot = os.path.splitext(new_file_name)[0]
|
||||||
@ -155,13 +155,8 @@ class HTTPDownloaderAgent(object):
|
|||||||
params = cgi.parse_header(cont_type)[1]
|
params = cgi.parse_header(cont_type)[1]
|
||||||
encoding = params.get('charset', None)
|
encoding = params.get('charset', None)
|
||||||
response.deliverBody(
|
response.deliverBody(
|
||||||
BodyHandler(
|
BodyHandler(response.request, finished, body_length, self, encoding)
|
||||||
response.request,
|
)
|
||||||
finished,
|
|
||||||
body_length,
|
|
||||||
self,
|
|
||||||
encoding,
|
|
||||||
))
|
|
||||||
|
|
||||||
return finished
|
return finished
|
||||||
|
|
||||||
@ -186,10 +181,7 @@ class HTTPDownloaderAgent(object):
|
|||||||
headers.addRawHeader('User-Agent', user_agent)
|
headers.addRawHeader('User-Agent', user_agent)
|
||||||
|
|
||||||
d = self.agent.request(
|
d = self.agent.request(
|
||||||
method=method,
|
method=method, uri=uri, headers=headers, bodyProducer=body_producer
|
||||||
uri=uri,
|
|
||||||
headers=headers,
|
|
||||||
bodyProducer=body_producer,
|
|
||||||
)
|
)
|
||||||
d.addCallback(self.request_callback)
|
d.addCallback(self.request_callback)
|
||||||
return d
|
return d
|
||||||
@ -212,8 +204,7 @@ def sanitise_filename(filename):
|
|||||||
if os.path.basename(filename) != filename:
|
if os.path.basename(filename) != filename:
|
||||||
# Dodgy server, log it
|
# Dodgy server, log it
|
||||||
log.warning(
|
log.warning(
|
||||||
'Potentially malicious server: trying to write to file: %s',
|
'Potentially malicious server: trying to write to file: %s', filename
|
||||||
filename,
|
|
||||||
)
|
)
|
||||||
# Only use the basename
|
# Only use the basename
|
||||||
filename = os.path.basename(filename)
|
filename = os.path.basename(filename)
|
||||||
@ -222,15 +213,15 @@ def sanitise_filename(filename):
|
|||||||
if filename.startswith('.') or ';' in filename or '|' in filename:
|
if filename.startswith('.') or ';' in filename or '|' in filename:
|
||||||
# Dodgy server, log it
|
# Dodgy server, log it
|
||||||
log.warning(
|
log.warning(
|
||||||
'Potentially malicious server: trying to write to file: %s',
|
'Potentially malicious server: trying to write to file: %s', filename
|
||||||
filename,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
def _download_file(
|
def _download_file(
|
||||||
url, filename,
|
url,
|
||||||
|
filename,
|
||||||
callback=None,
|
callback=None,
|
||||||
headers=None,
|
headers=None,
|
||||||
force_filename=False,
|
force_filename=False,
|
||||||
@ -269,12 +260,7 @@ def _download_file(
|
|||||||
agent = client.RedirectAgent(agent)
|
agent = client.RedirectAgent(agent)
|
||||||
|
|
||||||
agent = HTTPDownloaderAgent(
|
agent = HTTPDownloaderAgent(
|
||||||
agent,
|
agent, filename, callback, force_filename, allow_compression, handle_redirects
|
||||||
filename,
|
|
||||||
callback,
|
|
||||||
force_filename,
|
|
||||||
allow_compression,
|
|
||||||
handle_redirects,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# The Headers init expects dict values to be a list.
|
# 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.PageRedirect: If handle_redirects is False.
|
||||||
t.w.e.Error: For all other HTTP response errors.
|
t.w.e.Error: For all other HTTP response errors.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_download_success(result):
|
def on_download_success(result):
|
||||||
log.debug('Download success!')
|
log.debug('Download success!')
|
||||||
return result
|
return result
|
||||||
@ -324,14 +311,19 @@ def download_file(
|
|||||||
def on_download_fail(failure):
|
def on_download_fail(failure):
|
||||||
log.warning(
|
log.warning(
|
||||||
'Error occurred downloading file from "%s": %s',
|
'Error occurred downloading file from "%s": %s',
|
||||||
url, failure.getErrorMessage(),
|
url,
|
||||||
|
failure.getErrorMessage(),
|
||||||
)
|
)
|
||||||
result = failure
|
result = failure
|
||||||
return result
|
return result
|
||||||
|
|
||||||
d = _download_file(
|
d = _download_file(
|
||||||
url, filename, callback=callback, headers=headers,
|
url,
|
||||||
force_filename=force_filename, allow_compression=allow_compression,
|
filename,
|
||||||
|
callback=callback,
|
||||||
|
headers=headers,
|
||||||
|
force_filename=force_filename,
|
||||||
|
allow_compression=allow_compression,
|
||||||
handle_redirects=handle_redirects,
|
handle_redirects=handle_redirects,
|
||||||
)
|
)
|
||||||
d.addCallbacks(on_download_success, on_download_fail)
|
d.addCallbacks(on_download_success, on_download_fail)
|
||||||
|
|||||||
@ -29,7 +29,9 @@ LoggingLoggerClass = logging.getLoggerClass()
|
|||||||
if 'dev' in common.get_version():
|
if 'dev' in common.get_version():
|
||||||
DEFAULT_LOGGING_FORMAT = '%%(asctime)s.%%(msecs)03.0f [%%(levelname)-8s][%%(name)-%ds:%%(lineno)-4d] %%(message)s'
|
DEFAULT_LOGGING_FORMAT = '%%(asctime)s.%%(msecs)03.0f [%%(levelname)-8s][%%(name)-%ds:%%(lineno)-4d] %%(message)s'
|
||||||
else:
|
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
|
MAX_LOGGER_NAME_LENGTH = 10
|
||||||
|
|
||||||
|
|
||||||
@ -43,10 +45,12 @@ class Logging(LoggingLoggerClass):
|
|||||||
if len(logger_name) > MAX_LOGGER_NAME_LENGTH:
|
if len(logger_name) > MAX_LOGGER_NAME_LENGTH:
|
||||||
MAX_LOGGER_NAME_LENGTH = len(logger_name)
|
MAX_LOGGER_NAME_LENGTH = len(logger_name)
|
||||||
for handler in logging.getLogger().handlers:
|
for handler in logging.getLogger().handlers:
|
||||||
handler.setFormatter(logging.Formatter(
|
handler.setFormatter(
|
||||||
|
logging.Formatter(
|
||||||
DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH,
|
DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH,
|
||||||
datefmt='%H:%M:%S',
|
datefmt='%H:%M:%S',
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def garbage(self, msg, *args, **kwargs):
|
def garbage(self, msg, *args, **kwargs):
|
||||||
@ -112,8 +116,12 @@ levels = {
|
|||||||
|
|
||||||
|
|
||||||
def setup_logger(
|
def setup_logger(
|
||||||
level='error', filename=None, filemode='w', logrotate=None,
|
level='error',
|
||||||
output_stream=sys.stdout, twisted_observer=True,
|
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
|
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:
|
if filename and logrotate:
|
||||||
handler = logging.handlers.RotatingFileHandler(
|
handler = logging.handlers.RotatingFileHandler(
|
||||||
filename, maxBytes=logrotate,
|
filename, maxBytes=logrotate, backupCount=5, encoding='utf-8'
|
||||||
backupCount=5, encoding='utf-8',
|
|
||||||
)
|
)
|
||||||
elif filename and filemode == 'w':
|
elif filename and filemode == 'w':
|
||||||
handler_cls = logging.FileHandler
|
handler_cls = logging.FileHandler
|
||||||
if not common.windows_check():
|
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')
|
handler = handler_cls(filename, mode=filemode, encoding='utf-8')
|
||||||
else:
|
else:
|
||||||
handler = logging.StreamHandler(stream=output_stream)
|
handler = logging.StreamHandler(stream=output_stream)
|
||||||
@ -154,8 +163,7 @@ def setup_logger(
|
|||||||
handler.setLevel(level)
|
handler.setLevel(level)
|
||||||
|
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH,
|
DEFAULT_LOGGING_FORMAT % MAX_LOGGER_NAME_LENGTH, datefmt='%H:%M:%S'
|
||||||
datefmt='%H:%M:%S',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
@ -190,7 +198,9 @@ class TwistedLoggingObserver(PythonLoggingObserver):
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
if 'log_failure' in event_dict:
|
if 'log_failure' in event_dict:
|
||||||
fmt = '%(log_namespace)s \n%(log_failure)s'
|
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:
|
else:
|
||||||
PythonLoggingObserver.emit(self, event_dict)
|
PythonLoggingObserver.emit(self, event_dict)
|
||||||
|
|
||||||
@ -214,13 +224,13 @@ def tweak_logging_levels():
|
|||||||
the command line.
|
the command line.
|
||||||
"""
|
"""
|
||||||
from deluge import configmanager
|
from deluge import configmanager
|
||||||
|
|
||||||
logging_config_file = os.path.join(configmanager.get_config_dir(), 'logging.conf')
|
logging_config_file = os.path.join(configmanager.get_config_dir(), 'logging.conf')
|
||||||
if not os.path.isfile(logging_config_file):
|
if not os.path.isfile(logging_config_file):
|
||||||
return
|
return
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.warning(
|
log.warning(
|
||||||
'logging.conf found! tweaking logging levels from %s',
|
'logging.conf found! tweaking logging levels from %s', logging_config_file
|
||||||
logging_config_file,
|
|
||||||
)
|
)
|
||||||
with open(logging_config_file, 'r') as _file:
|
with open(logging_config_file, 'r') as _file:
|
||||||
for line in _file:
|
for line in _file:
|
||||||
@ -249,6 +259,7 @@ def set_logger_level(level, logger_name=None):
|
|||||||
|
|
||||||
def get_plugin_logger(logger_name):
|
def get_plugin_logger(logger_name):
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
stack = inspect.stack()
|
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
|
module_stack = stack.pop(0) # The module that called the log function
|
||||||
@ -256,8 +267,10 @@ def get_plugin_logger(logger_name):
|
|||||||
# In some weird cases caller_module might be None, try to continue
|
# In some weird cases caller_module might be None, try to continue
|
||||||
caller_module_name = getattr(caller_module, '__name__', '')
|
caller_module_name = getattr(caller_module, '__name__', '')
|
||||||
warnings.warn_explicit(
|
warnings.warn_explicit(
|
||||||
DEPRECATION_WARNING, DeprecationWarning,
|
DEPRECATION_WARNING,
|
||||||
module_stack[1], module_stack[2],
|
DeprecationWarning,
|
||||||
|
module_stack[1],
|
||||||
|
module_stack[2],
|
||||||
caller_module_name,
|
caller_module_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -292,6 +305,7 @@ Triggering code:"""
|
|||||||
class _BackwardsCompatibleLOG(object):
|
class _BackwardsCompatibleLOG(object):
|
||||||
def __getattribute__(self, name):
|
def __getattribute__(self, name):
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
logger_name = 'deluge'
|
logger_name = 'deluge'
|
||||||
stack = inspect.stack()
|
stack = inspect.stack()
|
||||||
stack.pop(0) # The logging call from this module
|
stack.pop(0) # The logging call from this module
|
||||||
@ -300,8 +314,10 @@ class _BackwardsCompatibleLOG(object):
|
|||||||
# In some weird cases caller_module might be None, try to continue
|
# In some weird cases caller_module might be None, try to continue
|
||||||
caller_module_name = getattr(caller_module, '__name__', '')
|
caller_module_name = getattr(caller_module, '__name__', '')
|
||||||
warnings.warn_explicit(
|
warnings.warn_explicit(
|
||||||
DEPRECATION_WARNING, DeprecationWarning,
|
DEPRECATION_WARNING,
|
||||||
module_stack[1], module_stack[2],
|
DeprecationWarning,
|
||||||
|
module_stack[1],
|
||||||
|
module_stack[2],
|
||||||
caller_module_name,
|
caller_module_name,
|
||||||
)
|
)
|
||||||
if caller_module:
|
if caller_module:
|
||||||
@ -320,7 +336,7 @@ class _BackwardsCompatibleLOG(object):
|
|||||||
else:
|
else:
|
||||||
logging.getLogger(logger_name).warning(
|
logging.getLogger(logger_name).warning(
|
||||||
"Unable to monkey-patch the calling module's `log` attribute! "
|
"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)
|
return getattr(logging.getLogger(logger_name), name)
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ from deluge.common import get_path_size, utf8_encode_structure
|
|||||||
|
|
||||||
class InvalidPath(Exception):
|
class InvalidPath(Exception):
|
||||||
"""Raised when an invalid path is supplied."""
|
"""Raised when an invalid path is supplied."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ class InvalidPieceSize(Exception):
|
|||||||
Note:
|
Note:
|
||||||
Piece sizes must be multiples of 16KiB.
|
Piece sizes must be multiples of 16KiB.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -42,6 +44,7 @@ class TorrentMetadata(object):
|
|||||||
>>> t.save('/tmp/test.torrent')
|
>>> t.save('/tmp/test.torrent')
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__data_path = None
|
self.__data_path = None
|
||||||
self.__piece_size = 0
|
self.__piece_size = 0
|
||||||
@ -66,9 +69,7 @@ class TorrentMetadata(object):
|
|||||||
if not self.data_path:
|
if not self.data_path:
|
||||||
raise InvalidPath('Need to set a data_path!')
|
raise InvalidPath('Need to set a data_path!')
|
||||||
|
|
||||||
torrent = {
|
torrent = {'info': {}}
|
||||||
'info': {},
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.comment:
|
if self.comment:
|
||||||
torrent['comment'] = self.comment
|
torrent['comment'] = self.comment
|
||||||
@ -121,7 +122,9 @@ class TorrentMetadata(object):
|
|||||||
# Collect a list of file paths and add padding files if necessary
|
# Collect a list of file paths and add padding files if necessary
|
||||||
for (dirpath, dirnames, filenames) in os.walk(self.data_path):
|
for (dirpath, dirnames, filenames) in os.walk(self.data_path):
|
||||||
for index, filename in enumerate(filenames):
|
for index, filename in enumerate(filenames):
|
||||||
size = get_path_size(os.path.join(self.data_path, dirpath, filename))
|
size = get_path_size(
|
||||||
|
os.path.join(self.data_path, dirpath, filename)
|
||||||
|
)
|
||||||
p = dirpath[len(self.data_path) :]
|
p = dirpath[len(self.data_path) :]
|
||||||
p = p.lstrip('/')
|
p = p.lstrip('/')
|
||||||
p = p.split('/')
|
p = p.split('/')
|
||||||
@ -156,7 +159,9 @@ class TorrentMetadata(object):
|
|||||||
buf = b''
|
buf = b''
|
||||||
fs[-1][b'attr'] = b'p'
|
fs[-1][b'attr'] = b'p'
|
||||||
else:
|
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))
|
r = _file.read(piece_size - len(buf))
|
||||||
while r:
|
while r:
|
||||||
buf += r
|
buf += r
|
||||||
|
|||||||
@ -50,14 +50,25 @@ class RemoteFileProgress(object):
|
|||||||
|
|
||||||
def __call__(self, piece_count, num_pieces):
|
def __call__(self, piece_count, num_pieces):
|
||||||
component.get('RPCServer').emit_event_for_session_id(
|
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(
|
def make_meta_file(
|
||||||
path, url, piece_length, progress=None, title=None, comment=None,
|
path,
|
||||||
safe=None, content_type=None, target=None, webseeds=None, name=None,
|
url,
|
||||||
private=False, created_by=None, trackers=None,
|
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())}
|
data = {'creation date': int(gmtime())}
|
||||||
if url:
|
if url:
|
||||||
@ -140,6 +151,7 @@ def makeinfo(path, piece_length, progress, name=None, content_type=None, private
|
|||||||
totalsize += os.path.getsize(f)
|
totalsize += os.path.getsize(f)
|
||||||
if totalsize >= piece_length:
|
if totalsize >= piece_length:
|
||||||
import math
|
import math
|
||||||
|
|
||||||
num_pieces = math.ceil(totalsize / piece_length)
|
num_pieces = math.ceil(totalsize / piece_length)
|
||||||
else:
|
else:
|
||||||
num_pieces = 1
|
num_pieces = 1
|
||||||
@ -149,10 +161,9 @@ def makeinfo(path, piece_length, progress, name=None, content_type=None, private
|
|||||||
size = os.path.getsize(f)
|
size = os.path.getsize(f)
|
||||||
p2 = [n.encode('utf8') for n in p]
|
p2 = [n.encode('utf8') for n in p]
|
||||||
if content_type:
|
if content_type:
|
||||||
fs.append({
|
fs.append(
|
||||||
'length': size, 'path': p2,
|
{'length': size, 'path': p2, 'content_type': content_type}
|
||||||
'content_type': content_type,
|
) # HEREDAVE. bad for batch!
|
||||||
}) # HEREDAVE. bad for batch!
|
|
||||||
else:
|
else:
|
||||||
fs.append({'length': size, 'path': p2})
|
fs.append({'length': size, 'path': p2})
|
||||||
with open(f, 'rb') as file_:
|
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:
|
if content_type is not None:
|
||||||
return {
|
return {
|
||||||
'pieces': b''.join(pieces),
|
'pieces': b''.join(pieces),
|
||||||
'piece length': piece_length, 'length': size,
|
'piece length': piece_length,
|
||||||
|
'length': size,
|
||||||
'name': name,
|
'name': name,
|
||||||
'content_type': content_type,
|
'content_type': content_type,
|
||||||
'private': private,
|
'private': private,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'pieces': b''.join(pieces),
|
'pieces': b''.join(pieces),
|
||||||
'piece length': piece_length, 'length': size,
|
'piece length': piece_length,
|
||||||
|
'length': size,
|
||||||
'name': name,
|
'name': name,
|
||||||
'private': private,
|
'private': private,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,9 +17,12 @@ def is_hidden(filepath):
|
|||||||
def has_hidden_attribute(filepath):
|
def has_hidden_attribute(filepath):
|
||||||
import win32api
|
import win32api
|
||||||
import win32con
|
import win32con
|
||||||
|
|
||||||
try:
|
try:
|
||||||
attribute = win32api.GetFileAttributes(filepath)
|
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):
|
except (AttributeError, AssertionError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@ -56,7 +56,9 @@ class PluginManagerBase(object):
|
|||||||
self.config = deluge.configmanager.ConfigManager(config_file)
|
self.config = deluge.configmanager.ConfigManager(config_file)
|
||||||
|
|
||||||
# Create the plugins folder if it doesn't exist
|
# 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'))
|
os.mkdir(os.path.join(deluge.configmanager.get_config_dir(), 'plugins'))
|
||||||
|
|
||||||
# This is the entry we want to load..
|
# This is the entry we want to load..
|
||||||
@ -149,7 +151,9 @@ class PluginManagerBase(object):
|
|||||||
log.error(ex)
|
log.error(ex)
|
||||||
return defer.succeed(False)
|
return defer.succeed(False)
|
||||||
except Exception as ex:
|
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)
|
log.exception(ex)
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
@ -161,35 +165,47 @@ class PluginManagerBase(object):
|
|||||||
|
|
||||||
if not instance.__module__.startswith('deluge.plugins.'):
|
if not instance.__module__.startswith('deluge.plugins.'):
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
warnings.warn_explicit(
|
warnings.warn_explicit(
|
||||||
DEPRECATION_WARNING % name,
|
DEPRECATION_WARNING % name,
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
instance.__module__, 0,
|
instance.__module__,
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
if self._component_state == 'Started':
|
if self._component_state == 'Started':
|
||||||
|
|
||||||
def on_enabled(result, instance):
|
def on_enabled(result, instance):
|
||||||
return component.start([instance.plugin._component_name])
|
return component.start([instance.plugin._component_name])
|
||||||
|
|
||||||
return_d.addCallback(on_enabled, instance)
|
return_d.addCallback(on_enabled, instance)
|
||||||
|
|
||||||
def on_started(result, instance):
|
def on_started(result, instance):
|
||||||
plugin_name_space = plugin_name.replace('-', ' ')
|
plugin_name_space = plugin_name.replace('-', ' ')
|
||||||
self.plugins[plugin_name_space] = instance
|
self.plugins[plugin_name_space] = instance
|
||||||
if plugin_name_space not in self.config['enabled_plugins']:
|
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)
|
self.config['enabled_plugins'].append(plugin_name_space)
|
||||||
log.info('Plugin %s enabled...', plugin_name_space)
|
log.info('Plugin %s enabled...', plugin_name_space)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def on_started_error(result, instance):
|
def on_started_error(result, instance):
|
||||||
log.error(
|
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'),
|
result.getTraceback(elideFrameworkCode=1, detail='brief'),
|
||||||
)
|
)
|
||||||
self.plugins[plugin_name.replace('-', ' ')] = instance
|
self.plugins[plugin_name.replace('-', ' ')] = instance
|
||||||
self.disable_plugin(plugin_name)
|
self.disable_plugin(plugin_name)
|
||||||
return False
|
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 return_d
|
||||||
|
|
||||||
return defer.succeed(False)
|
return defer.succeed(False)
|
||||||
@ -219,7 +235,9 @@ class PluginManagerBase(object):
|
|||||||
def on_disabled(result):
|
def on_disabled(result):
|
||||||
ret = True
|
ret = True
|
||||||
if isinstance(result, Failure):
|
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
|
ret = False
|
||||||
try:
|
try:
|
||||||
component.deregister(self.plugins[name].plugin)
|
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():
|
for line in self.pkg_env[name][0].get_metadata('PKG-INFO').splitlines():
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
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
|
# This is a continuation
|
||||||
cont_lines.append(line.strip())
|
cont_lines.append(line.strip())
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -20,6 +20,7 @@ from deluge.plugins.init import PluginInitBase
|
|||||||
class CorePlugin(PluginInitBase):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .core import Core as _pluginCls
|
from .core import Core as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ class CorePlugin(PluginInitBase):
|
|||||||
class GtkUIPlugin(PluginInitBase):
|
class GtkUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .gtkui import GtkUI as _pluginCls
|
from .gtkui import GtkUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -34,5 +36,6 @@ class GtkUIPlugin(PluginInitBase):
|
|||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .webui import WebUI as _pluginCls
|
from .webui import WebUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
super(WebUIPlugin, self).__init__(plugin_name)
|
||||||
|
|||||||
@ -35,10 +35,7 @@ from deluge.plugins.pluginbase import CorePluginBase
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_PREFS = {
|
DEFAULT_PREFS = {'watchdirs': {}, 'next_id': 1}
|
||||||
'watchdirs': {},
|
|
||||||
'next_id': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
OPTIONS_AVAILABLE = { # option: builtin
|
OPTIONS_AVAILABLE = { # option: builtin
|
||||||
@ -72,6 +69,7 @@ MAX_NUM_ATTEMPTS = 10
|
|||||||
|
|
||||||
class AutoaddOptionsChangedEvent(DelugeEvent):
|
class AutoaddOptionsChangedEvent(DelugeEvent):
|
||||||
"""Emitted when the options for the plugin are changed."""
|
"""Emitted when the options for the plugin are changed."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -92,7 +90,7 @@ class Core(CorePluginBase):
|
|||||||
|
|
||||||
self.rpcserver = component.get('RPCServer')
|
self.rpcserver = component.get('RPCServer')
|
||||||
component.get('EventManager').register_event_handler(
|
component.get('EventManager').register_event_handler(
|
||||||
'PreTorrentRemovedEvent', self.__on_pre_torrent_removed,
|
'PreTorrentRemovedEvent', self.__on_pre_torrent_removed
|
||||||
)
|
)
|
||||||
|
|
||||||
# Dict of Filename:Attempts
|
# Dict of Filename:Attempts
|
||||||
@ -110,7 +108,7 @@ class Core(CorePluginBase):
|
|||||||
def disable(self):
|
def disable(self):
|
||||||
# disable all running looping calls
|
# disable all running looping calls
|
||||||
component.get('EventManager').deregister_event_handler(
|
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():
|
for loopingcall in self.update_timers.values():
|
||||||
loopingcall.stop()
|
loopingcall.stop()
|
||||||
@ -124,14 +122,10 @@ class Core(CorePluginBase):
|
|||||||
"""Update the options for a watch folder."""
|
"""Update the options for a watch folder."""
|
||||||
watchdir_id = str(watchdir_id)
|
watchdir_id = str(watchdir_id)
|
||||||
options = self._make_unicode(options)
|
options = self._make_unicode(options)
|
||||||
check_input(
|
check_input(watchdir_id in self.watchdirs, _('Watch folder does not exist.'))
|
||||||
watchdir_id in self.watchdirs, _('Watch folder does not exist.'),
|
|
||||||
)
|
|
||||||
if 'path' in options:
|
if 'path' in options:
|
||||||
options['abspath'] = os.path.abspath(options['path'])
|
options['abspath'] = os.path.abspath(options['path'])
|
||||||
check_input(
|
check_input(os.path.isdir(options['abspath']), _('Path does not exist.'))
|
||||||
os.path.isdir(options['abspath']), _('Path does not exist.'),
|
|
||||||
)
|
|
||||||
for w_id, w in self.watchdirs.items():
|
for w_id, w in self.watchdirs.items():
|
||||||
if options['abspath'] == w['abspath'] and watchdir_id != w_id:
|
if options['abspath'] == w['abspath'] and watchdir_id != w_id:
|
||||||
raise Exception('Path is already being watched.')
|
raise Exception('Path is already being watched.')
|
||||||
@ -211,10 +205,7 @@ class Core(CorePluginBase):
|
|||||||
watchdir = self.watchdirs[watchdir_id]
|
watchdir = self.watchdirs[watchdir_id]
|
||||||
if not watchdir['enabled']:
|
if not watchdir['enabled']:
|
||||||
# We shouldn't be updating because this watchdir is not enabled
|
# We shouldn't be updating because this watchdir is not enabled
|
||||||
log.debug(
|
log.debug('Watchdir id %s is not enabled. Disabling it.', watchdir_id)
|
||||||
'Watchdir id %s is not enabled. Disabling it.',
|
|
||||||
watchdir_id,
|
|
||||||
)
|
|
||||||
self.disable_watchdir(watchdir_id)
|
self.disable_watchdir(watchdir_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -231,7 +222,10 @@ class Core(CorePluginBase):
|
|||||||
# without them is valid, and applies all its settings.
|
# without them is valid, and applies all its settings.
|
||||||
for option, value in watchdir.items():
|
for option, value in watchdir.items():
|
||||||
if OPTIONS_AVAILABLE.get(option):
|
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
|
options[option] = value
|
||||||
|
|
||||||
# Check for .magnet files containing multiple magnet links and
|
# Check for .magnet files containing multiple magnet links and
|
||||||
@ -240,19 +234,27 @@ class Core(CorePluginBase):
|
|||||||
try:
|
try:
|
||||||
filepath = os.path.join(watchdir['abspath'], filename)
|
filepath = os.path.join(watchdir['abspath'], filename)
|
||||||
except UnicodeDecodeError as ex:
|
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
|
continue
|
||||||
if os.path.isdir(filepath):
|
if os.path.isdir(filepath):
|
||||||
# Skip directories
|
# Skip directories
|
||||||
continue
|
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)
|
os.remove(filepath)
|
||||||
|
|
||||||
for filename in os.listdir(watchdir['abspath']):
|
for filename in os.listdir(watchdir['abspath']):
|
||||||
try:
|
try:
|
||||||
filepath = os.path.join(watchdir['abspath'], filename)
|
filepath = os.path.join(watchdir['abspath'], filename)
|
||||||
except UnicodeDecodeError as ex:
|
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
|
continue
|
||||||
|
|
||||||
if os.path.isdir(filepath):
|
if os.path.isdir(filepath):
|
||||||
@ -276,7 +278,8 @@ class Core(CorePluginBase):
|
|||||||
if self.invalid_torrents[filename] >= MAX_NUM_ATTEMPTS:
|
if self.invalid_torrents[filename] >= MAX_NUM_ATTEMPTS:
|
||||||
log.warning(
|
log.warning(
|
||||||
'Maximum attempts reached while trying to add the '
|
'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')
|
os.rename(filepath, filepath + '.invalid')
|
||||||
del self.invalid_torrents[filename]
|
del self.invalid_torrents[filename]
|
||||||
@ -296,7 +299,10 @@ class Core(CorePluginBase):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log.error('Unable to set label: %s', 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']:
|
if watchdir['queue_to_top']:
|
||||||
component.get('TorrentManager').queue_top(torrent_id)
|
component.get('TorrentManager').queue_top(torrent_id)
|
||||||
else:
|
else:
|
||||||
@ -312,7 +318,8 @@ class Core(CorePluginBase):
|
|||||||
copy_torrent_file = os.path.join(copy_torrent_path, filename)
|
copy_torrent_file = os.path.join(copy_torrent_path, filename)
|
||||||
log.debug(
|
log.debug(
|
||||||
'Moving added torrent file "%s" to "%s"',
|
'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)
|
shutil.move(filepath, copy_torrent_file)
|
||||||
else:
|
else:
|
||||||
@ -331,10 +338,12 @@ class Core(CorePluginBase):
|
|||||||
try:
|
try:
|
||||||
# The torrent looks good, so lets add it to the session.
|
# The torrent looks good, so lets add it to the session.
|
||||||
if magnet:
|
if magnet:
|
||||||
d = component.get('Core').add_torrent_magnet(filedump.strip(), options)
|
d = component.get('Core').add_torrent_magnet(
|
||||||
|
filedump.strip(), options
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
d = component.get('Core').add_torrent_file_async(
|
d = component.get('Core').add_torrent_file_async(
|
||||||
filename, b64encode(filedump), options,
|
filename, b64encode(filedump), options
|
||||||
)
|
)
|
||||||
d.addCallback(on_torrent_added, filename, filepath)
|
d.addCallback(on_torrent_added, filename, filepath)
|
||||||
d.addErrback(fail_torrent_add, filepath, magnet)
|
d.addErrback(fail_torrent_add, filepath, magnet)
|
||||||
@ -346,7 +355,8 @@ class Core(CorePluginBase):
|
|||||||
self.disable_watchdir(watchdir_id)
|
self.disable_watchdir(watchdir_id)
|
||||||
log.error(
|
log.error(
|
||||||
'Disabling "%s", error during update: %s',
|
'Disabling "%s", error during update: %s',
|
||||||
self.watchdirs[watchdir_id]['path'], failure,
|
self.watchdirs[watchdir_id]['path'],
|
||||||
|
failure,
|
||||||
)
|
)
|
||||||
|
|
||||||
@export
|
@export
|
||||||
@ -356,7 +366,7 @@ class Core(CorePluginBase):
|
|||||||
if w_id not in self.update_timers or not self.update_timers[w_id].running:
|
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] = LoopingCall(self.update_watchdir, w_id)
|
||||||
self.update_timers[w_id].start(5).addErrback(
|
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
|
# Update the config
|
||||||
if not self.watchdirs[w_id]['enabled']:
|
if not self.watchdirs[w_id]['enabled']:
|
||||||
@ -398,8 +408,8 @@ class Core(CorePluginBase):
|
|||||||
session_auth_level = self.rpcserver.get_session_auth_level()
|
session_auth_level = self.rpcserver.get_session_auth_level()
|
||||||
if session_auth_level == AUTH_LEVEL_ADMIN:
|
if session_auth_level == AUTH_LEVEL_ADMIN:
|
||||||
log.debug(
|
log.debug(
|
||||||
'Current logged in user %s is an ADMIN, send all '
|
'Current logged in user %s is an ADMIN, send all ' 'watchdirs',
|
||||||
'watchdirs', session_user,
|
session_user,
|
||||||
)
|
)
|
||||||
return self.watchdirs
|
return self.watchdirs
|
||||||
|
|
||||||
@ -410,7 +420,9 @@ class Core(CorePluginBase):
|
|||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
'Current logged in user %s is not an ADMIN, send only '
|
'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
|
return watchdirs
|
||||||
|
|
||||||
@ -451,7 +463,9 @@ class Core(CorePluginBase):
|
|||||||
def remove(self, watchdir_id):
|
def remove(self, watchdir_id):
|
||||||
"""Remove a watch folder."""
|
"""Remove a watch folder."""
|
||||||
watchdir_id = str(watchdir_id)
|
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']:
|
if self.watchdirs[watchdir_id]['enabled']:
|
||||||
self.disable_watchdir(watchdir_id)
|
self.disable_watchdir(watchdir_id)
|
||||||
del self.watchdirs[watchdir_id]
|
del self.watchdirs[watchdir_id]
|
||||||
@ -488,13 +502,16 @@ class Core(CorePluginBase):
|
|||||||
os.remove(torrent_fname_path)
|
os.remove(torrent_fname_path)
|
||||||
log.info(
|
log.info(
|
||||||
'Removed torrent file "%s" from "%s"',
|
'Removed torrent file "%s" from "%s"',
|
||||||
torrent_fname, copy_torrent_path,
|
torrent_fname,
|
||||||
|
copy_torrent_path,
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
except OSError as ex:
|
except OSError as ex:
|
||||||
log.info(
|
log.info(
|
||||||
'Failed to removed torrent file "%s" from "%s": %s',
|
'Failed to removed torrent file "%s" from "%s": %s',
|
||||||
torrent_fname, copy_torrent_path, ex,
|
torrent_fname,
|
||||||
|
copy_torrent_path,
|
||||||
|
ex,
|
||||||
)
|
)
|
||||||
|
|
||||||
@export
|
@export
|
||||||
|
|||||||
@ -38,8 +38,12 @@ class OptionsDialog(object):
|
|||||||
spin_ids = ['max_download_speed', 'max_upload_speed', 'stop_ratio']
|
spin_ids = ['max_download_speed', 'max_upload_speed', 'stop_ratio']
|
||||||
spin_int_ids = ['max_upload_slots', 'max_connections']
|
spin_int_ids = ['max_upload_slots', 'max_connections']
|
||||||
chk_ids = [
|
chk_ids = [
|
||||||
'stop_at_ratio', 'remove_at_ratio', 'move_completed',
|
'stop_at_ratio',
|
||||||
'add_paused', 'auto_managed', 'queue_to_top',
|
'remove_at_ratio',
|
||||||
|
'move_completed',
|
||||||
|
'add_paused',
|
||||||
|
'auto_managed',
|
||||||
|
'queue_to_top',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -52,13 +56,15 @@ class OptionsDialog(object):
|
|||||||
options = {}
|
options = {}
|
||||||
self.builder = gtk.Builder()
|
self.builder = gtk.Builder()
|
||||||
self.builder.add_from_file(get_resource('autoadd_options.ui'))
|
self.builder.add_from_file(get_resource('autoadd_options.ui'))
|
||||||
self.builder.connect_signals({
|
self.builder.connect_signals(
|
||||||
|
{
|
||||||
'on_opts_add': self.on_add,
|
'on_opts_add': self.on_add,
|
||||||
'on_opts_apply': self.on_apply,
|
'on_opts_apply': self.on_apply,
|
||||||
'on_opts_cancel': self.on_cancel,
|
'on_opts_cancel': self.on_cancel,
|
||||||
'on_options_dialog_close': self.on_cancel,
|
'on_options_dialog_close': self.on_cancel,
|
||||||
'on_toggle_toggled': self.on_toggle_toggled,
|
'on_toggle_toggled': self.on_toggle_toggled,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
self.dialog = self.builder.get_object('options_dialog')
|
self.dialog = self.builder.get_object('options_dialog')
|
||||||
self.dialog.set_transient_for(component.get('Preferences').pref_dialog)
|
self.dialog.set_transient_for(component.get('Preferences').pref_dialog)
|
||||||
|
|
||||||
@ -79,23 +85,21 @@ class OptionsDialog(object):
|
|||||||
def load_options(self, options):
|
def load_options(self, options):
|
||||||
self.builder.get_object('enabled').set_active(options.get('enabled', True))
|
self.builder.get_object('enabled').set_active(options.get('enabled', True))
|
||||||
self.builder.get_object('append_extension_toggle').set_active(
|
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(
|
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(
|
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(
|
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(
|
self.builder.get_object('delete_copy_torrent_toggle').set_active(
|
||||||
options.get('delete_copy_torrent_toggle', False),
|
options.get('delete_copy_torrent_toggle', False)
|
||||||
)
|
|
||||||
self.builder.get_object('seed_mode').set_active(
|
|
||||||
options.get('seed_mode', False),
|
|
||||||
)
|
)
|
||||||
|
self.builder.get_object('seed_mode').set_active(options.get('seed_mode', False))
|
||||||
self.accounts.clear()
|
self.accounts.clear()
|
||||||
self.labels.clear()
|
self.labels.clear()
|
||||||
combobox = self.builder.get_object('OwnerCombobox')
|
combobox = self.builder.get_object('OwnerCombobox')
|
||||||
@ -108,14 +112,20 @@ class OptionsDialog(object):
|
|||||||
label_widget.child.set_text(options.get('label', ''))
|
label_widget.child.set_text(options.get('label', ''))
|
||||||
label_widget.set_model(self.labels)
|
label_widget.set_model(self.labels)
|
||||||
label_widget.set_entry_text_column(0)
|
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:
|
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).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:
|
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).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):
|
if not options.get('add_paused', True):
|
||||||
self.builder.get_object('isnt_add_paused').set_active(True)
|
self.builder.get_object('isnt_add_paused').set_active(True)
|
||||||
if not options.get('queue_to_top', True):
|
if not options.get('queue_to_top', True):
|
||||||
@ -123,18 +133,20 @@ class OptionsDialog(object):
|
|||||||
if not options.get('auto_managed', True):
|
if not options.get('auto_managed', True):
|
||||||
self.builder.get_object('isnt_auto_managed').set_active(True)
|
self.builder.get_object('isnt_auto_managed').set_active(True)
|
||||||
for field in [
|
for field in [
|
||||||
'move_completed_path', 'path', 'download_location',
|
'move_completed_path',
|
||||||
|
'path',
|
||||||
|
'download_location',
|
||||||
'copy_torrent',
|
'copy_torrent',
|
||||||
]:
|
]:
|
||||||
if client.is_localhost():
|
if client.is_localhost():
|
||||||
self.builder.get_object(field + '_chooser').set_current_folder(
|
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 + '_chooser').show()
|
||||||
self.builder.get_object(field + '_entry').hide()
|
self.builder.get_object(field + '_entry').hide()
|
||||||
else:
|
else:
|
||||||
self.builder.get_object(field + '_entry').set_text(
|
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 + '_entry').show()
|
||||||
self.builder.get_object(field + '_chooser').hide()
|
self.builder.get_object(field + '_chooser').hide()
|
||||||
@ -143,36 +155,44 @@ class OptionsDialog(object):
|
|||||||
def on_core_config(config):
|
def on_core_config(config):
|
||||||
if client.is_localhost():
|
if client.is_localhost():
|
||||||
self.builder.get_object('download_location_chooser').set_current_folder(
|
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']):
|
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_toggle').set_active(True)
|
||||||
self.builder.get_object('move_completed_path_chooser').set_current_folder(
|
self.builder.get_object(
|
||||||
options.get('move_completed_path', config['move_completed_path']),
|
'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']):
|
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_toggle').set_active(True)
|
||||||
self.builder.get_object('copy_torrent_chooser').set_current_folder(
|
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:
|
else:
|
||||||
self.builder.get_object('download_location_entry').set_text(
|
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']):
|
if options.get('move_completed_toggle', config['move_completed']):
|
||||||
self.builder.get_object('move_completed_toggle').set_active(
|
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(
|
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']):
|
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_toggle').set_active(True)
|
||||||
self.builder.get_object('copy_torrent_entry').set_text(
|
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)
|
self.builder.get_object('delete_copy_torrent_toggle').set_active(True)
|
||||||
|
|
||||||
if not options:
|
if not options:
|
||||||
@ -183,9 +203,7 @@ class OptionsDialog(object):
|
|||||||
selected_iter = None
|
selected_iter = None
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
acc_iter = self.accounts.append()
|
acc_iter = self.accounts.append()
|
||||||
self.accounts.set_value(
|
self.accounts.set_value(acc_iter, 0, account['username'])
|
||||||
acc_iter, 0, account['username'],
|
|
||||||
)
|
|
||||||
if account['username'] == owner:
|
if account['username'] == owner:
|
||||||
selected_iter = acc_iter
|
selected_iter = acc_iter
|
||||||
self.builder.get_object('OwnerCombobox').set_active_iter(selected_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)
|
client.core.get_enabled_plugins().addCallback(on_get_enabled_plugins)
|
||||||
if client.get_auth_level() == deluge.common.AUTH_LEVEL_ADMIN:
|
if client.get_auth_level() == deluge.common.AUTH_LEVEL_ADMIN:
|
||||||
client.core.get_known_accounts().addCallback(
|
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)
|
).addErrback(on_accounts_failure)
|
||||||
else:
|
else:
|
||||||
acc_iter = self.accounts.append()
|
acc_iter = self.accounts.append()
|
||||||
@ -229,11 +247,19 @@ class OptionsDialog(object):
|
|||||||
|
|
||||||
def set_sensitive(self):
|
def set_sensitive(self):
|
||||||
maintoggles = [
|
maintoggles = [
|
||||||
'download_location', 'append_extension',
|
'download_location',
|
||||||
'move_completed', 'label', 'max_download_speed',
|
'append_extension',
|
||||||
'max_upload_speed', 'max_connections',
|
'move_completed',
|
||||||
'max_upload_slots', 'add_paused', 'auto_managed',
|
'label',
|
||||||
'stop_at_ratio', 'queue_to_top', 'copy_torrent',
|
'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:
|
for maintoggle in maintoggles:
|
||||||
self.on_toggle_toggled(self.builder.get_object(maintoggle + '_toggle'))
|
self.on_toggle_toggled(self.builder.get_object(maintoggle + '_toggle'))
|
||||||
@ -249,9 +275,13 @@ class OptionsDialog(object):
|
|||||||
elif toggle == 'copy_torrent':
|
elif toggle == 'copy_torrent':
|
||||||
self.builder.get_object('copy_torrent_entry').set_sensitive(isactive)
|
self.builder.get_object('copy_torrent_entry').set_sensitive(isactive)
|
||||||
self.builder.get_object('copy_torrent_chooser').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':
|
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_path_entry').set_sensitive(isactive)
|
||||||
self.builder.get_object('move_completed').set_active(isactive)
|
self.builder.get_object('move_completed').set_active(isactive)
|
||||||
elif toggle == 'label':
|
elif toggle == 'label':
|
||||||
@ -283,9 +313,9 @@ class OptionsDialog(object):
|
|||||||
def on_apply(self, event=None):
|
def on_apply(self, event=None):
|
||||||
try:
|
try:
|
||||||
options = self.generate_opts()
|
options = self.generate_opts()
|
||||||
client.autoadd.set_options(
|
client.autoadd.set_options(str(self.watchdir_id), options).addCallbacks(
|
||||||
str(self.watchdir_id), options,
|
self.on_added, self.on_error_show
|
||||||
).addCallbacks(self.on_added, self.on_error_show)
|
)
|
||||||
except IncompatibleOption as ex:
|
except IncompatibleOption as ex:
|
||||||
dialogs.ErrorDialog(_('Incompatible Option'), str(ex), self.dialog).run()
|
dialogs.ErrorDialog(_('Incompatible Option'), str(ex), self.dialog).run()
|
||||||
|
|
||||||
@ -314,54 +344,72 @@ class OptionsDialog(object):
|
|||||||
if client.is_localhost():
|
if client.is_localhost():
|
||||||
options['path'] = self.builder.get_object('path_chooser').get_filename()
|
options['path'] = self.builder.get_object('path_chooser').get_filename()
|
||||||
options['download_location'] = self.builder.get_object(
|
options['download_location'] = self.builder.get_object(
|
||||||
'download_location_chooser',
|
'download_location_chooser'
|
||||||
).get_filename()
|
).get_filename()
|
||||||
options['move_completed_path'] = self.builder.get_object(
|
options['move_completed_path'] = self.builder.get_object(
|
||||||
'move_completed_path_chooser',
|
'move_completed_path_chooser'
|
||||||
).get_filename()
|
).get_filename()
|
||||||
options['copy_torrent'] = self.builder.get_object(
|
options['copy_torrent'] = self.builder.get_object(
|
||||||
'copy_torrent_chooser',
|
'copy_torrent_chooser'
|
||||||
).get_filename()
|
).get_filename()
|
||||||
else:
|
else:
|
||||||
options['path'] = self.builder.get_object('path_entry').get_text()
|
options['path'] = self.builder.get_object('path_entry').get_text()
|
||||||
options['download_location'] = self.builder.get_object(
|
options['download_location'] = self.builder.get_object(
|
||||||
'download_location_entry',
|
'download_location_entry'
|
||||||
).get_text()
|
).get_text()
|
||||||
options['move_completed_path'] = self.builder.get_object(
|
options['move_completed_path'] = self.builder.get_object(
|
||||||
'move_completed_path_entry',
|
'move_completed_path_entry'
|
||||||
).get_text()
|
).get_text()
|
||||||
options['copy_torrent'] = self.builder.get_object(
|
options['copy_torrent'] = self.builder.get_object(
|
||||||
'copy_torrent_entry',
|
'copy_torrent_entry'
|
||||||
).get_text()
|
).get_text()
|
||||||
|
|
||||||
options['label'] = self.builder.get_object('label').child.get_text().lower()
|
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[
|
options['owner'] = self.accounts[
|
||||||
self.builder.get_object('OwnerCombobox').get_active()
|
self.builder.get_object('OwnerCombobox').get_active()
|
||||||
][0]
|
][0]
|
||||||
|
|
||||||
for key in [
|
for key in [
|
||||||
'append_extension_toggle', 'download_location_toggle',
|
'append_extension_toggle',
|
||||||
'label_toggle', 'copy_torrent_toggle',
|
'download_location_toggle',
|
||||||
'delete_copy_torrent_toggle', 'seed_mode',
|
'label_toggle',
|
||||||
|
'copy_torrent_toggle',
|
||||||
|
'delete_copy_torrent_toggle',
|
||||||
|
'seed_mode',
|
||||||
]:
|
]:
|
||||||
options[key] = self.builder.get_object(key).get_active()
|
options[key] = self.builder.get_object(key).get_active()
|
||||||
|
|
||||||
for spin_id in self.spin_ids:
|
for spin_id in self.spin_ids:
|
||||||
options[spin_id] = self.builder.get_object(spin_id).get_value()
|
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:
|
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(
|
||||||
options[spin_int_id + '_toggle'] = self.builder.get_object(spin_int_id + '_toggle').get_active()
|
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:
|
for chk_id in self.chk_ids:
|
||||||
options[chk_id] = self.builder.get_object(chk_id).get_active()
|
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']:
|
if (
|
||||||
raise IncompatibleOption(_(
|
options['copy_torrent_toggle']
|
||||||
|
and options['path'] == options['copy_torrent']
|
||||||
|
):
|
||||||
|
raise IncompatibleOption(
|
||||||
|
_(
|
||||||
'"Watch Folder" directory and "Copy of .torrent'
|
'"Watch Folder" directory and "Copy of .torrent'
|
||||||
' files to" directory cannot be the same!',
|
' files to" directory cannot be the same!'
|
||||||
))
|
)
|
||||||
|
)
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
|
||||||
@ -374,13 +422,13 @@ class GtkUI(GtkPluginBase):
|
|||||||
self.opts_dialog = OptionsDialog()
|
self.opts_dialog = OptionsDialog()
|
||||||
|
|
||||||
component.get('PluginManager').register_hook(
|
component.get('PluginManager').register_hook(
|
||||||
'on_apply_prefs', self.on_apply_prefs,
|
'on_apply_prefs', self.on_apply_prefs
|
||||||
)
|
)
|
||||||
component.get('PluginManager').register_hook(
|
component.get('PluginManager').register_hook(
|
||||||
'on_show_prefs', self.on_show_prefs,
|
'on_show_prefs', self.on_show_prefs
|
||||||
)
|
)
|
||||||
client.register_event_handler(
|
client.register_event_handler(
|
||||||
'AutoaddOptionsChangedEvent', self.on_options_changed_event,
|
'AutoaddOptionsChangedEvent', self.on_options_changed_event
|
||||||
)
|
)
|
||||||
|
|
||||||
self.watchdirs = {}
|
self.watchdirs = {}
|
||||||
@ -403,31 +451,35 @@ class GtkUI(GtkPluginBase):
|
|||||||
sw.add(self.treeView)
|
sw.add(self.treeView)
|
||||||
sw.show_all()
|
sw.show_all()
|
||||||
component.get('Preferences').add_page(
|
component.get('Preferences').add_page(
|
||||||
_('AutoAdd'), self.builder.get_object('prefs_box'),
|
_('AutoAdd'), self.builder.get_object('prefs_box')
|
||||||
)
|
)
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
component.get('Preferences').remove_page(_('AutoAdd'))
|
component.get('Preferences').remove_page(_('AutoAdd'))
|
||||||
component.get('PluginManager').deregister_hook(
|
component.get('PluginManager').deregister_hook(
|
||||||
'on_apply_prefs', self.on_apply_prefs,
|
'on_apply_prefs', self.on_apply_prefs
|
||||||
)
|
)
|
||||||
component.get('PluginManager').deregister_hook(
|
component.get('PluginManager').deregister_hook(
|
||||||
'on_show_prefs', self.on_show_prefs,
|
'on_show_prefs', self.on_show_prefs
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_model(self):
|
def create_model(self):
|
||||||
store = gtk.ListStore(str, bool, str, str)
|
store = gtk.ListStore(str, bool, str, str)
|
||||||
for watchdir_id, watchdir in self.watchdirs.items():
|
for watchdir_id, watchdir in self.watchdirs.items():
|
||||||
store.append([
|
store.append(
|
||||||
watchdir_id, watchdir['enabled'],
|
[
|
||||||
watchdir.get('owner', 'localclient'), watchdir['path'],
|
watchdir_id,
|
||||||
])
|
watchdir['enabled'],
|
||||||
|
watchdir.get('owner', 'localclient'),
|
||||||
|
watchdir['path'],
|
||||||
|
]
|
||||||
|
)
|
||||||
return store
|
return store
|
||||||
|
|
||||||
def create_columns(self, treeview):
|
def create_columns(self, treeview):
|
||||||
renderer_toggle = gtk.CellRendererToggle()
|
renderer_toggle = gtk.CellRendererToggle()
|
||||||
column = gtk.TreeViewColumn(
|
column = gtk.TreeViewColumn(
|
||||||
_('Active'), renderer_toggle, activatable=1, active=1,
|
_('Active'), renderer_toggle, activatable=1, active=1
|
||||||
)
|
)
|
||||||
column.set_sort_column_id(1)
|
column.set_sort_column_id(1)
|
||||||
treeview.append_column(column)
|
treeview.append_column(column)
|
||||||
@ -505,10 +557,14 @@ class GtkUI(GtkPluginBase):
|
|||||||
self.watchdirs = watchdirs or {}
|
self.watchdirs = watchdirs or {}
|
||||||
self.store.clear()
|
self.store.clear()
|
||||||
for watchdir_id, watchdir in self.watchdirs.items():
|
for watchdir_id, watchdir in self.watchdirs.items():
|
||||||
self.store.append([
|
self.store.append(
|
||||||
watchdir_id, watchdir['enabled'],
|
[
|
||||||
watchdir.get('owner', 'localclient'), watchdir['path'],
|
watchdir_id,
|
||||||
])
|
watchdir['enabled'],
|
||||||
|
watchdir.get('owner', 'localclient'),
|
||||||
|
watchdir['path'],
|
||||||
|
]
|
||||||
|
)
|
||||||
# Workaround for cached glade signal appearing when re-enabling plugin in same session
|
# Workaround for cached glade signal appearing when re-enabling plugin in same session
|
||||||
if self.builder.get_object('edit_button'):
|
if self.builder.get_object('edit_button'):
|
||||||
# Disable the remove and edit buttons, because nothing in the store is selected
|
# Disable the remove and edit buttons, because nothing in the store is selected
|
||||||
|
|||||||
@ -37,7 +37,6 @@ setup(
|
|||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
namespace_packages=['deluge', 'deluge.plugins'],
|
namespace_packages=['deluge', 'deluge.plugins'],
|
||||||
package_data=__pkg_data__,
|
package_data=__pkg_data__,
|
||||||
|
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[deluge.plugin.core]
|
[deluge.plugin.core]
|
||||||
%s = deluge.plugins.%s:CorePlugin
|
%s = deluge.plugins.%s:CorePlugin
|
||||||
@ -45,5 +44,6 @@ setup(
|
|||||||
%s = deluge.plugins.%s:GtkUIPlugin
|
%s = deluge.plugins.%s:GtkUIPlugin
|
||||||
[deluge.plugin.web]
|
[deluge.plugin.web]
|
||||||
%s = deluge.plugins.%s:WebUIPlugin
|
%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):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .core import Core as _pluginCls
|
from .core import Core as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ class CorePlugin(PluginInitBase):
|
|||||||
class GtkUIPlugin(PluginInitBase):
|
class GtkUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .gtkui import GtkUI as _pluginCls
|
from .gtkui import GtkUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -29,5 +31,6 @@ class GtkUIPlugin(PluginInitBase):
|
|||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .webui import WebUI as _pluginCls
|
from .webui import WebUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
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.
|
function to raise all exceptions as the specified error type.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
"""Returns a function which wraps the given func to raise all exceptions as error."""
|
"""Returns a function which wraps the given func to raise all exceptions as error."""
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(self, *args, **kwargs):
|
def wrapper(self, *args, **kwargs):
|
||||||
"""Wraps the function in a try..except block and calls it with the specified args.
|
"""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:
|
except Exception:
|
||||||
(value, tb) = exc_info()[1:]
|
(value, tb) = exc_info()[1:]
|
||||||
six.reraise(error, value, tb)
|
six.reraise(error, value, tb)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
@ -166,5 +170,7 @@ class IP(object):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s long=%s address="%s">' % (
|
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.file_progress = 0.0
|
||||||
|
|
||||||
self.core = component.get('Core')
|
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:
|
if 'whitelisted' not in self.config:
|
||||||
self.config['whitelisted'] = []
|
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):
|
if not isinstance(self.config['last_update'], float):
|
||||||
self.config.config['last_update'] = 0.0
|
self.config.config['last_update'] = 0.0
|
||||||
@ -91,10 +95,15 @@ class Core(CorePluginBase):
|
|||||||
if self.config['last_update']:
|
if self.config['last_update']:
|
||||||
last_update = datetime.fromtimestamp(self.config['last_update'])
|
last_update = datetime.fromtimestamp(self.config['last_update'])
|
||||||
check_period = timedelta(days=self.config['check_after_days'])
|
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
|
update_now = True
|
||||||
else:
|
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)
|
d.addCallbacks(self.on_import_complete, self.on_import_error)
|
||||||
if self.need_to_resume_session:
|
if self.need_to_resume_session:
|
||||||
d.addBoth(self.resume_session)
|
d.addBoth(self.resume_session)
|
||||||
@ -104,14 +113,14 @@ class Core(CorePluginBase):
|
|||||||
self.update_timer = LoopingCall(self.check_import)
|
self.update_timer = LoopingCall(self.check_import)
|
||||||
if self.config['check_after_days'] > 0:
|
if self.config['check_after_days'] > 0:
|
||||||
self.update_timer.start(
|
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):
|
def disable(self):
|
||||||
self.config.save()
|
self.config.save()
|
||||||
log.debug('Reset IP filter')
|
log.debug('Reset IP filter')
|
||||||
self.core.session.get_ip_filter().add_rule(
|
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')
|
log.debug('Blocklist: Plugin disabled')
|
||||||
|
|
||||||
@ -189,7 +198,7 @@ class Core(CorePluginBase):
|
|||||||
try:
|
try:
|
||||||
ip = IP.parse(ip)
|
ip = IP.parse(ip)
|
||||||
self.blocklist.add_rule(
|
self.blocklist.add_rule(
|
||||||
ip.address, ip.address, ALLOW_RANGE,
|
ip.address, ip.address, ALLOW_RANGE
|
||||||
)
|
)
|
||||||
saved.add(ip.address)
|
saved.add(ip.address)
|
||||||
log.debug('Added %s to whitelisted', ip)
|
log.debug('Added %s to whitelisted', ip)
|
||||||
@ -217,13 +226,16 @@ class Core(CorePluginBase):
|
|||||||
if self.config['last_update']:
|
if self.config['last_update']:
|
||||||
last_update = datetime.fromtimestamp(self.config['last_update'])
|
last_update = datetime.fromtimestamp(self.config['last_update'])
|
||||||
check_period = timedelta(days=self.config['check_after_days'])
|
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
|
update_now = True
|
||||||
if self.update_timer.running:
|
if self.update_timer.running:
|
||||||
self.update_timer.stop()
|
self.update_timer.stop()
|
||||||
if self.config['check_after_days'] > 0:
|
if self.config['check_after_days'] > 0:
|
||||||
self.update_timer.start(
|
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
|
continue
|
||||||
self.config[key] = config[key]
|
self.config[key] = config[key]
|
||||||
@ -232,7 +244,7 @@ class Core(CorePluginBase):
|
|||||||
log.debug(
|
log.debug(
|
||||||
'IP addresses were removed from the whitelist. Since we '
|
'IP addresses were removed from the whitelist. Since we '
|
||||||
'do not know if they were blocked before. Re-import '
|
'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
|
self.has_imported = False
|
||||||
d = self.import_list(deluge.configmanager.get_config_dir('blocklist.cache'))
|
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.
|
Deferred: a Deferred which fires once the blocklist has been downloaded.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_retrieve_data(data, current_length, total_length):
|
def on_retrieve_data(data, current_length, total_length):
|
||||||
if total_length:
|
if total_length:
|
||||||
fp = current_length / total_length
|
fp = current_length / total_length
|
||||||
@ -306,6 +319,7 @@ class Core(CorePluginBase):
|
|||||||
self.file_progress = fp
|
self.file_progress = fp
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
socket.setdefaulttimeout(self.config['timeout'])
|
socket.setdefaulttimeout(self.config['timeout'])
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
@ -313,14 +327,18 @@ class Core(CorePluginBase):
|
|||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
if self.config['last_update'] and not self.force_download:
|
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('Attempting to download blocklist %s', url)
|
||||||
log.debug('Sending headers: %s', headers)
|
log.debug('Sending headers: %s', headers)
|
||||||
self.is_downloading = True
|
self.is_downloading = True
|
||||||
return download_file(
|
return download_file(
|
||||||
url, deluge.configmanager.get_config_dir('blocklist.download'),
|
url,
|
||||||
on_retrieve_data, headers,
|
deluge.configmanager.get_config_dir('blocklist.download'),
|
||||||
|
on_retrieve_data,
|
||||||
|
headers,
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_download_complete(self, blocklist):
|
def on_download_complete(self, blocklist):
|
||||||
@ -369,7 +387,8 @@ class Core(CorePluginBase):
|
|||||||
if self.failed_attempts < self.config['try_times']:
|
if self.failed_attempts < self.config['try_times']:
|
||||||
log.debug(
|
log.debug(
|
||||||
'Try downloading blocklist again... (%s/%s)',
|
'Try downloading blocklist again... (%s/%s)',
|
||||||
self.failed_attempts, self.config['try_times'],
|
self.failed_attempts,
|
||||||
|
self.config['try_times'],
|
||||||
)
|
)
|
||||||
self.failed_attempts += 1
|
self.failed_attempts += 1
|
||||||
d = self.download_list()
|
d = self.download_list()
|
||||||
@ -430,7 +449,11 @@ class Core(CorePluginBase):
|
|||||||
log.exception(failure)
|
log.exception(failure)
|
||||||
|
|
||||||
log.debug('Importing using reader: %s', self.reader)
|
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')
|
log.debug('Clearing current ip filtering')
|
||||||
# self.blocklist.add_rule('0.0.0.0', '255.255.255.255', ALLOW_RANGE)
|
# 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)
|
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_compression'] = detect_compression(blocklist)
|
||||||
self.config['list_type'] = detect_format(blocklist, self.config['list_compression'])
|
self.config['list_type'] = detect_format(
|
||||||
log.debug('Auto-detected type: %s compression: %s', self.config['list_type'], self.config['list_compression'])
|
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']:
|
if not self.config['list_type']:
|
||||||
self.config['list_compression'] = ''
|
self.config['list_compression'] = ''
|
||||||
raise UnknownFormatError
|
raise UnknownFormatError
|
||||||
else:
|
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):
|
def pause_session(self):
|
||||||
self.need_to_resume_session = not self.core.session.is_paused()
|
self.need_to_resume_session = not self.core.session.is_paused()
|
||||||
|
|||||||
@ -17,25 +17,31 @@ import zipfile
|
|||||||
|
|
||||||
def Zipped(reader): # NOQA: N802
|
def Zipped(reader): # NOQA: N802
|
||||||
"""Blocklist reader for zipped blocklists"""
|
"""Blocklist reader for zipped blocklists"""
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
z = zipfile.ZipFile(self.file)
|
z = zipfile.ZipFile(self.file)
|
||||||
f = z.open(z.namelist()[0])
|
f = z.open(z.namelist()[0])
|
||||||
return f
|
return f
|
||||||
|
|
||||||
reader.open = _open
|
reader.open = _open
|
||||||
return reader
|
return reader
|
||||||
|
|
||||||
|
|
||||||
def GZipped(reader): # NOQA: N802
|
def GZipped(reader): # NOQA: N802
|
||||||
"""Blocklist reader for gzipped blocklists"""
|
"""Blocklist reader for gzipped blocklists"""
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
return gzip.open(self.file)
|
return gzip.open(self.file)
|
||||||
|
|
||||||
reader.open = _open
|
reader.open = _open
|
||||||
return reader
|
return reader
|
||||||
|
|
||||||
|
|
||||||
def BZipped2(reader): # NOQA: N802
|
def BZipped2(reader): # NOQA: N802
|
||||||
"""Blocklist reader for bzipped2 blocklists"""
|
"""Blocklist reader for bzipped2 blocklists"""
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
return bz2.BZ2File(self.file)
|
return bz2.BZ2File(self.file)
|
||||||
|
|
||||||
reader.open = _open
|
reader.open = _open
|
||||||
return reader
|
return reader
|
||||||
|
|||||||
@ -12,17 +12,9 @@ from __future__ import unicode_literals
|
|||||||
from .decompressers import BZipped2, GZipped, Zipped
|
from .decompressers import BZipped2, GZipped, Zipped
|
||||||
from .readers import EmuleReader, PeerGuardianReader, SafePeerReader
|
from .readers import EmuleReader, PeerGuardianReader, SafePeerReader
|
||||||
|
|
||||||
COMPRESSION_TYPES = {
|
COMPRESSION_TYPES = {'PK': 'Zip', '\x1f\x8b': 'GZip', 'BZ': 'BZip2'}
|
||||||
'PK': 'Zip',
|
|
||||||
'\x1f\x8b': 'GZip',
|
|
||||||
'BZ': 'BZip2',
|
|
||||||
}
|
|
||||||
|
|
||||||
DECOMPRESSERS = {
|
DECOMPRESSERS = {'Zip': Zipped, 'GZip': GZipped, 'BZip2': BZipped2}
|
||||||
'Zip': Zipped,
|
|
||||||
'GZip': GZipped,
|
|
||||||
'BZip2': BZipped2,
|
|
||||||
}
|
|
||||||
|
|
||||||
READERS = {
|
READERS = {
|
||||||
'Emule': EmuleReader,
|
'Emule': EmuleReader,
|
||||||
|
|||||||
@ -67,9 +67,11 @@ class GtkUI(GtkPluginBase):
|
|||||||
self.builder.get_object('image_up_to_date').hide()
|
self.builder.get_object('image_up_to_date').hide()
|
||||||
|
|
||||||
self.status_item.set_text(
|
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.set_fraction(status['file_progress'])
|
||||||
self.progress_bar.show()
|
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('button_force_download').set_sensitive(False)
|
||||||
self.builder.get_object('image_up_to_date').hide()
|
self.builder.get_object('image_up_to_date').hide()
|
||||||
|
|
||||||
self.status_item.set_text(
|
self.status_item.set_text('Importing ' + str(status['num_blocked']))
|
||||||
'Importing ' + str(status['num_blocked']),
|
|
||||||
)
|
|
||||||
self.progress_bar.set_text('Importing %s' % (status['num_blocked']))
|
self.progress_bar.set_text('Importing %s' % (status['num_blocked']))
|
||||||
self.progress_bar.pulse()
|
self.progress_bar.pulse()
|
||||||
self.progress_bar.show()
|
self.progress_bar.show()
|
||||||
@ -99,15 +99,13 @@ class GtkUI(GtkPluginBase):
|
|||||||
self.status_item.set_text('%(num_blocked)s/%(num_whited)s' % status)
|
self.status_item.set_text('%(num_blocked)s/%(num_whited)s' % status)
|
||||||
|
|
||||||
self.builder.get_object('label_filesize').set_text(
|
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(
|
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_type').set_text(status['file_type'])
|
||||||
self.builder.get_object('label_url').set_text(
|
self.builder.get_object('label_url').set_text(status['file_url'])
|
||||||
status['file_url'],
|
|
||||||
)
|
|
||||||
|
|
||||||
client.blocklist.get_status().addCallback(_on_get_status)
|
client.blocklist.get_status().addCallback(_on_get_status)
|
||||||
|
|
||||||
@ -115,8 +113,12 @@ class GtkUI(GtkPluginBase):
|
|||||||
def _on_get_config(config):
|
def _on_get_config(config):
|
||||||
log.trace('Loaded config: %s', config)
|
log.trace('Loaded config: %s', config)
|
||||||
self.builder.get_object('entry_url').set_text(config['url'])
|
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('spin_check_days').set_value(
|
||||||
self.builder.get_object('chk_import_on_start').set_active(config['load_on_start'])
|
config['check_after_days']
|
||||||
|
)
|
||||||
|
self.builder.get_object('chk_import_on_start').set_active(
|
||||||
|
config['load_on_start']
|
||||||
|
)
|
||||||
self.populate_whitelist(config['whitelisted'])
|
self.populate_whitelist(config['whitelisted'])
|
||||||
|
|
||||||
client.blocklist.get_config().addCallback(_on_get_config)
|
client.blocklist.get_config().addCallback(_on_get_config)
|
||||||
@ -124,9 +126,15 @@ class GtkUI(GtkPluginBase):
|
|||||||
def _on_apply_prefs(self):
|
def _on_apply_prefs(self):
|
||||||
config = {}
|
config = {}
|
||||||
config['url'] = self.builder.get_object('entry_url').get_text().strip()
|
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['check_after_days'] = self.builder.get_object(
|
||||||
config['load_on_start'] = self.builder.get_object('chk_import_on_start').get_active()
|
'spin_check_days'
|
||||||
config['whitelisted'] = [ip[0] for ip in self.whitelist_model if ip[0] != 'IP HERE']
|
).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)
|
client.blocklist.set_config(config)
|
||||||
|
|
||||||
def _on_button_check_download_clicked(self, widget):
|
def _on_button_check_download_clicked(self, widget):
|
||||||
@ -157,7 +165,8 @@ class GtkUI(GtkPluginBase):
|
|||||||
# Create the whitelisted model
|
# Create the whitelisted model
|
||||||
self.build_whitelist_model_treeview()
|
self.build_whitelist_model_treeview()
|
||||||
|
|
||||||
self.builder.connect_signals({
|
self.builder.connect_signals(
|
||||||
|
{
|
||||||
'on_button_check_download_clicked': self._on_button_check_download_clicked,
|
'on_button_check_download_clicked': self._on_button_check_download_clicked,
|
||||||
'on_button_force_download_clicked': self._on_button_force_download_clicked,
|
'on_button_force_download_clicked': self._on_button_force_download_clicked,
|
||||||
'on_whitelist_add_clicked': (
|
'on_whitelist_add_clicked': (
|
||||||
@ -168,15 +177,16 @@ class GtkUI(GtkPluginBase):
|
|||||||
self.on_delete_button_clicked,
|
self.on_delete_button_clicked,
|
||||||
self.whitelist_treeview,
|
self.whitelist_treeview,
|
||||||
),
|
),
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Set button icons
|
# Set button icons
|
||||||
self.builder.get_object('image_download').set_from_file(
|
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(
|
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
|
# Update the preferences page with config values from the core
|
||||||
@ -184,15 +194,14 @@ class GtkUI(GtkPluginBase):
|
|||||||
|
|
||||||
# Add the page to the preferences dialog
|
# Add the page to the preferences dialog
|
||||||
self.plugin.add_preferences_page(
|
self.plugin.add_preferences_page(
|
||||||
_('Blocklist'),
|
_('Blocklist'), self.builder.get_object('blocklist_prefs_box')
|
||||||
self.builder.get_object('blocklist_prefs_box'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_whitelist_model_treeview(self):
|
def build_whitelist_model_treeview(self):
|
||||||
self.whitelist_treeview = self.builder.get_object('whitelist_treeview')
|
self.whitelist_treeview = self.builder.get_object('whitelist_treeview')
|
||||||
treeview_selection = self.whitelist_treeview.get_selection()
|
treeview_selection = self.whitelist_treeview.get_selection()
|
||||||
treeview_selection.connect(
|
treeview_selection.connect(
|
||||||
'changed', self.on_whitelist_treeview_selection_changed,
|
'changed', self.on_whitelist_treeview_selection_changed
|
||||||
)
|
)
|
||||||
self.whitelist_model = gtk.ListStore(str, bool)
|
self.whitelist_model = gtk.ListStore(str, bool)
|
||||||
renderer = gtk.CellRendererText()
|
renderer = gtk.CellRendererText()
|
||||||
@ -213,21 +222,16 @@ class GtkUI(GtkPluginBase):
|
|||||||
except common.BadIP as ex:
|
except common.BadIP as ex:
|
||||||
model.remove(model.get_iter_from_string(path_string))
|
model.remove(model.get_iter_from_string(path_string))
|
||||||
from deluge.ui.gtkui import dialogs
|
from deluge.ui.gtkui import dialogs
|
||||||
|
|
||||||
d = dialogs.ErrorDialog(_('Bad IP address'), ex.message)
|
d = dialogs.ErrorDialog(_('Bad IP address'), ex.message)
|
||||||
d.run()
|
d.run()
|
||||||
|
|
||||||
def on_whitelist_treeview_selection_changed(self, selection):
|
def on_whitelist_treeview_selection_changed(self, selection):
|
||||||
model, selected_connection_iter = selection.get_selected()
|
model, selected_connection_iter = selection.get_selected()
|
||||||
if selected_connection_iter:
|
if selected_connection_iter:
|
||||||
self.builder.get_object('whitelist_delete').set_property(
|
self.builder.get_object('whitelist_delete').set_property('sensitive', True)
|
||||||
'sensitive',
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.builder.get_object('whitelist_delete').set_property(
|
self.builder.get_object('whitelist_delete').set_property('sensitive', False)
|
||||||
'sensitive',
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_add_button_clicked(self, widget, treeview):
|
def on_add_button_clicked(self, widget, treeview):
|
||||||
model = treeview.get_model()
|
model = treeview.get_model()
|
||||||
@ -243,6 +247,4 @@ class GtkUI(GtkPluginBase):
|
|||||||
def populate_whitelist(self, whitelist):
|
def populate_whitelist(self, whitelist):
|
||||||
self.whitelist_model.clear()
|
self.whitelist_model.clear()
|
||||||
for ip in whitelist:
|
for ip in whitelist:
|
||||||
self.whitelist_model.set(
|
self.whitelist_model.set(self.whitelist_model.append(), 0, ip, 1, True)
|
||||||
self.whitelist_model.append(), 0, ip, 1, True,
|
|
||||||
)
|
|
||||||
|
|||||||
@ -24,7 +24,6 @@ class PGException(Exception):
|
|||||||
# Incrementally reads PeerGuardian blocklists v1 and v2.
|
# Incrementally reads PeerGuardian blocklists v1 and v2.
|
||||||
# See http://wiki.phoenixlabs.org/wiki/P2B_Format
|
# See http://wiki.phoenixlabs.org/wiki/P2B_Format
|
||||||
class PGReader(object):
|
class PGReader(object):
|
||||||
|
|
||||||
def __init__(self, filename):
|
def __init__(self, filename):
|
||||||
log.debug('PGReader loading: %s', filename)
|
log.debug('PGReader loading: %s', filename)
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class ReaderParseError(Exception):
|
|||||||
|
|
||||||
class BaseReader(object):
|
class BaseReader(object):
|
||||||
"""Base reader for blocklist files"""
|
"""Base reader for blocklist files"""
|
||||||
|
|
||||||
def __init__(self, _file):
|
def __init__(self, _file):
|
||||||
"""Creates a new BaseReader given a file"""
|
"""Creates a new BaseReader given a file"""
|
||||||
self.file = _file
|
self.file = _file
|
||||||
@ -60,8 +61,9 @@ class BaseReader(object):
|
|||||||
if not self.is_ignored(line):
|
if not self.is_ignored(line):
|
||||||
try:
|
try:
|
||||||
(start, end) = self.parse(line)
|
(start, end) = self.parse(line)
|
||||||
if not re.match(r'^(\d{1,3}\.){4}$', start + '.') or \
|
if not re.match(r'^(\d{1,3}\.){4}$', start + '.') or not re.match(
|
||||||
not re.match(r'^(\d{1,3}\.){4}$', end + '.'):
|
r'^(\d{1,3}\.){4}$', end + '.'
|
||||||
|
):
|
||||||
valid = False
|
valid = False
|
||||||
except Exception:
|
except Exception:
|
||||||
valid = False
|
valid = False
|
||||||
@ -82,16 +84,19 @@ class BaseReader(object):
|
|||||||
|
|
||||||
class EmuleReader(BaseReader):
|
class EmuleReader(BaseReader):
|
||||||
"""Blocklist reader for emule style blocklists"""
|
"""Blocklist reader for emule style blocklists"""
|
||||||
|
|
||||||
def parse(self, line):
|
def parse(self, line):
|
||||||
return line.strip().split(' , ')[0].split(' - ')
|
return line.strip().split(' , ')[0].split(' - ')
|
||||||
|
|
||||||
|
|
||||||
class SafePeerReader(BaseReader):
|
class SafePeerReader(BaseReader):
|
||||||
"""Blocklist reader for SafePeer style blocklists"""
|
"""Blocklist reader for SafePeer style blocklists"""
|
||||||
|
|
||||||
def parse(self, line):
|
def parse(self, line):
|
||||||
return line.strip().split(':')[-1].split('-')
|
return line.strip().split(':')[-1].split('-')
|
||||||
|
|
||||||
|
|
||||||
class PeerGuardianReader(SafePeerReader):
|
class PeerGuardianReader(SafePeerReader):
|
||||||
"""Blocklist reader for PeerGuardian style blocklists"""
|
"""Blocklist reader for PeerGuardian style blocklists"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -39,5 +39,6 @@ setup(
|
|||||||
%s = deluge.plugins.%s:GtkUIPlugin
|
%s = deluge.plugins.%s:GtkUIPlugin
|
||||||
[deluge.plugin.web]
|
[deluge.plugin.web]
|
||||||
%s = deluge.plugins.%s:WebUIPlugin
|
%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):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .core import Core as _pluginCls
|
from .core import Core as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ class CorePlugin(PluginInitBase):
|
|||||||
class GtkUIPlugin(PluginInitBase):
|
class GtkUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .gtkui import GtkUI as _pluginCls
|
from .gtkui import GtkUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -29,5 +31,6 @@ class GtkUIPlugin(PluginInitBase):
|
|||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .webui import WebUI as _pluginCls
|
from .webui import WebUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
super(WebUIPlugin, self).__init__(plugin_name)
|
||||||
|
|||||||
@ -25,9 +25,7 @@ from deluge.plugins.pluginbase import CorePluginBase
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {'commands': []}
|
||||||
'commands': [],
|
|
||||||
}
|
|
||||||
|
|
||||||
EXECUTE_ID = 0
|
EXECUTE_ID = 0
|
||||||
EXECUTE_EVENT = 1
|
EXECUTE_EVENT = 1
|
||||||
@ -44,6 +42,7 @@ class ExecuteCommandAddedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a new command is added.
|
Emitted when a new command is added.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, command_id, event, command):
|
def __init__(self, command_id, event, command):
|
||||||
self._args = [command_id, event, command]
|
self._args = [command_id, event, command]
|
||||||
|
|
||||||
@ -52,6 +51,7 @@ class ExecuteCommandRemovedEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a command is removed.
|
Emitted when a command is removed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, command_id):
|
def __init__(self, command_id):
|
||||||
self._args = [command_id]
|
self._args = [command_id]
|
||||||
|
|
||||||
@ -72,11 +72,15 @@ class Core(CorePluginBase):
|
|||||||
def create_event_handler(event):
|
def create_event_handler(event):
|
||||||
def event_handler(torrent_id, *arg):
|
def event_handler(torrent_id, *arg):
|
||||||
self.execute_commands(torrent_id, event, *arg)
|
self.execute_commands(torrent_id, event, *arg)
|
||||||
|
|
||||||
return event_handler
|
return event_handler
|
||||||
|
|
||||||
event_handler = create_event_handler(event)
|
event_handler = create_event_handler(event)
|
||||||
event_manager.register_event_handler(EVENT_MAP[event], event_handler)
|
event_manager.register_event_handler(EVENT_MAP[event], event_handler)
|
||||||
if event == 'removed':
|
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
|
self.registered_events[event] = event_handler
|
||||||
|
|
||||||
log.debug('Execute core plugin enabled!')
|
log.debug('Execute core plugin enabled!')
|
||||||
@ -85,14 +89,20 @@ class Core(CorePluginBase):
|
|||||||
# Get and store the torrent info before it is removed
|
# Get and store the torrent info before it is removed
|
||||||
torrent = component.get('TorrentManager').torrents[torrent_id]
|
torrent = component.get('TorrentManager').torrents[torrent_id]
|
||||||
info = torrent.get_status(['name', 'download_location'])
|
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):
|
def execute_commands(self, torrent_id, event, *arg):
|
||||||
if event == 'added' and arg[0]:
|
if event == 'added' and arg[0]:
|
||||||
# No futher action as from_state (arg[0]) is True
|
# No futher action as from_state (arg[0]) is True
|
||||||
return
|
return
|
||||||
elif event == 'removed':
|
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:
|
else:
|
||||||
torrent = component.get('TorrentManager').torrents[torrent_id]
|
torrent = component.get('TorrentManager').torrents[torrent_id]
|
||||||
info = torrent.get_status(['name', 'download_location'])
|
info = torrent.get_status(['name', 'download_location'])
|
||||||
@ -119,7 +129,8 @@ class Core(CorePluginBase):
|
|||||||
command = os.path.expanduser(command)
|
command = os.path.expanduser(command)
|
||||||
|
|
||||||
cmd_args = [
|
cmd_args = [
|
||||||
torrent_id.encode('utf8'), torrent_name.encode('utf8'),
|
torrent_id.encode('utf8'),
|
||||||
|
torrent_name.encode('utf8'),
|
||||||
download_location.encode('utf8'),
|
download_location.encode('utf8'),
|
||||||
]
|
]
|
||||||
if windows_check():
|
if windows_check():
|
||||||
@ -146,7 +157,9 @@ class Core(CorePluginBase):
|
|||||||
command_id = hashlib.sha1(str(time.time())).hexdigest()
|
command_id = hashlib.sha1(str(time.time())).hexdigest()
|
||||||
self.config['commands'].append((command_id, event, command))
|
self.config['commands'].append((command_id, event, command))
|
||||||
self.config.save()
|
self.config.save()
|
||||||
component.get('EventManager').emit(ExecuteCommandAddedEvent(command_id, event, command))
|
component.get('EventManager').emit(
|
||||||
|
ExecuteCommandAddedEvent(command_id, event, command)
|
||||||
|
)
|
||||||
|
|
||||||
@export
|
@export
|
||||||
def get_commands(self):
|
def get_commands(self):
|
||||||
@ -157,7 +170,9 @@ class Core(CorePluginBase):
|
|||||||
for command in self.config['commands']:
|
for command in self.config['commands']:
|
||||||
if command[EXECUTE_ID] == command_id:
|
if command[EXECUTE_ID] == command_id:
|
||||||
self.config['commands'].remove(command)
|
self.config['commands'].remove(command)
|
||||||
component.get('EventManager').emit(ExecuteCommandRemovedEvent(command_id))
|
component.get('EventManager').emit(
|
||||||
|
ExecuteCommandRemovedEvent(command_id)
|
||||||
|
)
|
||||||
break
|
break
|
||||||
self.config.save()
|
self.config.save()
|
||||||
|
|
||||||
|
|||||||
@ -55,14 +55,20 @@ class ExecutePreferences(object):
|
|||||||
events.set_model(store)
|
events.set_model(store)
|
||||||
events.set_active(0)
|
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_show_prefs', self.load_commands)
|
||||||
self.plugin.register_hook('on_apply_prefs', self.on_apply_prefs)
|
self.plugin.register_hook('on_apply_prefs', self.on_apply_prefs)
|
||||||
|
|
||||||
self.load_commands()
|
self.load_commands()
|
||||||
|
|
||||||
client.register_event_handler('ExecuteCommandAddedEvent', self.on_command_added_event)
|
client.register_event_handler(
|
||||||
client.register_event_handler('ExecuteCommandRemovedEvent', self.on_command_removed_event)
|
'ExecuteCommandAddedEvent', self.on_command_added_event
|
||||||
|
)
|
||||||
|
client.register_event_handler(
|
||||||
|
'ExecuteCommandRemovedEvent', self.on_command_removed_event
|
||||||
|
)
|
||||||
|
|
||||||
def unload(self):
|
def unload(self):
|
||||||
self.plugin.remove_preferences_page(_('Execute'))
|
self.plugin.remove_preferences_page(_('Execute'))
|
||||||
@ -145,7 +151,6 @@ class ExecutePreferences(object):
|
|||||||
|
|
||||||
|
|
||||||
class GtkUI(GtkPluginBase):
|
class GtkUI(GtkPluginBase):
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.plugin = component.get('PluginManager')
|
self.plugin = component.get('PluginManager')
|
||||||
self.preferences = ExecutePreferences(self.plugin)
|
self.preferences = ExecutePreferences(self.plugin)
|
||||||
|
|||||||
@ -28,11 +28,9 @@ setup(
|
|||||||
url=__url__,
|
url=__url__,
|
||||||
license=__license__,
|
license=__license__,
|
||||||
long_description=__long_description__,
|
long_description=__long_description__,
|
||||||
|
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
namespace_packages=['deluge', 'deluge.plugins'],
|
namespace_packages=['deluge', 'deluge.plugins'],
|
||||||
package_data=__pkg_data__,
|
package_data=__pkg_data__,
|
||||||
|
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[deluge.plugin.core]
|
[deluge.plugin.core]
|
||||||
%s = deluge.plugins.%s:CorePlugin
|
%s = deluge.plugins.%s:CorePlugin
|
||||||
@ -40,5 +38,6 @@ setup(
|
|||||||
%s = deluge.plugins.%s:GtkUIPlugin
|
%s = deluge.plugins.%s:GtkUIPlugin
|
||||||
[deluge.plugin.web]
|
[deluge.plugin.web]
|
||||||
%s = deluge.plugins.%s:WebUIPlugin
|
%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):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .core import Core as _pluginCls
|
from .core import Core as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ class CorePlugin(PluginInitBase):
|
|||||||
class GtkUIPlugin(PluginInitBase):
|
class GtkUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .gtkui import GtkUI as _pluginCls
|
from .gtkui import GtkUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -33,5 +35,6 @@ class GtkUIPlugin(PluginInitBase):
|
|||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .webui import WebUI as _pluginCls
|
from .webui import WebUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
super(WebUIPlugin, self).__init__(plugin_name)
|
||||||
|
|||||||
@ -28,10 +28,7 @@ from deluge.plugins.pluginbase import CorePluginBase
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_PREFS = {
|
DEFAULT_PREFS = {'extract_path': '', 'use_name_folder': True}
|
||||||
'extract_path': '',
|
|
||||||
'use_name_folder': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
if windows_check():
|
if windows_check():
|
||||||
win_7z_exes = [
|
win_7z_exes = [
|
||||||
@ -61,10 +58,7 @@ if windows_check():
|
|||||||
# ".tar.bz2", ".tbz",
|
# ".tar.bz2", ".tbz",
|
||||||
# ".tar.lzma", ".tlz",
|
# ".tar.lzma", ".tlz",
|
||||||
# ".tar.xz", ".txz",
|
# ".tar.xz", ".txz",
|
||||||
exts_7z = [
|
exts_7z = ['.rar', '.zip', '.tar', '.7z', '.xz', '.lzma']
|
||||||
'.rar', '.zip', '.tar',
|
|
||||||
'.7z', '.xz', '.lzma',
|
|
||||||
]
|
|
||||||
for win_7z_exe in win_7z_exes:
|
for win_7z_exe in win_7z_exes:
|
||||||
if which(win_7z_exe):
|
if which(win_7z_exe):
|
||||||
EXTRACT_COMMANDS = dict.fromkeys(exts_7z, [win_7z_exe, switch_7z])
|
EXTRACT_COMMANDS = dict.fromkeys(exts_7z, [win_7z_exe, switch_7z])
|
||||||
@ -82,10 +76,14 @@ else:
|
|||||||
'.rar': ['unrar', 'x -o+ -y'],
|
'.rar': ['unrar', 'x -o+ -y'],
|
||||||
'.tar': ['tar', '-xf'],
|
'.tar': ['tar', '-xf'],
|
||||||
'.zip': ['unzip', ''],
|
'.zip': ['unzip', ''],
|
||||||
'.tar.gz': ['tar', '-xzf'], '.tgz': ['tar', '-xzf'],
|
'.tar.gz': ['tar', '-xzf'],
|
||||||
'.tar.bz2': ['tar', '-xjf'], '.tbz': ['tar', '-xjf'],
|
'.tgz': ['tar', '-xzf'],
|
||||||
'.tar.lzma': ['tar', '--lzma -xf'], '.tlz': ['tar', '--lzma -xf'],
|
'.tar.bz2': ['tar', '-xjf'],
|
||||||
'.tar.xz': ['tar', '--xz -xf'], '.txz': ['tar', '--xz -xf'],
|
'.tbz': ['tar', '-xjf'],
|
||||||
|
'.tar.lzma': ['tar', '--lzma -xf'],
|
||||||
|
'.tlz': ['tar', '--lzma -xf'],
|
||||||
|
'.tar.xz': ['tar', '--xz -xf'],
|
||||||
|
'.txz': ['tar', '--xz -xf'],
|
||||||
'.7z': ['7zr', 'x'],
|
'.7z': ['7zr', 'x'],
|
||||||
}
|
}
|
||||||
# Test command exists and if not, remove.
|
# Test command exists and if not, remove.
|
||||||
@ -102,13 +100,21 @@ if not EXTRACT_COMMANDS:
|
|||||||
|
|
||||||
class Core(CorePluginBase):
|
class Core(CorePluginBase):
|
||||||
def enable(self):
|
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']:
|
if not self.config['extract_path']:
|
||||||
self.config['extract_path'] = deluge.configmanager.ConfigManager('core.conf')['download_location']
|
self.config['extract_path'] = deluge.configmanager.ConfigManager(
|
||||||
component.get('EventManager').register_event_handler('TorrentFinishedEvent', self._on_torrent_finished)
|
'core.conf'
|
||||||
|
)['download_location']
|
||||||
|
component.get('EventManager').register_event_handler(
|
||||||
|
'TorrentFinishedEvent', self._on_torrent_finished
|
||||||
|
)
|
||||||
|
|
||||||
def disable(self):
|
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):
|
def update(self):
|
||||||
pass
|
pass
|
||||||
@ -136,7 +142,9 @@ class Core(CorePluginBase):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
cmd = EXTRACT_COMMANDS[file_ext]
|
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'])
|
dest = os.path.normpath(self.config['extract_path'])
|
||||||
if self.config['use_name_folder']:
|
if self.config['use_name_folder']:
|
||||||
dest = os.path.join(dest, tid_status['name'])
|
dest = os.path.join(dest, tid_status['name'])
|
||||||
@ -153,11 +161,22 @@ class Core(CorePluginBase):
|
|||||||
if not result[2]:
|
if not result[2]:
|
||||||
log.info('Extract successful: %s (%s)', fpath, torrent_id)
|
log.info('Extract successful: %s (%s)', fpath, torrent_id)
|
||||||
else:
|
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.
|
# 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)
|
log.debug(
|
||||||
d = getProcessOutputAndValue(cmd[0], cmd[1].split() + [str(fpath)], os.environ, str(dest))
|
'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)
|
d.addCallback(on_extract, torrent_id, fpath)
|
||||||
|
|
||||||
@export
|
@export
|
||||||
|
|||||||
@ -31,15 +31,25 @@ class GtkUI(GtkPluginBase):
|
|||||||
self.builder = gtk.Builder()
|
self.builder = gtk.Builder()
|
||||||
self.builder.add_from_file(get_resource('extractor_prefs.ui'))
|
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('Preferences').add_page(
|
||||||
component.get('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs)
|
_('Extractor'), self.builder.get_object('extractor_prefs_box')
|
||||||
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.on_show_prefs()
|
self.on_show_prefs()
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
component.get('Preferences').remove_page(_('Extractor'))
|
component.get('Preferences').remove_page(_('Extractor'))
|
||||||
component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs)
|
component.get('PluginManager').deregister_hook(
|
||||||
component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs)
|
'on_apply_prefs', self.on_apply_prefs
|
||||||
|
)
|
||||||
|
component.get('PluginManager').deregister_hook(
|
||||||
|
'on_show_prefs', self.on_show_prefs
|
||||||
|
)
|
||||||
del self.builder
|
del self.builder
|
||||||
|
|
||||||
def on_apply_prefs(self):
|
def on_apply_prefs(self):
|
||||||
@ -66,10 +76,14 @@ class GtkUI(GtkPluginBase):
|
|||||||
|
|
||||||
def on_get_config(config):
|
def on_get_config(config):
|
||||||
if client.is_localhost():
|
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:
|
else:
|
||||||
self.builder.get_object('entry_path').set_text(config['extract_path'])
|
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)
|
client.extractor.get_config().addCallback(on_get_config)
|
||||||
|
|||||||
@ -41,11 +41,9 @@ setup(
|
|||||||
url=__url__,
|
url=__url__,
|
||||||
license=__license__,
|
license=__license__,
|
||||||
long_description=__long_description__ if __long_description__ else __description__,
|
long_description=__long_description__ if __long_description__ else __description__,
|
||||||
|
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
namespace_packages=['deluge', 'deluge.plugins'],
|
namespace_packages=['deluge', 'deluge.plugins'],
|
||||||
package_data=__pkg_data__,
|
package_data=__pkg_data__,
|
||||||
|
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[deluge.plugin.core]
|
[deluge.plugin.core]
|
||||||
%s = deluge.plugins.%s:CorePlugin
|
%s = deluge.plugins.%s:CorePlugin
|
||||||
@ -53,5 +51,6 @@ setup(
|
|||||||
%s = deluge.plugins.%s:GtkUIPlugin
|
%s = deluge.plugins.%s:GtkUIPlugin
|
||||||
[deluge.plugin.web]
|
[deluge.plugin.web]
|
||||||
%s = deluge.plugins.%s:WebUIPlugin
|
%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):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .core import Core as _pluginCls
|
from .core import Core as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ class CorePlugin(PluginInitBase):
|
|||||||
class GtkUIPlugin(PluginInitBase):
|
class GtkUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .gtkui import GtkUI as _pluginCls
|
from .gtkui import GtkUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -33,5 +35,6 @@ class GtkUIPlugin(PluginInitBase):
|
|||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .webui import WebUI as _pluginCls
|
from .webui import WebUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
super(WebUIPlugin, self).__init__(plugin_name)
|
||||||
|
|||||||
@ -73,6 +73,7 @@ class Core(CorePluginBase):
|
|||||||
self.labels = {label_id:label_options_dict}
|
self.labels = {label_id:label_options_dict}
|
||||||
self.torrent_labels = {torrent_id:label_id}
|
self.torrent_labels = {torrent_id:label_id}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
log.info('*** Start Label plugin ***')
|
log.info('*** Start Label plugin ***')
|
||||||
self.plugin = component.get('CorePluginManager')
|
self.plugin = component.get('CorePluginManager')
|
||||||
@ -90,19 +91,29 @@ class Core(CorePluginBase):
|
|||||||
|
|
||||||
self.clean_initial_config()
|
self.clean_initial_config()
|
||||||
|
|
||||||
component.get('EventManager').register_event_handler('TorrentAddedEvent', self.post_torrent_add)
|
component.get('EventManager').register_event_handler(
|
||||||
component.get('EventManager').register_event_handler('TorrentRemovedEvent', self.post_torrent_remove)
|
'TorrentAddedEvent', self.post_torrent_add
|
||||||
|
)
|
||||||
|
component.get('EventManager').register_event_handler(
|
||||||
|
'TorrentRemovedEvent', self.post_torrent_remove
|
||||||
|
)
|
||||||
|
|
||||||
# register tree:
|
# 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..')
|
log.debug('Label plugin enabled..')
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self.plugin.deregister_status_field('label')
|
self.plugin.deregister_status_field('label')
|
||||||
component.get('FilterManager').deregister_tree_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(
|
||||||
component.get('EventManager').deregister_event_handler('TorrentRemovedEvent', self.post_torrent_remove)
|
'TorrentAddedEvent', self.post_torrent_add
|
||||||
|
)
|
||||||
|
component.get('EventManager').deregister_event_handler(
|
||||||
|
'TorrentRemovedEvent', self.post_torrent_remove
|
||||||
|
)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
pass
|
pass
|
||||||
@ -169,7 +180,9 @@ class Core(CorePluginBase):
|
|||||||
see label_set_options for more options.
|
see label_set_options for more options.
|
||||||
"""
|
"""
|
||||||
label_id = label_id.lower()
|
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(label_id, _('Empty Label'))
|
||||||
check_input(not (label_id in self.labels), _('Label already exists'))
|
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': options['move_completed'],
|
||||||
'move_completed_path': options['move_completed_path'],
|
'move_completed_path': options['move_completed_path'],
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _unset_torrent_options(self, torrent_id, label_id):
|
def _unset_torrent_options(self, torrent_id, label_id):
|
||||||
@ -217,11 +230,21 @@ class Core(CorePluginBase):
|
|||||||
torrent = self.torrents[torrent_id]
|
torrent = self.torrents[torrent_id]
|
||||||
|
|
||||||
if options['apply_max']:
|
if options['apply_max']:
|
||||||
torrent.set_max_download_speed(self.core_cfg.config['max_download_speed_per_torrent'])
|
torrent.set_max_download_speed(
|
||||||
torrent.set_max_upload_speed(self.core_cfg.config['max_upload_speed_per_torrent'])
|
self.core_cfg.config['max_download_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_max_upload_speed(
|
||||||
torrent.set_prioritize_first_last_pieces(self.core_cfg.config['prioritize_first_last_pieces'])
|
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']:
|
if options['apply_queue']:
|
||||||
torrent.set_auto_managed(self.core_cfg.config['auto_managed'])
|
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': self.core_cfg.config['move_completed'],
|
||||||
'move_completed_path': self.core_cfg.config['move_completed_path'],
|
'move_completed_path': self.core_cfg.config['move_completed_path'],
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _has_auto_match(self, torrent, label_options):
|
def _has_auto_match(self, torrent, label_options):
|
||||||
@ -311,8 +334,7 @@ class Core(CorePluginBase):
|
|||||||
def get_config(self):
|
def get_config(self):
|
||||||
"""see : label_set_config"""
|
"""see : label_set_config"""
|
||||||
return {
|
return {
|
||||||
key: self.config[key]
|
key: self.config[key] for key in CORE_OPTIONS if key in self.config.config
|
||||||
for key in CORE_OPTIONS if key in self.config.config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@export
|
@export
|
||||||
|
|||||||
@ -33,7 +33,9 @@ class LabelConfig(object):
|
|||||||
builder = Builder()
|
builder = Builder()
|
||||||
builder.add_from_file(get_resource('label_pref.ui'))
|
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_show_prefs', self.load_settings)
|
||||||
self.plugin.register_hook('on_apply_prefs', self.on_apply_prefs)
|
self.plugin.register_hook('on_apply_prefs', self.on_apply_prefs)
|
||||||
|
|
||||||
|
|||||||
@ -80,7 +80,7 @@ class LabelSidebarMenu(object):
|
|||||||
for item in self.items:
|
for item in self.items:
|
||||||
item.show()
|
item.show()
|
||||||
# default items
|
# 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:
|
for item in self.items:
|
||||||
item.set_sensitive(sensitive)
|
item.set_sensitive(sensitive)
|
||||||
|
|
||||||
@ -127,13 +127,28 @@ class OptionsDialog(object):
|
|||||||
spin_ids = ['max_download_speed', 'max_upload_speed', 'stop_ratio']
|
spin_ids = ['max_download_speed', 'max_upload_speed', 'stop_ratio']
|
||||||
spin_int_ids = ['max_upload_slots', 'max_connections']
|
spin_int_ids = ['max_upload_slots', 'max_connections']
|
||||||
chk_ids = [
|
chk_ids = [
|
||||||
'apply_max', 'apply_queue', 'stop_at_ratio', 'apply_queue', 'remove_at_ratio',
|
'apply_max',
|
||||||
'apply_move_completed', 'move_completed', 'is_auto_managed', 'auto_add',
|
'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.
|
# list of tuples, because order matters when nesting.
|
||||||
sensitive_groups = [
|
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']),
|
('apply_queue', ['is_auto_managed', 'stop_at_ratio']),
|
||||||
('stop_at_ratio', ['remove_at_ratio', 'stop_ratio']), # nested
|
('stop_at_ratio', ['remove_at_ratio', 'stop_ratio']), # nested
|
||||||
('apply_move_completed', ['move_completed']),
|
('apply_move_completed', ['move_completed']),
|
||||||
@ -152,7 +167,9 @@ class OptionsDialog(object):
|
|||||||
self.dialog.set_transient_for(component.get('MainWindow').window)
|
self.dialog.set_transient_for(component.get('MainWindow').window)
|
||||||
self.builder.connect_signals(self)
|
self.builder.connect_signals(self)
|
||||||
# Show the label name in the header label
|
# 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:
|
for chk_id, group in self.sensitive_groups:
|
||||||
chk = self.builder.get_object(chk_id)
|
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]))
|
self.builder.get_object(chk_id).set_active(bool(options[chk_id]))
|
||||||
|
|
||||||
if client.is_localhost():
|
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').show()
|
||||||
self.builder.get_object('move_completed_path_entry').hide()
|
self.builder.get_object('move_completed_path_entry').hide()
|
||||||
else:
|
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_entry').show()
|
||||||
self.builder.get_object('move_completed_path').hide()
|
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()
|
self.apply_sensitivity()
|
||||||
|
|
||||||
@ -190,18 +213,32 @@ class OptionsDialog(object):
|
|||||||
for spin_id in self.spin_ids:
|
for spin_id in self.spin_ids:
|
||||||
options[spin_id] = self.builder.get_object(spin_id).get_value()
|
options[spin_id] = self.builder.get_object(spin_id).get_value()
|
||||||
for spin_int_id in self.spin_int_ids:
|
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:
|
for chk_id in self.chk_ids:
|
||||||
options[chk_id] = self.builder.get_object(chk_id).get_active()
|
options[chk_id] = self.builder.get_object(chk_id).get_active()
|
||||||
|
|
||||||
if client.is_localhost():
|
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:
|
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...
|
buff = self.builder.get_object(
|
||||||
tracker_lst = buff.get_text(buff.get_start_iter(), buff.get_end_iter()).strip().split('\n')
|
'auto_add_trackers'
|
||||||
options['auto_add_trackers'] = [x for x in tracker_lst if x] # filter out empty lines.
|
).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)
|
log.debug(options)
|
||||||
client.label.set_options(self.label, options)
|
client.label.set_options(self.label, options)
|
||||||
|
|||||||
@ -32,11 +32,9 @@ setup(
|
|||||||
url=__url__,
|
url=__url__,
|
||||||
license=__license__,
|
license=__license__,
|
||||||
long_description=__long_description__,
|
long_description=__long_description__,
|
||||||
|
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
namespace_packages=['deluge', 'deluge.plugins'],
|
namespace_packages=['deluge', 'deluge.plugins'],
|
||||||
package_data=__pkg_data__,
|
package_data=__pkg_data__,
|
||||||
|
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[deluge.plugin.core]
|
[deluge.plugin.core]
|
||||||
%s = deluge.plugins.%s:CorePlugin
|
%s = deluge.plugins.%s:CorePlugin
|
||||||
@ -44,5 +42,6 @@ setup(
|
|||||||
%s = deluge.plugins.%s:GtkUIPlugin
|
%s = deluge.plugins.%s:GtkUIPlugin
|
||||||
[deluge.plugin.web]
|
[deluge.plugin.web]
|
||||||
%s = deluge.plugins.%s:WebUIPlugin
|
%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):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .core import Core as _pluginCls
|
from .core import Core as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ class CorePlugin(PluginInitBase):
|
|||||||
class GtkUIPlugin(PluginInitBase):
|
class GtkUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .gtkui import GtkUI as _pluginCls
|
from .gtkui import GtkUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -34,5 +36,6 @@ class GtkUIPlugin(PluginInitBase):
|
|||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .webui import WebUI as _pluginCls
|
from .webui import WebUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
super(WebUIPlugin, self).__init__(plugin_name)
|
||||||
|
|||||||
@ -27,18 +27,14 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def get_resource(filename):
|
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):
|
class CustomNotifications(object):
|
||||||
|
|
||||||
def __init__(self, plugin_name=None):
|
def __init__(self, plugin_name=None):
|
||||||
self.custom_notifications = {
|
self.custom_notifications = {'email': {}, 'popup': {}, 'blink': {}, 'sound': {}}
|
||||||
'email': {},
|
|
||||||
'popup': {},
|
|
||||||
'blink': {},
|
|
||||||
'sound': {},
|
|
||||||
}
|
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
pass
|
pass
|
||||||
@ -50,7 +46,13 @@ class CustomNotifications(object):
|
|||||||
self._deregister_custom_provider(kind, eventtype)
|
self._deregister_custom_provider(kind, eventtype)
|
||||||
|
|
||||||
def _handle_custom_providers(self, kind, eventtype, *args, **kwargs):
|
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]:
|
if eventtype in self.config['subscriptions'][kind]:
|
||||||
wrapper, handler = self.custom_notifications[kind][eventtype]
|
wrapper, handler = self.custom_notifications[kind][eventtype]
|
||||||
log.debug('Found handler for kind %s: %s', kind, handler)
|
log.debug('Found handler for kind %s: %s', kind, handler)
|
||||||
@ -65,17 +67,18 @@ class CustomNotifications(object):
|
|||||||
if not self._handled_eventtype(eventtype, handler):
|
if not self._handled_eventtype(eventtype, handler):
|
||||||
return defer.succeed('Event not handled')
|
return defer.succeed('Event not handled')
|
||||||
if eventtype not in self.custom_notifications:
|
if eventtype not in self.custom_notifications:
|
||||||
|
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
return self._handle_custom_providers(kind, eventtype, *args, **kwargs)
|
return self._handle_custom_providers(kind, eventtype, *args, **kwargs)
|
||||||
|
|
||||||
self.custom_notifications[kind][eventtype] = (wrapper, handler)
|
self.custom_notifications[kind][eventtype] = (wrapper, handler)
|
||||||
else:
|
else:
|
||||||
wrapper, handler = self.custom_notifications[kind][eventtype]
|
wrapper, handler = self.custom_notifications[kind][eventtype]
|
||||||
try:
|
try:
|
||||||
component.get('EventManager').register_event_handler(
|
component.get('EventManager').register_event_handler(eventtype, wrapper)
|
||||||
eventtype, wrapper,
|
|
||||||
)
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
from deluge.ui.client import client
|
from deluge.ui.client import client
|
||||||
|
|
||||||
client.register_event_handler(eventtype, wrapper)
|
client.register_event_handler(eventtype, wrapper)
|
||||||
|
|
||||||
def _deregister_custom_provider(self, kind, eventtype):
|
def _deregister_custom_provider(self, kind, eventtype):
|
||||||
@ -83,10 +86,11 @@ class CustomNotifications(object):
|
|||||||
wrapper, handler = self.custom_notifications[kind][eventtype]
|
wrapper, handler = self.custom_notifications[kind][eventtype]
|
||||||
try:
|
try:
|
||||||
component.get('EventManager').deregister_event_handler(
|
component.get('EventManager').deregister_event_handler(
|
||||||
eventtype, wrapper,
|
eventtype, wrapper
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
from deluge.ui.client import client
|
from deluge.ui.client import client
|
||||||
|
|
||||||
client.deregister_event_handler(eventtype, wrapper)
|
client.deregister_event_handler(eventtype, wrapper)
|
||||||
self.custom_notifications[kind].pop(eventtype)
|
self.custom_notifications[kind].pop(eventtype)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -101,7 +105,7 @@ class CustomNotifications(object):
|
|||||||
return True
|
return True
|
||||||
log.error(
|
log.error(
|
||||||
'You cannot register custom notification providers '
|
'You cannot register custom notification providers '
|
||||||
'for built-in event types.',
|
'for built-in event types.'
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -40,22 +40,18 @@ DEFAULT_PREFS = {
|
|||||||
'smtp_tls': False, # SSL or TLS
|
'smtp_tls': False, # SSL or TLS
|
||||||
'smtp_recipients': [],
|
'smtp_recipients': [],
|
||||||
# Subscriptions
|
# Subscriptions
|
||||||
'subscriptions': {
|
'subscriptions': {'email': []},
|
||||||
'email': [],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CoreNotifications(CustomNotifications):
|
class CoreNotifications(CustomNotifications):
|
||||||
|
|
||||||
def __init__(self, plugin_name=None):
|
def __init__(self, plugin_name=None):
|
||||||
CustomNotifications.__init__(self, plugin_name)
|
CustomNotifications.__init__(self, plugin_name)
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
CustomNotifications.enable(self)
|
CustomNotifications.enable(self)
|
||||||
self.register_custom_email_notification(
|
self.register_custom_email_notification(
|
||||||
'TorrentFinishedEvent',
|
'TorrentFinishedEvent', self._on_torrent_finished_event
|
||||||
self._on_torrent_finished_event,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
@ -80,8 +76,7 @@ class CoreNotifications(CustomNotifications):
|
|||||||
return defer.succeed('SMTP notification not enabled.')
|
return defer.succeed('SMTP notification not enabled.')
|
||||||
subject, message = result
|
subject, message = result
|
||||||
log.debug(
|
log.debug(
|
||||||
'Spawning new thread to send email with subject: %s: %s',
|
'Spawning new thread to send email with subject: %s: %s', subject, message
|
||||||
subject, message,
|
|
||||||
)
|
)
|
||||||
# Spawn thread because we don't want Deluge to lock up while we send the
|
# Spawn thread because we don't want Deluge to lock up while we send the
|
||||||
# email.
|
# email.
|
||||||
@ -109,20 +104,25 @@ class CoreNotifications(CustomNotifications):
|
|||||||
'smtp_recipients': to_addrs_str,
|
'smtp_recipients': to_addrs_str,
|
||||||
'date': formatdate(),
|
'date': formatdate(),
|
||||||
}
|
}
|
||||||
headers = """\
|
headers = (
|
||||||
|
"""\
|
||||||
From: %(smtp_from)s
|
From: %(smtp_from)s
|
||||||
To: %(smtp_recipients)s
|
To: %(smtp_recipients)s
|
||||||
Subject: %(subject)s
|
Subject: %(subject)s
|
||||||
Date: %(date)s
|
Date: %(date)s
|
||||||
|
|
||||||
|
|
||||||
""" % headers_dict
|
"""
|
||||||
|
% headers_dict
|
||||||
|
)
|
||||||
|
|
||||||
message = '\r\n'.join((headers + message).splitlines())
|
message = '\r\n'.join((headers + message).splitlines())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 2.6
|
# 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:
|
except Exception 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)
|
log.error(err_msg)
|
||||||
@ -154,7 +154,9 @@ Date: %(date)s
|
|||||||
try:
|
try:
|
||||||
server.sendmail(self.config['smtp_from'], to_addrs, message)
|
server.sendmail(self.config['smtp_from'], to_addrs, message)
|
||||||
except smtplib.SMTPException as ex:
|
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)
|
log.error(err_msg)
|
||||||
return ex
|
return ex
|
||||||
finally:
|
finally:
|
||||||
@ -162,6 +164,7 @@ Date: %(date)s
|
|||||||
# avoid false failure detection when the server closes
|
# avoid false failure detection when the server closes
|
||||||
# the SMTP connection with TLS enabled
|
# the SMTP connection with TLS enabled
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server.quit()
|
server.quit()
|
||||||
except socket.sslerror:
|
except socket.sslerror:
|
||||||
@ -176,13 +179,16 @@ Date: %(date)s
|
|||||||
torrent_status = torrent.get_status({})
|
torrent_status = torrent.get_status({})
|
||||||
# Email
|
# Email
|
||||||
subject = _('Finished Torrent "%(name)s"') % torrent_status
|
subject = _('Finished Torrent "%(name)s"') % torrent_status
|
||||||
message = _(
|
message = (
|
||||||
|
_(
|
||||||
'This email is to inform you that Deluge has finished '
|
'This email is to inform you that Deluge has finished '
|
||||||
'downloading "%(name)s", which includes %(num_files)i files.'
|
'downloading "%(name)s", which includes %(num_files)i files.'
|
||||||
'\nTo stop receiving these alerts, simply turn off email '
|
'\nTo stop receiving these alerts, simply turn off email '
|
||||||
"notification in Deluge's preferences.\n\n"
|
"notification in Deluge's preferences.\n\n"
|
||||||
'Thank you,\nDeluge.',
|
'Thank you,\nDeluge.'
|
||||||
) % torrent_status
|
)
|
||||||
|
% torrent_status
|
||||||
|
)
|
||||||
return subject, message
|
return subject, message
|
||||||
|
|
||||||
# d = defer.maybeDeferred(self.handle_custom_email_notification,
|
# d = defer.maybeDeferred(self.handle_custom_email_notification,
|
||||||
@ -201,7 +207,7 @@ class Core(CorePluginBase, CoreNotifications):
|
|||||||
def enable(self):
|
def enable(self):
|
||||||
CoreNotifications.enable(self)
|
CoreNotifications.enable(self)
|
||||||
self.config = deluge.configmanager.ConfigManager(
|
self.config = deluge.configmanager.ConfigManager(
|
||||||
'notifications-core.conf', DEFAULT_PREFS,
|
'notifications-core.conf', DEFAULT_PREFS
|
||||||
)
|
)
|
||||||
log.debug('ENABLING CORE NOTIFICATIONS')
|
log.debug('ENABLING CORE NOTIFICATIONS')
|
||||||
|
|
||||||
|
|||||||
@ -34,12 +34,14 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
SOUND_AVAILABLE = True
|
SOUND_AVAILABLE = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
SOUND_AVAILABLE = False
|
SOUND_AVAILABLE = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pynotify
|
import pynotify
|
||||||
|
|
||||||
POPUP_AVAILABLE = True
|
POPUP_AVAILABLE = True
|
||||||
if deluge.common.windows_check():
|
if deluge.common.windows_check():
|
||||||
POPUP_AVAILABLE = False
|
POPUP_AVAILABLE = False
|
||||||
@ -59,36 +61,35 @@ DEFAULT_PREFS = {
|
|||||||
'sound_path': '',
|
'sound_path': '',
|
||||||
'custom_sounds': {},
|
'custom_sounds': {},
|
||||||
# Subscriptions
|
# Subscriptions
|
||||||
'subscriptions': {
|
'subscriptions': {'popup': [], 'blink': [], 'sound': []},
|
||||||
'popup': [],
|
|
||||||
'blink': [],
|
|
||||||
'sound': [],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RECIPIENT_FIELD, RECIPIENT_EDIT = list(range(2))
|
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,
|
SUB_NOT_SOUND,
|
||||||
) = list(range(6))
|
) = list(range(6))
|
||||||
SND_EVENT, SND_EVENT_DOC, SND_NAME, SND_PATH = list(range(4))
|
SND_EVENT, SND_EVENT_DOC, SND_NAME, SND_PATH = list(range(4))
|
||||||
|
|
||||||
|
|
||||||
class GtkUiNotifications(CustomNotifications):
|
class GtkUiNotifications(CustomNotifications):
|
||||||
|
|
||||||
def __init__(self, plugin_name=None):
|
def __init__(self, plugin_name=None):
|
||||||
CustomNotifications.__init__(self, plugin_name)
|
CustomNotifications.__init__(self, plugin_name)
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
CustomNotifications.enable(self)
|
CustomNotifications.enable(self)
|
||||||
self.register_custom_blink_notification(
|
self.register_custom_blink_notification(
|
||||||
'TorrentFinishedEvent', self._on_torrent_finished_event_blink,
|
'TorrentFinishedEvent', self._on_torrent_finished_event_blink
|
||||||
)
|
)
|
||||||
self.register_custom_sound_notification(
|
self.register_custom_sound_notification(
|
||||||
'TorrentFinishedEvent', self._on_torrent_finished_event_sound,
|
'TorrentFinishedEvent', self._on_torrent_finished_event_sound
|
||||||
)
|
)
|
||||||
self.register_custom_popup_notification(
|
self.register_custom_popup_notification(
|
||||||
'TorrentFinishedEvent', self._on_torrent_finished_event_popup,
|
'TorrentFinishedEvent', self._on_torrent_finished_event_popup
|
||||||
)
|
)
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
@ -150,19 +151,19 @@ class GtkUiNotifications(CustomNotifications):
|
|||||||
return defer.maybeDeferred(self.__blink)
|
return defer.maybeDeferred(self.__blink)
|
||||||
return defer.succeed(
|
return defer.succeed(
|
||||||
'Will not blink. The returned value from the custom '
|
'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):
|
def handle_custom_sound_notification(self, result, eventtype):
|
||||||
if isinstance(result, ''.__class__):
|
if isinstance(result, ''.__class__):
|
||||||
if not result and eventtype in self.config['custom_sounds']:
|
if not result and eventtype in self.config['custom_sounds']:
|
||||||
return defer.maybeDeferred(
|
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.maybeDeferred(self.__play_sound, result)
|
||||||
return defer.succeed(
|
return defer.succeed(
|
||||||
'Will not play sound. The returned value from the '
|
'Will not play sound. The returned value from the '
|
||||||
'custom handler was: %s' % result,
|
'custom handler was: %s' % result
|
||||||
)
|
)
|
||||||
|
|
||||||
def __blink(self):
|
def __blink(self):
|
||||||
@ -176,7 +177,9 @@ class GtkUiNotifications(CustomNotifications):
|
|||||||
return defer.fail(_('pynotify is not installed'))
|
return defer.fail(_('pynotify is not installed'))
|
||||||
|
|
||||||
if pynotify.init('Deluge'):
|
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 = pynotify.Notification(title, message)
|
||||||
self.note.set_icon_from_pixbuf(icon)
|
self.note.set_icon_from_pixbuf(icon)
|
||||||
if not self.note.show():
|
if not self.note.show():
|
||||||
@ -227,15 +230,17 @@ class GtkUiNotifications(CustomNotifications):
|
|||||||
|
|
||||||
def _on_torrent_finished_event_got_torrent_status(self, torrent_status):
|
def _on_torrent_finished_event_got_torrent_status(self, torrent_status):
|
||||||
log.debug(
|
log.debug(
|
||||||
'Handler for TorrentFinishedEvent GTKUI called. '
|
'Handler for TorrentFinishedEvent GTKUI called. ' 'Got Torrent Status'
|
||||||
'Got Torrent Status',
|
|
||||||
)
|
)
|
||||||
title = _('Finished Torrent')
|
title = _('Finished Torrent')
|
||||||
torrent_status['num_files'] = torrent_status['file_progress'].count(1.0)
|
torrent_status['num_files'] = torrent_status['file_progress'].count(1.0)
|
||||||
message = _(
|
message = (
|
||||||
|
_(
|
||||||
'The torrent "%(name)s" including %(num_files)i file(s) '
|
'The torrent "%(name)s" including %(num_files)i file(s) '
|
||||||
'has finished downloading.',
|
'has finished downloading.'
|
||||||
) % torrent_status
|
)
|
||||||
|
% torrent_status
|
||||||
|
)
|
||||||
return title, message
|
return title, message
|
||||||
|
|
||||||
|
|
||||||
@ -246,7 +251,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.config = deluge.configmanager.ConfigManager(
|
self.config = deluge.configmanager.ConfigManager(
|
||||||
'notifications-gtk.conf', DEFAULT_PREFS,
|
'notifications-gtk.conf', DEFAULT_PREFS
|
||||||
)
|
)
|
||||||
self.builder = gtk.Builder()
|
self.builder = gtk.Builder()
|
||||||
self.builder.add_from_file(get_resource('config.ui'))
|
self.builder.add_from_file(get_resource('config.ui'))
|
||||||
@ -259,10 +264,11 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
self.build_notifications_model_populate_treeview()
|
self.build_notifications_model_populate_treeview()
|
||||||
|
|
||||||
client.notifications.get_handled_events().addCallback(
|
client.notifications.get_handled_events().addCallback(
|
||||||
self.popuplate_what_needs_handled_events,
|
self.popuplate_what_needs_handled_events
|
||||||
)
|
)
|
||||||
|
|
||||||
self.builder.connect_signals({
|
self.builder.connect_signals(
|
||||||
|
{
|
||||||
'on_add_button_clicked': (
|
'on_add_button_clicked': (
|
||||||
self.on_add_button_clicked,
|
self.on_add_button_clicked,
|
||||||
self.recipients_treeview,
|
self.recipients_treeview,
|
||||||
@ -276,48 +282,34 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
'on_sounds_edit_button_clicked': self.on_sounds_edit_button_clicked,
|
'on_sounds_edit_button_clicked': self.on_sounds_edit_button_clicked,
|
||||||
'on_sounds_revert_button_clicked': self.on_sounds_revert_button_clicked,
|
'on_sounds_revert_button_clicked': self.on_sounds_revert_button_clicked,
|
||||||
'on_sound_path_update_preview': self.on_sound_path_update_preview,
|
'on_sound_path_update_preview': self.on_sound_path_update_preview,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
component.get('Preferences').add_page(_('Notifications'), self.prefs)
|
component.get('Preferences').add_page(_('Notifications'), self.prefs)
|
||||||
|
|
||||||
component.get('PluginManager').register_hook(
|
component.get('PluginManager').register_hook(
|
||||||
'on_apply_prefs',
|
'on_apply_prefs', self.on_apply_prefs
|
||||||
self.on_apply_prefs,
|
|
||||||
)
|
)
|
||||||
component.get('PluginManager').register_hook(
|
component.get('PluginManager').register_hook(
|
||||||
'on_show_prefs',
|
'on_show_prefs', self.on_show_prefs
|
||||||
self.on_show_prefs,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not POPUP_AVAILABLE:
|
if not POPUP_AVAILABLE:
|
||||||
self.builder.get_object('popup_enabled').set_property(
|
self.builder.get_object('popup_enabled').set_property('sensitive', False)
|
||||||
'sensitive',
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
if not SOUND_AVAILABLE:
|
if not SOUND_AVAILABLE:
|
||||||
# for widget_name in ('sound_enabled', 'sound_path', 'sounds_page', 'sounds_page_label'):
|
# 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(widget_name).set_property('sensitive', False)
|
||||||
self.builder.get_object('sound_enabled').set_property(
|
self.builder.get_object('sound_enabled').set_property('sensitive', False)
|
||||||
'sensitive',
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
self.builder.get_object('sound_path').set_property('sensitive', False)
|
self.builder.get_object('sound_path').set_property('sensitive', False)
|
||||||
self.builder.get_object('sounds_page').set_property(
|
self.builder.get_object('sounds_page').set_property('sensitive', False)
|
||||||
'sensitive',
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
self.builder.get_object('sounds_page_label').set_property(
|
self.builder.get_object('sounds_page_label').set_property(
|
||||||
'sensitive',
|
'sensitive', False
|
||||||
False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.systray = component.get('SystemTray')
|
self.systray = component.get('SystemTray')
|
||||||
if not hasattr(self.systray, 'tray'):
|
if not hasattr(self.systray, 'tray'):
|
||||||
# Tray is not beeing used
|
# Tray is not beeing used
|
||||||
self.builder.get_object('blink_enabled').set_property(
|
self.builder.get_object('blink_enabled').set_property('sensitive', False)
|
||||||
'sensitive',
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
|
|
||||||
GtkUiNotifications.enable(self)
|
GtkUiNotifications.enable(self)
|
||||||
|
|
||||||
@ -325,12 +317,10 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
GtkUiNotifications.disable(self)
|
GtkUiNotifications.disable(self)
|
||||||
component.get('Preferences').remove_page(_('Notifications'))
|
component.get('Preferences').remove_page(_('Notifications'))
|
||||||
component.get('PluginManager').deregister_hook(
|
component.get('PluginManager').deregister_hook(
|
||||||
'on_apply_prefs',
|
'on_apply_prefs', self.on_apply_prefs
|
||||||
self.on_apply_prefs,
|
|
||||||
)
|
)
|
||||||
component.get('PluginManager').deregister_hook(
|
component.get('PluginManager').deregister_hook(
|
||||||
'on_show_prefs',
|
'on_show_prefs', self.on_show_prefs
|
||||||
self.on_show_prefs,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_recipients_model_populate_treeview(self):
|
def build_recipients_model_populate_treeview(self):
|
||||||
@ -338,7 +328,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
self.recipients_treeview = self.builder.get_object('smtp_recipients')
|
self.recipients_treeview = self.builder.get_object('smtp_recipients')
|
||||||
treeview_selection = self.recipients_treeview.get_selection()
|
treeview_selection = self.recipients_treeview.get_selection()
|
||||||
treeview_selection.connect(
|
treeview_selection.connect(
|
||||||
'changed', self.on_recipients_treeview_selection_changed,
|
'changed', self.on_recipients_treeview_selection_changed
|
||||||
)
|
)
|
||||||
self.recipients_model = gtk.ListStore(str, bool)
|
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.connect('edited', self.on_cell_edited, self.recipients_model)
|
||||||
renderer.set_data('recipient', RECIPIENT_FIELD)
|
renderer.set_data('recipient', RECIPIENT_FIELD)
|
||||||
column = gtk.TreeViewColumn(
|
column = gtk.TreeViewColumn(
|
||||||
'Recipients', renderer,
|
'Recipients', renderer, text=RECIPIENT_FIELD, editable=RECIPIENT_EDIT
|
||||||
text=RECIPIENT_FIELD,
|
|
||||||
editable=RECIPIENT_EDIT,
|
|
||||||
)
|
)
|
||||||
column.set_expand(True)
|
column.set_expand(True)
|
||||||
self.recipients_treeview.append_column(column)
|
self.recipients_treeview.append_column(column)
|
||||||
@ -358,9 +346,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
# Sound customisation treeview/model
|
# Sound customisation treeview/model
|
||||||
self.sounds_treeview = self.builder.get_object('sounds_treeview')
|
self.sounds_treeview = self.builder.get_object('sounds_treeview')
|
||||||
sounds_selection = self.sounds_treeview.get_selection()
|
sounds_selection = self.sounds_treeview.get_selection()
|
||||||
sounds_selection.connect(
|
sounds_selection.connect('changed', self.on_sounds_treeview_selection_changed)
|
||||||
'changed', self.on_sounds_treeview_selection_changed,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.sounds_treeview.set_tooltip_column(SND_EVENT_DOC)
|
self.sounds_treeview.set_tooltip_column(SND_EVENT_DOC)
|
||||||
self.sounds_model = gtk.ListStore(str, str, str, str)
|
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')
|
self.subscriptions_treeview = self.builder.get_object('subscriptions_treeview')
|
||||||
subscriptions_selection = self.subscriptions_treeview.get_selection()
|
subscriptions_selection = self.subscriptions_treeview.get_selection()
|
||||||
subscriptions_selection.connect(
|
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_treeview.set_tooltip_column(SUB_EVENT_DOC)
|
||||||
self.subscriptions_model = gtk.ListStore(str, str, bool, bool, bool, bool)
|
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.append_column(column)
|
||||||
self.subscriptions_treeview.set_model(self.subscriptions_model)
|
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:
|
if email_subscriptions is None:
|
||||||
email_subscriptions = []
|
email_subscriptions = []
|
||||||
self.populate_subscriptions(handled_events, email_subscriptions)
|
self.populate_subscriptions(handled_events, email_subscriptions)
|
||||||
@ -458,10 +446,14 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
if snd_path:
|
if snd_path:
|
||||||
self.sounds_model.set(
|
self.sounds_model.set(
|
||||||
self.sounds_model.append(),
|
self.sounds_model.append(),
|
||||||
SND_EVENT, event_name,
|
SND_EVENT,
|
||||||
SND_EVENT_DOC, event_doc,
|
event_name,
|
||||||
SND_NAME, basename(snd_path),
|
SND_EVENT_DOC,
|
||||||
SND_PATH, snd_path,
|
event_doc,
|
||||||
|
SND_NAME,
|
||||||
|
basename(snd_path),
|
||||||
|
SND_PATH,
|
||||||
|
snd_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
def populate_subscriptions(self, handled_events, email_subscriptions=None):
|
def populate_subscriptions(self, handled_events, email_subscriptions=None):
|
||||||
@ -473,12 +465,18 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
for event_name, event_doc in handled_events:
|
for event_name, event_doc in handled_events:
|
||||||
self.subscriptions_model.set(
|
self.subscriptions_model.set(
|
||||||
self.subscriptions_model.append(),
|
self.subscriptions_model.append(),
|
||||||
SUB_EVENT, event_name,
|
SUB_EVENT,
|
||||||
SUB_EVENT_DOC, event_doc,
|
event_name,
|
||||||
SUB_NOT_EMAIL, event_name in email_subscriptions,
|
SUB_EVENT_DOC,
|
||||||
SUB_NOT_POPUP, event_name in subscriptions_dict['popup'],
|
event_doc,
|
||||||
SUB_NOT_BLINK, event_name in subscriptions_dict['blink'],
|
SUB_NOT_EMAIL,
|
||||||
SUB_NOT_SOUND, event_name in subscriptions_dict['sound'],
|
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):
|
def on_apply_prefs(self):
|
||||||
@ -501,8 +499,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
old_sound_file = self.config['sound_path']
|
old_sound_file = self.config['sound_path']
|
||||||
new_sound_file = self.builder.get_object('sound_path').get_filename()
|
new_sound_file = self.builder.get_object('sound_path').get_filename()
|
||||||
log.debug(
|
log.debug(
|
||||||
'Old Default sound file: %s New one: %s',
|
'Old Default sound file: %s New one: %s', old_sound_file, new_sound_file
|
||||||
old_sound_file, new_sound_file,
|
|
||||||
)
|
)
|
||||||
custom_sounds = {}
|
custom_sounds = {}
|
||||||
for event_name, event_doc, filename, filepath in self.sounds_model:
|
for event_name, event_doc, filename, filepath in self.sounds_model:
|
||||||
@ -511,7 +508,8 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
continue
|
continue
|
||||||
custom_sounds[event_name] = filepath
|
custom_sounds[event_name] = filepath
|
||||||
|
|
||||||
self.config.config.update({
|
self.config.config.update(
|
||||||
|
{
|
||||||
'popup_enabled': self.builder.get_object('popup_enabled').get_active(),
|
'popup_enabled': self.builder.get_object('popup_enabled').get_active(),
|
||||||
'blink_enabled': self.builder.get_object('blink_enabled').get_active(),
|
'blink_enabled': self.builder.get_object('blink_enabled').get_active(),
|
||||||
'sound_enabled': self.builder.get_object('sound_enabled').get_active(),
|
'sound_enabled': self.builder.get_object('sound_enabled').get_active(),
|
||||||
@ -522,7 +520,8 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
'sound': current_sound_subscriptions,
|
'sound': current_sound_subscriptions,
|
||||||
},
|
},
|
||||||
'custom_sounds': custom_sounds,
|
'custom_sounds': custom_sounds,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
self.config.save()
|
self.config.save()
|
||||||
|
|
||||||
core_config = {
|
core_config = {
|
||||||
@ -534,8 +533,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
'smtp_from': self.builder.get_object('smtp_from').get_text(),
|
'smtp_from': self.builder.get_object('smtp_from').get_text(),
|
||||||
'smtp_tls': self.builder.get_object('smtp_tls').get_active(),
|
'smtp_tls': self.builder.get_object('smtp_tls').get_active(),
|
||||||
'smtp_recipients': [
|
'smtp_recipients': [
|
||||||
dest[0] for dest in self.recipients_model if
|
dest[0] for dest in self.recipients_model if dest[0] != 'USER@HOST'
|
||||||
dest[0] != 'USER@HOST'
|
|
||||||
],
|
],
|
||||||
'subscriptions': {'email': current_email_subscriptions},
|
'subscriptions': {'email': current_email_subscriptions},
|
||||||
}
|
}
|
||||||
@ -558,20 +556,20 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
for recipient in core_config['smtp_recipients']:
|
for recipient in core_config['smtp_recipients']:
|
||||||
self.recipients_model.set(
|
self.recipients_model.set(
|
||||||
self.recipients_model.append(),
|
self.recipients_model.append(),
|
||||||
RECIPIENT_FIELD, recipient,
|
RECIPIENT_FIELD,
|
||||||
RECIPIENT_EDIT, False,
|
recipient,
|
||||||
)
|
RECIPIENT_EDIT,
|
||||||
self.builder.get_object('smtp_enabled').set_active(
|
False,
|
||||||
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.builder.get_object('sound_enabled').set_active(
|
||||||
self.config['sound_enabled'],
|
self.config['sound_enabled']
|
||||||
)
|
)
|
||||||
self.builder.get_object('popup_enabled').set_active(
|
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.builder.get_object('blink_enabled').set_active(
|
||||||
self.config['blink_enabled'],
|
self.config['blink_enabled']
|
||||||
)
|
)
|
||||||
if self.config['sound_path']:
|
if self.config['sound_path']:
|
||||||
sound_path = 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):
|
def on_sound_path_update_preview(self, filechooser):
|
||||||
client.notifications.get_handled_events().addCallback(
|
client.notifications.get_handled_events().addCallback(self.populate_sounds)
|
||||||
self.populate_sounds,
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_add_button_clicked(self, widget, treeview):
|
def on_add_button_clicked(self, widget, treeview):
|
||||||
model = treeview.get_model()
|
model = treeview.get_model()
|
||||||
model.set(
|
model.set(model.append(), RECIPIENT_FIELD, 'USER@HOST', RECIPIENT_EDIT, True)
|
||||||
model.append(),
|
|
||||||
RECIPIENT_FIELD, 'USER@HOST',
|
|
||||||
RECIPIENT_EDIT, True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_delete_button_clicked(self, widget, treeview):
|
def on_delete_button_clicked(self, widget, treeview):
|
||||||
selection = treeview.get_selection()
|
selection = treeview.get_selection()
|
||||||
@ -613,42 +605,40 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
def on_recipients_treeview_selection_changed(self, selection):
|
def on_recipients_treeview_selection_changed(self, selection):
|
||||||
model, selected_connection_iter = selection.get_selected()
|
model, selected_connection_iter = selection.get_selected()
|
||||||
if selected_connection_iter:
|
if selected_connection_iter:
|
||||||
self.builder.get_object('delete_button').set_property(
|
self.builder.get_object('delete_button').set_property('sensitive', True)
|
||||||
'sensitive',
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.builder.get_object('delete_button').set_property(
|
self.builder.get_object('delete_button').set_property('sensitive', False)
|
||||||
'sensitive',
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_subscriptions_treeview_selection_changed(self, selection):
|
def on_subscriptions_treeview_selection_changed(self, selection):
|
||||||
model, selected_connection_iter = selection.get_selected()
|
model, selected_connection_iter = selection.get_selected()
|
||||||
if selected_connection_iter:
|
if selected_connection_iter:
|
||||||
self.builder.get_object('delete_button').set_property(
|
self.builder.get_object('delete_button').set_property('sensitive', True)
|
||||||
'sensitive',
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.builder.get_object('delete_button').set_property(
|
self.builder.get_object('delete_button').set_property('sensitive', False)
|
||||||
'sensitive',
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_sounds_treeview_selection_changed(self, selection):
|
def on_sounds_treeview_selection_changed(self, selection):
|
||||||
model, selected_iter = selection.get_selected()
|
model, selected_iter = selection.get_selected()
|
||||||
if selected_iter:
|
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]
|
path = model.get(selected_iter, SND_PATH)[0]
|
||||||
log.debug('Sound selection changed: %s', path)
|
log.debug('Sound selection changed: %s', path)
|
||||||
if path != self.config['sound_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:
|
else:
|
||||||
self.builder.get_object('sounds_revert_button').set_property('sensitive', False)
|
self.builder.get_object('sounds_revert_button').set_property(
|
||||||
|
'sensitive', False
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.builder.get_object('sounds_edit_button').set_property('sensitive', False)
|
self.builder.get_object('sounds_edit_button').set_property(
|
||||||
self.builder.get_object('sounds_revert_button').set_property('sensitive', False)
|
'sensitive', False
|
||||||
|
)
|
||||||
|
self.builder.get_object('sounds_revert_button').set_property(
|
||||||
|
'sensitive', False
|
||||||
|
)
|
||||||
|
|
||||||
def on_sounds_revert_button_clicked(self, widget):
|
def on_sounds_revert_button_clicked(self, widget):
|
||||||
log.debug('on_sounds_revert_button_clicked')
|
log.debug('on_sounds_revert_button_clicked')
|
||||||
@ -658,8 +648,10 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
log.debug('on_sounds_revert_button_clicked: got iter')
|
log.debug('on_sounds_revert_button_clicked: got iter')
|
||||||
model.set(
|
model.set(
|
||||||
selected_iter,
|
selected_iter,
|
||||||
SND_PATH, self.config['sound_path'],
|
SND_PATH,
|
||||||
SND_NAME, basename(self.config['sound_path']),
|
self.config['sound_path'],
|
||||||
|
SND_NAME,
|
||||||
|
basename(self.config['sound_path']),
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_sounds_edit_button_clicked(self, widget):
|
def on_sounds_edit_button_clicked(self, widget):
|
||||||
@ -686,9 +678,12 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
log.debug(new_filename)
|
log.debug(new_filename)
|
||||||
model.set(
|
model.set(
|
||||||
selected_iter,
|
selected_iter,
|
||||||
SND_PATH, new_filename,
|
SND_PATH,
|
||||||
SND_NAME, basename(new_filename),
|
new_filename,
|
||||||
|
SND_NAME,
|
||||||
|
basename(new_filename),
|
||||||
)
|
)
|
||||||
|
|
||||||
d = defer.maybeDeferred(dialog.run)
|
d = defer.maybeDeferred(dialog.run)
|
||||||
d.addCallback(update_model)
|
d.addCallback(update_model)
|
||||||
|
|
||||||
@ -696,35 +691,29 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
|
|
||||||
def on_enabled_toggled(self, widget):
|
def on_enabled_toggled(self, widget):
|
||||||
for widget_name in (
|
for widget_name in (
|
||||||
'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass',
|
'smtp_host',
|
||||||
'smtp_pass', 'smtp_tls', 'smtp_from',
|
'smtp_port',
|
||||||
|
'smtp_user',
|
||||||
|
'smtp_pass',
|
||||||
|
'smtp_pass',
|
||||||
|
'smtp_tls',
|
||||||
|
'smtp_from',
|
||||||
'smtp_recipients',
|
'smtp_recipients',
|
||||||
):
|
):
|
||||||
self.builder.get_object(widget_name).set_property(
|
self.builder.get_object(widget_name).set_property(
|
||||||
'sensitive',
|
'sensitive', widget.get_active()
|
||||||
widget.get_active(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_sound_enabled_toggled(self, widget):
|
def on_sound_enabled_toggled(self, widget):
|
||||||
if widget.get_active():
|
if widget.get_active():
|
||||||
self.builder.get_object('sound_path').set_property('sensitive', True)
|
self.builder.get_object('sound_path').set_property('sensitive', True)
|
||||||
self.builder.get_object('sounds_page').set_property(
|
self.builder.get_object('sounds_page').set_property('sensitive', True)
|
||||||
'sensitive',
|
self.builder.get_object('sounds_page_label').set_property('sensitive', True)
|
||||||
True,
|
|
||||||
)
|
|
||||||
self.builder.get_object('sounds_page_label').set_property(
|
|
||||||
'sensitive',
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.builder.get_object('sound_path').set_property('sensitive', False)
|
self.builder.get_object('sound_path').set_property('sensitive', False)
|
||||||
self.builder.get_object('sounds_page').set_property(
|
self.builder.get_object('sounds_page').set_property('sensitive', False)
|
||||||
'sensitive',
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
self.builder.get_object('sounds_page_label').set_property(
|
self.builder.get_object('sounds_page_label').set_property(
|
||||||
'sensitive',
|
'sensitive', False
|
||||||
False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# for widget_name in ('sounds_path', 'sounds_page', 'sounds_page_label'):
|
# for widget_name in ('sounds_path', 'sounds_page', 'sounds_page_label'):
|
||||||
@ -732,21 +721,25 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||||||
# widget.get_active())
|
# widget.get_active())
|
||||||
|
|
||||||
def _on_email_col_toggled(self, cell, path):
|
def _on_email_col_toggled(self, cell, path):
|
||||||
self.subscriptions_model[path][SUB_NOT_EMAIL] = \
|
self.subscriptions_model[path][SUB_NOT_EMAIL] = not self.subscriptions_model[
|
||||||
not self.subscriptions_model[path][SUB_NOT_EMAIL]
|
path
|
||||||
|
][SUB_NOT_EMAIL]
|
||||||
return
|
return
|
||||||
|
|
||||||
def _on_popup_col_toggled(self, cell, path):
|
def _on_popup_col_toggled(self, cell, path):
|
||||||
self.subscriptions_model[path][SUB_NOT_POPUP] = \
|
self.subscriptions_model[path][SUB_NOT_POPUP] = not self.subscriptions_model[
|
||||||
not self.subscriptions_model[path][SUB_NOT_POPUP]
|
path
|
||||||
|
][SUB_NOT_POPUP]
|
||||||
return
|
return
|
||||||
|
|
||||||
def _on_blink_col_toggled(self, cell, path):
|
def _on_blink_col_toggled(self, cell, path):
|
||||||
self.subscriptions_model[path][SUB_NOT_BLINK] = \
|
self.subscriptions_model[path][SUB_NOT_BLINK] = not self.subscriptions_model[
|
||||||
not self.subscriptions_model[path][SUB_NOT_BLINK]
|
path
|
||||||
|
][SUB_NOT_BLINK]
|
||||||
return
|
return
|
||||||
|
|
||||||
def _on_sound_col_toggled(self, cell, path):
|
def _on_sound_col_toggled(self, cell, path):
|
||||||
self.subscriptions_model[path][SUB_NOT_SOUND] = \
|
self.subscriptions_model[path][SUB_NOT_SOUND] = not self.subscriptions_model[
|
||||||
not self.subscriptions_model[path][SUB_NOT_SOUND]
|
path
|
||||||
|
][SUB_NOT_SOUND]
|
||||||
return
|
return
|
||||||
|
|||||||
@ -32,10 +32,7 @@ class TestEmailNotifications(component.Component):
|
|||||||
self.__imp = imp
|
self.__imp = imp
|
||||||
self.lc = task.LoopingCall(self.update)
|
self.lc = task.LoopingCall(self.update)
|
||||||
self.n = 1
|
self.n = 1
|
||||||
self.events = [
|
self.events = [FooEvent(), CustomEvent()]
|
||||||
FooEvent(),
|
|
||||||
CustomEvent(),
|
|
||||||
]
|
|
||||||
self.events_classes = []
|
self.events_classes = []
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
@ -44,22 +41,18 @@ class TestEmailNotifications(component.Component):
|
|||||||
if self.__imp == 'core':
|
if self.__imp == 'core':
|
||||||
# component.get('CorePlugin.Notifications').register_custom_email_notification(
|
# component.get('CorePlugin.Notifications').register_custom_email_notification(
|
||||||
component.get('Notifications').register_custom_email_notification(
|
component.get('Notifications').register_custom_email_notification(
|
||||||
event.__class__.__name__,
|
event.__class__.__name__, self.custom_email_message_provider
|
||||||
self.custom_email_message_provider,
|
|
||||||
)
|
)
|
||||||
elif self.__imp == 'gtk':
|
elif self.__imp == 'gtk':
|
||||||
notifications_component = component.get('Notifications')
|
notifications_component = component.get('Notifications')
|
||||||
notifications_component.register_custom_popup_notification(
|
notifications_component.register_custom_popup_notification(
|
||||||
event.__class__.__name__,
|
event.__class__.__name__, self.custom_popup_message_provider
|
||||||
self.custom_popup_message_provider,
|
|
||||||
)
|
)
|
||||||
notifications_component.register_custom_blink_notification(
|
notifications_component.register_custom_blink_notification(
|
||||||
event.__class__.__name__,
|
event.__class__.__name__, self.custom_blink_message_provider
|
||||||
self.custom_blink_message_provider,
|
|
||||||
)
|
)
|
||||||
notifications_component.register_custom_sound_notification(
|
notifications_component.register_custom_sound_notification(
|
||||||
event.__class__.__name__,
|
event.__class__.__name__, self.custom_sound_message_provider
|
||||||
self.custom_sound_message_provider,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.lc.start(60, False)
|
self.lc.start(60, False)
|
||||||
|
|||||||
@ -40,11 +40,9 @@ setup(
|
|||||||
url=__url__,
|
url=__url__,
|
||||||
license=__license__,
|
license=__license__,
|
||||||
long_description=__long_description__ if __long_description__ else __description__,
|
long_description=__long_description__ if __long_description__ else __description__,
|
||||||
|
|
||||||
packages=find_packages(exclude=['**/test.py']),
|
packages=find_packages(exclude=['**/test.py']),
|
||||||
namespace_packages=['deluge', 'deluge.plugins'],
|
namespace_packages=['deluge', 'deluge.plugins'],
|
||||||
package_data=__pkg_data__,
|
package_data=__pkg_data__,
|
||||||
|
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[deluge.plugin.core]
|
[deluge.plugin.core]
|
||||||
%s = deluge.plugins.%s:CorePlugin
|
%s = deluge.plugins.%s:CorePlugin
|
||||||
@ -52,5 +50,6 @@ setup(
|
|||||||
%s = deluge.plugins.%s:GtkUIPlugin
|
%s = deluge.plugins.%s:GtkUIPlugin
|
||||||
[deluge.plugin.web]
|
[deluge.plugin.web]
|
||||||
%s = deluge.plugins.%s:WebUIPlugin
|
%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):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .core import Core as _pluginCls
|
from .core import Core as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ class CorePlugin(PluginInitBase):
|
|||||||
class GtkUIPlugin(PluginInitBase):
|
class GtkUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .gtkui import GtkUI as _pluginCls
|
from .gtkui import GtkUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -33,5 +35,6 @@ class GtkUIPlugin(PluginInitBase):
|
|||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .webui import WebUI as _pluginCls
|
from .webui import WebUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
super(WebUIPlugin, self).__init__(plugin_name)
|
||||||
|
|||||||
@ -35,11 +35,7 @@ DEFAULT_PREFS = {
|
|||||||
'button_state': [[0] * 7 for dummy in range(24)],
|
'button_state': [[0] * 7 for dummy in range(24)],
|
||||||
}
|
}
|
||||||
|
|
||||||
STATES = {
|
STATES = {0: 'Green', 1: 'Yellow', 2: 'Red'}
|
||||||
0: 'Green',
|
|
||||||
1: 'Yellow',
|
|
||||||
2: 'Red',
|
|
||||||
}
|
|
||||||
|
|
||||||
CONTROLLED_SETTINGS = [
|
CONTROLLED_SETTINGS = [
|
||||||
'max_download_speed',
|
'max_download_speed',
|
||||||
@ -54,6 +50,7 @@ class SchedulerEvent(DelugeEvent):
|
|||||||
"""
|
"""
|
||||||
Emitted when a schedule state changes.
|
Emitted when a schedule state changes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, colour):
|
def __init__(self, colour):
|
||||||
"""
|
"""
|
||||||
:param colour: str, the current scheduler state
|
: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_down'] = core_config['max_active_downloading']
|
||||||
DEFAULT_PREFS['low_active_up'] = core_config['max_active_seeding']
|
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()
|
self.state = self.get_state()
|
||||||
|
|
||||||
@ -84,12 +83,16 @@ class Core(CorePluginBase):
|
|||||||
self.timer = reactor.callLater(secs_to_next_hour, self.do_schedule)
|
self.timer = reactor.callLater(secs_to_next_hour, self.do_schedule)
|
||||||
|
|
||||||
# Register for config changes so state isn't overridden
|
# 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):
|
def disable(self):
|
||||||
if self.timer.active():
|
if self.timer.active():
|
||||||
self.timer.cancel()
|
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()
|
self.__apply_set_functions()
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
@ -105,7 +108,9 @@ class Core(CorePluginBase):
|
|||||||
"""
|
"""
|
||||||
core_config = deluge.configmanager.ConfigManager('core.conf')
|
core_config = deluge.configmanager.ConfigManager('core.conf')
|
||||||
for setting in CONTROLLED_SETTINGS:
|
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
|
# Resume the session if necessary
|
||||||
component.get('Core').resume_session()
|
component.get('Core').resume_session()
|
||||||
|
|
||||||
|
|||||||
@ -32,8 +32,10 @@ class SchedulerSelectWidget(gtk.DrawingArea):
|
|||||||
def __init__(self, hover):
|
def __init__(self, hover):
|
||||||
super(SchedulerSelectWidget, self).__init__()
|
super(SchedulerSelectWidget, self).__init__()
|
||||||
self.set_events(
|
self.set_events(
|
||||||
gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK |
|
gtk.gdk.BUTTON_PRESS_MASK
|
||||||
gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.LEAVE_NOTIFY_MASK,
|
| gtk.gdk.BUTTON_RELEASE_MASK
|
||||||
|
| gtk.gdk.POINTER_MOTION_MASK
|
||||||
|
| gtk.gdk.LEAVE_NOTIFY_MASK
|
||||||
)
|
)
|
||||||
|
|
||||||
self.connect('expose_event', self.expose)
|
self.connect('expose_event', self.expose)
|
||||||
@ -65,7 +67,9 @@ class SchedulerSelectWidget(gtk.DrawingArea):
|
|||||||
# redraw the whole thing
|
# redraw the whole thing
|
||||||
def expose(self, widget, event):
|
def expose(self, widget, event):
|
||||||
context = self.window.cairo_create()
|
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()
|
context.clip()
|
||||||
|
|
||||||
width = self.window.get_size()[0]
|
width = self.window.get_size()[0]
|
||||||
@ -76,11 +80,14 @@ class SchedulerSelectWidget(gtk.DrawingArea):
|
|||||||
context.set_source_rgba(
|
context.set_source_rgba(
|
||||||
self.colors[self.button_state[x][y]][0],
|
self.colors[self.button_state[x][y]][0],
|
||||||
self.colors[self.button_state[x][y]][1],
|
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(
|
context.rectangle(
|
||||||
width * (6 * x / 145 + 1 / 145), height * (6 * y / 43 + 1 / 43),
|
width * (6 * x / 145 + 1 / 145),
|
||||||
5 * width / 145, 5 * height / 43,
|
height * (6 * y / 43 + 1 / 43),
|
||||||
|
5 * width / 145,
|
||||||
|
5 * height / 43,
|
||||||
)
|
)
|
||||||
context.fill_preserve()
|
context.fill_preserve()
|
||||||
context.set_source_rgba(0.5, 0.5, 0.5, 0.5)
|
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_point = self.get_point(event)
|
||||||
|
|
||||||
self.hover_label.set_text(
|
self.hover_label.set_text(
|
||||||
self.hover_days[self.hover_point[1]] +
|
self.hover_days[self.hover_point[1]]
|
||||||
' ' + str(self.hover_point[0]) +
|
+ ' '
|
||||||
':00 - ' + str(self.hover_point[0]) + ':59',
|
+ str(self.hover_point[0])
|
||||||
|
+ ':00 - '
|
||||||
|
+ str(self.hover_point[0])
|
||||||
|
+ ':59'
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.mouse_press:
|
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 x in range(min(points[0]), max(points[0]) + 1):
|
||||||
for y in range(min(points[1]), max(points[1]) + 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()
|
self.queue_draw()
|
||||||
|
|
||||||
@ -156,8 +171,12 @@ class GtkUI(GtkPluginBase):
|
|||||||
def enable(self):
|
def enable(self):
|
||||||
self.create_prefs_page()
|
self.create_prefs_page()
|
||||||
|
|
||||||
component.get('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs)
|
component.get('PluginManager').register_hook(
|
||||||
component.get('PluginManager').register_hook('on_show_prefs', self.on_show_prefs)
|
'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.statusbar = component.get('StatusBar')
|
||||||
self.status_item = self.statusbar.add_item(
|
self.status_item = self.statusbar.add_item(
|
||||||
image=get_resource('green.png'),
|
image=get_resource('green.png'),
|
||||||
@ -169,20 +188,29 @@ class GtkUI(GtkPluginBase):
|
|||||||
def on_state_deferred(state):
|
def on_state_deferred(state):
|
||||||
self.state = state
|
self.state = state
|
||||||
self.on_scheduler_event(state)
|
self.on_scheduler_event(state)
|
||||||
|
|
||||||
client.scheduler.get_state().addCallback(on_state_deferred)
|
client.scheduler.get_state().addCallback(on_state_deferred)
|
||||||
client.register_event_handler('SchedulerEvent', self.on_scheduler_event)
|
client.register_event_handler('SchedulerEvent', self.on_scheduler_event)
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
component.get('Preferences').remove_page(_('Scheduler'))
|
component.get('Preferences').remove_page(_('Scheduler'))
|
||||||
# Reset statusbar dict.
|
# Reset statusbar dict.
|
||||||
self.statusbar.config_value_changed_dict['max_download_speed'] = self.statusbar._on_max_download_speed
|
self.statusbar.config_value_changed_dict[
|
||||||
self.statusbar.config_value_changed_dict['max_upload_speed'] = self.statusbar._on_max_upload_speed
|
'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.
|
# Remove statusbar item.
|
||||||
self.statusbar.remove_item(self.status_item)
|
self.statusbar.remove_item(self.status_item)
|
||||||
del self.status_item
|
del self.status_item
|
||||||
|
|
||||||
component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs)
|
component.get('PluginManager').deregister_hook(
|
||||||
component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs)
|
'on_apply_prefs', self.on_apply_prefs
|
||||||
|
)
|
||||||
|
component.get('PluginManager').deregister_hook(
|
||||||
|
'on_show_prefs', self.on_show_prefs
|
||||||
|
)
|
||||||
|
|
||||||
def on_apply_prefs(self):
|
def on_apply_prefs(self):
|
||||||
log.debug('applying prefs for Scheduler')
|
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.
|
# Skip error due to Plugin being enabled before statusbar items created on startup.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.statusbar.config_value_changed_dict['max_download_speed'] = self.statusbar._on_max_download_speed
|
self.statusbar.config_value_changed_dict[
|
||||||
self.statusbar.config_value_changed_dict['max_upload_speed'] = self.statusbar._on_max_upload_speed
|
'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):
|
def update_config_values(config):
|
||||||
try:
|
try:
|
||||||
@ -231,7 +263,10 @@ class GtkUI(GtkPluginBase):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Skip error due to Plugin being enabled before statusbar items created on startup.
|
# Skip error due to Plugin being enabled before statusbar items created on startup.
|
||||||
pass
|
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):
|
def on_status_item_clicked(self, widget, event):
|
||||||
component.get('Preferences').show('Scheduler')
|
component.get('Preferences').show('Scheduler')
|
||||||
|
|||||||
@ -32,11 +32,9 @@ setup(
|
|||||||
url=__url__,
|
url=__url__,
|
||||||
license=__license__,
|
license=__license__,
|
||||||
long_description=__long_description__ if __long_description__ else __description__,
|
long_description=__long_description__ if __long_description__ else __description__,
|
||||||
|
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
namespace_packages=['deluge', 'deluge.plugins'],
|
namespace_packages=['deluge', 'deluge.plugins'],
|
||||||
package_data=__pkg_data__,
|
package_data=__pkg_data__,
|
||||||
|
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[deluge.plugin.core]
|
[deluge.plugin.core]
|
||||||
%s = deluge.plugins.%s:CorePlugin
|
%s = deluge.plugins.%s:CorePlugin
|
||||||
@ -44,5 +42,6 @@ setup(
|
|||||||
%s = deluge.plugins.%s:GtkUIPlugin
|
%s = deluge.plugins.%s:GtkUIPlugin
|
||||||
[deluge.plugin.web]
|
[deluge.plugin.web]
|
||||||
%s = deluge.plugins.%s:WebUIPlugin
|
%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):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .core import Core as _pluginCls
|
from .core import Core as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ class CorePlugin(PluginInitBase):
|
|||||||
class GtkUIPlugin(PluginInitBase):
|
class GtkUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .gtkui import GtkUI as _pluginCls
|
from .gtkui import GtkUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -33,5 +35,6 @@ class GtkUIPlugin(PluginInitBase):
|
|||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .webui import WebUI as _pluginCls
|
from .webui import WebUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
super(WebUIPlugin, self).__init__(plugin_name)
|
||||||
|
|||||||
@ -119,13 +119,15 @@ class Core(CorePluginBase):
|
|||||||
stats.update(self.core.get_session_status([key]))
|
stats.update(self.core.get_session_status([key]))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
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['dht_cache_nodes'] = stats['dht.dht_node_cache']
|
||||||
stats.update(self.core.get_config_values([
|
stats.update(
|
||||||
'max_download',
|
self.core.get_config_values(
|
||||||
'max_upload',
|
['max_download', 'max_upload', 'max_num_connections']
|
||||||
'max_num_connections',
|
)
|
||||||
]))
|
)
|
||||||
# status = self.core.session.status()
|
# status = self.core.session.status()
|
||||||
# for stat in dir(status):
|
# for stat in dir(status):
|
||||||
# if not stat.startswith('_') and stat not in stats:
|
# if not stat.startswith('_') and stat not in stats:
|
||||||
@ -195,7 +197,12 @@ class Core(CorePluginBase):
|
|||||||
@export
|
@export
|
||||||
def get_session_totals(self):
|
def get_session_totals(self):
|
||||||
return self.core.get_session_status(
|
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
|
@export
|
||||||
|
|||||||
@ -136,7 +136,9 @@ class Graph(object):
|
|||||||
seconds_to_step = math.ceil(start / x_step) * x_step - start
|
seconds_to_step = math.ceil(start / x_step) * x_step - start
|
||||||
|
|
||||||
for i in range(0, duration // x_step + 1):
|
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
|
# + 0.5 to allign x to nearest pixel
|
||||||
x = int(ratio * (seconds_to_step + i * x_step) + left) + 0.5
|
x = int(ratio * (seconds_to_step + i * x_step) + left) + 0.5
|
||||||
self.draw_x_text(text, x, bottom)
|
self.draw_x_text(text, x, bottom)
|
||||||
@ -171,6 +173,7 @@ class Graph(object):
|
|||||||
def space_required(text):
|
def space_required(text):
|
||||||
te = self.ctx.text_extents(text)
|
te = self.ctx.text_extents(text)
|
||||||
return math.ceil(te[4] - te[0])
|
return math.ceil(te[4] - te[0])
|
||||||
|
|
||||||
y_tick_width = max((space_required(text) for text in y_tick_text))
|
y_tick_width = max((space_required(text) for text in y_tick_text))
|
||||||
|
|
||||||
top = font_extents[2] / 2
|
top = font_extents[2] / 2
|
||||||
@ -220,7 +223,9 @@ class Graph(object):
|
|||||||
else:
|
else:
|
||||||
interval = interval * 2
|
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
|
return intervals
|
||||||
|
|
||||||
def draw_left_axis(self, bounds, y_ticks, y_tick_text):
|
def draw_left_axis(self, bounds, y_ticks, y_tick_text):
|
||||||
@ -247,7 +252,9 @@ class Graph(object):
|
|||||||
for stat, info in stats.items():
|
for stat, info in stats.items():
|
||||||
if len(info['values']) > 0:
|
if len(info['values']) > 0:
|
||||||
self.draw_value_poly(info['values'], info['color'], max_value, bounds)
|
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):
|
def draw_legend(self):
|
||||||
pass
|
pass
|
||||||
@ -271,10 +278,7 @@ class Graph(object):
|
|||||||
self.ctx.line_to(x, int(bottom - value * ratio))
|
self.ctx.line_to(x, int(bottom - value * ratio))
|
||||||
x -= step
|
x -= step
|
||||||
|
|
||||||
self.ctx.line_to(
|
self.ctx.line_to(int(right - (len(values) - 1) * step), bottom)
|
||||||
int(right - (len(values) - 1) * step),
|
|
||||||
bottom,
|
|
||||||
)
|
|
||||||
self.ctx.close_path()
|
self.ctx.close_path()
|
||||||
|
|
||||||
def draw_value_poly(self, values, color, max_value, bounds, fill=False):
|
def draw_value_poly(self, values, color, max_value, bounds, fill=False):
|
||||||
@ -305,7 +309,9 @@ class Graph(object):
|
|||||||
ascent = fe[0]
|
ascent = fe[0]
|
||||||
x_bearing = te[0]
|
x_bearing = te[0]
|
||||||
width = te[4]
|
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.set_source_rgba(*self.black)
|
||||||
self.ctx.show_text(text)
|
self.ctx.show_text(text)
|
||||||
|
|
||||||
|
|||||||
@ -43,9 +43,7 @@ DEFAULT_CONF = {
|
|||||||
'dht_torrents': str(gtk.gdk.Color('green')),
|
'dht_torrents': str(gtk.gdk.Color('green')),
|
||||||
'num_connections': str(gtk.gdk.Color('darkred')),
|
'num_connections': str(gtk.gdk.Color('darkred')),
|
||||||
},
|
},
|
||||||
'seeds_graph': {
|
'seeds_graph': {'num_peers': str(gtk.gdk.Color('blue'))},
|
||||||
'num_peers': str(gtk.gdk.Color('blue')),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +124,9 @@ class GraphsTab(Tab):
|
|||||||
def graph_expose(self, widget, event):
|
def graph_expose(self, widget, event):
|
||||||
context = self.graph_widget.window.cairo_create()
|
context = self.graph_widget.window.cairo_create()
|
||||||
# set a clip region
|
# 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()
|
context.clip()
|
||||||
self.graph.draw_to_context(
|
self.graph.draw_to_context(
|
||||||
context,
|
context,
|
||||||
@ -143,6 +143,7 @@ class GraphsTab(Tab):
|
|||||||
def _update_complete(result):
|
def _update_complete(result):
|
||||||
self.graph_widget.queue_draw()
|
self.graph_widget.queue_draw()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
d1.addCallback(_update_complete)
|
d1.addCallback(_update_complete)
|
||||||
return d1
|
return d1
|
||||||
|
|
||||||
@ -158,16 +159,17 @@ class GraphsTab(Tab):
|
|||||||
self.graph = Graph()
|
self.graph = Graph()
|
||||||
colors = self.colors['bandwidth_graph']
|
colors = self.colors['bandwidth_graph']
|
||||||
self.graph.add_stat(
|
self.graph.add_stat(
|
||||||
'download_rate', label='Download Rate',
|
'download_rate',
|
||||||
|
label='Download Rate',
|
||||||
color=gtk_to_graph_color(colors['download_rate']),
|
color=gtk_to_graph_color(colors['download_rate']),
|
||||||
)
|
)
|
||||||
self.graph.add_stat(
|
self.graph.add_stat(
|
||||||
'upload_rate', label='Upload Rate',
|
'upload_rate',
|
||||||
|
label='Upload Rate',
|
||||||
color=gtk_to_graph_color(colors['upload_rate']),
|
color=gtk_to_graph_color(colors['upload_rate']),
|
||||||
)
|
)
|
||||||
self.graph.set_left_axis(
|
self.graph.set_left_axis(
|
||||||
formatter=fspeed_shortform, min=10240,
|
formatter=fspeed_shortform, min=10240, formatter_scale=size_formatter_scale
|
||||||
formatter_scale=size_formatter_scale,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def select_connections_graph(self):
|
def select_connections_graph(self):
|
||||||
@ -177,9 +179,13 @@ class GraphsTab(Tab):
|
|||||||
self.graph = g
|
self.graph = g
|
||||||
colors = self.colors['connections_graph']
|
colors = self.colors['connections_graph']
|
||||||
g.add_stat('dht_nodes', color=gtk_to_graph_color(colors['dht_nodes']))
|
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('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)
|
g.set_left_axis(formatter=int_str, min=10)
|
||||||
|
|
||||||
def select_seeds_graph(self):
|
def select_seeds_graph(self):
|
||||||
@ -194,9 +200,7 @@ class GraphsTab(Tab):
|
|||||||
self.colors = colors
|
self.colors = colors
|
||||||
# Fake switch page to update the graph colors (HACKY)
|
# Fake switch page to update the graph colors (HACKY)
|
||||||
self._on_notebook_switch_page(
|
self._on_notebook_switch_page(
|
||||||
self.notebook,
|
self.notebook, None, self.notebook.get_current_page() # This is unused
|
||||||
None, # This is unused
|
|
||||||
self.notebook.get_current_page(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _on_intervals_changed(self, intervals):
|
def _on_intervals_changed(self, intervals):
|
||||||
@ -233,17 +237,24 @@ class GraphsTab(Tab):
|
|||||||
|
|
||||||
|
|
||||||
class GtkUI(GtkPluginBase):
|
class GtkUI(GtkPluginBase):
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
log.debug('Stats plugin enable called')
|
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 = gtk.Builder()
|
||||||
self.builder.add_from_file(get_resource('config.ui'))
|
self.builder.add_from_file(get_resource('config.ui'))
|
||||||
|
|
||||||
component.get('Preferences').add_page('Stats', self.builder.get_object('prefs_box'))
|
component.get('Preferences').add_page(
|
||||||
component.get('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs)
|
'Stats', self.builder.get_object('prefs_box')
|
||||||
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.on_show_prefs()
|
self.on_show_prefs()
|
||||||
|
|
||||||
self.graphs_tab = GraphsTab(self.config['colors'])
|
self.graphs_tab = GraphsTab(self.config['colors'])
|
||||||
@ -252,8 +263,12 @@ class GtkUI(GtkPluginBase):
|
|||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
component.get('Preferences').remove_page('Stats')
|
component.get('Preferences').remove_page('Stats')
|
||||||
component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs)
|
component.get('PluginManager').deregister_hook(
|
||||||
component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs)
|
'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())
|
self.torrent_details.remove_tab(self.graphs_tab.get_name())
|
||||||
|
|
||||||
def on_apply_prefs(self):
|
def on_apply_prefs(self):
|
||||||
|
|||||||
@ -27,7 +27,6 @@ def print_totals(totals):
|
|||||||
|
|
||||||
|
|
||||||
class StatsTestCase(BaseTestCase):
|
class StatsTestCase(BaseTestCase):
|
||||||
|
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
defer.setDebugging(True)
|
defer.setDebugging(True)
|
||||||
tests_common.set_tmp_config_dir()
|
tests_common.set_tmp_config_dir()
|
||||||
|
|||||||
@ -36,11 +36,9 @@ setup(
|
|||||||
url=__url__,
|
url=__url__,
|
||||||
license=__license__,
|
license=__license__,
|
||||||
long_description=__long_description__,
|
long_description=__long_description__,
|
||||||
|
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
namespace_packages=['deluge', 'deluge.plugins'],
|
namespace_packages=['deluge', 'deluge.plugins'],
|
||||||
package_data=__pkg_data__,
|
package_data=__pkg_data__,
|
||||||
|
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[deluge.plugin.core]
|
[deluge.plugin.core]
|
||||||
%s = deluge.plugins.%s:CorePlugin
|
%s = deluge.plugins.%s:CorePlugin
|
||||||
@ -48,5 +46,6 @@ setup(
|
|||||||
%s = deluge.plugins.%s:GtkUIPlugin
|
%s = deluge.plugins.%s:GtkUIPlugin
|
||||||
[deluge.plugin.web]
|
[deluge.plugin.web]
|
||||||
%s = deluge.plugins.%s:WebUIPlugin
|
%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):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .core import Core as _pluginCls
|
from .core import Core as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ class CorePlugin(PluginInitBase):
|
|||||||
class GtkUIPlugin(PluginInitBase):
|
class GtkUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .gtkui import GtkUI as _pluginCls
|
from .gtkui import GtkUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -34,5 +36,6 @@ class GtkUIPlugin(PluginInitBase):
|
|||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .webui import WebUI as _pluginCls
|
from .webui import WebUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
super(WebUIPlugin, self).__init__(plugin_name)
|
||||||
|
|||||||
@ -22,8 +22,7 @@ from deluge.plugins.pluginbase import CorePluginBase
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_PREFS = {
|
DEFAULT_PREFS = {}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Core(CorePluginBase):
|
class Core(CorePluginBase):
|
||||||
|
|||||||
@ -29,8 +29,10 @@ class GtkUI(GtkPluginBase):
|
|||||||
self.plugin = component.get('PluginManager')
|
self.plugin = component.get('PluginManager')
|
||||||
self.separator = self.plugin.add_toolbar_separator()
|
self.separator = self.plugin.add_toolbar_separator()
|
||||||
self.button = self.plugin.add_toolbar_button(
|
self.button = self.plugin.add_toolbar_button(
|
||||||
self._on_button_clicked, label='Pause Session',
|
self._on_button_clicked,
|
||||||
stock='gtk-media-pause', tooltip='Pause the session',
|
label='Pause Session',
|
||||||
|
stock='gtk-media-pause',
|
||||||
|
tooltip='Pause the session',
|
||||||
)
|
)
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
@ -47,6 +49,7 @@ class GtkUI(GtkPluginBase):
|
|||||||
self.button.set_label('Pause Session')
|
self.button.set_label('Pause Session')
|
||||||
self.button.set_tooltip_text('Pause the session')
|
self.button.set_tooltip_text('Pause the session')
|
||||||
self.button.set_stock_id('gtk-media-pause')
|
self.button.set_stock_id('gtk-media-pause')
|
||||||
|
|
||||||
self.core.get_status().addCallback(_on_get_status)
|
self.core.get_status().addCallback(_on_get_status)
|
||||||
|
|
||||||
def _on_button_clicked(self, widget):
|
def _on_button_clicked(self, widget):
|
||||||
|
|||||||
@ -33,11 +33,9 @@ setup(
|
|||||||
url=__url__,
|
url=__url__,
|
||||||
license=__license__,
|
license=__license__,
|
||||||
long_description=__long_description__ if __long_description__ else __description__,
|
long_description=__long_description__ if __long_description__ else __description__,
|
||||||
|
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
namespace_packages=['deluge', 'deluge.plugins'],
|
namespace_packages=['deluge', 'deluge.plugins'],
|
||||||
package_data=__pkg_data__,
|
package_data=__pkg_data__,
|
||||||
|
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[deluge.plugin.core]
|
[deluge.plugin.core]
|
||||||
%s = deluge.plugins.%s:CorePlugin
|
%s = deluge.plugins.%s:CorePlugin
|
||||||
@ -45,5 +43,6 @@ setup(
|
|||||||
%s = deluge.plugins.%s:GtkUIPlugin
|
%s = deluge.plugins.%s:GtkUIPlugin
|
||||||
[deluge.plugin.web]
|
[deluge.plugin.web]
|
||||||
%s = deluge.plugins.%s:WebUIPlugin
|
%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):
|
class CorePlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .core import Core as _pluginCls
|
from .core import Core as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(CorePlugin, self).__init__(plugin_name)
|
super(CorePlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ class CorePlugin(PluginInitBase):
|
|||||||
class GtkUIPlugin(PluginInitBase):
|
class GtkUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from .gtkui import GtkUI as _pluginCls
|
from .gtkui import GtkUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||||
|
|
||||||
@ -33,5 +35,6 @@ class GtkUIPlugin(PluginInitBase):
|
|||||||
class WebUIPlugin(PluginInitBase):
|
class WebUIPlugin(PluginInitBase):
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
from webui import WebUI as _pluginCls
|
from webui import WebUI as _pluginCls
|
||||||
|
|
||||||
self._plugin_cls = _pluginCls
|
self._plugin_cls = _pluginCls
|
||||||
super(WebUIPlugin, self).__init__(plugin_name)
|
super(WebUIPlugin, self).__init__(plugin_name)
|
||||||
|
|||||||
@ -30,11 +30,7 @@ except ImportError:
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_PREFS = {
|
DEFAULT_PREFS = {'enabled': False, 'ssl': False, 'port': 8112}
|
||||||
'enabled': False,
|
|
||||||
'ssl': False,
|
|
||||||
'port': 8112,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Core(CorePluginBase):
|
class Core(CorePluginBase):
|
||||||
|
|||||||
@ -31,16 +31,26 @@ class GtkUI(GtkPluginBase):
|
|||||||
self.builder = gtk.Builder()
|
self.builder = gtk.Builder()
|
||||||
self.builder.add_from_file(get_resource('config.ui'))
|
self.builder.add_from_file(get_resource('config.ui'))
|
||||||
|
|
||||||
component.get('Preferences').add_page(_('WebUi'), self.builder.get_object('prefs_box'))
|
component.get('Preferences').add_page(
|
||||||
component.get('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs)
|
_('WebUi'), self.builder.get_object('prefs_box')
|
||||||
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
|
||||||
|
)
|
||||||
client.webui.get_config().addCallback(self.cb_get_config)
|
client.webui.get_config().addCallback(self.cb_get_config)
|
||||||
client.webui.got_deluge_web().addCallback(self.cb_chk_deluge_web)
|
client.webui.got_deluge_web().addCallback(self.cb_chk_deluge_web)
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
component.get('Preferences').remove_page(_('WebUi'))
|
component.get('Preferences').remove_page(_('WebUi'))
|
||||||
component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs)
|
component.get('PluginManager').deregister_hook(
|
||||||
component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs)
|
'on_apply_prefs', self.on_apply_prefs
|
||||||
|
)
|
||||||
|
component.get('PluginManager').deregister_hook(
|
||||||
|
'on_show_prefs', self.on_show_prefs
|
||||||
|
)
|
||||||
|
|
||||||
def on_apply_prefs(self):
|
def on_apply_prefs(self):
|
||||||
if not self.have_web:
|
if not self.have_web:
|
||||||
@ -71,14 +81,18 @@ class GtkUI(GtkPluginBase):
|
|||||||
vbox = self.builder.get_object('prefs_box')
|
vbox = self.builder.get_object('prefs_box')
|
||||||
|
|
||||||
hbox = gtk.HBox()
|
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)
|
icon.set_padding(5, 5)
|
||||||
hbox.pack_start(icon, False, False)
|
hbox.pack_start(icon, False, False)
|
||||||
|
|
||||||
label = gtk.Label(_(
|
label = gtk.Label(
|
||||||
|
_(
|
||||||
'The Deluge web interface is not installed, '
|
'The Deluge web interface is not installed, '
|
||||||
'please install the\ninterface and try again',
|
'please install the\ninterface and try again'
|
||||||
))
|
)
|
||||||
|
)
|
||||||
label.set_alignment(0, 0.5)
|
label.set_alignment(0, 0.5)
|
||||||
label.set_padding(5, 5)
|
label.set_padding(5, 5)
|
||||||
hbox.pack_start(label)
|
hbox.pack_start(label)
|
||||||
|
|||||||
@ -21,7 +21,6 @@ common.disable_new_release_check()
|
|||||||
|
|
||||||
|
|
||||||
class WebUIPluginTestCase(BaseTestCase):
|
class WebUIPluginTestCase(BaseTestCase):
|
||||||
|
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
common.set_tmp_config_dir()
|
common.set_tmp_config_dir()
|
||||||
self.rpcserver = RPCServer(listen=False)
|
self.rpcserver = RPCServer(listen=False)
|
||||||
@ -29,10 +28,10 @@ class WebUIPluginTestCase(BaseTestCase):
|
|||||||
return component.start()
|
return component.start()
|
||||||
|
|
||||||
def tear_down(self):
|
def tear_down(self):
|
||||||
|
|
||||||
def on_shutdown(result):
|
def on_shutdown(result):
|
||||||
del self.rpcserver
|
del self.rpcserver
|
||||||
del self.core
|
del self.core
|
||||||
|
|
||||||
return component.shutdown().addCallback(on_shutdown)
|
return component.shutdown().addCallback(on_shutdown)
|
||||||
|
|
||||||
def test_enable_webui(self):
|
def test_enable_webui(self):
|
||||||
|
|||||||
@ -32,15 +32,14 @@ setup(
|
|||||||
url=__url__,
|
url=__url__,
|
||||||
license=__license__,
|
license=__license__,
|
||||||
long_description=__long_description__ if __long_description__ else __description__,
|
long_description=__long_description__ if __long_description__ else __description__,
|
||||||
|
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
namespace_packages=['deluge', 'deluge.plugins'],
|
namespace_packages=['deluge', 'deluge.plugins'],
|
||||||
package_data=__pkg_data__,
|
package_data=__pkg_data__,
|
||||||
|
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[deluge.plugin.core]
|
[deluge.plugin.core]
|
||||||
%s = deluge.plugins.%s:CorePlugin
|
%s = deluge.plugins.%s:CorePlugin
|
||||||
[deluge.plugin.gtkui]
|
[deluge.plugin.gtkui]
|
||||||
%s = deluge.plugins.%s:GtkUIPlugin
|
%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):
|
class CorePluginBase(PluginBase):
|
||||||
|
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
super(CorePluginBase, self).__init__('CorePlugin.' + plugin_name)
|
super(CorePluginBase, self).__init__('CorePlugin.' + plugin_name)
|
||||||
# Register RPC methods
|
# Register RPC methods
|
||||||
@ -49,7 +48,6 @@ class CorePluginBase(PluginBase):
|
|||||||
|
|
||||||
|
|
||||||
class GtkPluginBase(PluginBase):
|
class GtkPluginBase(PluginBase):
|
||||||
|
|
||||||
def __init__(self, plugin_name):
|
def __init__(self, plugin_name):
|
||||||
super(GtkPluginBase, self).__init__('GtkPlugin.' + plugin_name)
|
super(GtkPluginBase, self).__init__('GtkPlugin.' + plugin_name)
|
||||||
log.debug('GtkPlugin initialized..')
|
log.debug('GtkPlugin initialized..')
|
||||||
|
|||||||
@ -78,10 +78,14 @@ if py3:
|
|||||||
|
|
||||||
def int2byte(c):
|
def int2byte(c):
|
||||||
return bytes([c])
|
return bytes([c])
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
def int2byte(c):
|
def int2byte(c):
|
||||||
return chr(c)
|
return chr(c)
|
||||||
|
|
||||||
|
|
||||||
# Default number of bits for serialized floats, either 32 or 64 (also a parameter for dumps()).
|
# Default number of bits for serialized floats, either 32 or 64 (also a parameter for dumps()).
|
||||||
DEFAULT_FLOAT_BITS = 32
|
DEFAULT_FLOAT_BITS = 32
|
||||||
|
|
||||||
@ -253,7 +257,9 @@ def make_fixed_length_string_decoders():
|
|||||||
if _decode_utf8:
|
if _decode_utf8:
|
||||||
s = s.decode('utf8')
|
s = s.decode('utf8')
|
||||||
return (s, f + 1 + slen)
|
return (s, f + 1 + slen)
|
||||||
|
|
||||||
return f
|
return f
|
||||||
|
|
||||||
for i in range(STR_FIXED_COUNT):
|
for i in range(STR_FIXED_COUNT):
|
||||||
decode_func[int2byte(STR_FIXED_START + i)] = make_decoder(i)
|
decode_func[int2byte(STR_FIXED_START + i)] = make_decoder(i)
|
||||||
|
|
||||||
@ -269,7 +275,9 @@ def make_fixed_length_list_decoders():
|
|||||||
v, f = decode_func[x[f : f + 1]](x, f)
|
v, f = decode_func[x[f : f + 1]](x, f)
|
||||||
r.append(v)
|
r.append(v)
|
||||||
return (tuple(r), f)
|
return (tuple(r), f)
|
||||||
|
|
||||||
return f
|
return f
|
||||||
|
|
||||||
for i in range(LIST_FIXED_COUNT):
|
for i in range(LIST_FIXED_COUNT):
|
||||||
decode_func[int2byte(LIST_FIXED_START + i)] = make_decoder(i)
|
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 make_decoder(j):
|
||||||
def f(x, f):
|
def f(x, f):
|
||||||
return (j, f + 1)
|
return (j, f + 1)
|
||||||
|
|
||||||
return f
|
return f
|
||||||
|
|
||||||
for i in range(INT_POS_FIXED_COUNT):
|
for i in range(INT_POS_FIXED_COUNT):
|
||||||
decode_func[int2byte(INT_POS_FIXED_START + i)] = make_decoder(i)
|
decode_func[int2byte(INT_POS_FIXED_START + i)] = make_decoder(i)
|
||||||
for i in range(INT_NEG_FIXED_COUNT):
|
for i in range(INT_NEG_FIXED_COUNT):
|
||||||
@ -299,7 +309,9 @@ def make_fixed_length_dict_decoders():
|
|||||||
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)
|
r[k], f = decode_func[x[f : f + 1]](x, f)
|
||||||
return (r, f)
|
return (r, f)
|
||||||
|
|
||||||
return f
|
return f
|
||||||
|
|
||||||
for i in range(DICT_FIXED_COUNT):
|
for i in range(DICT_FIXED_COUNT):
|
||||||
decode_func[int2byte(DICT_FIXED_START + i)] = make_decoder(i)
|
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]
|
f3 = struct.unpack('!f', struct.pack('!f', -0.6))[0]
|
||||||
ld = (
|
ld = (
|
||||||
(
|
(
|
||||||
{b'a': 15, b'bb': f1, b'ccc': f2, b'': (f3, (), False, True, b'')}, (b'a', 10**20),
|
{b'a': 15, b'bb': f1, b'ccc': f2, b'': (f3, (), False, True, b'')},
|
||||||
tuple(range(-100000, 100000)), b'b' * 31, b'b' * 62, b'b' * 64, 2**30, 2**33, 2**62,
|
(b'a', 10 ** 20),
|
||||||
2**64, 2**30, 2**33, 2**62, 2**64, False, False, True, -1, 2, 0,
|
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
|
assert loads(dumps(ld)) == ld
|
||||||
d = dict(zip(range(-100000, 100000), range(-100000, 100000)))
|
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''})
|
ld = (d, {}, {5: 6}, {7: 7, True: 8}, {9: 10, 22: 39, 49: 50, 44: b''})
|
||||||
assert loads(dumps(ld)) == ld
|
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
|
assert loads(dumps(ld)) == ld
|
||||||
ld = tuple(dict(zip(range(n), range(n))) for n in range(100)) + (b'b',)
|
ld = tuple(dict(zip(range(n), range(n))) for n in range(100)) + (b'b',)
|
||||||
assert loads(dumps(ld)) == ld
|
assert loads(dumps(ld)) == ld
|
||||||
@ -468,6 +508,7 @@ def test():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import psyco
|
import psyco
|
||||||
|
|
||||||
psyco.bind(dumps)
|
psyco.bind(dumps)
|
||||||
psyco.bind(loads)
|
psyco.bind(loads)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@ -17,19 +17,35 @@ from datetime import datetime
|
|||||||
import deluge.common
|
import deluge.common
|
||||||
|
|
||||||
parser = ArgumentParser()
|
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(
|
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',
|
help='Author name,for the GPL header',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
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',
|
help='Author email,for the GPL header',
|
||||||
)
|
)
|
||||||
parser.add_argument('-u', '--url', metavar='<URL>', help='Homepage URL')
|
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()
|
options = parser.parse_args()
|
||||||
|
|
||||||
|
|||||||
@ -32,33 +32,48 @@ def is_float_digit(string):
|
|||||||
|
|
||||||
# set up command-line options
|
# set up command-line options
|
||||||
parser = OptionParser()
|
parser = OptionParser()
|
||||||
parser.add_option('--port', help='port for deluge backend host (default: 58846)', default='58846', dest='port')
|
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'--host', help='hostname of deluge backend to connect to (default: localhost)',
|
'--port',
|
||||||
default='localhost', dest='host',
|
help='port for deluge backend host (default: 58846)',
|
||||||
|
default='58846',
|
||||||
|
dest='port',
|
||||||
)
|
)
|
||||||
parser.add_option(
|
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',
|
help='sets the absolute maximum number of active torrents on the deluge backend',
|
||||||
)
|
)
|
||||||
parser.add_option(
|
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',
|
help='sets the maximum number of active downloading torrents on the deluge backend',
|
||||||
)
|
)
|
||||||
parser.add_option(
|
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',
|
help='sets the maximum number of active seeding torrents on the deluge backend',
|
||||||
)
|
)
|
||||||
parser.add_option(
|
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',
|
dest='max_download_speed',
|
||||||
)
|
)
|
||||||
parser.add_option(
|
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',
|
dest='max_upload_speed',
|
||||||
)
|
)
|
||||||
parser.add_option(
|
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',
|
dest='debug',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -79,7 +94,10 @@ if options.max_active_limit:
|
|||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
if options.max_active_downloading:
|
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)
|
settings['max_active_downloading'] = int(options.max_active_downloading)
|
||||||
else:
|
else:
|
||||||
sys.stderr.write('ERROR: Invalid max_active_downloading parameter!\n')
|
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 options.max_download_speed:
|
||||||
if is_float_digit(options.max_download_speed) and (
|
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)
|
settings['max_download_speed'] = float(options.max_download_speed)
|
||||||
else:
|
else:
|
||||||
@ -103,7 +122,8 @@ if options.max_download_speed:
|
|||||||
|
|
||||||
if options.max_upload_speed:
|
if options.max_upload_speed:
|
||||||
if is_float_digit(options.max_upload_speed) and (
|
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)
|
settings['max_upload_speed'] = float(options.max_upload_speed)
|
||||||
else:
|
else:
|
||||||
@ -114,6 +134,7 @@ if options.max_upload_speed:
|
|||||||
if settings:
|
if settings:
|
||||||
# create connection to daemon
|
# create connection to daemon
|
||||||
from deluge.ui.client import sclient as client
|
from deluge.ui.client import sclient as client
|
||||||
|
|
||||||
client.set_core_uri('http://' + options.host + ':' + options.port)
|
client.set_core_uri('http://' + options.host + ':' + options.port)
|
||||||
|
|
||||||
# commit configurations changes
|
# commit configurations changes
|
||||||
|
|||||||
@ -22,13 +22,14 @@ class BaseTestCase(unittest.TestCase):
|
|||||||
have finished.
|
have finished.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self): # NOQA: N803
|
def setUp(self): # NOQA: N803
|
||||||
|
|
||||||
if len(component._ComponentRegistry.components) != 0:
|
if len(component._ComponentRegistry.components) != 0:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'The component._ComponentRegistry.components is not empty on test setup.\n'
|
'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' %
|
'This is probably caused by another test that did not clean up after finishing!: %s'
|
||||||
component._ComponentRegistry.components,
|
% component._ComponentRegistry.components
|
||||||
)
|
)
|
||||||
d = maybeDeferred(self.set_up)
|
d = maybeDeferred(self.set_up)
|
||||||
|
|
||||||
|
|||||||
@ -60,7 +60,6 @@ def todo_test(caller):
|
|||||||
|
|
||||||
|
|
||||||
def add_watchdog(deferred, timeout=0.05, message=None):
|
def add_watchdog(deferred, timeout=0.05, message=None):
|
||||||
|
|
||||||
def callback(value):
|
def callback(value):
|
||||||
if not watchdog.called and not watchdog.cancelled:
|
if not watchdog.called and not watchdog.cancelled:
|
||||||
watchdog.cancel()
|
watchdog.cancel()
|
||||||
@ -102,8 +101,9 @@ class ReactorOverride(object):
|
|||||||
|
|
||||||
|
|
||||||
class ProcessOutputHandler(protocol.ProcessProtocol):
|
class ProcessOutputHandler(protocol.ProcessProtocol):
|
||||||
|
def __init__(
|
||||||
def __init__(self, script, callbacks, logfile=None, print_stdout=True, print_stderr=True):
|
self, script, callbacks, logfile=None, print_stdout=True, print_stderr=True
|
||||||
|
):
|
||||||
"""Executes a script and handle the process' output to stdout and stderr.
|
"""Executes a script and handle the process' output to stdout and stderr.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -210,8 +210,14 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
|
|||||||
|
|
||||||
|
|
||||||
def start_core(
|
def start_core(
|
||||||
listen_port=58846, logfile=None, timeout=10, timeout_msg=None,
|
listen_port=58846,
|
||||||
custom_script='', print_stdout=True, print_stderr=True, extra_callbacks=None,
|
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.
|
"""Start the deluge core as a daemon.
|
||||||
|
|
||||||
@ -252,7 +258,11 @@ try:
|
|||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
sys.stderr.write('Exception raised:\\n %%s' %% traceback.format_exc())
|
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 = []
|
callbacks = []
|
||||||
default_core_cb = {'deferred': Deferred(), 'types': 'stdout'}
|
default_core_cb = {'deferred': Deferred(), 'types': 'stdout'}
|
||||||
@ -263,15 +273,20 @@ except Exception:
|
|||||||
default_core_cb['triggers'] = [
|
default_core_cb['triggers'] = [
|
||||||
{'expr': 'Finished loading ', 'value': lambda reader, data, data_all: reader},
|
{'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(
|
'value': lambda reader, data, data_all: CannotListenError(
|
||||||
'localhost', listen_port,
|
'localhost',
|
||||||
|
listen_port,
|
||||||
'Could not start deluge test client!\n%s' % data,
|
'Could not start deluge test client!\n%s' % data,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'expr': 'Traceback', 'type': 'errback',
|
'expr': 'Traceback',
|
||||||
'value': lambda reader, data, data_all: DelugeError('Traceback found when starting daemon:\n%s' % data),
|
'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:
|
if extra_callbacks:
|
||||||
callbacks.extend(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
|
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.
|
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__)))
|
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
|
# Add timeouts to deferreds
|
||||||
for c in callbacks:
|
for c in callbacks:
|
||||||
if 'timeout' in c:
|
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)
|
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
|
return process_protocol
|
||||||
|
|||||||
@ -26,6 +26,7 @@ class WebServerTestBase(BaseTestCase, DaemonBase):
|
|||||||
Base class for tests that need a running webapi
|
Base class for tests that need a running webapi
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
self.host_id = None
|
self.host_id = None
|
||||||
deluge.ui.web.server.reactor = ReactorOverride()
|
deluge.ui.web.server.reactor = ReactorOverride()
|
||||||
@ -60,16 +61,16 @@ class WebServerMockBase(object):
|
|||||||
Class with utility functions for mocking with tests using the webserver
|
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):
|
def check_request(request, method=None, level=None):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.patch(auth, 'check_request', check_request)
|
self.patch(auth, 'check_request', check_request)
|
||||||
|
|
||||||
def mock_compress_body(self):
|
def mock_compress_body(self):
|
||||||
|
|
||||||
def compress(contents, request):
|
def compress(contents, request):
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
# Patch compress to avoid having to decompress output with zlib
|
# Patch compress to avoid having to decompress output with zlib
|
||||||
self.patch(deluge.ui.web.json_api, 'compress', compress)
|
self.patch(deluge.ui.web.json_api, 'compress', compress)
|
||||||
|
|||||||
@ -41,8 +41,15 @@ class DaemonBase(object):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def start_core(
|
def start_core(
|
||||||
self, arg, custom_script='', logfile='', print_stdout=True, print_stderr=True, timeout=5,
|
self,
|
||||||
port_range=10, extra_callbacks=None,
|
arg,
|
||||||
|
custom_script='',
|
||||||
|
logfile='',
|
||||||
|
print_stdout=True,
|
||||||
|
print_stderr=True,
|
||||||
|
timeout=5,
|
||||||
|
port_range=10,
|
||||||
|
extra_callbacks=None,
|
||||||
):
|
):
|
||||||
if logfile == '':
|
if logfile == '':
|
||||||
logfile = 'daemon_%s.log' % self.id()
|
logfile = 'daemon_%s.log' % self.id()
|
||||||
@ -59,8 +66,10 @@ class DaemonBase(object):
|
|||||||
for dummy in range(port_range):
|
for dummy in range(port_range):
|
||||||
try:
|
try:
|
||||||
d, self.core = common.start_core(
|
d, self.core = common.start_core(
|
||||||
listen_port=self.listen_port, logfile=logfile,
|
listen_port=self.listen_port,
|
||||||
timeout=timeout, timeout_msg='Timeout!',
|
logfile=logfile,
|
||||||
|
timeout=timeout,
|
||||||
|
timeout_msg='Timeout!',
|
||||||
custom_script=custom_script,
|
custom_script=custom_script,
|
||||||
print_stdout=print_stdout,
|
print_stdout=print_stdout,
|
||||||
print_stderr=print_stderr,
|
print_stderr=print_stderr,
|
||||||
|
|||||||
@ -14,7 +14,6 @@ from .basetest import BaseTestCase
|
|||||||
|
|
||||||
|
|
||||||
class AlertManagerTestCase(BaseTestCase):
|
class AlertManagerTestCase(BaseTestCase):
|
||||||
|
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
self.core = Core()
|
self.core = Core()
|
||||||
self.core.config.config['lsd'] = False
|
self.core.config.config['lsd'] = False
|
||||||
|
|||||||
@ -24,7 +24,4 @@ class AuthManagerTestCase(BaseTestCase):
|
|||||||
return component.shutdown()
|
return component.shutdown()
|
||||||
|
|
||||||
def test_authorize(self):
|
def test_authorize(self):
|
||||||
self.assertEqual(
|
self.assertEqual(self.auth.authorize(*get_localhost_auth()), AUTH_LEVEL_ADMIN)
|
||||||
self.auth.authorize(*get_localhost_auth()),
|
|
||||||
AUTH_LEVEL_ADMIN,
|
|
||||||
)
|
|
||||||
|
|||||||
@ -14,7 +14,6 @@ from . import common
|
|||||||
|
|
||||||
|
|
||||||
class BencodeTestCase(unittest.TestCase):
|
class BencodeTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def test_bencode_unicode_metainfo(self):
|
def test_bencode_unicode_metainfo(self):
|
||||||
filename = common.get_test_data_file('test.torrent')
|
filename = common.get_test_data_file('test.torrent')
|
||||||
with open(filename, 'rb') as _file:
|
with open(filename, 'rb') as _file:
|
||||||
|
|||||||
@ -35,9 +35,12 @@ class NoVersionSendingDaemonSSLProxy(DaemonSSLProxy):
|
|||||||
|
|
||||||
|
|
||||||
class NoVersionSendingClient(Client):
|
class NoVersionSendingClient(Client):
|
||||||
|
|
||||||
def connect(
|
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,
|
skip_authentication=False,
|
||||||
):
|
):
|
||||||
self._daemon_proxy = NoVersionSendingDaemonSSLProxy()
|
self._daemon_proxy = NoVersionSendingDaemonSSLProxy()
|
||||||
@ -104,7 +107,9 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
|||||||
|
|
||||||
def test_connect_localclient(self):
|
def test_connect_localclient(self):
|
||||||
username, password = get_localhost_auth()
|
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):
|
def on_connect(result):
|
||||||
self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
|
self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
|
||||||
@ -116,13 +121,12 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
|||||||
|
|
||||||
def test_connect_bad_password(self):
|
def test_connect_bad_password(self):
|
||||||
username, password = get_localhost_auth()
|
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):
|
def on_failure(failure):
|
||||||
self.assertEqual(
|
self.assertEqual(failure.trap(error.BadLoginError), error.BadLoginError)
|
||||||
failure.trap(error.BadLoginError),
|
|
||||||
error.BadLoginError,
|
|
||||||
)
|
|
||||||
self.assertEqual(failure.value.message, 'Password does not match')
|
self.assertEqual(failure.value.message, 'Password does not match')
|
||||||
self.addCleanup(client.disconnect)
|
self.addCleanup(client.disconnect)
|
||||||
|
|
||||||
@ -134,10 +138,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
|||||||
d = client.connect('localhost', self.listen_port, username='invalid-user')
|
d = client.connect('localhost', self.listen_port, username='invalid-user')
|
||||||
|
|
||||||
def on_failure(failure):
|
def on_failure(failure):
|
||||||
self.assertEqual(
|
self.assertEqual(failure.trap(error.BadLoginError), error.BadLoginError)
|
||||||
failure.trap(error.BadLoginError),
|
|
||||||
error.BadLoginError,
|
|
||||||
)
|
|
||||||
self.assertEqual(failure.value.message, 'Username does not exist')
|
self.assertEqual(failure.value.message, 'Username does not exist')
|
||||||
self.addCleanup(client.disconnect)
|
self.addCleanup(client.disconnect)
|
||||||
|
|
||||||
@ -150,8 +151,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
|||||||
|
|
||||||
def on_failure(failure):
|
def on_failure(failure):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
failure.trap(error.AuthenticationRequired),
|
failure.trap(error.AuthenticationRequired), error.AuthenticationRequired
|
||||||
error.AuthenticationRequired,
|
|
||||||
)
|
)
|
||||||
self.assertEqual(failure.value.username, username)
|
self.assertEqual(failure.value.username, username)
|
||||||
self.addCleanup(client.disconnect)
|
self.addCleanup(client.disconnect)
|
||||||
@ -162,10 +162,14 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
|||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_connect_with_password(self):
|
def test_connect_with_password(self):
|
||||||
username, password = get_localhost_auth()
|
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.core.create_account('testuser', 'testpw', 'DEFAULT')
|
||||||
yield client.disconnect()
|
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)
|
self.assertEqual(ret, AUTH_LEVEL_NORMAL)
|
||||||
yield
|
yield
|
||||||
|
|
||||||
@ -175,8 +179,11 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
|||||||
d = client.core.invalid_method()
|
d = client.core.invalid_method()
|
||||||
|
|
||||||
def on_failure(failure):
|
def on_failure(failure):
|
||||||
self.assertEqual(failure.trap(error.WrappedException), error.WrappedException)
|
self.assertEqual(
|
||||||
|
failure.trap(error.WrappedException), error.WrappedException
|
||||||
|
)
|
||||||
self.addCleanup(client.disconnect)
|
self.addCleanup(client.disconnect)
|
||||||
|
|
||||||
d.addCallbacks(self.fail, on_failure)
|
d.addCallbacks(self.fail, on_failure)
|
||||||
yield d
|
yield d
|
||||||
|
|
||||||
@ -184,13 +191,12 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
|||||||
username, password = get_localhost_auth()
|
username, password = get_localhost_auth()
|
||||||
no_version_sending_client = NoVersionSendingClient()
|
no_version_sending_client = NoVersionSendingClient()
|
||||||
d = no_version_sending_client.connect(
|
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):
|
def on_failure(failure):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
failure.trap(error.IncompatibleClient),
|
failure.trap(error.IncompatibleClient), error.IncompatibleClient
|
||||||
error.IncompatibleClient,
|
|
||||||
)
|
)
|
||||||
self.addCleanup(no_version_sending_client.disconnect)
|
self.addCleanup(no_version_sending_client.disconnect)
|
||||||
|
|
||||||
|
|||||||
@ -12,8 +12,24 @@ import tarfile
|
|||||||
|
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
|
|
||||||
from deluge.common import (VersionSplit, archive_files, fdate, fpcnt, fpeer, fsize, fspeed, ftime, get_path_size,
|
from deluge.common import (
|
||||||
is_infohash, is_ip, is_ipv4, is_ipv6, is_magnet, is_url, windows_check)
|
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 deluge.ui.translations_util import setup_translations
|
||||||
|
|
||||||
from .common import get_test_data_file, set_tmp_config_dir
|
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'))
|
self.assertFalse(is_url('file://test.torrent'))
|
||||||
|
|
||||||
def test_is_magnet(self):
|
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))
|
self.assertFalse(is_magnet(None))
|
||||||
|
|
||||||
def test_is_infohash(self):
|
def test_is_infohash(self):
|
||||||
@ -121,6 +139,7 @@ class CommonTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def test_parse_human_size(self):
|
def test_parse_human_size(self):
|
||||||
from deluge.common import parse_human_size
|
from deluge.common import parse_human_size
|
||||||
|
|
||||||
sizes = [
|
sizes = [
|
||||||
('1', 1),
|
('1', 1),
|
||||||
('10 bytes', 10),
|
('10 bytes', 10),
|
||||||
@ -137,7 +156,9 @@ class CommonTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
for human_size, byte_size in sizes:
|
for human_size, byte_size in sizes:
|
||||||
parsed = parse_human_size(human_size)
|
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):
|
def test_archive_files(self):
|
||||||
arc_filelist = [
|
arc_filelist = [
|
||||||
@ -149,4 +170,6 @@ class CommonTestCase(unittest.TestCase):
|
|||||||
with tarfile.open(arc_filepath, 'r') as tar:
|
with tarfile.open(arc_filepath, 'r') as tar:
|
||||||
for tar_info in tar:
|
for tar_info in tar:
|
||||||
self.assertTrue(tar_info.isfile())
|
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):
|
class ComponentTester(component.Component):
|
||||||
|
|
||||||
def __init__(self, name, depend=None):
|
def __init__(self, name, depend=None):
|
||||||
component.Component.__init__(self, name, depend=depend)
|
component.Component.__init__(self, name, depend=depend)
|
||||||
self.start_count = 0
|
self.start_count = 0
|
||||||
@ -30,20 +29,21 @@ class ComponentTester(component.Component):
|
|||||||
|
|
||||||
|
|
||||||
class ComponentTesterDelayStart(ComponentTester):
|
class ComponentTesterDelayStart(ComponentTester):
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
def do_sleep():
|
def do_sleep():
|
||||||
import time
|
import time
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
d = threads.deferToThread(do_sleep)
|
d = threads.deferToThread(do_sleep)
|
||||||
|
|
||||||
def on_done(result):
|
def on_done(result):
|
||||||
self.start_count += 1
|
self.start_count += 1
|
||||||
|
|
||||||
return d.addCallback(on_done)
|
return d.addCallback(on_done)
|
||||||
|
|
||||||
|
|
||||||
class ComponentTesterUpdate(component.Component):
|
class ComponentTesterUpdate(component.Component):
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
component.Component.__init__(self, name)
|
component.Component.__init__(self, name)
|
||||||
self.counter = 0
|
self.counter = 0
|
||||||
@ -58,7 +58,6 @@ class ComponentTesterUpdate(component.Component):
|
|||||||
|
|
||||||
|
|
||||||
class ComponentTesterShutdown(component.Component):
|
class ComponentTesterShutdown(component.Component):
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
component.Component.__init__(self, name)
|
component.Component.__init__(self, name)
|
||||||
self.shutdowned = False
|
self.shutdowned = False
|
||||||
@ -72,7 +71,6 @@ class ComponentTesterShutdown(component.Component):
|
|||||||
|
|
||||||
|
|
||||||
class ComponentTestClass(BaseTestCase):
|
class ComponentTestClass(BaseTestCase):
|
||||||
|
|
||||||
def tear_down(self):
|
def tear_down(self):
|
||||||
return component.shutdown()
|
return component.shutdown()
|
||||||
|
|
||||||
@ -98,7 +96,9 @@ class ComponentTestClass(BaseTestCase):
|
|||||||
self.assertEqual(c2._component_state, 'Started')
|
self.assertEqual(c2._component_state, 'Started')
|
||||||
self.assertEqual(c1.start_count, 1)
|
self.assertEqual(c1.start_count, 1)
|
||||||
self.assertEqual(c2.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')
|
c1 = ComponentTester('test_start_depends_c1')
|
||||||
c2 = ComponentTester('test_start_depends_c2', depend=['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):
|
def start_with_depends(self):
|
||||||
c1 = ComponentTesterDelayStart('test_start_all_c1')
|
c1 = ComponentTesterDelayStart('test_start_all_c1')
|
||||||
c2 = ComponentTester('test_start_all_c2', depend=['test_start_all_c4'])
|
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'])
|
c4 = ComponentTester('test_start_all_c4', depend=['test_start_all_c3'])
|
||||||
c5 = ComponentTester('test_start_all_c5')
|
c5 = ComponentTester('test_start_all_c5')
|
||||||
|
|
||||||
@ -214,8 +216,12 @@ class ComponentTestClass(BaseTestCase):
|
|||||||
try:
|
try:
|
||||||
result = self.failureResultOf(test_comp._component_start())
|
result = self.failureResultOf(test_comp._component_start())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise SkipTest('This test requires trial failureResultOf() in Twisted version >= 13')
|
raise SkipTest(
|
||||||
self.assertEqual(result.check(component.ComponentException), component.ComponentException)
|
'This test requires trial failureResultOf() in Twisted version >= 13'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
result.check(component.ComponentException), component.ComponentException
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_start_paused_error(self):
|
def test_start_paused_error(self):
|
||||||
@ -231,14 +237,17 @@ class ComponentTestClass(BaseTestCase):
|
|||||||
result = yield component.start()
|
result = yield component.start()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[(result[0][0], result[0][1].value)],
|
[(result[0][0], result[0][1].value)],
|
||||||
[(
|
[
|
||||||
|
(
|
||||||
defer.FAILURE,
|
defer.FAILURE,
|
||||||
component.ComponentException(
|
component.ComponentException(
|
||||||
'Trying to start component "%s" but it is '
|
'Trying to start component "%s" but it is '
|
||||||
'not in a stopped state. Current state: %s' %
|
'not in a stopped state. Current state: %s'
|
||||||
('test_pause_c1', 'Paused'), '',
|
% ('test_pause_c1', 'Paused'),
|
||||||
|
'',
|
||||||
),
|
),
|
||||||
)],
|
)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_shutdown(self):
|
def test_shutdown(self):
|
||||||
|
|||||||
@ -19,7 +19,13 @@ from deluge.config import Config
|
|||||||
|
|
||||||
from .common import set_tmp_config_dir
|
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):
|
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
|
# Test opening a previous 1.2 config file of just a json object
|
||||||
import json
|
import json
|
||||||
|
|
||||||
with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file:
|
with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file:
|
||||||
json.dump(DEFAULTS, getwriter('utf8')(_file), **JSON_FORMAT)
|
json.dump(DEFAULTS, getwriter('utf8')(_file), **JSON_FORMAT)
|
||||||
|
|
||||||
|
|||||||
@ -35,22 +35,24 @@ common.disable_new_release_check()
|
|||||||
|
|
||||||
|
|
||||||
class CookieResource(Resource):
|
class CookieResource(Resource):
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
if request.getCookie(b'password') != b'deluge':
|
if request.getCookie(b'password') != b'deluge':
|
||||||
request.setResponseCode(FORBIDDEN)
|
request.setResponseCode(FORBIDDEN)
|
||||||
return
|
return
|
||||||
|
|
||||||
request.setHeader(b'Content-Type', b'application/x-bittorrent')
|
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()
|
data = _file.read()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class PartialDownload(Resource):
|
class PartialDownload(Resource):
|
||||||
|
|
||||||
def render(self, request):
|
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()
|
data = _file.read()
|
||||||
request.setHeader(b'Content-Type', len(data))
|
request.setHeader(b'Content-Type', len(data))
|
||||||
request.setHeader(b'Content-Type', b'application/x-bittorrent')
|
request.setHeader(b'Content-Type', b'application/x-bittorrent')
|
||||||
@ -60,7 +62,6 @@ class PartialDownload(Resource):
|
|||||||
|
|
||||||
|
|
||||||
class RedirectResource(Resource):
|
class RedirectResource(Resource):
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
request.redirect(b'/ubuntu-9.04-desktop-i386.iso.torrent')
|
request.redirect(b'/ubuntu-9.04-desktop-i386.iso.torrent')
|
||||||
return b''
|
return b''
|
||||||
@ -82,7 +83,6 @@ class TopLevelResource(Resource):
|
|||||||
|
|
||||||
|
|
||||||
class CoreTestCase(BaseTestCase):
|
class CoreTestCase(BaseTestCase):
|
||||||
|
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
common.set_tmp_config_dir()
|
common.set_tmp_config_dir()
|
||||||
self.rpcserver = RPCServer(listen=False)
|
self.rpcserver = RPCServer(listen=False)
|
||||||
@ -109,7 +109,6 @@ class CoreTestCase(BaseTestCase):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def tear_down(self):
|
def tear_down(self):
|
||||||
|
|
||||||
def on_shutdown(result):
|
def on_shutdown(result):
|
||||||
del self.rpcserver
|
del self.rpcserver
|
||||||
del self.core
|
del self.core
|
||||||
@ -120,11 +119,7 @@ class CoreTestCase(BaseTestCase):
|
|||||||
def add_torrent(self, filename, paused=False):
|
def add_torrent(self, filename, paused=False):
|
||||||
if not paused:
|
if not paused:
|
||||||
# Patch libtorrent flags starting torrents paused
|
# Patch libtorrent flags starting torrents paused
|
||||||
self.patch(
|
self.patch(deluge.core.torrentmanager, 'LT_DEFAULT_ADD_TORRENT_FLAGS', 592)
|
||||||
deluge.core.torrentmanager,
|
|
||||||
'LT_DEFAULT_ADD_TORRENT_FLAGS',
|
|
||||||
592,
|
|
||||||
)
|
|
||||||
options = {'add_paused': paused, 'auto_managed': False}
|
options = {'add_paused': paused, 'auto_managed': False}
|
||||||
filepath = common.get_test_data_file(filename)
|
filepath = common.get_test_data_file(filename)
|
||||||
with open(filepath, 'rb') as _file:
|
with open(filepath, 'rb') as _file:
|
||||||
@ -169,6 +164,7 @@ class CoreTestCase(BaseTestCase):
|
|||||||
|
|
||||||
# Get the info hash from the test.torrent
|
# Get the info hash from the test.torrent
|
||||||
from deluge.bencode import bdecode, bencode
|
from deluge.bencode import bdecode, bencode
|
||||||
|
|
||||||
with open(filename, 'rb') as _file:
|
with open(filename, 'rb') as _file:
|
||||||
info_hash = sha(bencode(bdecode(_file.read())[b'info'])).hexdigest()
|
info_hash = sha(bencode(bdecode(_file.read())[b'info'])).hexdigest()
|
||||||
self.assertEqual(torrent_id, info_hash)
|
self.assertEqual(torrent_id, info_hash)
|
||||||
@ -176,11 +172,16 @@ class CoreTestCase(BaseTestCase):
|
|||||||
def test_add_torrent_file_invalid_filedump(self):
|
def test_add_torrent_file_invalid_filedump(self):
|
||||||
options = {}
|
options = {}
|
||||||
filename = common.get_test_data_file('test.torrent')
|
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
|
@defer.inlineCallbacks
|
||||||
def test_add_torrent_url(self):
|
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 = {}
|
options = {}
|
||||||
info_hash = '60d5d82328b4547511fdeac9bf4d0112daa0ce00'
|
info_hash = '60d5d82328b4547511fdeac9bf4d0112daa0ce00'
|
||||||
|
|
||||||
@ -340,7 +341,12 @@ class CoreTestCase(BaseTestCase):
|
|||||||
self.assertEqual(len(self.core.get_session_state()), 0)
|
self.assertEqual(len(self.core.get_session_state()), 0)
|
||||||
|
|
||||||
def test_remove_torrent_invalid(self):
|
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
|
@defer.inlineCallbacks
|
||||||
def test_remove_torrents(self):
|
def test_remove_torrents(self):
|
||||||
@ -353,15 +359,19 @@ class CoreTestCase(BaseTestCase):
|
|||||||
filename2 = common.get_test_data_file('unicode_filenames.torrent')
|
filename2 = common.get_test_data_file('unicode_filenames.torrent')
|
||||||
with open(filename2, 'rb') as _file:
|
with open(filename2, 'rb') as _file:
|
||||||
filedump = b64encode(_file.read())
|
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)
|
d = self.core.remove_torrents([torrent_id, torrent_id2], True)
|
||||||
|
|
||||||
def test_ret(val):
|
def test_ret(val):
|
||||||
self.assertTrue(val == [])
|
self.assertTrue(val == [])
|
||||||
|
|
||||||
d.addCallback(test_ret)
|
d.addCallback(test_ret)
|
||||||
|
|
||||||
def test_session_state(val):
|
def test_session_state(val):
|
||||||
self.assertEqual(len(self.core.get_session_state()), 0)
|
self.assertEqual(len(self.core.get_session_state()), 0)
|
||||||
|
|
||||||
d.addCallback(test_session_state)
|
d.addCallback(test_session_state)
|
||||||
yield d
|
yield d
|
||||||
|
|
||||||
@ -371,15 +381,24 @@ class CoreTestCase(BaseTestCase):
|
|||||||
filename = common.get_test_data_file('test.torrent')
|
filename = common.get_test_data_file('test.torrent')
|
||||||
with open(filename, 'rb') as _file:
|
with open(filename, 'rb') as _file:
|
||||||
filedump = b64encode(_file.read())
|
filedump = b64encode(_file.read())
|
||||||
torrent_id = yield self.core.add_torrent_file_async(filename, filedump, options)
|
torrent_id = yield self.core.add_torrent_file_async(
|
||||||
val = yield self.core.remove_torrents(['invalidid1', 'invalidid2', torrent_id], False)
|
filename, filedump, options
|
||||||
|
)
|
||||||
|
val = yield self.core.remove_torrents(
|
||||||
|
['invalidid1', 'invalidid2', torrent_id], False
|
||||||
|
)
|
||||||
self.assertEqual(len(val), 2)
|
self.assertEqual(len(val), 2)
|
||||||
self.assertEqual(val[0], ('invalidid1', 'torrent_id invalidid1 not in session.'))
|
self.assertEqual(
|
||||||
self.assertEqual(val[1], ('invalidid2', 'torrent_id invalidid2 not in session.'))
|
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):
|
def test_get_session_status(self):
|
||||||
status = self.core.get_session_status(
|
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.assertIsInstance(status, dict)
|
||||||
self.assertEqual(status['net.recv_tracker_bytes'], 0)
|
self.assertEqual(status['net.recv_tracker_bytes'], 0)
|
||||||
self.assertEqual(status['net.sent_tracker_bytes'], 0)
|
self.assertEqual(status['net.sent_tracker_bytes'], 0)
|
||||||
@ -402,8 +421,7 @@ class CoreTestCase(BaseTestCase):
|
|||||||
self.assertEqual(status['upload_rate'], 0)
|
self.assertEqual(status['upload_rate'], 0)
|
||||||
|
|
||||||
def test_get_session_status_ratio(self):
|
def test_get_session_status_ratio(self):
|
||||||
status = self.core.get_session_status([
|
status = self.core.get_session_status(['write_hit_ratio', 'read_hit_ratio'])
|
||||||
'write_hit_ratio', 'read_hit_ratio'])
|
|
||||||
self.assertIsInstance(status, dict)
|
self.assertIsInstance(status, dict)
|
||||||
self.assertEqual(status['write_hit_ratio'], 0.0)
|
self.assertEqual(status['write_hit_ratio'], 0.0)
|
||||||
self.assertEqual(status['read_hit_ratio'], 0.0)
|
self.assertEqual(status['read_hit_ratio'], 0.0)
|
||||||
@ -438,14 +456,23 @@ class CoreTestCase(BaseTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
for key in pathlist:
|
for key in pathlist:
|
||||||
self.assertEqual(deluge.core.torrent.sanitize_filepath(key, folder=False), pathlist[key])
|
self.assertEqual(
|
||||||
self.assertEqual(deluge.core.torrent.sanitize_filepath(key, folder=True), pathlist[key] + '/')
|
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):
|
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.assertEqual(self.core.get_config_value('foobar'), None)
|
||||||
self.core.set_config({'abc': 'def', 'foo': 10, 'foobar': 'barfoo'})
|
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')
|
self.assertEqual(self.core.get_config_value('foobar'), 'barfoo')
|
||||||
|
|
||||||
def test_read_only_config_keys(self):
|
def test_read_only_config_keys(self):
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class ErrorTestCase(unittest.TestCase):
|
|||||||
e = deluge.error.DelugeError(msg)
|
e = deluge.error.DelugeError(msg)
|
||||||
self.assertEqual(str(e), msg)
|
self.assertEqual(str(e), msg)
|
||||||
from twisted.internet.defer import DebugInfo
|
from twisted.internet.defer import DebugInfo
|
||||||
|
|
||||||
del DebugInfo.__del__ # Hides all errors
|
del DebugInfo.__del__ # Hides all errors
|
||||||
self.assertEqual(e._args, (msg,))
|
self.assertEqual(e._args, (msg,))
|
||||||
self.assertEqual(e._kwargs, {})
|
self.assertEqual(e._kwargs, {})
|
||||||
@ -32,15 +33,19 @@ class ErrorTestCase(unittest.TestCase):
|
|||||||
version = '1.3.6'
|
version = '1.3.6'
|
||||||
e = deluge.error.IncompatibleClient(version)
|
e = deluge.error.IncompatibleClient(version)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
str(e), 'Your deluge client is not compatible with the daemon. \
|
str(e),
|
||||||
Please upgrade your client to %s' % version,
|
'Your deluge client is not compatible with the daemon. \
|
||||||
|
Please upgrade your client to %s'
|
||||||
|
% version,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_not_authorized_error(self):
|
def test_not_authorized_error(self):
|
||||||
current_level = 5
|
current_level = 5
|
||||||
required_level = 10
|
required_level = 10
|
||||||
e = deluge.error.NotAuthorizedError(current_level, required_level)
|
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):
|
def test_bad_login_error(self):
|
||||||
message = 'Login failed'
|
message = 'Login failed'
|
||||||
|
|||||||
@ -28,6 +28,7 @@ try:
|
|||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
libs_available = False
|
libs_available = False
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
setup_translations()
|
setup_translations()
|
||||||
@ -35,7 +36,6 @@ setup_translations()
|
|||||||
|
|
||||||
@pytest.mark.gtkui
|
@pytest.mark.gtkui
|
||||||
class FilesTabTestCase(BaseTestCase):
|
class FilesTabTestCase(BaseTestCase):
|
||||||
|
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
if libs_available is False:
|
if libs_available is False:
|
||||||
raise unittest.SkipTest('GTKUI dependencies not available')
|
raise unittest.SkipTest('GTKUI dependencies not available')
|
||||||
@ -70,7 +70,6 @@ class FilesTabTestCase(BaseTestCase):
|
|||||||
print('')
|
print('')
|
||||||
|
|
||||||
def verify_treestore(self, treestore, tree):
|
def verify_treestore(self, treestore, tree):
|
||||||
|
|
||||||
def _verify_treestore(itr, tree_values):
|
def _verify_treestore(itr, tree_values):
|
||||||
i = 0
|
i = 0
|
||||||
while itr:
|
while itr:
|
||||||
@ -83,6 +82,7 @@ class FilesTabTestCase(BaseTestCase):
|
|||||||
itr = treestore.iter_next(itr)
|
itr = treestore.iter_next(itr)
|
||||||
i += 1
|
i += 1
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return _verify_treestore(treestore.get_iter_root(), tree)
|
return _verify_treestore(treestore.get_iter_root(), tree)
|
||||||
|
|
||||||
def test_files_tab(self):
|
def test_files_tab(self):
|
||||||
@ -91,9 +91,14 @@ class FilesTabTestCase(BaseTestCase):
|
|||||||
{'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
|
{'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
|
||||||
)
|
)
|
||||||
self.filestab.update_files()
|
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:
|
if not ret:
|
||||||
self.print_treestore('Treestore not expected:', self.filestab.treestore)
|
self.print_treestore('Treestore not expected:', self.filestab.treestore)
|
||||||
self.assertTrue(ret)
|
self.assertTrue(ret)
|
||||||
@ -106,9 +111,14 @@ class FilesTabTestCase(BaseTestCase):
|
|||||||
{'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
|
{'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
|
||||||
)
|
)
|
||||||
self.filestab.update_files()
|
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:
|
if not ret:
|
||||||
self.print_treestore('Treestore not expected:', self.filestab.treestore)
|
self.print_treestore('Treestore not expected:', self.filestab.treestore)
|
||||||
self.assertTrue(ret)
|
self.assertTrue(ret)
|
||||||
@ -121,9 +131,13 @@ class FilesTabTestCase(BaseTestCase):
|
|||||||
{'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
|
{'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
|
||||||
)
|
)
|
||||||
self.filestab.update_files()
|
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:
|
if not ret:
|
||||||
self.print_treestore('Treestore not expected:', self.filestab.treestore)
|
self.print_treestore('Treestore not expected:', self.filestab.treestore)
|
||||||
self.assertTrue(ret)
|
self.assertTrue(ret)
|
||||||
@ -134,15 +148,13 @@ class FilesTabTestCase(BaseTestCase):
|
|||||||
{'index': 1, 'path': '1/test_100.txt', 'offset': 13, 'size': 14},
|
{'index': 1, 'path': '1/test_100.txt', 'offset': 13, 'size': 14},
|
||||||
)
|
)
|
||||||
self.filestab.update_files()
|
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(
|
ret = self.verify_treestore(
|
||||||
self.filestab.treestore, [[
|
self.filestab.treestore,
|
||||||
'1/', [
|
[['1/', [['2/', [['test_100.txt']]], ['test_10.txt']]]],
|
||||||
['2/', [['test_100.txt']]],
|
|
||||||
['test_10.txt'],
|
|
||||||
],
|
|
||||||
]],
|
|
||||||
)
|
)
|
||||||
if not ret:
|
if not ret:
|
||||||
self.print_treestore('Treestore not expected:', self.filestab.treestore)
|
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},
|
{'index': 1, 'path': '2/test_100.txt', 'offset': 13, 'size': 14},
|
||||||
)
|
)
|
||||||
self.filestab.update_files()
|
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:
|
if not ret:
|
||||||
self.print_treestore('Treestore not expected:', self.filestab.treestore)
|
self.print_treestore('Treestore not expected:', self.filestab.treestore)
|
||||||
self.assertTrue(ret)
|
self.assertTrue(ret)
|
||||||
|
|||||||
@ -33,26 +33,20 @@ def fname(name):
|
|||||||
|
|
||||||
|
|
||||||
class RedirectResource(Resource):
|
class RedirectResource(Resource):
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
url = self.get_url().encode('utf8')
|
url = self.get_url().encode('utf8')
|
||||||
return redirectTo(url, request)
|
return redirectTo(url, request)
|
||||||
|
|
||||||
|
|
||||||
class RenameResource(Resource):
|
class RenameResource(Resource):
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
filename = request.args.get(b'filename', [b'renamed_file'])[0]
|
filename = request.args.get(b'filename', [b'renamed_file'])[0]
|
||||||
request.setHeader(b'Content-Type', b'text/plain')
|
request.setHeader(b'Content-Type', b'text/plain')
|
||||||
request.setHeader(
|
request.setHeader(b'Content-Disposition', b'attachment; filename=' + filename)
|
||||||
b'Content-Disposition', b'attachment; filename=' +
|
|
||||||
filename,
|
|
||||||
)
|
|
||||||
return b'This file should be called ' + filename
|
return b'This file should be called ' + filename
|
||||||
|
|
||||||
|
|
||||||
class AttachmentResource(Resource):
|
class AttachmentResource(Resource):
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
request.setHeader(b'Content-Type', b'text/plain')
|
request.setHeader(b'Content-Type', b'text/plain')
|
||||||
request.setHeader(b'Content-Disposition', b'attachment')
|
request.setHeader(b'Content-Disposition', b'attachment')
|
||||||
@ -60,7 +54,6 @@ class AttachmentResource(Resource):
|
|||||||
|
|
||||||
|
|
||||||
class CookieResource(Resource):
|
class CookieResource(Resource):
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
request.setHeader(b'Content-Type', b'text/plain')
|
request.setHeader(b'Content-Type', b'text/plain')
|
||||||
if request.getCookie(b'password') is None:
|
if request.getCookie(b'password') is None:
|
||||||
@ -73,7 +66,6 @@ class CookieResource(Resource):
|
|||||||
|
|
||||||
|
|
||||||
class GzipResource(Resource):
|
class GzipResource(Resource):
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
message = request.args.get(b'msg', [b'EFFICIENCY!'])[0]
|
message = request.args.get(b'msg', [b'EFFICIENCY!'])[0]
|
||||||
request.setHeader(b'Content-Type', b'text/plain')
|
request.setHeader(b'Content-Type', b'text/plain')
|
||||||
@ -81,7 +73,6 @@ class GzipResource(Resource):
|
|||||||
|
|
||||||
|
|
||||||
class PartialDownloadResource(Resource):
|
class PartialDownloadResource(Resource):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
Resource.__init__(self)
|
Resource.__init__(self)
|
||||||
self.render_count = 0
|
self.render_count = 0
|
||||||
@ -126,7 +117,6 @@ class TopLevelResource(Resource):
|
|||||||
|
|
||||||
|
|
||||||
class DownloadFileTestCase(unittest.TestCase):
|
class DownloadFileTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def get_url(self, path=''):
|
def get_url(self, path=''):
|
||||||
return 'http://localhost:%d/%s' % (self.listen_port, path)
|
return 'http://localhost:%d/%s' % (self.listen_port, path)
|
||||||
|
|
||||||
@ -245,6 +235,7 @@ class DownloadFileTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def cb(result):
|
def cb(result):
|
||||||
print(result)
|
print(result)
|
||||||
|
|
||||||
d.addCallback(self.assertNotContains, b'fail', file_mode='rb')
|
d.addCallback(self.assertNotContains, b'fail', file_mode='rb')
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -255,6 +246,7 @@ class DownloadFileTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def on_redirect(failure):
|
def on_redirect(failure):
|
||||||
self.assertTrue(type(failure), PageRedirect)
|
self.assertTrue(type(failure), PageRedirect)
|
||||||
|
|
||||||
d.addErrback(on_redirect)
|
d.addErrback(on_redirect)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|||||||
@ -34,10 +34,11 @@ common.disable_new_release_check()
|
|||||||
|
|
||||||
|
|
||||||
class JSONBase(BaseTestCase, DaemonBase):
|
class JSONBase(BaseTestCase, DaemonBase):
|
||||||
|
|
||||||
def connect_client(self, *args, **kwargs):
|
def connect_client(self, *args, **kwargs):
|
||||||
return client.connect(
|
return client.connect(
|
||||||
'localhost', self.listen_port, username=kwargs.get('user', ''),
|
'localhost',
|
||||||
|
self.listen_port,
|
||||||
|
username=kwargs.get('user', ''),
|
||||||
password=kwargs.get('password', ''),
|
password=kwargs.get('password', ''),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,7 +53,6 @@ class JSONBase(BaseTestCase, DaemonBase):
|
|||||||
|
|
||||||
|
|
||||||
class JSONTestCase(JSONBase):
|
class JSONTestCase(JSONBase):
|
||||||
|
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
d = self.common_set_up()
|
d = self.common_set_up()
|
||||||
d.addCallback(self.start_core)
|
d.addCallback(self.start_core)
|
||||||
@ -81,6 +81,7 @@ class JSONTestCase(JSONBase):
|
|||||||
|
|
||||||
def compress(contents, request):
|
def compress(contents, request):
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
self.patch(deluge.ui.web.json_api, 'compress', compress)
|
self.patch(deluge.ui.web.json_api, 'compress', compress)
|
||||||
|
|
||||||
def write(response_str):
|
def write(response_str):
|
||||||
@ -88,7 +89,9 @@ class JSONTestCase(JSONBase):
|
|||||||
response = json_lib.loads(response_str)
|
response = json_lib.loads(response_str)
|
||||||
self.assertEqual(response['result'], None)
|
self.assertEqual(response['result'], None)
|
||||||
self.assertEqual(response['id'], 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)
|
self.assertEqual(response['error']['code'], 5)
|
||||||
|
|
||||||
request.write = write
|
request.write = write
|
||||||
@ -127,7 +130,6 @@ class JSONTestCase(JSONBase):
|
|||||||
|
|
||||||
|
|
||||||
class JSONCustomUserTestCase(JSONBase):
|
class JSONCustomUserTestCase(JSONBase):
|
||||||
|
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
d = self.common_set_up()
|
d = self.common_set_up()
|
||||||
d.addCallback(self.start_core)
|
d.addCallback(self.start_core)
|
||||||
@ -152,7 +154,6 @@ class JSONCustomUserTestCase(JSONBase):
|
|||||||
|
|
||||||
|
|
||||||
class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
|
class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
|
||||||
|
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
d = self.common_set_up()
|
d = self.common_set_up()
|
||||||
custom_script = """
|
custom_script = """
|
||||||
@ -176,6 +177,7 @@ class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
|
|||||||
|
|
||||||
def get_session_id(s_id):
|
def get_session_id(s_id):
|
||||||
return s_id
|
return s_id
|
||||||
|
|
||||||
self.patch(deluge.ui.web.auth, 'get_session_id', get_session_id)
|
self.patch(deluge.ui.web.auth, 'get_session_id', get_session_id)
|
||||||
auth_conf = {'session_timeout': 10, 'sessions': {}}
|
auth_conf = {'session_timeout': 10, 'sessions': {}}
|
||||||
auth = Auth(auth_conf)
|
auth = Auth(auth_conf)
|
||||||
@ -195,12 +197,12 @@ class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
|
|||||||
|
|
||||||
def on_error(error):
|
def on_error(error):
|
||||||
self.assertEqual(error.type, DelugeError)
|
self.assertEqual(error.type, DelugeError)
|
||||||
|
|
||||||
result.addErrback(on_error)
|
result.addErrback(on_error)
|
||||||
yield result
|
yield result
|
||||||
|
|
||||||
|
|
||||||
class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
|
class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
|
||||||
|
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
d = self.common_set_up()
|
d = self.common_set_up()
|
||||||
custom_script = """
|
custom_script = """
|
||||||
@ -219,13 +221,17 @@ class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
|
|||||||
daemon.rpcserver.register_object(test)
|
daemon.rpcserver.register_object(test)
|
||||||
"""
|
"""
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred
|
||||||
|
|
||||||
extra_callback = {
|
extra_callback = {
|
||||||
'deferred': Deferred(), 'types': ['stderr'],
|
'deferred': Deferred(),
|
||||||
|
'types': ['stderr'],
|
||||||
'timeout': 10,
|
'timeout': 10,
|
||||||
'triggers': [{
|
'triggers': [
|
||||||
|
{
|
||||||
'expr': 'in test_raise_error',
|
'expr': 'in test_raise_error',
|
||||||
'value': lambda reader, data, data_all: 'Test',
|
'value': lambda reader, data, data_all: 'Test',
|
||||||
}],
|
}
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
def on_test_raise(*args):
|
def on_test_raise(*args):
|
||||||
@ -234,8 +240,12 @@ class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
|
|||||||
|
|
||||||
extra_callback['deferred'].addCallback(on_test_raise)
|
extra_callback['deferred'].addCallback(on_test_raise)
|
||||||
d.addCallback(
|
d.addCallback(
|
||||||
self.start_core, custom_script=custom_script, print_stdout=False, print_stderr=False,
|
self.start_core,
|
||||||
timeout=5, extra_callbacks=[extra_callback],
|
custom_script=custom_script,
|
||||||
|
print_stdout=False,
|
||||||
|
print_stderr=False,
|
||||||
|
timeout=5,
|
||||||
|
extra_callbacks=[extra_callback],
|
||||||
)
|
)
|
||||||
d.addCallbacks(self.connect_client, self.terminate_core)
|
d.addCallbacks(self.connect_client, self.terminate_core)
|
||||||
return d
|
return d
|
||||||
@ -278,5 +288,6 @@ class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
|
|||||||
def on_success(arg):
|
def on_success(arg):
|
||||||
self.assertEqual(arg, server.NOT_DONE_YET)
|
self.assertEqual(arg, server.NOT_DONE_YET)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
d.addCallbacks(on_success, self.fail)
|
d.addCallbacks(on_success, self.fail)
|
||||||
yield d
|
yield d
|
||||||
|
|||||||
@ -27,6 +27,7 @@ class LogTestCase(BaseTestCase):
|
|||||||
|
|
||||||
def test_old_log_deprecation_warning(self):
|
def test_old_log_deprecation_warning(self):
|
||||||
from deluge.log import LOG
|
from deluge.log import LOG
|
||||||
|
|
||||||
with warnings.catch_warnings(record=True) as w:
|
with warnings.catch_warnings(record=True) as w:
|
||||||
# Cause all warnings to always be triggered.
|
# Cause all warnings to always be triggered.
|
||||||
warnings.simplefilter('always')
|
warnings.simplefilter('always')
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user