New plugin API

Eion Robb eion at robbmob.com
Thu Aug 8 22:08:04 EDT 2013


I like where a lot of this is going :)




I have a few questions/comments though:

I think that protocols should extend from PurplePlugin still... most of
that stuff in the PurplePluginInfo object is needed in a protocol,
including dependencies, names, icons, versions etc and to duplicate that in
a PurplePluginProtocolInfo would be yucky.  Or maybe I'm missing something
about the encapsulating thing you were talking about... a PurplePlugin
contains PurpleProtocolPluginInfo's?  If so, are they free'd when the
plugin is unloaded?

What's "PURPLE_ABI_VERSION"?  If I wanted to say my plugin works on version
3.0.0 onwards, but I'm building on 3.2.1 source, what value would I use?

If I wanted to sub-class a protocol (e.g. make a 'new' xmpp-based
protocol), how would I do so in the new model?

Are GPLUGIN_PLUGIN_INFO_FLAGS_INTERNAL and
GPLUGIN_PLUGIN_INFO_FLAGS_LOAD_ON_QUERY actually part of GPlugin, or are
they part of libpurple (and should thusly be renamed)?

Your comment about "A plugin is deemed to not be loadable if..." seems to
be a bit light on details, eg "The ABI version requirement for the plugin
does not match" match what?  Are we moving away from the
backwards-compatible thing, or did you mean to say that the ABI version has
to be less-than-or-equal-to what the UI has been compiled with?
What happens if two plugins with the same id's try to be loaded at the same
time?

If a plugin depends on another one, is that one loaded first, if it hasn't
yet been loaded?  Are we also able to specify a version of plugin that we
depend on?

Should the plugin "category" property be translated, or are they id's that
the UI translates?

Why have purple_plugins_find_with_name() and
purple_plugins_find_with_basename()  been removed?  A quick google search
shows that they're being used by UI's and plugins.


That's it for now :)

Cheers,
Eion


On 9 August 2013 07:37, Ankit Vani <a at nevitus.org> wrote:

