summaryrefslogtreecommitdiffstats
path: root/deluge/plugins/Blocklist/deluge_blocklist
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/plugins/Blocklist/deluge_blocklist')
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/__init__.py33
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/common.py172
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/core.py549
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/data/blocklist.js429
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/data/blocklist16.pngbin0 -> 586 bytes
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_download24.pngbin0 -> 764 bytes
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_import24.pngbin0 -> 1091 bytes
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_pref.ui603
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/decompressers.py44
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/detect.py48
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/gtkui.py254
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/peerguardian.py66
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/readers.py99
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/webui.py27
14 files changed, 2324 insertions, 0 deletions
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/__init__.py b/deluge/plugins/Blocklist/deluge_blocklist/__init__.py
new file mode 100644
index 0000000..40ce1d1
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/__init__.py
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from deluge.plugins.init import PluginInitBase
+
+
+class CorePlugin(PluginInitBase):
+ def __init__(self, plugin_name):
+ from .core import Core as _pluginCls
+
+ self._plugin_cls = _pluginCls
+ super().__init__(plugin_name)
+
+
+class GtkUIPlugin(PluginInitBase):
+ def __init__(self, plugin_name):
+ from .gtkui import GtkUI as _pluginCls
+
+ self._plugin_cls = _pluginCls
+ super().__init__(plugin_name)
+
+
+class WebUIPlugin(PluginInitBase):
+ def __init__(self, plugin_name):
+ from .webui import WebUI as _pluginCls
+
+ self._plugin_cls = _pluginCls
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/common.py b/deluge/plugins/Blocklist/deluge_blocklist/common.py
new file mode 100644
index 0000000..35b2f87
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/common.py
@@ -0,0 +1,172 @@
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# 2007-2009 Andrew Resch <andrewresch@gmail.com>
+# 2009 Damien Churchill <damoxc@gmail.com>
+# 2010 Pedro Algarvio <pedro@algarvio.me>
+# 2017 Calum Lind <calumlind+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+import os.path
+from functools import wraps
+from sys import exc_info
+
+from pkg_resources import resource_filename
+
+
+def get_resource(filename):
+ return resource_filename(__package__, os.path.join('data', filename))
+
+
+def raises_errors_as(error):
+ """Factory class that returns a decorator which wraps the decorated
+ function to raise all exceptions as the specified error type.
+
+ """
+
+ def decorator(func):
+ """Returns a function which wraps the given func to raise all exceptions as error."""
+
+ @wraps(func)
+ def wrapper(self, *args, **kwargs):
+ """Wraps the function in a try..except block and calls it with the specified args.
+
+ Raises:
+ Any exceptions as error preserving the message and traceback.
+
+ """
+ try:
+ return func(self, *args, **kwargs)
+ except Exception:
+ (value, tb) = exc_info()[1:]
+ raise error(value).with_traceback(tb) from None
+
+ return wrapper
+
+ return decorator
+
+
+def remove_zeros(ip):
+ """Removes unneeded zeros from ip addresses.
+
+ Args:
+ ip (str): The ip address.
+
+ Returns:
+ str: The ip address without the unneeded zeros.
+
+ Example:
+ 000.000.000.003 -> 0.0.0.3
+
+ """
+ return '.'.join([part.lstrip('0').zfill(1) for part in ip.split('.')])
+
+
+class BadIP(Exception):
+ _message = None
+
+ def __init__(self, message):
+ super().__init__(message)
+
+ def __set_message(self, message):
+ self._message = message
+
+ def __get_message(self):
+ return self._message
+
+ message = property(__get_message, __set_message)
+ del __get_message, __set_message
+
+
+class IP:
+ __slots__ = ('q1', 'q2', 'q3', 'q4', '_long')
+
+ def __init__(self, q1, q2, q3, q4):
+ self.q1 = q1
+ self.q2 = q2
+ self.q3 = q3
+ self.q4 = q4
+ self._long = 0
+ for q in self.quadrants():
+ self._long = (self._long << 8) | int(q)
+
+ @property
+ def address(self):
+ return '.'.join([str(q) for q in [self.q1, self.q2, self.q3, self.q4]])
+
+ @property
+ def long(self):
+ return self._long
+
+ @classmethod
+ def parse(cls, ip):
+ try:
+ q1, q2, q3, q4 = (int(q) for q in ip.split('.'))
+ except ValueError:
+ raise BadIP(_('The IP address "%s" is badly formed' % ip))
+ if q1 < 0 or q2 < 0 or q3 < 0 or q4 < 0:
+ raise BadIP(_('The IP address "%s" is badly formed' % ip))
+ elif q1 > 255 or q2 > 255 or q3 > 255 or q4 > 255:
+ raise BadIP(_('The IP address "%s" is badly formed' % ip))
+ return cls(q1, q2, q3, q4)
+
+ def quadrants(self):
+ return (self.q1, self.q2, self.q3, self.q4)
+
+ # def next_ip(self):
+ # (q1, q2, q3, q4) = self.quadrants()
+ # if q4 >= 255:
+ # if q3 >= 255:
+ # if q2 >= 255:
+ # if q1 >= 255:
+ # raise BadIP(_('There is not a next IP address'))
+ # q1 += 1
+ # else:
+ # q2 += 1
+ # else:
+ # q3 += 1
+ # else:
+ # q4 += 1
+ # return IP(q1, q2, q3, q4)
+ #
+ # def previous_ip(self):
+ # (q1, q2, q3, q4) = self.quadrants()
+ # if q4 <= 1:
+ # if q3 <= 1:
+ # if q2 <= 1:
+ # if q1 <= 1:
+ # raise BadIP(_('There is not a previous IP address'))
+ # q1 -= 1
+ # else:
+ # q2 -= 1
+ # else:
+ # q3 -= 1
+ # else:
+ # q4 -= 1
+ # return IP(q1, q2, q3, q4)
+
+ def __lt__(self, other):
+ if isinstance(other, ''.__class__):
+ other = IP.parse(other)
+ return self.long < other.long
+
+ def __gt__(self, other):
+ if isinstance(other, ''.__class__):
+ other = IP.parse(other)
+ return self.long > other.long
+
+ def __eq__(self, other):
+ if isinstance(other, ''.__class__):
+ other = IP.parse(other)
+ return self.long == other.long
+
+ def __repr__(self):
+ return '<{} long={} address="{}">'.format(
+ self.__class__.__name__,
+ self.long,
+ self.address,
+ )
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/core.py b/deluge/plugins/Blocklist/deluge_blocklist/core.py
new file mode 100644
index 0000000..1765767
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/core.py
@@ -0,0 +1,549 @@
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+import logging
+import os
+import shutil
+import time
+from datetime import datetime, timedelta
+from email.utils import formatdate
+from urllib.parse import urljoin
+
+from twisted.internet import defer, threads
+from twisted.internet.task import LoopingCall
+from twisted.web import error
+
+import deluge.component as component
+import deluge.configmanager
+from deluge.common import is_url
+from deluge.core.rpcserver import export
+from deluge.httpdownloader import download_file
+from deluge.plugins.pluginbase import CorePluginBase
+
+from .common import IP, BadIP
+from .detect import UnknownFormatError, create_reader, detect_compression, detect_format
+from .readers import ReaderParseError
+
+# TODO: check return values for deferred callbacks
+# TODO: review class attributes for redundancy
+
+log = logging.getLogger(__name__)
+
+DEFAULT_PREFS = {
+ 'url': '',
+ 'load_on_start': False,
+ 'check_after_days': 4,
+ 'list_compression': '',
+ 'list_type': '',
+ 'last_update': 0.0,
+ 'list_size': 0,
+ 'timeout': 180,
+ 'try_times': 3,
+ 'whitelisted': [],
+}
+
+# Constants
+ALLOW_RANGE = 0
+BLOCK_RANGE = 1
+
+
+class Core(CorePluginBase):
+ def enable(self):
+ log.debug('Blocklist: Plugin enabled...')
+
+ self.is_url = True
+ self.is_downloading = False
+ self.is_importing = False
+ self.has_imported = False
+ self.up_to_date = False
+ self.need_to_resume_session = False
+ self.num_whited = 0
+ self.num_blocked = 0
+ self.file_progress = 0.0
+
+ self.core = component.get('Core')
+ self.config = deluge.configmanager.ConfigManager(
+ 'blocklist.conf', DEFAULT_PREFS
+ )
+ if 'whitelisted' not in self.config:
+ self.config['whitelisted'] = []
+
+ self.reader = create_reader(
+ self.config['list_type'], self.config['list_compression']
+ )
+
+ if not isinstance(self.config['last_update'], float):
+ self.config.config['last_update'] = 0.0
+
+ update_now = False
+ if self.config['load_on_start']:
+ self.pause_session()
+ if self.config['last_update']:
+ last_update = datetime.fromtimestamp(self.config['last_update'])
+ check_period = timedelta(days=self.config['check_after_days'])
+ if (
+ not self.config['last_update']
+ or last_update + check_period < datetime.now()
+ ):
+ update_now = True
+ else:
+ d = self.import_list(
+ deluge.configmanager.get_config_dir('blocklist.cache')
+ )
+ d.addCallbacks(self.on_import_complete, self.on_import_error)
+ if self.need_to_resume_session:
+ d.addBoth(self.resume_session)
+
+ # This function is called every 'check_after_days' days, to download
+ # and import a new list if needed.
+ self.update_timer = LoopingCall(self.check_import)
+ if self.config['check_after_days'] > 0:
+ self.update_timer.start(
+ self.config['check_after_days'] * 24 * 60 * 60, update_now
+ )
+
+ def disable(self):
+ self.config.save()
+ log.debug('Reset IP filter')
+ self.core.session.get_ip_filter().add_rule(
+ '0.0.0.0', '255.255.255.255', ALLOW_RANGE
+ )
+ log.debug('Blocklist: Plugin disabled')
+
+ def update(self):
+ pass
+
+ # Exported RPC methods #
+ @export
+ def check_import(self, force=False):
+ """Imports latest blocklist specified by blocklist url.
+
+ Args:
+ force (bool, optional): Force the download/import, default is False.
+
+ Returns:
+ Deferred: A Deferred which fires when the blocklist has been imported.
+
+ """
+ if not self.config['url']:
+ return
+
+ # Reset variables
+ self.filename = None
+ self.force_download = force
+ self.failed_attempts = 0
+ self.auto_detected = False
+ self.up_to_date = False
+ if force:
+ self.reader = None
+ self.is_url = is_url(self.config['url'])
+
+ # Start callback chain
+ if self.is_url:
+ d = self.download_list()
+ d.addCallbacks(self.on_download_complete, self.on_download_error)
+ d.addCallback(self.import_list)
+ else:
+ d = self.import_list(self.config['url'])
+ d.addCallbacks(self.on_import_complete, self.on_import_error)
+ if self.need_to_resume_session:
+ d.addBoth(self.resume_session)
+
+ return d
+
+ @export
+ def get_config(self):
+ """Gets the blocklist config dictionary.
+
+ Returns:
+ dict: The config dictionary.
+
+ """
+ return self.config.config
+
+ @export
+ def set_config(self, config):
+ """Sets the blocklist config.
+
+ Args:
+ config (dict): config to set.
+
+ """
+ needs_blocklist_import = False
+ for key in config:
+ if key == 'whitelisted':
+ saved = set(self.config[key])
+ update = set(config[key])
+ diff = saved.symmetric_difference(update)
+ if diff:
+ log.debug('Whitelist changed. Updating...')
+ added = update.intersection(diff)
+ removed = saved.intersection(diff)
+ if added:
+ for ip in added:
+ try:
+ ip = IP.parse(ip)
+ self.blocklist.add_rule(
+ ip.address, ip.address, ALLOW_RANGE
+ )
+ saved.add(ip.address)
+ log.debug('Added %s to whitelisted', ip)
+ self.num_whited += 1
+ except BadIP as ex:
+ log.error('Bad IP: %s', ex)
+ continue
+ if removed:
+ needs_blocklist_import = True
+ for ip in removed:
+ try:
+ ip = IP.parse(ip)
+ saved.remove(ip.address)
+ log.debug('Removed %s from whitelisted', ip)
+ except BadIP as ex:
+ log.error('Bad IP: %s', ex)
+ continue
+
+ self.config[key] = list(saved)
+ continue
+ elif key == 'check_after_days':
+ if self.config[key] != config[key]:
+ self.config[key] = config[key]
+ update_now = False
+ if self.config['last_update']:
+ last_update = datetime.fromtimestamp(self.config['last_update'])
+ check_period = timedelta(days=self.config['check_after_days'])
+ if (
+ not self.config['last_update']
+ or last_update + check_period < datetime.now()
+ ):
+ update_now = True
+ if self.update_timer.running:
+ self.update_timer.stop()
+ if self.config['check_after_days'] > 0:
+ self.update_timer.start(
+ self.config['check_after_days'] * 24 * 60 * 60, update_now
+ )
+ continue
+ self.config[key] = config[key]
+
+ if needs_blocklist_import:
+ log.debug(
+ 'IP addresses were removed from the whitelist. Since we '
+ 'do not know if they were blocked before. Re-import '
+ 'current blocklist and re-add whitelisted.'
+ )
+ self.has_imported = False
+ d = self.import_list(deluge.configmanager.get_config_dir('blocklist.cache'))
+ d.addCallbacks(self.on_import_complete, self.on_import_error)
+
+ @export
+ def get_status(self):
+ """Get the status of the plugin.
+
+ Returns:
+ dict: The status dict of the plugin.
+
+ """
+ status = {}
+ if self.is_downloading:
+ status['state'] = 'Downloading'
+ elif self.is_importing:
+ status['state'] = 'Importing'
+ else:
+ status['state'] = 'Idle'
+
+ status['up_to_date'] = self.up_to_date
+ status['num_whited'] = self.num_whited
+ status['num_blocked'] = self.num_blocked
+ status['file_progress'] = self.file_progress
+ status['file_url'] = self.config['url']
+ status['file_size'] = self.config['list_size']
+ status['file_date'] = self.config['last_update']
+ status['file_type'] = self.config['list_type']
+ status['whitelisted'] = self.config['whitelisted']
+ if self.config['list_compression']:
+ status['file_type'] += ' (%s)' % self.config['list_compression']
+ return status
+
+ ####
+
+ def update_info(self, blocklist):
+ """Updates blocklist info.
+
+ Args:
+ blocklist (str): Path of blocklist.
+
+ Returns:
+ str: Path of blocklist.
+
+ """
+ log.debug('Updating blocklist info: %s', blocklist)
+ self.config['last_update'] = time.time()
+ self.config['list_size'] = os.path.getsize(blocklist)
+ self.filename = blocklist
+ return blocklist
+
+ def download_list(self, url=None):
+ """Downloads the blocklist specified by 'url' in the config.
+
+ Args:
+ url (str, optional): url to download from, defaults to config value.
+
+ Returns:
+ Deferred: a Deferred which fires once the blocklist has been downloaded.
+
+ """
+
+ def on_retrieve_data(data, current_length, total_length):
+ if total_length:
+ fp = current_length / total_length
+ if fp > 1.0:
+ fp = 1.0
+ else:
+ fp = 0.0
+
+ self.file_progress = fp
+
+ import socket
+
+ socket.setdefaulttimeout(self.config['timeout'])
+
+ if not url:
+ url = self.config['url']
+
+ headers = {}
+ if self.config['last_update'] and not self.force_download:
+ headers['If-Modified-Since'] = formatdate(
+ self.config['last_update'], usegmt=True
+ )
+
+ log.debug('Attempting to download blocklist %s', url)
+ log.debug('Sending headers: %s', headers)
+ self.is_downloading = True
+ return download_file(
+ url,
+ deluge.configmanager.get_config_dir('blocklist.download'),
+ on_retrieve_data,
+ headers,
+ )
+
+ def on_download_complete(self, blocklist):
+ """Runs any download clean up functions.
+
+ Args:
+ blocklist (str): Path of blocklist.
+
+ Returns:
+ Deferred: a Deferred which fires when clean up is done.
+
+ """
+ log.debug('Blocklist download complete: %s', blocklist)
+ self.is_downloading = False
+ return threads.deferToThread(self.update_info, blocklist)
+
+ def on_download_error(self, f):
+ """Recovers from download error.
+
+ Args:
+ f (Failure): Failure that occurred.
+
+ Returns:
+ Deferred or Failure: A Deferred if recovery was possible else original Failure.
+
+ """
+ self.is_downloading = False
+ error_msg = f.getErrorMessage()
+ d = f
+ if f.check(error.PageRedirect):
+ # Handle redirect errors
+ location = urljoin(self.config['url'], error_msg.split(' to ')[1])
+ if 'Moved Permanently' in error_msg:
+ log.debug('Setting blocklist url to %s', location)
+ self.config['url'] = location
+ d = self.download_list(location)
+ d.addCallbacks(self.on_download_complete, self.on_download_error)
+ else:
+ if 'Not Modified' in error_msg:
+ log.debug('Blocklist is up-to-date!')
+ self.up_to_date = True
+ blocklist = deluge.configmanager.get_config_dir('blocklist.cache')
+ d = threads.deferToThread(self.update_info, blocklist)
+ else:
+ log.warning('Blocklist download failed: %s', error_msg)
+ if self.failed_attempts < self.config['try_times']:
+ log.debug(
+ 'Try downloading blocklist again... (%s/%s)',
+ self.failed_attempts,
+ self.config['try_times'],
+ )
+ self.failed_attempts += 1
+ d = self.download_list()
+ d.addCallbacks(self.on_download_complete, self.on_download_error)
+ return d
+
+ def import_list(self, blocklist):
+ """Imports the downloaded blocklist into the session.
+
+ Args:
+ blocklist (str): path of blocklist.
+
+ Returns:
+ Deferred: A Deferred that fires when the blocklist has been imported.
+
+ """
+ log.trace('on import_list')
+
+ def on_read_ip_range(start, end):
+ """Add ip range to blocklist"""
+ # log.trace('Adding ip range %s - %s to ipfilter as blocked', start, end)
+ self.blocklist.add_rule(start.address, end.address, BLOCK_RANGE)
+ self.num_blocked += 1
+
+ def on_finish_read(result):
+ """Add any whitelisted IP's and add the blocklist to session"""
+ # White listing happens last because the last rules added have
+ # priority
+ log.info('Added %d ranges to ipfilter as blocked', self.num_blocked)
+ for ip in self.config['whitelisted']:
+ ip = IP.parse(ip)
+ self.blocklist.add_rule(ip.address, ip.address, ALLOW_RANGE)
+ self.num_whited += 1
+ log.trace('Added %s to the ipfiler as white-listed', ip.address)
+ log.info('Added %d ranges to ipfilter as white-listed', self.num_whited)
+ self.core.session.set_ip_filter(self.blocklist)
+ return result
+
+ # TODO: double check logic
+ if self.up_to_date and self.has_imported:
+ log.debug('Latest blocklist is already imported')
+ return defer.succeed(blocklist)
+
+ self.is_importing = True
+ self.num_blocked = 0
+ self.num_whited = 0
+ self.blocklist = self.core.session.get_ip_filter()
+
+ if not blocklist:
+ blocklist = self.filename
+
+ if not self.reader:
+ self.auto_detect(blocklist)
+ self.auto_detected = True
+
+ def on_reader_failure(failure):
+ log.error('Failed to read!!!!!!')
+ log.exception(failure)
+
+ log.debug('Importing using reader: %s', self.reader)
+ log.debug(
+ 'Reader type: %s compression: %s',
+ self.config['list_type'],
+ self.config['list_compression'],
+ )
+ log.debug('Clearing current ip filtering')
+ # self.blocklist.add_rule('0.0.0.0', '255.255.255.255', ALLOW_RANGE)
+ d = threads.deferToThread(self.reader(blocklist).read, on_read_ip_range)
+ d.addCallback(on_finish_read).addErrback(on_reader_failure)
+
+ return d
+
+ def on_import_complete(self, blocklist):
+ """Runs any import clean up functions.
+
+ Args:
+ blocklist (str): Path of blocklist.
+
+ Returns:
+ Deferred: A Deferred that fires when clean up is done.
+
+ """
+ log.trace('on_import_list_complete')
+ d = blocklist
+ self.is_importing = False
+ self.has_imported = True
+ log.debug('Blocklist import complete!')
+ cache = deluge.configmanager.get_config_dir('blocklist.cache')
+ if blocklist != cache:
+ if self.is_url:
+ log.debug('Moving %s to %s', blocklist, cache)
+ d = threads.deferToThread(shutil.move, blocklist, cache)
+ else:
+ log.debug('Copying %s to %s', blocklist, cache)
+ d = threads.deferToThread(shutil.copy, blocklist, cache)
+ return d
+
+ def on_import_error(self, f):
+ """Recovers from import error.
+
+ Args:
+ f (Failure): Failure that occurred.
+
+ Returns:
+ Deferred or Failure: A Deferred if recovery was possible else original Failure.
+
+ """
+ log.trace('on_import_error: %s', f)
+ d = f
+ self.is_importing = False
+ try_again = False
+ cache = deluge.configmanager.get_config_dir('blocklist.cache')
+
+ if f.check(ReaderParseError) and not self.auto_detected:
+ # Invalid / corrupt list, let's detect it
+ log.warning('Invalid / corrupt blocklist')
+ self.reader = None
+ blocklist = None
+ try_again = True
+ elif self.filename != cache and os.path.exists(cache):
+ # If we have a backup and we haven't already used it
+ log.warning('Error reading blocklist: %s', f.getErrorMessage())
+ blocklist = cache
+ try_again = True
+
+ if try_again:
+ d = self.import_list(blocklist)
+ d.addCallbacks(self.on_import_complete, self.on_import_error)
+
+ return d
+
+ def auto_detect(self, blocklist):
+ """Attempts to auto-detect the blocklist type.
+
+ Args:
+ blocklist (str): Path of blocklist.
+
+ Raises:
+ UnknownFormatError: If the format cannot be detected.
+
+ """
+ self.config['list_compression'] = detect_compression(blocklist)
+ self.config['list_type'] = detect_format(
+ blocklist, self.config['list_compression']
+ )
+ log.debug(
+ 'Auto-detected type: %s compression: %s',
+ self.config['list_type'],
+ self.config['list_compression'],
+ )
+ if not self.config['list_type']:
+ self.config['list_compression'] = ''
+ raise UnknownFormatError
+ else:
+ self.reader = create_reader(
+ self.config['list_type'], self.config['list_compression']
+ )
+
+ def pause_session(self):
+ self.need_to_resume_session = not self.core.session.is_paused()
+ self.core.pause_session()
+
+ def resume_session(self, result):
+ self.core.resume_session()
+ self.need_to_resume_session = False
+ return result
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist.js b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist.js
new file mode 100644
index 0000000..3c10b81
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist.js
@@ -0,0 +1,429 @@
+/**
+ * blocklist.js
+ *
+ * Copyright (C) Omar Alvarez 2014 <omar.alvarez@udc.es>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ *
+ */
+
+Ext.ns('Deluge.ux.preferences');
+
+/**
+ * @class Deluge.ux.preferences.BlocklistPage
+ * @extends Ext.Panel
+ */
+Deluge.ux.preferences.BlocklistPage = Ext.extend(Ext.Panel, {
+ title: _('Blocklist'),
+ header: false,
+ layout: 'fit',
+ border: false,
+ autoScroll: true,
+
+ initComponent: function () {
+ Deluge.ux.preferences.BlocklistPage.superclass.initComponent.call(this);
+
+ this.URLFset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('General'),
+ autoHeight: true,
+ defaultType: 'textfield',
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ autoWidth: true,
+ labelWidth: 40,
+ });
+
+ this.URL = this.URLFset.add({
+ fieldLabel: _('URL:'),
+ labelSeparator: '',
+ name: 'url',
+ width: '80%',
+ });
+
+ this.SettingsFset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Settings'),
+ autoHeight: true,
+ defaultType: 'spinnerfield',
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ autoWidth: true,
+ labelWidth: 160,
+ });
+
+ this.checkListDays = this.SettingsFset.add({
+ fieldLabel: _('Check for new list every (days):'),
+ labelSeparator: '',
+ name: 'check_list_days',
+ value: 4,
+ decimalPrecision: 0,
+ width: 80,
+ });
+
+ this.chkImportOnStart = this.SettingsFset.add({
+ xtype: 'checkbox',
+ fieldLabel: _('Import blocklist on startup'),
+ name: 'check_import_startup',
+ });
+
+ this.OptionsFset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Options'),
+ autoHeight: true,
+ defaultType: 'button',
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ autoWidth: false,
+ width: '80%',
+ labelWidth: 0,
+ });
+
+ this.checkDownload = this.OptionsFset.add({
+ fieldLabel: _(''),
+ name: 'check_download',
+ xtype: 'container',
+ layout: 'hbox',
+ margins: '4 0 0 5',
+ items: [
+ {
+ xtype: 'button',
+ text: ' Check Download and Import ',
+ scale: 'medium',
+ },
+ {
+ xtype: 'box',
+ autoEl: {
+ tag: 'img',
+ src: '../icons/ok.png',
+ },
+ margins: '4 0 0 3',
+ },
+ ],
+ });
+
+ this.forceDownload = this.OptionsFset.add({
+ fieldLabel: _(''),
+ name: 'force_download',
+ text: ' Force Download and Import ',
+ margins: '2 0 0 0',
+ //icon: '../icons/blocklist_import24.png',
+ scale: 'medium',
+ });
+
+ this.ProgressFset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Info'),
+ autoHeight: true,
+ defaultType: 'progress',
+ style: 'margin-top: 1px; margin-bottom: 0px; padding-bottom: 0px;',
+ autoWidth: true,
+ labelWidth: 0,
+ hidden: true,
+ });
+
+ this.downProgBar = this.ProgressFset.add({
+ fieldLabel: _(''),
+ name: 'progress_bar',
+ width: '90%',
+ });
+
+ this.InfoFset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Info'),
+ autoHeight: true,
+ defaultType: 'label',
+ style: 'margin-top: 0px; margin-bottom: 0px; padding-bottom: 0px;',
+ labelWidth: 60,
+ });
+
+ this.lblFileSize = this.InfoFset.add({
+ fieldLabel: _('File Size:'),
+ labelSeparator: '',
+ name: 'file_size',
+ });
+
+ this.lblDate = this.InfoFset.add({
+ fieldLabel: _('Date:'),
+ labelSeparator: '',
+ name: 'date',
+ });
+
+ this.lblType = this.InfoFset.add({
+ fieldLabel: _('Type:'),
+ labelSeparator: '',
+ name: 'type',
+ });
+
+ this.lblURL = this.InfoFset.add({
+ fieldLabel: _('URL:'),
+ labelSeparator: '',
+ name: 'lbl_URL',
+ });
+
+ this.WhitelistFset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Whitelist'),
+ autoHeight: true,
+ defaultType: 'editorgrid',
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ autoWidth: true,
+ labelWidth: 0,
+ items: [
+ {
+ fieldLabel: _(''),
+ name: 'whitelist',
+ margins: '2 0 5 5',
+ height: 100,
+ width: 260,
+ autoExpandColumn: 'ip',
+ viewConfig: {
+ emptyText: _('Add an IP...'),
+ deferEmptyText: false,
+ },
+ colModel: new Ext.grid.ColumnModel({
+ columns: [
+ {
+ id: 'ip',
+ header: _('IP'),
+ dataIndex: 'ip',
+ sortable: true,
+ hideable: false,
+ editable: true,
+ editor: {
+ xtype: 'textfield',
+ },
+ },
+ ],
+ }),
+ selModel: new Ext.grid.RowSelectionModel({
+ singleSelect: false,
+ moveEditorOnEnter: false,
+ }),
+ store: new Ext.data.ArrayStore({
+ autoDestroy: true,
+ fields: [{ name: 'ip' }],
+ }),
+ listeners: {
+ afteredit: function (e) {
+ e.record.commit();
+ },
+ },
+ setEmptyText: function (text) {
+ if (this.viewReady) {
+ this.getView().emptyText = text;
+ this.getView().refresh();
+ } else {
+ Ext.apply(this.viewConfig, { emptyText: text });
+ }
+ },
+ loadData: function (data) {
+ this.getStore().loadData(data);
+ if (this.viewReady) {
+ this.getView().updateHeaders();
+ }
+ },
+ },
+ ],
+ });
+
+ this.ipButtonsContainer = this.WhitelistFset.add({
+ xtype: 'container',
+ layout: 'hbox',
+ margins: '4 0 0 5',
+ items: [
+ {
+ xtype: 'button',
+ text: ' Add IP ',
+ margins: '0 5 0 0',
+ },
+ {
+ xtype: 'button',
+ text: ' Delete IP ',
+ },
+ ],
+ });
+
+ this.updateTask = Ext.TaskMgr.start({
+ interval: 2000,
+ run: this.onUpdate,
+ scope: this,
+ });
+
+ this.on('show', this.updateConfig, this);
+
+ this.ipButtonsContainer.getComponent(0).setHandler(this.addIP, this);
+ this.ipButtonsContainer.getComponent(1).setHandler(this.deleteIP, this);
+
+ this.checkDownload.getComponent(0).setHandler(this.checkDown, this);
+ this.forceDownload.setHandler(this.forceDown, this);
+ },
+
+ onApply: function () {
+ var config = {};
+
+ config['url'] = this.URL.getValue();
+ config['check_after_days'] = this.checkListDays.getValue();
+ config['load_on_start'] = this.chkImportOnStart.getValue();
+
+ var ipList = [];
+ var store = this.WhitelistFset.getComponent(0).getStore();
+
+ for (var i = 0; i < store.getCount(); i++) {
+ var record = store.getAt(i);
+ var ip = record.get('ip');
+ ipList.push(ip);
+ }
+
+ config['whitelisted'] = ipList;
+
+ deluge.client.blocklist.set_config(config);
+ },
+
+ onOk: function () {
+ this.onApply();
+ },
+
+ onUpdate: function () {
+ deluge.client.blocklist.get_status({
+ success: function (status) {
+ if (status['state'] == 'Downloading') {
+ this.InfoFset.hide();
+ this.checkDownload.getComponent(0).setDisabled(true);
+ this.checkDownload.getComponent(1).hide();
+ this.forceDownload.setDisabled(true);
+
+ this.ProgressFset.show();
+ this.downProgBar.updateProgress(
+ status['file_progress'],
+ 'Downloading '
+ .concat((status['file_progress'] * 100).toFixed(2))
+ .concat('%'),
+ true
+ );
+ } else if (status['state'] == 'Importing') {
+ this.InfoFset.hide();
+ this.checkDownload.getComponent(0).setDisabled(true);
+ this.checkDownload.getComponent(1).hide();
+ this.forceDownload.setDisabled(true);
+
+ this.ProgressFset.show();
+ this.downProgBar.updateText(
+ 'Importing '.concat(status['num_blocked'])
+ );
+ } else if (status['state'] == 'Idle') {
+ this.ProgressFset.hide();
+ this.checkDownload.getComponent(0).setDisabled(false);
+ this.forceDownload.setDisabled(false);
+ if (status['up_to_date']) {
+ this.checkDownload.getComponent(1).show();
+ this.checkDownload.doLayout();
+ } else {
+ this.checkDownload.getComponent(1).hide();
+ }
+ this.InfoFset.show();
+ this.lblFileSize.setText(fsize(status['file_size']));
+ this.lblDate.setText(fdate(status['file_date']));
+ this.lblType.setText(status['file_type']);
+ this.lblURL.setText(
+ status['file_url'].substr(0, 40).concat('...')
+ );
+ }
+ },
+ scope: this,
+ });
+ },
+
+ checkDown: function () {
+ this.onApply();
+ deluge.client.blocklist.check_import();
+ },
+
+ forceDown: function () {
+ this.onApply();
+ deluge.client.blocklist.check_import((force = true));
+ },
+
+ updateConfig: function () {
+ deluge.client.blocklist.get_config({
+ success: function (config) {
+ this.URL.setValue(config['url']);
+ this.checkListDays.setValue(config['check_after_days']);
+ this.chkImportOnStart.setValue(config['load_on_start']);
+
+ var data = [];
+ var keys = Ext.keys(config['whitelisted']);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ data.push([config['whitelisted'][key]]);
+ }
+
+ this.WhitelistFset.getComponent(0).loadData(data);
+ },
+ scope: this,
+ });
+
+ deluge.client.blocklist.get_status({
+ success: function (status) {
+ this.lblFileSize.setText(fsize(status['file_size']));
+ this.lblDate.setText(fdate(status['file_date']));
+ this.lblType.setText(status['file_type']);
+ this.lblURL.setText(
+ status['file_url'].substr(0, 40).concat('...')
+ );
+ },
+ scope: this,
+ });
+ },
+
+ addIP: function () {
+ var store = this.WhitelistFset.getComponent(0).getStore();
+ var IP = store.recordType;
+ var i = new IP({
+ ip: '',
+ });
+ this.WhitelistFset.getComponent(0).stopEditing();
+ store.insert(0, i);
+ this.WhitelistFset.getComponent(0).startEditing(0, 0);
+ },
+
+ deleteIP: function () {
+ var selections = this.WhitelistFset.getComponent(0)
+ .getSelectionModel()
+ .getSelections();
+ var store = this.WhitelistFset.getComponent(0).getStore();
+
+ this.WhitelistFset.getComponent(0).stopEditing();
+ for (var i = 0; i < selections.length; i++) store.remove(selections[i]);
+ store.commitChanges();
+ },
+
+ onDestroy: function () {
+ Ext.TaskMgr.stop(this.updateTask);
+
+ deluge.preferences.un('show', this.updateConfig, this);
+
+ Deluge.ux.preferences.BlocklistPage.superclass.onDestroy.call(this);
+ },
+});
+
+Deluge.plugins.BlocklistPlugin = Ext.extend(Deluge.Plugin, {
+ name: 'Blocklist',
+
+ onDisable: function () {
+ deluge.preferences.removePage(this.prefsPage);
+ },
+
+ onEnable: function () {
+ this.prefsPage = deluge.preferences.addPage(
+ new Deluge.ux.preferences.BlocklistPage()
+ );
+ },
+});
+
+Deluge.registerPlugin('Blocklist', Deluge.plugins.BlocklistPlugin);
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist16.png b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist16.png
new file mode 100644
index 0000000..15b4299
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist16.png
Binary files differ
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_download24.png b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_download24.png
new file mode 100644
index 0000000..6de3a0d
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_download24.png
Binary files differ
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_import24.png b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_import24.png
new file mode 100644
index 0000000..f1a02e7
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_import24.png
Binary files differ
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_pref.ui b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_pref.ui
new file mode 100644
index 0000000..8c1f7a7
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_pref.ui
@@ -0,0 +1,603 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">1</property>
+ <property name="upper">100</property>
+ <property name="value">1</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkWindow" id="window1">
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="blocklist_prefs_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">URL:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_url">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="ypad">5</property>
+ <property name="label" translatable="yes">&lt;b&gt;General&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">5</property>
+ <property name="row_spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Days</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_check_days">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Check for new list every (days):</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_import_on_start">
+ <property name="label" translatable="yes">Import blocklist on startup</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="ypad">5</property>
+ <property name="label" translatable="yes">&lt;b&gt;Settings&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="xscale">0</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkButton" id="button_check_download">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Download the blocklist file if necessary and import the file.</property>
+ <signal name="clicked" handler="on_button_check_download_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image_download">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-missing-image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Check Download and Import</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_force_download">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Download a new blocklist file and import it.</property>
+ <signal name="clicked" handler="on_button_force_download_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image_import">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-missing-image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Force Download and Import</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image_up_to_date">
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Blocklist is up to date</property>
+ <property name="yalign">0.15000000596046448</property>
+ <property name="xpad">2</property>
+ <property name="stock">gtk-yes</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="ypad">5</property>
+ <property name="label" translatable="yes">&lt;b&gt;Options&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkProgressBar" id="progressbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table_info">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label_url">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_type">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_modified">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_filesize">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">URL:</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Type:</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Date:</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">File Size:</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Info&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="whitelist_frame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkTreeView" id="whitelist_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="headers_clickable">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVButtonBox" id="vbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <property name="layout_style">start</property>
+ <child>
+ <object class="GtkButton" id="whitelist_add">
+ <property name="label">gtk-add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_whitelist_add_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="whitelist_delete">
+ <property name="label">gtk-delete</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_whitelist_remove_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Whitelist&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/decompressers.py b/deluge/plugins/Blocklist/deluge_blocklist/decompressers.py
new file mode 100644
index 0000000..cd2ee8c
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/decompressers.py
@@ -0,0 +1,44 @@
+#
+# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+# pylint: disable=redefined-builtin
+
+import bz2
+import gzip
+import zipfile
+
+
+def Zipped(reader): # NOQA: N802
+ """Blocklist reader for zipped blocklists"""
+
+ def _open(self):
+ z = zipfile.ZipFile(self.file)
+ f = z.open(z.namelist()[0])
+ return f
+
+ reader.open = _open
+ return reader
+
+
+def GZipped(reader): # NOQA: N802
+ """Blocklist reader for gzipped blocklists"""
+
+ def _open(self):
+ return gzip.open(self.file)
+
+ reader.open = _open
+ return reader
+
+
+def BZipped2(reader): # NOQA: N802
+ """Blocklist reader for bzipped2 blocklists"""
+
+ def _open(self):
+ return bz2.BZ2File(self.file)
+
+ reader.open = _open
+ return reader
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/detect.py b/deluge/plugins/Blocklist/deluge_blocklist/detect.py
new file mode 100644
index 0000000..43ad305
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/detect.py
@@ -0,0 +1,48 @@
+#
+# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from .decompressers import BZipped2, GZipped, Zipped
+from .readers import EmuleReader, PeerGuardianReader, SafePeerReader
+
+COMPRESSION_TYPES = {b'PK': 'Zip', b'\x1f\x8b': 'GZip', b'BZ': 'BZip2'}
+
+DECOMPRESSERS = {'Zip': Zipped, 'GZip': GZipped, 'BZip2': BZipped2}
+
+READERS = {
+ 'Emule': EmuleReader,
+ 'SafePeer': SafePeerReader,
+ 'PeerGuardian': PeerGuardianReader,
+}
+
+
+class UnknownFormatError(Exception):
+ pass
+
+
+def detect_compression(filename):
+ with open(filename, 'rb') as _file:
+ magic_number = _file.read(2)
+ return COMPRESSION_TYPES.get(magic_number, '')
+
+
+def detect_format(filename, compression=''):
+ file_format = ''
+ for reader in READERS:
+ if create_reader(reader, compression)(filename).is_valid():
+ file_format = reader
+ break
+ return file_format
+
+
+def create_reader(file_format, compression=''):
+ reader = READERS.get(file_format)
+ if reader and compression:
+ decompressor = DECOMPRESSERS.get(compression)
+ if decompressor:
+ reader = decompressor(reader)
+ return reader
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/gtkui.py b/deluge/plugins/Blocklist/deluge_blocklist/gtkui.py
new file mode 100644
index 0000000..e6105cd
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/gtkui.py
@@ -0,0 +1,254 @@
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+import logging
+from datetime import datetime
+
+import gi # isort:skip (Required before Gtk import).
+
+gi.require_version('Gtk', '3.0')
+
+# isort:imports-thirdparty
+from gi.repository import Gtk
+
+# isort:imports-firstparty
+import deluge.common
+import deluge.component as component
+from deluge.plugins.pluginbase import Gtk3PluginBase
+from deluge.ui.client import client
+
+# isort:imports-localfolder
+from . import common
+
+log = logging.getLogger(__name__)
+
+
+class GtkUI(Gtk3PluginBase):
+ def enable(self):
+ log.debug('Blocklist GtkUI enable..')
+ self.plugin = component.get('PluginManager')
+
+ self.load_preferences_page()
+
+ self.status_item = component.get('StatusBar').add_item(
+ image=common.get_resource('blocklist16.png'),
+ text='',
+ callback=self._on_status_item_clicked,
+ tooltip=_('Blocked IP Ranges /Whitelisted IP Ranges'),
+ )
+
+ # Register some hooks
+ self.plugin.register_hook('on_apply_prefs', self._on_apply_prefs)
+ self.plugin.register_hook('on_show_prefs', self._on_show_prefs)
+
+ def disable(self):
+ log.debug('Blocklist GtkUI disable..')
+
+ # Remove the preferences page
+ self.plugin.remove_preferences_page(_('Blocklist'))
+
+ # Remove status item
+ component.get('StatusBar').remove_item(self.status_item)
+ del self.status_item
+
+ # Deregister the hooks
+ self.plugin.deregister_hook('on_apply_prefs', self._on_apply_prefs)
+ self.plugin.deregister_hook('on_show_prefs', self._on_show_prefs)
+
+ del self.glade
+
+ def update(self):
+ def _on_get_status(status):
+ if status['state'] == 'Downloading':
+ self.table_info.hide()
+ self.builder.get_object('button_check_download').set_sensitive(False)
+ self.builder.get_object('button_force_download').set_sensitive(False)
+ self.builder.get_object('image_up_to_date').hide()
+
+ self.status_item.set_text(
+ 'Downloading %.2f%%' % (status['file_progress'] * 100)
+ )
+ self.progress_bar.set_text(
+ 'Downloading %.2f%%' % (status['file_progress'] * 100)
+ )
+ self.progress_bar.set_fraction(status['file_progress'])
+ self.progress_bar.show()
+
+ elif status['state'] == 'Importing':
+ self.table_info.hide()
+ self.builder.get_object('button_check_download').set_sensitive(False)
+ self.builder.get_object('button_force_download').set_sensitive(False)
+ self.builder.get_object('image_up_to_date').hide()
+
+ self.status_item.set_text('Importing ' + str(status['num_blocked']))
+ self.progress_bar.set_text('Importing %s' % (status['num_blocked']))
+ self.progress_bar.pulse()
+ self.progress_bar.show()
+
+ elif status['state'] == 'Idle':
+ self.progress_bar.hide()
+ self.builder.get_object('button_check_download').set_sensitive(True)
+ self.builder.get_object('button_force_download').set_sensitive(True)
+ if status['up_to_date']:
+ self.builder.get_object('image_up_to_date').show()
+ else:
+ self.builder.get_object('image_up_to_date').hide()
+
+ self.table_info.show()
+ self.status_item.set_text('%(num_blocked)s/%(num_whited)s' % status)
+
+ self.builder.get_object('label_filesize').set_text(
+ deluge.common.fsize(status['file_size'])
+ )
+ self.builder.get_object('label_modified').set_text(
+ datetime.fromtimestamp(status['file_date']).strftime('%c')
+ )
+ self.builder.get_object('label_type').set_text(status['file_type'])
+ self.builder.get_object('label_url').set_text(status['file_url'])
+
+ client.blocklist.get_status().addCallback(_on_get_status)
+
+ def _on_show_prefs(self):
+ def _on_get_config(config):
+ log.trace('Loaded config: %s', config)
+ self.builder.get_object('entry_url').set_text(config['url'])
+ self.builder.get_object('spin_check_days').set_value(
+ config['check_after_days']
+ )
+ self.builder.get_object('chk_import_on_start').set_active(
+ config['load_on_start']
+ )
+ self.populate_whitelist(config['whitelisted'])
+
+ client.blocklist.get_config().addCallback(_on_get_config)
+
+ def _on_apply_prefs(self):
+ config = {}
+ config['url'] = self.builder.get_object('entry_url').get_text().strip()
+ config['check_after_days'] = self.builder.get_object(
+ 'spin_check_days'
+ ).get_value_as_int()
+ config['load_on_start'] = self.builder.get_object(
+ 'chk_import_on_start'
+ ).get_active()
+ config['whitelisted'] = [
+ ip[0] for ip in self.whitelist_model if ip[0] != 'IP HERE'
+ ]
+ client.blocklist.set_config(config)
+
+ def _on_button_check_download_clicked(self, widget):
+ self._on_apply_prefs()
+ client.blocklist.check_import()
+
+ def _on_button_force_download_clicked(self, widget):
+ self._on_apply_prefs()
+ client.blocklist.check_import(force=True)
+
+ def _on_status_item_clicked(self, widget, event):
+ component.get('Preferences').show(_('Blocklist'))
+
+ def load_preferences_page(self):
+ """Initializes the preferences page and adds it to the preferences dialog"""
+ # Load the preferences page
+ self.builder = Gtk.Builder()
+ self.builder.add_from_file(common.get_resource('blocklist_pref.ui'))
+
+ self.whitelist_frame = self.builder.get_object('whitelist_frame')
+ self.progress_bar = self.builder.get_object('progressbar')
+ self.table_info = self.builder.get_object('table_info')
+
+ # Hide the progress bar initially
+ self.progress_bar.hide()
+ self.table_info.show()
+
+ # Create the whitelisted model
+ self.build_whitelist_model_treeview()
+
+ self.builder.connect_signals(
+ {
+ 'on_button_check_download_clicked': self._on_button_check_download_clicked,
+ 'on_button_force_download_clicked': self._on_button_force_download_clicked,
+ 'on_whitelist_add_clicked': (
+ self.on_add_button_clicked,
+ self.whitelist_treeview,
+ ),
+ 'on_whitelist_remove_clicked': (
+ self.on_delete_button_clicked,
+ self.whitelist_treeview,
+ ),
+ }
+ )
+
+ # Set button icons
+ self.builder.get_object('image_download').set_from_file(
+ common.get_resource('blocklist_download24.png')
+ )
+
+ self.builder.get_object('image_import').set_from_file(
+ common.get_resource('blocklist_import24.png')
+ )
+
+ # Update the preferences page with config values from the core
+ self._on_show_prefs()
+
+ # Add the page to the preferences dialog
+ self.plugin.add_preferences_page(
+ _('Blocklist'), self.builder.get_object('blocklist_prefs_box')
+ )
+
+ def build_whitelist_model_treeview(self):
+ self.whitelist_treeview = self.builder.get_object('whitelist_treeview')
+ treeview_selection = self.whitelist_treeview.get_selection()
+ treeview_selection.connect(
+ 'changed', self.on_whitelist_treeview_selection_changed
+ )
+ self.whitelist_model = Gtk.ListStore(str, bool)
+ renderer = Gtk.CellRendererText()
+ renderer.connect('edited', self.on_cell_edited, self.whitelist_model)
+ renderer.ip = 0
+
+ column = Gtk.TreeViewColumn('IPs', renderer, text=0, editable=1)
+ column.set_expand(True)
+ self.whitelist_treeview.append_column(column)
+ self.whitelist_treeview.set_model(self.whitelist_model)
+
+ def on_cell_edited(self, cell, path_string, new_text, model):
+ # iter = model.get_iter_from_string(path_string)
+ # path = model.get_path(iter)[0]
+ try:
+ ip = common.IP.parse(new_text)
+ model.set(model.get_iter_from_string(path_string), 0, ip.address)
+ except common.BadIP as ex:
+ model.remove(model.get_iter_from_string(path_string))
+ from deluge.ui.gtkui import dialogs
+
+ d = dialogs.ErrorDialog(_('Bad IP address'), ex.message)
+ d.run()
+
+ def on_whitelist_treeview_selection_changed(self, selection):
+ model, selected_connection_iter = selection.get_selected()
+ if selected_connection_iter:
+ self.builder.get_object('whitelist_delete').set_property('sensitive', True)
+ else:
+ self.builder.get_object('whitelist_delete').set_property('sensitive', False)
+
+ def on_add_button_clicked(self, widget, treeview):
+ model = treeview.get_model()
+ model.set(model.append(), 0, 'IP HERE', 1, True)
+
+ def on_delete_button_clicked(self, widget, treeview):
+ selection = treeview.get_selection()
+ model, selected_iter = selection.get_selected()
+ if selected_iter:
+ # path = model.get_path(iter)[0]
+ model.remove(selected_iter)
+
+ def populate_whitelist(self, whitelist):
+ self.whitelist_model.clear()
+ for ip in whitelist:
+ self.whitelist_model.set(self.whitelist_model.append(), 0, ip, 1, True)
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/peerguardian.py b/deluge/plugins/Blocklist/deluge_blocklist/peerguardian.py
new file mode 100644
index 0000000..b5fb181
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/peerguardian.py
@@ -0,0 +1,66 @@
+#
+# Copyright (C) 2007 Steve 'Tarka' Smith (tarka@internode.on.net)
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+import gzip
+import logging
+import socket
+from struct import unpack
+
+log = logging.getLogger(__name__)
+
+
+class PGException(Exception):
+ pass
+
+
+# Incrementally reads PeerGuardian blocklists v1 and v2.
+# See http://wiki.phoenixlabs.org/wiki/P2B_Format
+class PGReader:
+ def __init__(self, filename):
+ log.debug('PGReader loading: %s', filename)
+
+ try:
+ with gzip.open(filename, 'rb') as _file:
+ self.fd = _file
+ except OSError:
+ log.debug('Blocklist: PGReader: Incorrect file type or list is corrupt')
+
+ # 4 bytes, should be 0xffffffff
+ buf = self.fd.read(4)
+ hdr = unpack('l', buf)[0]
+ if hdr != -1:
+ raise PGException(_('Invalid leader') + ' %d' % hdr)
+
+ magic = self.fd.read(3)
+ if magic != 'P2B':
+ raise PGException(_('Invalid magic code'))
+
+ buf = self.fd.read(1)
+ ver = ord(buf)
+ if ver != 1 and ver != 2:
+ raise PGException(_('Invalid version') + ' %d' % ver)
+
+ def __next__(self):
+ # Skip over the string
+ buf = -1
+ while buf != 0:
+ buf = self.fd.read(1)
+ if buf == '': # EOF
+ return False
+ buf = ord(buf)
+
+ buf = self.fd.read(4)
+ start = socket.inet_ntoa(buf)
+
+ buf = self.fd.read(4)
+ end = socket.inet_ntoa(buf)
+
+ return (start, end)
+
+ def close(self):
+ self.fd.close()
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/readers.py b/deluge/plugins/Blocklist/deluge_blocklist/readers.py
new file mode 100644
index 0000000..14230ed
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/readers.py
@@ -0,0 +1,99 @@
+#
+# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+import logging
+import re
+
+from deluge.common import decode_bytes
+
+from .common import IP, BadIP, raises_errors_as
+
+log = logging.getLogger(__name__)
+
+
+class ReaderParseError(Exception):
+ pass
+
+
+class BaseReader:
+ """Base reader for blocklist files"""
+
+ def __init__(self, _file):
+ """Creates a new BaseReader given a file"""
+ self.file = _file
+
+ def open(self):
+ """Opens the associated file for reading"""
+ return open(self.file)
+
+ def parse(self, line):
+ """Extracts ip range from given line"""
+ raise NotImplementedError
+
+ def read(self, callback):
+ """Calls callback on each ip range in the file"""
+ for start, end in self.readranges():
+ try:
+ callback(IP.parse(start), IP.parse(end))
+ except BadIP as ex:
+ log.error('Failed to parse IP: %s', ex)
+ return self.file
+
+ def is_ignored(self, line):
+ """Ignore commented lines and blank lines"""
+ line = line.strip()
+ return line.startswith('#') or not line
+
+ def is_valid(self):
+ """Determines whether file is valid for this reader"""
+ blocklist = self.open()
+ valid = True
+ for line in blocklist:
+ line = decode_bytes(line)
+ if not self.is_ignored(line):
+ try:
+ (start, end) = self.parse(line)
+ if not re.match(r'^(\d{1,3}\.){4}$', start + '.') or not re.match(
+ r'^(\d{1,3}\.){4}$', end + '.'
+ ):
+ valid = False
+ except Exception:
+ valid = False
+ break
+ blocklist.close()
+ return valid
+
+ @raises_errors_as(ReaderParseError)
+ def readranges(self):
+ """Yields each ip range from the file"""
+ blocklist = self.open()
+ for line in blocklist:
+ line = decode_bytes(line)
+ if not self.is_ignored(line):
+ yield self.parse(line)
+ blocklist.close()
+
+
+class EmuleReader(BaseReader):
+ """Blocklist reader for emule style blocklists"""
+
+ def parse(self, line):
+ return line.strip().split(' , ')[0].split(' - ')
+
+
+class SafePeerReader(BaseReader):
+ """Blocklist reader for SafePeer style blocklists"""
+
+ def parse(self, line):
+ return line.strip().split(':')[-1].split('-')
+
+
+class PeerGuardianReader(SafePeerReader):
+ """Blocklist reader for PeerGuardian style blocklists"""
+
+ pass
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/webui.py b/deluge/plugins/Blocklist/deluge_blocklist/webui.py
new file mode 100644
index 0000000..8ba4911
--- /dev/null
+++ b/deluge/plugins/Blocklist/deluge_blocklist/webui.py
@@ -0,0 +1,27 @@
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+import logging
+
+from deluge.plugins.pluginbase import WebPluginBase
+
+from .common import get_resource
+
+log = logging.getLogger(__name__)
+
+FORMAT_LIST = [
+ ('gzmule', _('Emule IP list (GZip)')),
+ ('spzip', _('SafePeer Text (Zipped)')),
+ ('pgtext', _('PeerGuardian Text (Uncompressed)')),
+ ('p2bgz', _('PeerGuardian P2B (GZip)')),
+]
+
+
+class WebUI(WebPluginBase):
+ scripts = [get_resource('blocklist.js')]
+ debug_scripts = scripts