From a01481b26f1c6b3941bb07e7badc83ec3baff92f Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Thu, 1 Nov 2018 18:34:52 +0000 Subject: [PATCH] [Plugins] Update create script and add GTK3 how-to doc - Updated create_plugin script to create a GTK3 plugin. - Added a document for updating a 1.3 plugin to be compatible with 2.0. --- deluge/scripts/create_plugin.py | 142 +++++++++--------- docs/source/devguide/how-to/index.md | 4 + .../devguide/how-to/update-1.3-plugin.md | 132 ++++++++++++++++ 3 files changed, 204 insertions(+), 74 deletions(-) create mode 100644 docs/source/devguide/how-to/update-1.3-plugin.md diff --git a/deluge/scripts/create_plugin.py b/deluge/scripts/create_plugin.py index 638c036db..6479d1f31 100644 --- a/deluge/scripts/create_plugin.py +++ b/deluge/scripts/create_plugin.py @@ -112,11 +112,11 @@ def create_plugin(): write_file(deluge_namespace, '__init__.py', NAMESPACE_INIT, False) write_file(plugins_namespace, '__init__.py', NAMESPACE_INIT, False) write_file(src, '__init__.py', INIT) - write_file(src, 'gtkui.py', GTKUI) + write_file(src, 'gtk3ui.py', GTK3UI) write_file(src, 'webui.py', WEBUI) write_file(src, 'core.py', CORE) write_file(src, 'common.py', COMMON) - write_file(data_dir, 'config.glade', GLADE) + write_file(data_dir, 'config.ui', GLADE) write_file(data_dir, '%s.js' % safe_name, DEFAULT_JS) # add an input parameter for this? @@ -127,8 +127,7 @@ def create_plugin(): os.system(dev_link_path) -CORE = """ -from __future__ import unicode_literals +CORE = """from __future__ import unicode_literals import logging @@ -145,7 +144,8 @@ DEFAULT_PREFS = { class Core(CorePluginBase): def enable(self): - self.config = deluge.configmanager.ConfigManager('%(safe_name)s.conf', DEFAULT_PREFS) + self.config = deluge.configmanager.ConfigManager( + '%(safe_name)s.conf', DEFAULT_PREFS) def disable(self): pass @@ -166,34 +166,32 @@ class Core(CorePluginBase): return self.config.config """ -INIT = """ -from deluge.plugins.init import PluginInitBase +INIT = """from deluge.plugins.init import PluginInitBase class CorePlugin(PluginInitBase): def __init__(self, plugin_name): - from core import Core as PluginClass + from .core import Core as PluginClass self._plugin_cls = PluginClass super(CorePlugin, self).__init__(plugin_name) -class GtkUIPlugin(PluginInitBase): +class Gtk3UIPlugin(PluginInitBase): def __init__(self, plugin_name): - from gtkui import GtkUI as PluginClass + from .gtk3ui import Gtk3UI as PluginClass self._plugin_cls = PluginClass - super(GtkUIPlugin, self).__init__(plugin_name) + super(Gtk3UIPlugin, self).__init__(plugin_name) class WebUIPlugin(PluginInitBase): def __init__(self, plugin_name): - from webui import WebUI as PluginClass + from .webui import WebUI as PluginClass self._plugin_cls = PluginClass super(WebUIPlugin, self).__init__(plugin_name) """ -SETUP = """ -from setuptools import find_packages, setup +SETUP = """from setuptools import find_packages, setup __plugin_name__ = '%(name)s' __author__ = '%(author_name)s' @@ -222,16 +220,15 @@ setup( entry_points=\"\"\" [deluge.plugin.core] %%s = deluge.plugins.%%s:CorePlugin - [deluge.plugin.gtkui] - %%s = deluge.plugins.%%s:GtkUIPlugin + [deluge.plugin.gtk3ui] + %%s = deluge.plugins.%%s:Gtk3UIPlugin [deluge.plugin.web] %%s = deluge.plugins.%%s:WebUIPlugin \"\"\" %% ((__plugin_name__, __plugin_name__.lower()) * 3) ) """ -COMMON = """ -from __future__ import unicode_literals +COMMON = """from __future__ import unicode_literals import os.path @@ -239,17 +236,18 @@ from pkg_resources import resource_filename def get_resource(filename): - return resource_filename('deluge.plugins.%(safe_name)s', os.path.join('data', filename)) + return resource_filename( + 'deluge.plugins.%(safe_name)s', os.path.join('data', filename)) """ -GTKUI = """ -from __future__ import unicode_literals +GTK3UI = """from __future__ import unicode_literals -import gtk import logging +from gi.repository import Gtk + import deluge.component as component -from deluge.plugins.pluginbase import GtkPluginBase +from deluge.plugins.pluginbase import Gtk3PluginBase from deluge.ui.client import client from .common import get_resource @@ -257,23 +255,29 @@ from .common import get_resource log = logging.getLogger(__name__) -class GtkUI(GtkPluginBase): +class Gtk3UI(Gtk3PluginBase): def enable(self): - self.glade = gtk.glade.XML(get_resource('config.glade')) + self.builder = Gtk.Builder() + self.builder.add_from_file(get_resource('config.glade')) - component.get('Preferences').add_page('%(name)s', self.glade.get_widget('prefs_box')) - component.get('PluginManager').register_hook('on_apply_prefs', self.on_apply_prefs) - component.get('PluginManager').register_hook('on_show_prefs', self.on_show_prefs) + component.get('Preferences').add_page( + '%(name)s', self.builder.get_object('prefs_box')) + component.get('PluginManager').register_hook( + 'on_apply_prefs', self.on_apply_prefs) + component.get('PluginManager').register_hook( + 'on_show_prefs', self.on_show_prefs) def disable(self): component.get('Preferences').remove_page('%(name)s') - component.get('PluginManager').deregister_hook('on_apply_prefs', self.on_apply_prefs) - component.get('PluginManager').deregister_hook('on_show_prefs', self.on_show_prefs) + component.get('PluginManager').deregister_hook( + 'on_apply_prefs', self.on_apply_prefs) + component.get('PluginManager').deregister_hook( + 'on_show_prefs', self.on_show_prefs) def on_apply_prefs(self): log.debug('applying prefs for %(name)s') config = { - 'test': self.glade.get_widget('txt_test').get_text() + 'test': self.builder.get_object('txt_test').get_text() } client.%(safe_name)s.set_config(config) @@ -282,45 +286,43 @@ class GtkUI(GtkPluginBase): def cb_get_config(self, config): \"\"\"callback for on show_prefs\"\"\" - self.glade.get_widget('txt_test').set_text(config['test']) + self.builder.get_object('txt_test').set_text(config['test']) """ -GLADE = """ - - - - +GLADE = """ + + + + - + True - + True Test config value: - + - + True True - + 1 - + - - + + """ -WEBUI = """ -from __future__ import unicode_literals +WEBUI = """from __future__ import unicode_literals import logging from deluge.plugins.pluginbase import WebPluginBase -from deluge.ui.client import client from .common import get_resource @@ -338,17 +340,17 @@ class WebUI(WebPluginBase): pass """ -DEFAULT_JS = """/* -Script: %(filename)s - The client-side javascript code for the %(name)s plugin. - -Copyright: - (C) %(author_name)s %(current_year)s <%(author_email)s> - - This file is part of %(name)s 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. -*/ +DEFAULT_JS = """/** + * Script: %(filename)s + * The client-side javascript code for the %(name)s plugin. + * + * Copyright: + * (C) %(author_name)s %(current_year)s <%(author_email)s> + * + * This file is part of %(name)s and is licensed under GNU GPL 3.0, or + * later, with the additional special exception to link portions of this + * program with the OpenSSL library. See LICENSE for more details. + */ %(name)sPlugin = Ext.extend(Deluge.Plugin, { constructor: function(config) { @@ -363,29 +365,21 @@ Copyright: }, onEnable: function() { - this.prefsPage = deluge.preferences.addPage(new Deluge.ux.preferences.%(name)sPage()); + this.prefsPage = deluge.preferences.addPage( + new Deluge.ux.preferences.%(name)sPage()); } }); new %(name)sPlugin(); """ -GPL = """# -# -*- coding: utf-8 -*-# - +GPL = """# -*- coding: utf-8 -*- # Copyright (C) %(current_year)d %(author_name)s <%(author_email)s> # -# Basic plugin template created by: -# Copyright (C) 2008 Martijn Voncken -# 2007-2009 Andrew Resch -# 2009 Damien Churchill -# 2010 Pedro Algarvio -# 2017 Calum Lind +# Basic plugin template created by the Deluge Team. # -# This file is part of %(name)s 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. -# - +# This file is part of %(name)s and is licensed under GNU GPL 3.0, or later, +# with the additional special exception to link portions of this program with +# the OpenSSL library. See LICENSE for more details. """ NAMESPACE_INIT = """__import__('pkg_resources').declare_namespace(__name__) diff --git a/docs/source/devguide/how-to/index.md b/docs/source/devguide/how-to/index.md index da978c16b..6260d3ccc 100644 --- a/docs/source/devguide/how-to/index.md +++ b/docs/source/devguide/how-to/index.md @@ -6,3 +6,7 @@ the tutorials. ## Web JSON-RPC - [Connect to JSON-RPC using curl](curl-jsonrpc.md) + +## Plugins + +- [Update 1.3 plugin for 2.0](update-1.3-plugin.md) diff --git a/docs/source/devguide/how-to/update-1.3-plugin.md b/docs/source/devguide/how-to/update-1.3-plugin.md new file mode 100644 index 000000000..837739294 --- /dev/null +++ b/docs/source/devguide/how-to/update-1.3-plugin.md @@ -0,0 +1,132 @@ +# How to update a Deluge 1.3 plugin for 2.0 + +With the new code in Deluge 2.0 there are changes that require authors of +existing plugins to update their plugins to work on Deluge 2.0. + +The main changes are with Python 3 support and the new GTK3 user interface with +the dropping of GTK2. However it is still possible for a 1.3 plugin to be made +compatible with 2.0 and this guide aims to helps with that process. + +Note that the Deluge 2.0 plugins now use namespace packaging which is not +compatible with Deluge 1.3. + +## Python + +### Python version matching + +Ensure your code is both Python 2.7 and Python >=3.5 compatible. + +In `1.3-stable` the plugins that were built with a specfific version of Python +could on be loaded if the system Python also matched. + +This has change in Deluge 2.0 and it will load any Python version of plugin +eggs so compatibility is essential for end-users not to encounter issues. + +### Six + +Use [six] to assist with compatibility. + +[six]: https://pythonhosted.org/six/ + +### Unicode literals + +Add this to files to ensure strings and bytes separatation so there are no +surprises when running on Python 3. + +## GTK 3 addition + +In order to support both Deluge 1.3 and 2.0 all existing plugin GTK UI files +must be copied and then converted to contain only GTK3 code with the old files +still using PyGTK e.g.: + + cp gtkui.py gtk3ui.py + +### Convert from libglade to GtkBuilder + +With PyGTK there were two library options for creating the user interface from +XML files by default Deluge plugins used libglade but that has been deprecated +and removed in GTK3. So the libglade `.glade` files will need converted to +GtkBuilder `.ui` files and the Python code updated. + +https://developer.gnome.org/gtk2/stable/gtk-migrating-GtkBuilder.html + +#### gtk-builder-convert + +Install the `gtk-builder-convert` converter on Ubuntu with: + + sudo apt install libgtk2.0-dev + +To convert your GTK run it like so: + + gtk-builder-convert data/config.glade data/config.ui + +#### Glade UI designer for GTK2 + +The above conversion can be done in Glade UI designer (version <=3.8), ensuring +that the minimum Gtk version is set to 2.24 and any deprecated widgets are +fixed. The updated file should be saved with file extension `.ui`. + +#### Python code changes + +The code needs to replace `gtk.glade` references with `gtk.Builder` and the +first step is updating how the files are loaded: + +```diff +- self.glade = gtk.glade.XML(get_resource("config.glade")) ++ self.builder = gtk.Builder() ++ self.builder.add_from_file(get_resource("config.ui")) +``` + +The next stage is to replace every occurange of these `glade` methods with +the `builder` equivalents: + + glade.signal_autoconnect -> builder.connect_signals + glade.get_widget -> builder.get_object + +### Migrate XML files to GTK3 + +If you open and save the file it will update with the new requirement header: + + + + + +You can fix deprecated widgets but keep the minimum GTK version to <= 3.10 for +desktop compatiblity. + +An example of migrating a Deluge plugin to GtkBuilder: [AutoAdd GtkBuilder] + +### Gtk import rename + +Move from PyGTK to GTK3 using Python bindings. + +https://pygobject.readthedocs.io/en/latest/guide/porting.html + + wget https://gitlab.gnome.org/GNOME/pygobject/raw/master/tools/pygi-convert.sh + cp gtkui.py gtk3ui.py + sh pygi-convert.sh gtk3ui.py + +```diff +-import gtk ++from gi.repository import Gtk +``` + +```diff +- self.builder = gtk.Builder() ++ self.builder = Gtk.Builder() +``` + +### Deluge GTK3 + +Imports will need updated from `deluge.ui.gtkui` to `deluge.ui.gtk3`. + +### PluginBase + +```diff +-from deluge.plugins.pluginbase import GtkPluginBase ++from deluge.plugins.pluginbase import Gtk3PluginBase +-class GtkUI(GtkPluginBase): ++class Gtk3UI(Gtk3PluginBase): +``` + +[autoadd gtkbuilder]: https://git.deluge-torrent.org/deluge/commit/?h=develop&id=510a8b50b213cab804d693a5f122f9c0d9dd1fb3