> Hi everyone.
>
> I have been working on the plugin API for over two weeks now, and I feel I
> am in
> a good place with where it stands now to talk about it and ask for
> suggestions.
> There are things that I'm still not quite sure of, and things that could
> be done
> better. I would appreciate suggestions to make this implementation of the
> plugins API better.
>
> Repo:               http://hg.pidgin.im/soc/2013/ankitkv/gobjectification
> Branch:             soc.2013.gobjectification.plugins
> Files of interest:  libpurple/plugins.[ch]
>
> ---
>
> INTRODUCTION
> ============
>
> One of the goals of my Google Summer of Code proposals was the
> introduction of
> GObject-based plugins. For this, I decided to start with a new API, that
> would
> provide the ability to register new types in the GObject type system, and
> would
> provide higher flexibility without sacrificing simplicity for libpurple and
> plugin authors. Due to the problems in the current plugin API, such as the
> difficulty in writing a loaded loader or a loaded protocol due to the
> differentiation between protocols, loaders, and standard plugins, I
> decided not
> to just add GObjectification to the current plugin API. Free language
> loaders
> was also an interesting prospect of this change.
>
> As it turns out, Gary (grim) had already written a very promising plugin
> library
> that pretty much did exactly what we want, GPlugin
> (https://bitbucket.org/rw_grim/gplugin). It is robust, lets you register
> new
> types into the GObject type system, and can support plugins written in
> different
> programming languages via loaders (although I believe only native C is
> fully
> functional as of yet). Evaluating all the requirements for the new plugin
> API
> with Ethan, it seemed that if I was to not use GPlugin, I would have ended
> up
> with an API whose functionality resembled that of GPlugin anyway, but
> instead
> would've taken longer to write. So rather than spending time reinventing
> the
> wheel, we decided to use GPlugin as the backbone for the new purple plugin
> API.
>
>
> BRIEF OVERVIEW OF GPLUGIN
> =========================
>
> A brief introduction to the basic data structures of GPlugin:
>
> 1. GPluginPlugin
>    This is an abstract type that represents a plugin of any type in
> GPlugin.
>
> 2. GPluginNativePlugin
>    It inherits GPluginPlugin and implements the GTypePlugin interface of
> the
>    GObject library. It represents a native C plugin. It allows registering
> new
>    types and interfaces into the type system.
>
> 3. GPluginPluginLoader
>    It is an abstract type that represents a plugin loader. The plugin
> loader is
>    responsible for loading plugins of a particular language.
>
> 4. GPluginNativePluginLoader
>    It is the loader for native C plugins. This is where the actual module
>    loading, unloading, and querying happens for native plugins.
>
> 5. GPluginPluginInfo
>    This type holds the information about a plugin, such as the id, name,
> author,
>    website etc. This is a GObject instead of a struct, so that
> applications such
>    as libpurple can inherit it and add their own fields in the plugin's
> info.
>
> The gplugin_plugin_manager_* API provides the calls to add search paths,
> finding
> plugins, and loading/unloading of plugins. You don't really need to care
> much
> about loaders as GPlugin pretty much handles them transparently, unless
> you're
> writing a loader in which case you can register your loader using
> gplugin_plugin_manager_register_loader().
>
> Here is an example of a very basic plugin that you can write using GPlugin:
>
> https://bitbucket.org/rw_grim/gplugin/src/f64b9822d56dd25da027c26e47319f6817dffb0b/tests/plugins/basic-plugin.c?at=default
>
> A plugin requires three functions:
> 1. gplugin_plugin_query() returns a new plugin info object that specifies
> things
>    like id, name, author etc. for the plugin.
> 2. gplugin_plugin_load(plugin) runs code on load and returns TRUE to
> indicate a
>    successful load, or FALSE to cancel the load.
> 3. gplugin_plugin_unload(plugin) runs code on unload and returns TRUE if
> unload
>    succeeded, otherwise FALSE.
>
>
> GPLUGIN WITH LIBPURPLE
> ======================
>
> GPlugin has been added as an external dependency for libpurple. If
> --disable-plugins parameter is provided to configure, GPlugin is not
> required
> and all dynamic loading magic is disabled. Of course, protocols can still
> be
> linked statically in this case.
>
> Since GPluginPlugin represents a plugin, one way to integrate GPlugin with
> libpurple would be using GPluginPlugin instead of PurplePlugin throughout
> the
> codebase. We would use the GPlugin API directly, and for purple-related
> plugin
> stuff, we'd use a purple API. However, there are a number of problems with
> this
> approach:
>
> 1. When --disable-plugins is provided, we want to disable the entire
> dynamic
>    loading mechanism, and GPlugin should no longer be required. This is a
>    problem when there are calls to the GPlugin API and types such as
>    GPluginPlugin and GPluginPluginInfo all around the codebase.
> 2. It would be really confusing for developers and plugin authors, when
> some of
>    the things needed a GPlugin API and some things needed a purple API.
>    Moreover, at times you would need to cast purple stuff to GPlugin stuff,
>    which gets ugly soon enough.
> 3. Memory management becomes a problem with needing to remember to unref
>    returned GPlugin objects and lists all the time, since the rest of the
>    GObjectified libpurple does not ref and unref unless necessary. Forget
> an
>    unref once somewhere, and you'd have spend time and energy hunting down
> the
>    leak.
> 4. It becomes difficult to document everything, because not everything is
> in
>    libpurple. And the documentation in libpurple would at times have to
> assume
>    familiarity with the documentation of GPlugin.
>
> Thus, the solution I chose was to keep everything simple and purple, and
> only
> deal with GPlugin in plugins.[ch], where the purple plugin API is
> implemented.
> Basically, the purple plugin API wraps the GPlugin stuff wherever needed.
>
> When plugins support is enabled, gplugin.h is included and all GPlugin
> calls are
> enabled.
> In this case, PurplePluginInfo inherits GPluginPluginInfo to hold
> additional
> purple information for a plugin like the UI requirement, actions, whether
> the
> plugin is loadable, preference frame callback etc.
> PurplePlugin is a typedef for GPluginPlugin, and represents a plugin of any
> type.
>
> When plugins support is disabled, GPlugin is not linked, gplugin.h is not
> included, and all GPlugin stuff is disabled by an #ifdef PURPLE_PLUGINS
> guard.
> In this case, PurplePluginInfo inherits GObject, and isn't really going to
> be
> instantiated anywhere.
> PurplePlugin is a typedef for GObject, and again, isn't going to be
> instantiatd
> anywhere (protocols are no longer represented by their plugins, I get to
> that in
> the last section).
>
> A GPlugin plugin requires the three functions I mentioned in the previous
> section. However, due to the different semantics of loading and unloading
> when a
> plugin is to be linked dynamically or statically, a macro that does the
> appropriate work is introduced.
>
> This macro is:
> PURPLE_PLUGIN_INIT(plugin-name, query-func, load-func, unload-func)
>
> When linking dynamically, this expands to the
> gplugin_plugin_[query,load,unload]
>                           functions.
> When linking statically,  this expands to pluginname_plugin_[load,unload]
>                           functions, called by static_proto_load() and
>                           static_proto_unload().
>
>
> PURPLE PLUGIN API
> =================
>
> One of the main changes in the new plugin API is that there is no
> distinction of
> plugins based on whether they're protocols, loaders or standard plugins.
> You
> could write a standard plugin that provides a new protocol, or multiple
> protocols, or a loader, or both, etc (not saying that you should do them
> crazy
> stuffs). But this actually simplifies a lot of things, while giving plugin
> authors more flexibility.
>
> I will discuss the aspects that have changed in this API, and leave out the
> obvious stuff.
>
> ---
>
> Plugin info
> -----------
>
> Object hierarchy:
>
>  GObject
>   +----GPluginPluginInfo
>         +----PurplePluginInfo
>               +----FinchPluginInfo
>               +----PidginPluginInfo
>
> PurplePluginInfo inherits GPluginPluginInfo and holds information about a
> plugin. An instance of PurplePluginInfo has to be returned from the
> plugin's
> query function, by providing the appropriate values. See the example at
> the end
> of this section.
>
> A plugin info instance is created by calling
> purple_plugin_info_new(prop1-name, prop1-value, prop2-name, prop2-value,
> ...);
>
> Valid properties are:
>
> 1.  "id"                 (string): The ID of the plugin.
> 2.  "name"               (string): The name of the plugin.
> 3.  "version"            (string): Version of the plugin.
> 4.  "category"           (string): Primary category of the plugin.
> 5.  "summary"            (string): Summary of the plugin.
> 6.  "description"        (string): Description of the plugin.
> 7.  "author"             (string): Author of the plugin.
> 8.  "website"            (string): Website of the plugin.
> 9.  "icon"               (string): Path to a plugin's icon.
> 10. "license"            (string): The plugin's license.
> 11. "abi_version"        (guint32): The required ABI version for the
> plugin.
> 12. "dependencies"       (GSList): List of plugin IDs required by the
> plugin.
> 13. "preferences_frame"  (PurplePluginPrefFrameCallback): Callback that
> returns
>                                              a preferences frame for the
> plugin.
>
> All properties except "id" and "abi_version" are optional, and may not be
> specified.
>
> A "flags" property is also present. I talk about it in a subsequent
> subsection.
>
> - Since we also have a GSoC project that is working on a new plugins
> window, I
>   have temporarily added a property "category", which could organize
> plugins in
>   the plugins window.
> - "icon", which is a GPluginPluginInfo property may also be helpful in
> some way
>   in the new plugins window.
> - I'm not sure if we need a "license" property, which is also a
>   GPluginPluginInfo property.
>
> Please let me know if I should remove any of these properties, since every
> property has an appropriate get function in the API, for example
> purple_plugin_info_get_name(), purple_plugin_info_get_author() etc.
>
> An additional property "ui_requirement" is also present, but it should not
> be
> set manually.
> It is set if you create a plugin info instance using one of these functions
> instead of purple_plugin_info_new():
>
> 1. finch_plugin_info_new():  (in gntplugin) Creates a finch plugin, with UI
>                              requirement as finch. A property
>                              "finch_preferences_frame" can be set to a
> callback
>                              that creates a GNT frame.
> 1. pidgin_plugin_info_new(): (in gtkplugin) Creates a pidgin plugin, with
> UI
>                              requirement as pidgin. A property
>                              "pidgin_config_frame" can be set to a
> callback that
>                              creates a GTK frame.
>
> The PurplePluginInfo instance for a plugin can be retrieved by calling
> purple_plugin_get_info(plugin).
>
>
> Plugin requirements
> -------------------
>
> A plugin that does not meet all the requirements is not loadable. To check
> if a
> plugin is loadable, call purple_plugin_is_loadable(plugin).
> If it is not loadable, purple_plugin_get_error(plugin) should give you the
> reason why not.
>
> A plugin is deemed to not be loadable if:
> 1. It does not have an ID.
> 2. The UI requirement for the plugin does not match.
> 3. The ABI version requirement for the plugin does not match.
>
> This reason can be displayed by the UI, but attempts to load the plugin
> will
> fail.
>
> A plugin is also not loadable if it does not return an info instance.
> However,
> purple_plugin_get_error() will return NULL in this case because there
> would be
> nowhere to store the error.
>
>
> Internal and load-on-query plugins
> ----------------------------------
>
> A plugin can also specify a bitwise combination of two values as a "flags"
> property. These values are:
> 1. GPLUGIN_PLUGIN_INFO_FLAGS_INTERNAL:       Internal plugin.
> 2. GPLUGIN_PLUGIN_INFO_FLAGS_LOAD_ON_QUERY:  Auto-load on query.
>
> - Internal plugins are plugins that should not be shown in plugin lists of
> the
>   UI.
> - Load-on-query plugins are automatically loaded when
> purple_plugins_refresh()
>   is called. Such plugins are not saved by purple_plugins_save_loaded().
> Once
>   unloaded, a load-on-query plugin is not auto-loaded until the next
> restart.
>
> Protocol plugins are to be both internal and load-on-query.
>
>
> Actions
> -------
>
> Till now, the actions field was a callback, that returned a GList of
> actions
> that had to be instantiated and added to the list.
> Now, actions can be added using
> purple_plugin_add_action(plugin, label, callback) in the plugin's load
> function,
> without plugin authors needing to create a PurplePluginAction instance and
> adding it to a list.
>
> Use purple_plugin_get_actions(plugin) to get a list of PurplePluginAction
> instances of that plugin. Unlike before, this list is owned by the plugin
> info
> and should not be freed.
>
>
> Registering new types
> ---------------------
>
> GPlugin provides these functions to register a new type with the GObject
> type
> system:
> 1. GType gplugin_native_plugin_register_type(GPluginNativePlugin *plugin,
>                                           GType parent, const gchar *name,
>                                           const GTypeInfo *info,
>                                           GTypeFlags flags);
> 2. void gplugin_native_plugin_add_interface(GPluginNativePlugin *plugin,
>                                           GType instance_type,
>                                           GType interface_type,
>                                           const GInterfaceInfo
> *interface_info);
> 3. GType gplugin_native_plugin_register_enum(GPluginNativePlugin *plugin,
>                                           const gchar *name,
>                                           const GEnumValue *values);
> 4. GType gplugin_native_plugin_register_flags(GPluginNativePlugin *plugin,
>                                           const gchar *name,
>                                           const GFlagsValue *values);
>
> You can use these functions directly, however, for simplicity, uniformity,
> and
> to avoid plugin authors from having to cast PurplePlugin to
> GPluginNativePlugin,
> I have added these functions in the purple API to wrap the functions 1 and
> 2
> above:
> 1. GType purple_plugin_register_type(PurplePlugin *plugin, GType parent,
>                                      const gchar *name, const GTypeInfo
> *info,
>                                      GTypeFlags flags);
> 2. void  purple_plugin_add_interface(PurplePlugin *plugin, GType
> instance_type,
>                                      GType interface_type,
>                                      const GInterfaceInfo *interface_info);
>
> When more languages are supported, these functions can provide a
> transparent
> interface to register new types.
>
>
> Removed API
> -----------
>
> - IPC has not been implemented yet. I'm not sure if we need it - do we?
> Would
>   signals suffice?
> - purple_plugins_find_with_name() and purple_plugins_find_with_basename()
> have
>   no equivalents in the new API. Plugins can still be looked up by id or
>   filename.
>
>
> A basic plugin example
> ----------------------
>
> /*
>  * This example does pretty much the same thing as the helloworld plugin in
>  * the libpurple plugins, without the comments.
>  */
>
> #include "plugins.h"
>
> static void
> plugin_action_test_cb(PurplePluginAction *action)
> {
>     purple_notify_message(action->plugin, PURPLE_NOTIFY_MSG_INFO,
>                           "Plugin Actions Test",
>                           "This is a plugin actions test :)", NULL, NULL,
> NULL);
> }
>
> static PurplePluginInfo *
> plugin_query(void)
> {
>     return purple_plugin_info_new(
>         "id",           "core-example",
>         "name",         "Example plugin",
>         "version",      "1.0",
>         "summary",      "An example plugin",
>         "description",  "An example plugin",
>         "abi_version",  PURPLE_ABI_VERSION,
>         NULL
>     );
> }
>
> static gboolean
> plugin_load(PurplePlugin *plugin)
> {
>     purple_plugin_add_action(plugin, "Action Test", plugin_action_test_cb);
>
>     purple_notify_message(plugin, PURPLE_NOTIFY_MSG_INFO, "Hello World!",
>                           "This is the example plugin :)", NULL, NULL,
> NULL);
>
>     return TRUE;
> }
>
> static gboolean
> plugin_unload(PurplePlugin *plugin)
> {
>     return TRUE;
> }
>
> PURPLE_PLUGIN_INIT(example, plugin_query, plugin_load, plugin_unload);
>
> ---
>
> Please take a look at plugins.h and plugins.c in my repository to get the
> full
> picture.
>
>
> PROTOCOLS (TEMPORARY FIX)
> =========================
>
> Since a plugin cannot represent a protocol anymore (as a prpl), a protocol
> has
> to have its own representation that can be added and removed from the
> global
> protocols list by plugins. This is temporarily done by
> PurplePluginProtocolInfo,
> with three extra fields (id, name, actions). The way protocols are defined
> will
> probably be going through some changes as well after the plugin stuff is
> all
> done. But for now, this is the solution to get working protocols with the
> new
> plugin API without modifying the protocol interface (much) yet.
>
> For now, for example, this simplifies this:
> PurplePlugin *prpl;
> PurplePluginProtocolInfo *prpl_info;
> prpl = purple_connection_get_prpl(gc);
> prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
>
> to this:
> PurplePluginProtocolInfo *prpl_info;
> prpl_info = purple_connection_get_protocol_info(gc);
>
> To add a new protocol, a plugin has to call
> purple_protocols_add(prpl_info).
> To remove it, call purple_protocols_remove(prpl_info).
> Removing a protocol disconnects all connected accounts using that
> particular
> protocol, and frees its user splits, protocol options and actions.
>
> ---
>
> I will now start refactoring existing prpls and plugins to use the new
> plugin
> API. However, I want to ensure everything is as per what the community
> wants
> before I go all out doing so. Please suggest changes and improvements.
>
> I will post an update about any major changes done henceforth.
>
>                                                                          -
> Ankit
>
> _______________________________________________
> Devel mailing list
> Devel at pidgin.im
> http://pidgin.im/cgi-bin/mailman/listinfo/devel
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://pidgin.im/pipermail/devel/attachments/20130809/0dffd5c4/attachment-0001.html>


More information about the Devel mailing list