diff --git a/plugins/BlocklistImport/__init__.py b/plugins/BlocklistImport/__init__.py new file mode 100644 index 000000000..d754fbaf6 --- /dev/null +++ b/plugins/BlocklistImport/__init__.py @@ -0,0 +1,95 @@ + + +plugin_name = "Blocklist Importer" +plugin_author = "Steve 'Tarka' Smith" +plugin_version = "0.1" +plugin_description = "Downloads and import PeerGuardian blocklists" + +def deluge_init(deluge_path): + global path + path = deluge_path + +def enable(core, interface): + global path + return BlocklistImport(path, core, interface) + +#################### The plugin itself #################### + +import urllib, deluge.common, deluge.pref +from peerguardian import PGReader, PGException +from ui import GTKConfig, GTKProgress + +class BlocklistImport: + + def __init__(self, path, core, interface): + print "Loading blocklist plugin ..." + # Save the path, interface, and core so they can be used later + self.path = path + self.core = core + self.interface = interface + self.gtkconf = GTKConfig(self) + self.gtkprog = GTKProgress(self) + + self.blockfile = deluge.common.CONFIG_DIR + "/blocklist.p2b.gzip" + + conffile = deluge.common.CONFIG_DIR + "/blocklist.conf" + self.config = deluge.pref.Preferences(filename=conffile, + global_defaults=False) + self.config.load() + + if not self.config.has_key('url'): + self.configure() + else: + self.loadlist(fetch=self.config.get('load_on_start')) + + + def _download_update(self, curr, chunksize, size): + incs = float(size) / float(chunksize) + self.gtkprog.download_prog(curr/incs) + + def loadlist(self, fetch=False): + # FIXME + #self.gtkprog.start() + + # Attempt initial import + # FIXME: Make async + if fetch: + print "Downloading blocklist..." + filename, headers = urllib.urlretrieve(self.config.get('url'), + filename=self.blockfile, + reporthook=self._download_update) + print "Done" + + self.core.reset_ip_filter() + reader = PGReader(self.blockfile) + + ips = reader.next() + while ips: + print "Blocking",ips + self.core.add_range_to_ip_filter(*ips) + ips = reader.next() + + reader.close() + + # FIXME + #self.gtkprog.stop() + + def configure(self): + self.gtkconf.start() + + def setconfig(self, url, load_on_start): + self.config.set('url', url) + self.config.set('load_on_start', load_on_start) + self.config.save() + + self.loadlist(fetch=True) + + def disable(self): + self.core.reset_ip_filter() + + def unload(self): + #self.config.save_to_file(self.config_file) + self.core.reset_ip_filter() + + def update(self): + pass diff --git a/plugins/BlocklistImport/peerguardian.py b/plugins/BlocklistImport/peerguardian.py new file mode 100644 index 000000000..98d2c70d1 --- /dev/null +++ b/plugins/BlocklistImport/peerguardian.py @@ -0,0 +1,55 @@ + +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 + + # FIXME: Catch and convert exception? + self.fd = gzip.open(filename, "rb") + + # 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/plugins/BlocklistImport/ui.py b/plugins/BlocklistImport/ui.py new file mode 100644 index 000000000..e9aecab49 --- /dev/null +++ b/plugins/BlocklistImport/ui.py @@ -0,0 +1,92 @@ + +import gtk + +class GTKConfig(gtk.Dialog): + def __init__(self, plugin): + gtk.Dialog.__init__(self, title="Blocklist Config", + flags=gtk.DIALOG_MODAL, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, + gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + + # Setup + self.set_border_width(12) + self.vbox.set_spacing(6) + + # List source + label = gtk.Label() + label.set_markup('Blocklist URL') + self.url = gtk.Entry() + self.listtype = gtk.combo_box_new_text() + self.listtype.append_text("PeerGuardian (GZip)") + self.listtype.set_active(0) + + hbox = gtk.HBox(False, 6) + hbox.pack_start(label) + hbox.pack_start(self.url) + hbox.pack_start(self.listtype) + + self.vbox.pack_start(hbox) + + # Load on start + self.load_on_start = gtk.CheckButton("Load on start") + self.vbox.pack_start(self.load_on_start) + + self.connect('response', self.ok) + self.connect('close', self.cancel) + + self.hide_all() + + self.plugin = plugin + + + def ok(self, dialog, response): + self.hide_all() + + if response != gtk.RESPONSE_ACCEPT: + self.cancel(dialog) + return + + self.plugin.setconfig(self.url.get_text(), + self.load_on_start.get_active()) + + def cancel(self, dialog, response): + self.hide_all() + + def start(self): + self.show_all() + + +class GTKProgress(gtk.Dialog): + def __init__(self, plugin): + gtk.Dialog.__init__(self, title="Setting-Up Blocklist", + flags=gtk.DIALOG_MODAL, + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT)) + # Setup + self.set_border_width(12) + self.vbox.set_spacing(6) + + label = gtk.Label() + label.set_markup('Loading and installing blocklist') + self.vbox.pack_start(label) + + self.progress = gtk.ProgressBar() + self.vbox.pack_start(self.progress) + + self.connect('close', self.cancel) + + self.hide_all() + + def download_prog(self, fract): + if fract > 1.0: + fract = 1.0 + self.progress.set_fraction(fract) + + def cancel(self, dialog, response): + self.hide_all() + + def start(self): + print "showing all" + self.show_all() + + def stop(self): + self.hide_all() diff --git a/plugins/ExamplePlugin/__init__.py b/plugins/ExamplePlugin/__init__.py new file mode 100644 index 000000000..58f0e59eb --- /dev/null +++ b/plugins/ExamplePlugin/__init__.py @@ -0,0 +1,17 @@ +# An example plugin for use with Deluge + +plugin_name = "Example Plugin" # The name of the plugin +plugin_author = "Zach Tibbitts" # The author's Name +plugin_version = "0.5.0" # The plugin's version number +plugin_description = "An example plugin" # A description of the plugin + +def deluge_init(deluge_path): + global path + path = deluge_path + + +from ExamplePlugin.plugin import plugin_Example + +def enable(core, interface): + global path + return plugin_Example(path, core, interface) diff --git a/plugins/ExamplePlugin/plugin.py b/plugins/ExamplePlugin/plugin.py index 2651f54c7..c2f6ad2b2 100644 --- a/plugins/ExamplePlugin/plugin.py +++ b/plugins/ExamplePlugin/plugin.py @@ -1,5 +1,7 @@ # An example plugin for use with Deluge +import deluge.common, deluge.pref, gtk, gtk.glade + # This plugin is intended to be used with Deluge's default GTK interface class plugin_Example: # The plugin's class ## Your plugin's contructor should follow this format @@ -11,14 +13,12 @@ class plugin_Example: # The plugin's class self.path = path self.core = deluge_core self.interface = deluge_interface - # Classes must be imported as they are needed from within - # the plugin's functions - import common, gtk, gtk.glade, dgtk, pref + # Create an options file and try to load existing Values - self.config_file = common.CONFIG_DIR + "/example.conf" - self.config = pref.Preferences() + self.config_file = deluge.common.CONFIG_DIR + "/example.conf" + self.config = deluge.pref.Preferences() try: - self.config.load_from_file(self.config_file) + self.config.load(self.config_file) except IOError: # File does not exist pass @@ -41,7 +41,7 @@ class plugin_Example: # The plugin's class ## unload is called when the plugin is removed or Deluge is shut down def unload(self): self.toolbar.remove(self.button) # Remove the button from the toolbar - self.config.save_to_file(self.config_file) + self.config.save(self.config_file) ## update will be called every UPDATE_INTERVAL (usually about 1 second) def update(self): @@ -71,7 +71,6 @@ class plugin_Example: # The plugin's class ## This will be called whenever self.button is clicked def clicked(self, button): # Build a dialog from scratch rather than from a glade file - import gtk dialog = gtk.Dialog(title="Example Plugin", parent=self.interface.window, buttons=(gtk.STOCK_OK, 0)) dialog.set_icon_from_file(self.path + "/example-plugin.png") @@ -91,11 +90,3 @@ class plugin_Example: # The plugin's class dialog.hide() dialog.destroy() - -register_plugin("Example Plugin", # The name of the plugin - plugin_Example, # The plugin's class - "Zach Tibbitts", # The author's Name - "0.5.0", # The plugin's version number - "An example plugin", # A description of the plugin - config=True, # If the plugin can be configured - ) diff --git a/plugins/HelloWorld/__init__.py b/plugins/HelloWorld/__init__.py new file mode 100644 index 000000000..86d75ac4b --- /dev/null +++ b/plugins/HelloWorld/__init__.py @@ -0,0 +1,29 @@ +# A simple plugin to display Hello, World + +plugin_name = "Hello World" +plugin_author = "Zach Tibbitts" +plugin_version = "1.0" +plugin_description = 'Displays "Hello, World"' + +def deluge_init(deluge_path): + global path + path = deluge_path + +def enable(core, interface): + global path + return plugin_Hello(path, core, interface) + + +class plugin_Hello: + def __init__(self, path, deluge_core, deluge_interface): + self.path = path + self.core = deluge_core + self.interface = deluge_interface + + def unload(self): + pass + + def update(self): + print "Hello, World!" + + diff --git a/plugins/HelloWorld/plugin.py b/plugins/HelloWorld/plugin.py deleted file mode 100644 index f112d326a..000000000 --- a/plugins/HelloWorld/plugin.py +++ /dev/null @@ -1,19 +0,0 @@ -# A simple plugin to display Hello, World - -class plugin_Hello: - def __init__(self, path, deluge_core, deluge_interface): - self.path = path - self.core = deluge_core - self.interface = deluge_interface - - def unload(self): - pass - - def update(self): - print "Hello, World!" - -register_plugin("Hello World", - plugin_Hello, - "Zach Tibbitts", - "1.0", - 'Displays "Hello, World"') diff --git a/plugins/NetworkGraph/__init__.py b/plugins/NetworkGraph/__init__.py new file mode 100644 index 000000000..717fd9057 --- /dev/null +++ b/plugins/NetworkGraph/__init__.py @@ -0,0 +1,16 @@ + +plugin_name = "Network Activity Graph" +plugin_author = "Alon Zakai, Zach Tibbitts" +plugin_version = "0.2" +plugin_description = "Network Activity Graph plugin\n\nWritten by Kripkenstein" + + +def deluge_init(deluge_path): + global path + path = deluge_path + +from NetworkGraph.plugin import plugin_NetGraph + +def enable(core, interface): + global path + return plugin_NetGraph(path, core, interface) diff --git a/plugins/NetworkGraph/plugin.py b/plugins/NetworkGraph/plugin.py index 29a3ba52b..2a96f05d6 100644 --- a/plugins/NetworkGraph/plugin.py +++ b/plugins/NetworkGraph/plugin.py @@ -50,9 +50,6 @@ class plugin_NetGraph: self.parentNotebook.remove_page(page) break - def configure(self): - pass - def update(self): import gtk session_info = self.core.get_state() @@ -158,12 +155,3 @@ class plugin_NetGraph: self.ctx.stroke() -### Register plugin with Deluge - -register_plugin("Network Activity Graph", # The name of the plugin - plugin_NetGraph, # The plugin's class - "Alon Zakai, Zach Tibbitts", # Authors - "0.2", # The plugin's version number - "Network Activity Graph plugin\n\nWritten by Kripkenstein", # A description of the plugin - config=False, # If the plugin can be configured - ) diff --git a/plugins/NetworkHealth/__init__.py b/plugins/NetworkHealth/__init__.py new file mode 100644 index 000000000..8e177cf35 --- /dev/null +++ b/plugins/NetworkHealth/__init__.py @@ -0,0 +1,18 @@ + + +plugin_name = "Network Health Monitor" +plugin_author = "Alon Zakai, Zach Tibbitts" +plugin_version = "0.2" +plugin_description = "Network Health Monitor plugin\n\nWritten by Kripkenstein" + + +def deluge_init(deluge_path): + global path + path = deluge_path + + +from NetworkHealth.plugin import plugin_NetworkHealth + +def enable(core, interface): + global path + return plugin_NetworkHealth(path, core, interface) diff --git a/plugins/NetworkHealth/plugin.py b/plugins/NetworkHealth/plugin.py index a1094ebe9..cfb5d286f 100644 --- a/plugins/NetworkHealth/plugin.py +++ b/plugins/NetworkHealth/plugin.py @@ -7,12 +7,6 @@ class plugin_NetworkHealth: self.counter = 30 self.maxCount = self.counter - def config(self): - pass - - def unload(self): - pass - def update(self): session_info = self.core.get_state() if not session_info['has_incoming_connections'] and \ @@ -28,12 +22,3 @@ class plugin_NetworkHealth: self.counter = self.maxCount self.parent.statusbar_temp_msg = self.parent.statusbar_temp_msg + ' ' + message - -### Register plugin with Deluge -register_plugin("Network Health Monitor", # The name of the plugin - plugin_NetworkHealth, # The plugin's class - "Alon Zakai, Zach Tibbitts", # Authors - "0.2", # The plugin's version number - "Network Health Monitor plugin\n\nWritten by Kripkenstein", # A description of the plugin - config=False # If the plugin can be configured\ - ) diff --git a/plugins/TorrentCreator/__init__.py b/plugins/TorrentCreator/__init__.py new file mode 100644 index 000000000..0f5a9bd0b --- /dev/null +++ b/plugins/TorrentCreator/__init__.py @@ -0,0 +1,17 @@ + + +plugin_name = "Deluge Torrent Creator" +plugin_author = "regulate" +plugin_version = "0.1" +plugin_description = "A torrent creator plugin" + +def deluge_init(deluge_path): + global path + path = deluge_path + + +from TorrentCreator.plugin import plugin_tcreator + +def enable(core, interface): + global path + return plugin_tcreator(path, core, interface) diff --git a/plugins/TorrentCreator/plugin.py b/plugins/TorrentCreator/plugin.py index 9722055a4..549948f3c 100644 --- a/plugins/TorrentCreator/plugin.py +++ b/plugins/TorrentCreator/plugin.py @@ -91,5 +91,3 @@ class plugin_tcreator: ret = self.core.create_torrent(self.dest, src_dir, trackers, comments, size, author) return ret -register_plugin("Deluge Torrent Creator", plugin_tcreator, "regulate", "0.1", "A torrent creator plugin", config=False) - diff --git a/plugins/TorrentSearch/__init__.py b/plugins/TorrentSearch/__init__.py new file mode 100644 index 000000000..ec5f0193a --- /dev/null +++ b/plugins/TorrentSearch/__init__.py @@ -0,0 +1,17 @@ + +plugin_name = "Torrent Search" +plugin_author = "Zach Tibbitts" +plugin_version = "0.5" +plugin_description = "A searchbar for torrent search engines" + + +def deluge_init(deluge_path): + global path + path = deluge_path + + +from TorrentSearch.plugin import plugin_Search + +def enable(core, interface): + global path + return plugin_Search(path, core, interface) diff --git a/plugins/TorrentSearch/plugin.py b/plugins/TorrentSearch/plugin.py index 489f405a6..5e8fbfda0 100644 --- a/plugins/TorrentSearch/plugin.py +++ b/plugins/TorrentSearch/plugin.py @@ -151,11 +151,3 @@ class plugin_Search: self.menu_button.set_label("Search " + engine_string) self.se = engine_string - -register_plugin("Torrent Search", - plugin_Search, - "Zach Tibbitts", - "0.5", - "A searchbar for torrent search engines", - config=True - ) diff --git a/src/core.py b/src/core.py index 46f828980..1dfe14ca0 100644 --- a/src/core.py +++ b/src/core.py @@ -742,3 +742,11 @@ class Manager: def pe_settings(self, out_enc_policy, in_enc_policy, allowed_enc_level, prefer_rc4): return deluge_core.pe_settings(out_enc_policy, in_enc_policy, allowed_enc_level, prefer_rc4) + + # Creates/resets the IP filter list + def reset_ip_filter(self): + return deluge_core.reset_IP_filter() + + # Adds an IP range (as two dotted quad strings) to the filter + def add_range_to_ip_filter(self, start, end): + return deluge_core.add_range_to_IP_filter(start, end) diff --git a/src/deluge_core.cpp b/src/deluge_core.cpp index fa54a49c8..6523fb429 100644 --- a/src/deluge_core.cpp +++ b/src/deluge_core.cpp @@ -1191,43 +1191,33 @@ static PyObject *torrent_create_torrent(PyObject *self, PyObject *args) } -static PyObject *torrent_apply_IP_filter(PyObject *self, PyObject *args) +static PyObject *torrent_reset_IP_filter(PyObject *self, PyObject *args) { - PyObject *ranges; - if (!PyArg_ParseTuple(args, "O", &ranges)) - return NULL; - - long num_ranges = PyList_Size(ranges); - - // printf("Number of ranges: %ld\r\n", num_ranges); - // Py_INCREF(Py_None); return Py_None; - // Remove existing filter, if there is one if (M_the_filter != NULL) delete M_the_filter; M_the_filter = new ip_filter(); - address_v4 from, to; - PyObject *curr; - - // printf("Can I 10.10.10.10? %d\r\n", the_filter->access(address_v4::from_string("10.10.10.10"))); - - for (long i = 0; i < num_ranges; i++) - { - curr = PyList_GetItem(ranges, i); - // PyObject_Print(curr, stdout, 0); - from = address_v4::from_string(PyString_AsString(PyList_GetItem(curr, 0))); - to = address_v4::from_string(PyString_AsString(PyList_GetItem(curr, 1))); - // printf("Filtering: %s - %s\r\n", from.to_string().c_str(), to.to_string().c_str()); - M_the_filter->add_rule(from, to, ip_filter::blocked); - }; - - // printf("Can I 10.10.10.10? %d\r\n", the_filter->access(address_v4::from_string("10.10.10.10"))); - M_ses->set_ip_filter(*M_the_filter); - // printf("Can I 10.10.10.10? %d\r\n", the_filter->access(address_v4::from_string("10.10.10.10"))); + Py_INCREF(Py_None); return Py_None; +} + + +static PyObject *torrent_add_range_to_IP_filter(PyObject *self, PyObject *args) +{ + if (M_the_filter == NULL) { + RAISE_PTR(DelugeError, "No filter defined, use reset_IP_filter"); + } + + char *start, *end; + if (!PyArg_ParseTuple(args, "ss", &start, &end)) + return NULL; + + address_v4 inet_start = address_v4::from_string(start); + address_v4 inet_end = address_v4::from_string(end); + M_the_filter->add_rule(inet_start, inet_end, ip_filter::blocked); Py_INCREF(Py_None); return Py_None; } @@ -1286,7 +1276,8 @@ static PyMethodDef deluge_core_methods[] = {"stop_DHT", torrent_stop_DHT, METH_VARARGS, "."}, {"get_DHT_info", torrent_get_DHT_info, METH_VARARGS, "."}, {"create_torrent", torrent_create_torrent, METH_VARARGS, "."}, - {"apply_IP_filter", torrent_apply_IP_filter, METH_VARARGS, "."}, + {"reset_IP_filter", torrent_reset_IP_filter, METH_VARARGS, "."}, + {"add_range_to_IP_filter", torrent_add_range_to_IP_filter, METH_VARARGS, "."}, {NULL} }; diff --git a/src/dialogs.py b/src/dialogs.py index b2664678c..fdbc873dd 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -154,11 +154,11 @@ class PluginDlg: return True name = model.get_value(model.get_iter(path), 0) plugin = self.plugins.get_plugin(name) - author = plugin['author'] - version = plugin['version'] - config = plugin['config'] - description = plugin['description'] + author = plugin.plugin_author + version = plugin.plugin_version + description = plugin.plugin_description if name in self.plugins.get_enabled_plugins(): + config = self.plugins.configurable_plugin(name) self.glade.get_widget("plugin_conf").set_sensitive(config) else: self.glade.get_widget("plugin_conf").set_sensitive(False) @@ -174,8 +174,8 @@ class PluginDlg: self.store.set_value(plugin_iter, 1, plugin_value) if plugin_value: self.plugins.enable_plugin(plugin_name) - self.glade.get_widget("plugin_conf").set_sensitive( - self.plugins.get_plugin(plugin_name)['config']) + config = self.plugins.configurable_plugin(plugin_name) + self.glade.get_widget("plugin_conf").set_sensitive(config) else: self.plugins.disable_plugin(plugin_name) self.glade.get_widget("plugin_conf").set_sensitive(False) diff --git a/src/plugins.py b/src/plugins.py index 912f2cc8c..ee5a20f4b 100644 --- a/src/plugins.py +++ b/src/plugins.py @@ -20,7 +20,7 @@ # 51 Franklin Street, Fifth Floor # Boston, MA 02110-1301, USA. -import os +import os, sys, imp class PluginManager: def __init__(self, deluge_core, deluge_interface): @@ -32,15 +32,26 @@ class PluginManager: def add_plugin_dir(self, directory): self.plugin_dirs.append(directory) + sys.path.append(directory) + # Scans all defined plugin dirs for Deluge plugins. The resulting + # module object is store with the defined name. def scan_for_plugins(self): - register_plugin = self.register_plugin for folder in self.plugin_dirs: - plugin_folders = os.listdir(folder) - for plugin in plugin_folders: - if os.path.isfile(os.path.join(folder, plugin, "plugin.py")): - self.path = os.path.join(folder, plugin) - execfile(os.path.join(folder, plugin, "plugin.py")) + print "Scanning plugin dir",folder + for modname in os.listdir(folder): + path = folder+'/'+modname + if '__init__.py' in os.listdir(path): + # Import the found module. Note that the last + # parameter is important otherwise only the base + # modules (ie. 'plugins') is imported. This appears + # to be by design. + print "Loading module",modname + mod = __import__(modname, globals(), locals(), ['']) + if 'deluge_init' in dir(mod): + print "Initialising plugin",modname + mod.deluge_init(path) + self.available_plugins[mod.plugin_name] = mod def get_available_plugins(self): return self.available_plugins.keys() @@ -49,35 +60,38 @@ class PluginManager: return self.available_plugins[name] def enable_plugin(self, name): - self.enabled_plugins[name] = self.available_plugins[name]['class']( - self.available_plugins[name]['path'], self.core, self.interface) + plugin = self.available_plugins[name] + self.enabled_plugins[name] = plugin.enable(self.core, self.interface) def get_enabled_plugins(self): return self.enabled_plugins.keys() def disable_plugin(self, name): - self.enabled_plugins[name].unload() + plugin = self.enabled_plugins[name] + if 'unload' in dir(plugin): + plugin.unload() self.enabled_plugins.pop(name) + def configurable_plugin(self, name): + if name in self.enabled_plugins: + return 'configure' in dir(self.enabled_plugins[name]) + else: + return False + def configure_plugin(self, name): self.enabled_plugins[name].configure() def update_active_plugins(self): for name in self.enabled_plugins.keys(): - self.enabled_plugins[name].update() + plugin = self.enabled_plugins[name] + if 'update' in dir(plugin): + plugin.update() def shutdown_all_plugins(self): for name in self.enabled_plugins.keys(): - self.enabled_plugins[name].unload() + self.disable_plugin(name) self.enabled_plugins.clear() - def register_plugin(self, name, plugin_class, author, version, description, config=False): - self.available_plugins[name] = {'class': plugin_class, - 'author': author, - 'version': version, - 'description': description, - 'config': config, - 'path': self.path} ## Few lines of code to test functionality if __name__ == "__main__":