From e0c5820bf0f1e40d22950634b6292d5b75827b14 Mon Sep 17 00:00:00 2001 From: Mark Stahler Date: Sun, 9 Mar 2008 06:05:00 +0000 Subject: [PATCH] Initial version of blocklist for 0.6 --- deluge/data/pixmaps/blocklist16.png | Bin 0 -> 705 bytes deluge/data/pixmaps/blocklist_import24.png | Bin 0 -> 1240 bytes .../plugins/blocklist/blocklist/__init__.py | 56 +++ deluge/plugins/blocklist/blocklist/core.py | 119 ++++++ deluge/plugins/blocklist/blocklist/gtkui.py | 306 ++++++++++++++ .../blocklist/blocklist/peerguardian.py | 61 +++ deluge/plugins/blocklist/blocklist/text.py | 142 +++++++ .../blocklist/blocklist/torrentblocklist.py | 372 ++++++++++++++++++ deluge/plugins/blocklist/blocklist/ui.py | 53 +++ deluge/plugins/blocklist/setup.py | 54 +++ 10 files changed, 1163 insertions(+) create mode 100644 deluge/data/pixmaps/blocklist16.png create mode 100644 deluge/data/pixmaps/blocklist_import24.png create mode 100644 deluge/plugins/blocklist/blocklist/__init__.py create mode 100644 deluge/plugins/blocklist/blocklist/core.py create mode 100644 deluge/plugins/blocklist/blocklist/gtkui.py create mode 100644 deluge/plugins/blocklist/blocklist/peerguardian.py create mode 100644 deluge/plugins/blocklist/blocklist/text.py create mode 100644 deluge/plugins/blocklist/blocklist/torrentblocklist.py create mode 100644 deluge/plugins/blocklist/blocklist/ui.py create mode 100644 deluge/plugins/blocklist/setup.py diff --git a/deluge/data/pixmaps/blocklist16.png b/deluge/data/pixmaps/blocklist16.png new file mode 100644 index 0000000000000000000000000000000000000000..58240de7db77f74943870bc06dea91cdac51a288 GIT binary patch literal 705 zcmV;y0zUnTP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FZT01FZU(%pXi00007bV*G`2iO7{ z5+o(klahu200KctL_t(IjfIm>NRx3G$3MS!@4Kn*Og61hHX9lR)uD`{WEh0)U?H(6 ziomeKbrI?&-Xk)S4tDF>#i)xH=}@*6v@N1qt1zg!4dqpwv+2hEzFkr_o%X=*^nAV_ ze$NAsVEbyeSS(#a2$4u6UIW@jKx`3IIy*Z@T`t!xtJNwelSx9M(2CdVy{Kv0WI1T4 z5|(;;dd3F_2d~Y|%}McioKz}>EXziqD;kYHs(?49?RNVmkH_N<27^Q*5yIi{hjcof zFBA#@t^eNE0;kj2rzi?OpN~Kw@IupOM%vq3bBdynOeO)$+X@^GM{6t=!(h-Edw9z2 zpXwjK-gi_r8jaX(wG0m(ySJra;AYp|j>DGynM?+wV&Jsv2Ij_YetwA~gg|7{^j~%K z0!P*|a#`MT*)0vrh8({lI)z*wy_7(REc@CQIPYAfrPoH`R~Vq7nm}F-|G=`V27TVd zmF@=gQXIXMrJg8<%wgu=erHK8RE%6Mpsv}(!9DNjIPON0ilo1dur&Ld32&YgZ30!* zsoSu#O-nTIHlgl4i)yQ`nkO)dZ_4Z83|EiQ>|FtFZy@-_DYp)`^+O zPE0Wd1gi^4a-cSBxNcT nEaaDd{p*azE8x#YZms?T-dxR4E|Q|j00000NkvXXu0mjf{RTZi literal 0 HcmV?d00001 diff --git a/deluge/data/pixmaps/blocklist_import24.png b/deluge/data/pixmaps/blocklist_import24.png new file mode 100644 index 0000000000000000000000000000000000000000..ed2c75b222ac9b4c4c16bd87ff7a1c1166874d75 GIT binary patch literal 1240 zcmV;}1Sk86P);qd0wt7Cf+7d-MF(^Rn0b z!hgKfoj!PXZ*(e-qfH@%ei7*rL8I_msVoGM*FN#t z;sqL+qkruhbQF`MDaAq?!yg?cilSUAu7Ql$piT~cY!5B>jITeu|6KgWd$4bP--fok z@7dnbziEqZ?;hajhwsqS(@S~m6v>hhYiOi~&U<$Ou;=-~wxqGRrB7kw`eV-yuG{tKlU;WW^y>C}oKmsL7oTC$8cCXfc$~9H@BnD7h!)PX zu749zT;!2GuM%mMtw<1QRWC%+m0tOgMtzA=F{bEE6A%Jkgj(Iu-O&afoVBgGdV*O( zyrH9aBgM#rx(U!X>#rJlQb9#ULWviZ0uqF3-O$+{H;n*m48{ejmCNXmQZ*fT zr7x^VV7;8Hl;`oP9Up|;c)ZQ=qNvtW)^Fa*`pw&@)^n87B$>el)k+{37jP6Qm&?u= z^~(x?apC95>6yBZI`9FE4H%OVM3ZGXrOrM|U41lii}&E1!;8k4ylEZ;Q6(m(t~IQv z?^gn%!nao^rX#OQv~SpfZXdwrIYAV$RwPRcG-?YJqlna6RPgwqvAMyzKp-YHFuzbK zY$H9h0!^sYYfijdK48q=w*|0BbD*=tyH-zzd;YQ=LA-<6yckL0}9P~g4i~_*$0000 +# Copyright (C) 2008 Mark Stahler ('kramed') + +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from deluge.log import LOG as log + +from deluge.plugins.init import PluginBase + +class CorePlugin(PluginBase): + def __init__(self, plugin_api, plugin_name): + # Load the Core portion of the plugin + try: + from core import Core + self.plugin = Core(plugin_api, plugin_name) + except Exception, e: + log.debug("Did not load a Core plugin: %s", e) + +class GtkUIPlugin(PluginBase): + def __init__(self, plugin_api, plugin_name): + # Load the GtkUI portion of the plugin + try: + from gtkui import GtkUI + self.plugin = GtkUI(plugin_api, plugin_name) + except Exception, e: + log.debug("Did not load a GtkUI plugin: %s", e) diff --git a/deluge/plugins/blocklist/blocklist/core.py b/deluge/plugins/blocklist/blocklist/core.py new file mode 100644 index 000000000..f68bddeb0 --- /dev/null +++ b/deluge/plugins/blocklist/blocklist/core.py @@ -0,0 +1,119 @@ +# +# blocklist/core.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# Copyright (C) 2008 Mark Stahler ('kramed') + +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +from torrentblocklist import TorrentBlockList +from deluge.log import LOG as log +from deluge.plugins.corepluginbase import CorePluginBase + +class Core(CorePluginBase): + def enable(self): + self.blocklist = TorrentBlockList(self.plugin) + self.plugin.register_hook("post_session_load", self._post_session_load) + log.debug('Blocklist: Plugin enabled..') + + def disable(self): + log.debug('Blocklist: Plugin disabled') + self.plugin.deregister_hook("post_session_load", self._post_session_load) + self.plugin.reset_ip_filter() + # Delete the blocklist object + del self.blocklist + self.blocklist = None + + def update(self): + pass + + ## Hooks for core ## + def _post_session_load(self): + log.info('Blocklist: Session load hook caught') + if self.blocklist.load_on_start == True or self.blocklist.load_on_start == 'True': + # Wait until an idle time to load block list + import gobject + gobject.idle_add(self.blocklist.import_list) + + ## Callbacks + def register_import_hook(self): + self.plugin.register_hook("post_session_load", self._post_session_load) + return False + + # Exported functions for ui + def export_critical_setting(self): + if self.blocklist.old_url != self.blocklist.url or self.blocklist.old_listtype != self.blocklist.listtype: + log.info('Blocklist: Critical setting changed') + self.blocklist.download() + self.blocklist.import_list() + self.blocklist.return_count() # Return count after import + + # New settings are now old settings + self.blocklist.old_url = self.blocklist.url + self.blocklist.old_listtype = self.blocklist.listtype + + def export_count_ips(self): + log.debug('Blocklist: Count IPs imported into blocklist') + return self.blocklist.return_count() + + def export_import_list(self): + log.debug('Blocklist: Import started from GTK UI') + self.blocklist.import_list() + + def export_set_options(self, settings): + log.debug("Blocklist: Set Options") + self.blocklist.set_options(settings) + + def export_get_options(self): + log.debug("Blocklist: Get Options") + settings_dict = { + "url": self.blocklist.url, + "listtype": self.blocklist.listtype, + "check_after_days": self.blocklist.check_after_days, + "load_on_start":self.blocklist.load_on_start, + "try_times": self.blocklist.try_times, + "timeout": self.blocklist.timeout + } + log.info(settings_dict) + return settings_dict + + def export_get_config_value(self, key): + log.debug("Blocklist: Get configuration setting") + return self.blocklist.get_config_value(key) + + def export_set_config_value(self, key): + log.debug("Blocklist: Set configuration setting") + return self.blocklist.set_config_value(key) + + def export_get_formats(self): + log.debug('Blocklist: Get Reader Formats') + return self.blocklist.return_formats() + + \ No newline at end of file diff --git a/deluge/plugins/blocklist/blocklist/gtkui.py b/deluge/plugins/blocklist/blocklist/gtkui.py new file mode 100644 index 000000000..d52cab641 --- /dev/null +++ b/deluge/plugins/blocklist/blocklist/gtkui.py @@ -0,0 +1,306 @@ +# +# blocklist/gtkui.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# Copyright (C) 2008 Mark Stahler ('kramed') + +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import pkg_resources # remove possibly, double check +from deluge.log import LOG as log +import deluge.common # for pixmap +import deluge.component # for systray +import ui +import gtk, gobject + +FORMATS = { + 'gzmule': ["Emule IP list (GZip)", None], + 'spzip': ["SafePeer Text (Zipped)", None], + 'pgtext': ["PeerGuardian Text (Uncompressed)", None], + 'p2bgz': ["PeerGuardian P2B (GZip)", None] +} + +class GtkUI(ui.UI): + def __init__(self, plugin_api, plugin_name): + log.debug("Calling UI init") + # Call UI constructor + ui.UI.__init__(self, plugin_api, plugin_name) + log.debug("Blocklist GtkUI plugin initalized..") + + def enable(self): + self.plugin.register_hook("on_apply_prefs", self.apply_prefs) + + # Import File Format Readers from TorrentBlocklist + #ui.client.block_list_get_formats(self.callback_load_formats) # cant marshal classobj objects - wtf + + # Update Blocked IP status bar number - BETTER METHOD TO FOLLOW + # emit a blocklsit import signal + gobject.timeout_add(5000, self.get_ip_count) + gobject.timeout_add(10000, self.get_ip_count) + gobject.timeout_add(20000, self.get_ip_count) + + self.load_interface() + + def disable(self): + deluge.component.get("StatusBar").remove_item(self.blocklist_status) + self.plugin.deregister_hook("on_apply_prefs", self.apply_prefs) + + def add_status_icon(self, ip_count): + try: + deluge.component.get("StatusBar").remove_item(self.blocklist_status) + except: + pass + # self, image=None, stock=None, text=None, callback=None + self.blocklist_status = deluge.component.get("StatusBar").add_item(deluge.common.get_pixmap("blocklist16.png"), None, str(ip_count) + " Blocked IP Ranges ", None) + + def get_ip_count(self): + ui.client.block_list_count_ips(self.add_status_icon) + return False + + def start_import(self, widget, data=None): + ui.client.block_list_import_list(None) + log.debug('Blocklist: Import button') + gobject.timeout_add(20000, self.get_ip_count) + + def unload_interface(self): + self.plugin.remove_preferences_page("Blocklist") + +# def get_active_text(combobox): +# model = combobox.get_model() +# active = combobox.get_active() +# if active < 0: +# return None +# return model[active][0] + + def load_interface(self): + log.debug("Beginning gtk pane initialization") + self.blocklist_pref_page = gtk.VBox(False, 3) + self.blocklist_pref_page.set_spacing(6) + + label = gtk.Label() + label.set_markup('' + 'General' + '') + + frame = gtk.Frame() + frame.set_shadow_type(gtk.SHADOW_NONE) + + alignment = gtk.Alignment(0.5, 0.5, 1, 1) + alignment.set_padding(8, 5, 5, 5) + + table = gtk.Table(7, 2, False) + table.set_col_spacings(8) + table.set_row_spacings(10) + + # First row + label0 = gtk.Label() + label0.set_text('Blocklist Type') + + ls = gtk.ListStore(gobject.TYPE_STRING, # Long name + gobject.TYPE_STRING) # Short name + + self.listtype = gtk.ComboBox(model=ls) + cell = gtk.CellRendererText() + cell.set_property('xpad', 5) # padding for status text + self.listtype.pack_start(cell, False) + self.listtype.add_attribute(cell, 'text', 0) + + for k in FORMATS.keys(): + i = ls.append([FORMATS[k][0], k]) + FORMATS[k][1] = ls.get_path(i) + + self.listtype.set_active(0) + + table.attach(label0, 0, 1, 0 , 1) + table.attach(self.listtype, 1, 2, 0, 1) + + # Second row + label1 = gtk.Label() + label1.set_text('Blocklist URL') + + self.url = gtk.Entry() + + table.attach(label1, 0, 1, 1, 2) + table.attach(self.url, 1, 2, 1, 2) + + # Third row + label2 = gtk.Label() + label2.set_text('Check for a new blocklist every') + + self.check_after_days = gtk.SpinButton(None, 1.0, 0) + self.check_after_days.set_increments(1, 3) + self.check_after_days.set_range(-1, 14) + + label3 = gtk.Label() + label3.set_text('days') + + hbox = gtk.HBox(False, 3) + hbox.pack_start(label2, False, True) + hbox.pack_start(self.check_after_days, False, False, 4) + hbox.pack_start(label3) + + alignment.add(table) + table.attach(hbox, 0, 2, 2, 3) + + # Fourth row + label4 = gtk.Label() + label4.set_text('Timeout to download new blocklist') + + self.timeout = gtk.SpinButton(None, 5.0, 0) + self.timeout.set_increments(5, 20) + self.timeout.set_range(15, 360) + + label5 = gtk.Label() + label5.set_text('seconds') + + hbox1 = gtk.HBox(False, 3) + hbox1.pack_start(label4, False, True) + hbox1.pack_start(self.timeout) + hbox1.pack_start(label5, False, True) + + table.attach(hbox1, 0, 2, 3, 4) + + # Fifth row + label5 = gtk.Label() + label5.set_text('Times to attempt download of new list') + + self.try_times = gtk.SpinButton(None, 1.0, 0) + self.try_times.set_increments(1, 2) + self.try_times.set_range(1, 5) + + hbox2 = gtk.HBox(False, 3) + hbox2.pack_start(label5, False, True) + hbox2.pack_start(self.try_times) + + table.attach(hbox2, 0, 2, 4, 5) + + # sixth row + self.load_on_start = gtk.CheckButton('Import blocklist on daemon startup') + table.attach(self.load_on_start, 0, 2, 5, 6) + + # DO I NEED THIS STILL I DONT KNOW THINK ABOUT IT AND ASK MYSELF AGAIN LATER K THX BYE + +# # import button (Check and possibly download) + import_button = gtk.Button("_Import Blocklist", None, True) + import_button.connect("clicked", self.start_import, None) + + pixbuf = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("blocklist_import24.png")) + image = gtk.image_new_from_pixbuf(pixbuf) + import_button.set_image(image) + + table.attach(import_button, 0, 2, 6, 7) + + # finish frame + frame.set_label_widget(label) + frame.add(alignment) + + self.blocklist_pref_page.pack_start(frame) + + # Add preferences page to preferences page + log.debug('Adding Blocklist Preferences page') + self.plugin.add_preferences_page("Blocklist", self.blocklist_pref_page) + + # Load settings from config and fill widgets with settings + log.debug('Starting to load blocklist preferences') + self.fetch_prefs() + log.debug('Finished loading blocklist preferences') + + + def fetch_prefs(self): # Fetch settings dictionary from plugin core and pass it to GTK ui settings + log.info('Blocklist: Fetching and loading Preferences via GTK ui') + ui.client.block_list_get_options(self.callback_load_prefs) + + def apply_prefs(self): + log.info('Blocklist: Preferences saved via Gtk ui') + settings_dict = { + "url": self.url.get_text(), + "listtype": self.get_ltype(), + "check_after_days": self.check_after_days.get_value_as_int(), + "load_on_start":self.load_on_start.get_active(), + "try_times": self.try_times.get_value_as_int(), + "timeout": self.timeout.get_value_as_int() + } + ui.client.block_list_set_options(None, settings_dict) + # Needs to go in another thread or wait until window is closed + gobject.idle_add(self.call_critical_setting) + + def call_critical_setting(self): + ui.client.block_list_critical_setting(None) # This checks to see if url or listtype changed, if so download & import + self.get_ip_count() + + # GTK Gui Callback functions + def callback_load_prefs(self, dict): + log.info('Blocklist: Callback Load Prefs GTK ui') + self.settings_url(dict['url']) + self.settings_listtype(dict['listtype']) + self.settings_load(dict['load_on_start']) + self.settings_check_after_days(dict['check_after_days']) + self.settings_timeout(dict['timeout']) + self.settings_try_times(dict['try_times']) + + # Specific function to return proper listtype so we can save and open it again + def get_ltype(self): + ls = self.listtype.get_model() + log.debug(ls) + ltype = ls[self.listtype.get_active()][1] + log.debug(ltype) + return ltype + + # Settings functions + def settings_listtype(self, setting): + log.debug('Blocklist: listtype') + path = FORMATS[setting][1] + i = self.listtype.get_model().get_iter(path) + self.listtype.set_active_iter(i) + + def settings_url(self, setting): + log.debug('Blocklist: url') + self.url.set_text(setting) + + def settings_check_after_days(self, setting): + log.debug('Blocklist: days') + self.check_after_days.set_value(setting) + + def settings_load(self, setting): + log.debug('Blocklist: load_on_start') + log.info(setting) + if setting == True or setting == "True": + setting = 1 + else: + setting = 0 + self.load_on_start.set_active(setting) + + def settings_timeout(self, setting): + log.debug('Blocklist: timeout') + self.timeout.set_value(setting) + + def settings_try_times(self, setting): + log.debug('Blocklist: try times') + self.try_times.set_value(setting) + + \ No newline at end of file diff --git a/deluge/plugins/blocklist/blocklist/peerguardian.py b/deluge/plugins/blocklist/blocklist/peerguardian.py new file mode 100644 index 000000000..6a583ef5b --- /dev/null +++ b/deluge/plugins/blocklist/blocklist/peerguardian.py @@ -0,0 +1,61 @@ +## +# Copyright 2007 Steve 'Tarka' Smith (tarka@internode.on.net) +# Distributed under the same terms as Deluge +## + +from exceptions import Exception +from struct import unpack +import gzip, socket + +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): + print "PGReader loading",filename + + try: + self.fd = gzip.open(filename, "rb") + except IOError, e: + print '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: + print "LEADER IS",hdr + 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/blocklist/text.py b/deluge/plugins/blocklist/blocklist/text.py new file mode 100644 index 000000000..522aabc06 --- /dev/null +++ b/deluge/plugins/blocklist/blocklist/text.py @@ -0,0 +1,142 @@ +## +# Copyright 2007 Steve 'Tarka' Smith (tarka@internode.on.net) +# Distributed under the same terms as Deluge +## + + +from exceptions import Exception +import re, gzip, os +from socket import inet_aton +from struct import unpack +from zipfile import ZipFile + +class TextException(Exception): + pass + +class FormatException(TextException): + pass + +class TextBase: + + def __init__(self, fd, regexp): + print "TextBase loading" + self.count = 0 + self.fd = fd + self.re = re.compile(regexp) + + def next(self): + self.count += 1 + + txt = self.fd.readline() + if txt == "": + return False + + match = self.re.search(txt) + if not match: + print 'Blocklist: TextBase: Wrong file type or corrupted blocklist file.' + + try: + g = match.groups() + except AttributeError: + pass + else: + start = ".".join(g[0:4]) + end = ".".join(g[4:8]) + + return (start, end) + + def close(self): + self.fd.close() + + +# This reads PeerGuardian text-formatted block-lists +class PGTextReader(TextBase): + + def __init__(self, fd): + print "PGTextReader loading" + regexp = ':(\d+)\.(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.(\d+)\.(\d+)\s*$' + TextBase.__init__(self, fd, regexp) + + +# This reads uncompressed PG text list +class TextReader(PGTextReader): + + def __init__(self, filename): + print "TextReader loading",filename + try: + PGTextReader.__init__(self, open(filename, 'r')) + except: + print 'Wrong file type or corrupted blocklist file.' + + +# Reads Emule style blocklists (aka nipfilter) +class MuleReader(TextBase): + + def __init__(self, fd): + print "MuleReader loading" + regexp = '0*(\d+)\.0*(\d+)\.0*(\d+)\.0*(\d+)\s*-\s*0*(\d+)\.0*(\d+)\.0*(\d+)\.0*(\d+)\s*,' + TextBase.__init__(self, fd, regexp) + +class GZMuleReader(MuleReader): + + def __init__(self, filename): + print "GZMuleReader loading",filename + try: + MuleReader.__init__(self, gzip.open(filename, "r")) + except: + print 'Wrong file type or corrupted blocklist file.' + + +# Reads zip files from SafePeer style files +class PGZip(TextBase): + + def __init__(self, filename): + # Open zip and extract first file + try: + self.zfd = ZipFile(filename, 'r') + except: + print 'Blocklist: PGZip: Wrong file type or corrupted blocklist file.' + else: + self.files = self.zfd.namelist() + self.opennext() + + def opennext(self): + self.tmp = os.tmpfile() + f = self.files.pop() + print "Loading file",f + self.tmp.write(self.zfd.read(f)) + self.tmp.seek(0) + self.reader = PGTextReader(self.tmp) + + def next(self): + try: + ret = self.reader.next() + if ret == False: + # This bit is repeated below and could be moved into a + # new procedure. However I'm not clear on how this + # would effect tail recursion, so it remains + # broken-out for now. + if len(self.files) > 0: + self.opennext() + return self.next() + else: + # End of zip + return False + return ret + + except FormatException, e: + print 'Blocklist: PGZip: Got format exception for zipfile: ' + # Just skip + if len(self.files) > 0: + self.opennext() + return self.next() + else: + return False + except AttributeError: + pass + + def close(self): + try: + self.zfd.close() + except AttributeError: + pass diff --git a/deluge/plugins/blocklist/blocklist/torrentblocklist.py b/deluge/plugins/blocklist/blocklist/torrentblocklist.py new file mode 100644 index 000000000..31532d1e7 --- /dev/null +++ b/deluge/plugins/blocklist/blocklist/torrentblocklist.py @@ -0,0 +1,372 @@ +# +# blocklist/blocklist.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# Copyright (C) 2008 Mark Stahler ('kramed') + +# a snip or two used from johhnyg + +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +# TODO: +# - ZERO connections until after import +# - download timeouts / retries + +import urllib2, httplib, socket, os, datetime, sys +import deluge.common +import deluge.component # for libtorrent session reference to change connection number +from deluge.log import LOG as log +import ui # added by Mark for pausing torrents +from peerguardian import PGReader, PGException +from text import TextReader, GZMuleReader, PGZip + +BLOCKLIST_PREFS = { + "url": "http://www.bluetack.co.uk/config/pipfilter.dat.gz", + "load_on_start": "True", + "check_after_days": 2, + "listtype": "gzmule", + "timeout": 180, + "try_times": 3 +} + +BACKUP_PREFS = { + "url": "http://www.bluetack.co.uk/config/pipfilter.dat.gz", + "listtype": "gzmule", +} + +FORMATS = { + 'gzmule': ["Emule IP list (GZip)", GZMuleReader, None], + 'spzip': ["SafePeer Text (Zipped)", PGZip, None], + 'pgtext': ["PeerGuardian Text (Uncompressed)", TextReader, None], + 'p2bgz': ["PeerGuardian P2B (GZip)", PGReader, None] +} + +class HTTPConnection(httplib.HTTPConnection): + """A customized HTTPConnection allowing a per-connection timeout, specified at construction.""" + + def __init__(self, host, port=None, strict=None, timeout=None): + httplib.HTTPConnection.__init__(self, host, port, strict) + self.timeout = timeout + + def connect(self): + """Override HTTPConnection.connect to connect to + host/port specified in __init__.""" + + msg = "getaddrinfo returns an empty list" + for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + try: + self.sock = socket.socket(af, socktype, proto) + if self.timeout: + self.sock.settimeout(self.timeout) + self.sock.connect(sa) + except socket.error, msg: + if self.sock: + self.sock.close() + self.sock = None + continue + break + if not self.sock: + raise socket.error, msg + + +class HTTPHandler(urllib2.HTTPHandler): + """A customized HTTPHandler which times out connection after the duration specified at construction.""" + + def __init__(self, timeout=None): + urllib2.HTTPHandler.__init__(self) + self.timeout = timeout + + def http_open(self, req): + """Override http_open.""" + + def makeConnection(host, port=None, strict=None): + return HTTPConnection(host, port, strict, timeout = self.timeout) + + #print "HTTPHandler opening", req.get_full_url() + return self.do_open(makeConnection, req) + + +class TorrentBlockList: + def __init__(self, coreplugin): + self.plugin = coreplugin # reference from plugin core + log.info('Blocklist: TorrentBlockList instantiated') + deluge.component.get("Core").session.set_max_connections(0) + self.config = deluge.configmanager.ConfigManager("blocklist.conf", BLOCKLIST_PREFS) + self.curr = 0 + self.load_options() + + # Make sure we have a current block list file locally + self.fetch = False + self.local_blocklist = deluge.common.get_config_dir("blocklist.cache") + + # Check list for modifications from online version + self.check_update() + + # Initialize download attempt + self.attempt = 0 + + if self.fetch == True: + self.download() + + log.debug('Blocklist: TorrentBlockList Constructor finished') + + + def load_options(self): + #self.config.load() + # Fill variables with settings from block list configuration file + self.url = self.config['url'] + self.listtype = self.config['listtype'] + self.check_after_days = self.config['check_after_days'] + self.load_on_start = self.config['load_on_start'] + self.timeout = self.config['timeout'] + self.try_times = self.config['try_times'] + + self.old_url = self.url + self.old_listtype = self.listtype + + socket.setdefaulttimeout(self.timeout) + + def set_options(self, options): + log.info('Blocklist: Options saved...') + self.config.set('url', options['url']) + self.config.set('check_after_days', options['check_after_days']) + self.config.set('load_on_start', options['load_on_start']) + self.config.set('listtype', options['listtype']) + self.config.set('timeout', options['timeout']) + self.config.set('try_times', options['try_times']) + # Save settings to disk + self.config.save() + # Load newly set options to core plugin + self.load_options() + + def check_update(self): + log.info('Blocklist: Checking for updates') + + try: + # Check current block lists time stamp and decide if it needs to be replaced + list_stats = os.stat(self.local_blocklist) + list_time = datetime.datetime.fromtimestamp(list_stats.st_mtime) + list_size = list_stats.st_size + current_time = datetime.datetime.today() + + except: + self.fetch = True + return + + # If local blocklist file exists but nothing is in it + if list_size == 0: + self.fetch = True + return + + if current_time >= (list_time + datetime.timedelta(self.check_after_days)): + check_newer = True + log.debug('Blocklist: List may need to be replaced') + else: + check_newer = False + + # If the program decides it is time to get a new list + if check_newer == True: + log.debug('Blocklist: Attempting check') + + j = 0 # counter for loop + + while j < self.try_times: + # Get current online block lists time stamp and compare it with current + try: + http_handler = HTTPHandler(timeout = 15) + opener = urllib2.build_opener(http_handler) + request = urllib2.Request(self.url) + + try: + new_listinfo = opener.open(request) # Can Raise URLError + header = new_listinfo.info() + remote_size = int(header['content-length']) + remote_time = datetime.datetime.strptime(header['last-modified'],"%a, %d %b %Y %H:%M:%S GMT") + remote_type = header['content-type'] + + except Exception, e: + log.warning(e) + # HANDLE EXCEPTOIN + + log.debug(self.listtype) + + # check expected list type + if self.listtype == "spzip": + list_type = "application/zip" + elif self.listtype == "gzmule" or self.listtype == "p2bgz": + list_type = "application/x-gzip" + else: + list_type = "text/html" + + # Print remote file information and local + log.debug('Blocklist: remote') + log.debug(remote_type) + log.debug(remote_time) + log.debug(remote_size) + log.debug('Blocklist: local') + log.debug(list_type) + log.debug(list_time) + log.debug(list_size) + + # Compare MIME types of local and remote list + if list_type == remote_type: + log.info('Blocklist: Remote and Local have the same list type') + # Compare last modified times and decide to download a new list or not + if list_time < remote_time or list_size != remote_size: + self.fetch = True + log.info('Blocklist: Local blocklist list is out of date') + + else: + self.fetch = False + log.info('Blocklist: Local block list is up to date') + + return + + j+=1 + log.debug('Blocklist: 6 TRY AGAIN FOO') + + # Connection can't be made to check remote time stamps + except: # && urllib2.URLError + log.debug('Blocklist: Connection to remote server timed out') + self.fetch = False + j+=1 + + else: + log.info('Blocklist: Not enough time has passed to check for a new list') + return + + def download(self): + log.info('Blocklist: Beginning download') + self.attempt += 1 + + i = 0 + while i < self.try_times: + # Download a new block list + try: + log.info('Blocklist: Downloading new list...') + http_handler = HTTPHandler(timeout = 15) + opener = urllib2.build_opener(http_handler) + request = urllib2.Request(self.url) + new_list = opener.open(request) + file = open(deluge.common.get_config_dir("blocklist.cache"), 'w') + while 1: + data = new_list.read() + if not len(data): + break + file.write(data) + file.close() + + except OSError, e: + log.debug('Blocklist: Unable to write blocklist file') + return + + except: + if self.attempt > self.try_times: + log.warning('Blocklist: All download attempts failed') + return + + else: + log.warning('Blocklist: Try list download again') + i += 1 + + # CHECKSUM + + log.info('Blocklist: List downloaded sucessfully') + + + def import_list(self): + log.info('Blocklist: Importing list...') + try: + self.plugin.reset_ip_filter() + self.curr = 0 + log.info('Blocklist: IP Filter reset') + except: + log.debug('Blocklist: Reset filter failed') + pass + + # Instantiate format class that will read the lists file format + try: + log.info('Blocklist: ' + str(self.listtype)) + read_list = FORMATS[self.listtype][1](self.local_blocklist) + + except: + log.warning('Blocklist: Error: Format read error') + self.reset_critical_settings() + + try: + ips = read_list.next() + print ips + + while ips: + self.plugin.block_ip_range(ips) + ips = read_list.next() + self.curr += 1 + # Progress measurement here + + log.info(self.curr) + + except IOError, e: + log.debug('Blocklist: Problem with list, re-download') + return + + # Throw exception if curr = 0 reset critical settings + if self.curr == 0: + log.warning("Blocklist: Improper list read") + self.reset_critical_settings() + else: + deluge.component.get("Core").session.set_max_connections(deluge.configmanager.ConfigManager("core.conf")["max_connections_global"]) + log.info('Blocklist: Import completed sucessfully') + + def reset_critical_settings(self): + log.info('Blocklist: URL and List type reset') + reset_url = BACKUP_PREFS["url"] + reset_listtype = BACKUP_PREFS["listtype"] + + log.info(reset_url) + log.info(reset_listtype) + + self.config.set('url', reset_url) + self.config.set('listtype', reset_listtype) + self.config.save() + + self.load_options() + log.info(self.url) + log.info(self.listtype) + self.download() + self.import_list() + + def return_count(self): + return self.curr + + def get_config_value(self, key): # url, check_after_days, listtype + val = self.config[key] + log.debug('Blocklist: Get_config_val') + return val diff --git a/deluge/plugins/blocklist/blocklist/ui.py b/deluge/plugins/blocklist/blocklist/ui.py new file mode 100644 index 000000000..94b5f4b9f --- /dev/null +++ b/deluge/plugins/blocklist/blocklist/ui.py @@ -0,0 +1,53 @@ +# +# blocklist/ui.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# Copyright (C) 2008 Mark Stahler ('kramed') + +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import gettext +import locale +import pkg_resources +import deluge.component +from deluge.ui.client import aclient as client +from deluge.log import LOG as log + +class UI: + def __init__(self, plugin_api, plugin_name): + self.plugin = plugin_api + + def enable(self): + pass + + def disable(self): + pass + + \ No newline at end of file diff --git a/deluge/plugins/blocklist/setup.py b/deluge/plugins/blocklist/setup.py new file mode 100644 index 000000000..4d5f9dbfa --- /dev/null +++ b/deluge/plugins/blocklist/setup.py @@ -0,0 +1,54 @@ +# setup.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# Copyright (C) 2008 Mark Stahler ('kramed') + +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +""" +Download and import IP Blocklists +""" + +from setuptools import setup + +__author__ = "Mark Stahler" + +setup( + name="Block List", + version="1.0", + description=__doc__, + author=__author__, + packages=["blocklist"], + package_data = {"blocklist": ["glade/*.glade"]}, + entry_points=""" + [deluge.plugin.core] + Blocklist = blocklist:CorePlugin + [deluge.plugin.gtkui] + Blocklist = blocklist:GtkUIPlugin + """ +)