<div dir="ltr"><br><div>I like where a lot of this is going :)</div><div><br></div><div><br></div><div><br></div><div><br></div><div>I have a few questions/comments though:</div><div><br></div><div>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?</div>

<div><br></div><div>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?</div><div><br></div><div>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?</div>

<div><br></div><div>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)?</div><div><br></div><div>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? </div>

<div>What happens if two plugins with the same id's try to be loaded at the same time?</div><div><br></div><div>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?</div>

<div><br></div><div>Should the plugin "category" property be translated, or are they id's that the UI translates?</div><div><br></div><div>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.</div>

<div><br></div><div><br></div><div>That's it for now :)</div><div><br></div><div>Cheers,</div><div>Eion</div></div><div class="gmail_extra"><br><br><div class="gmail_quote">On 9 August 2013 07:37, Ankit Vani <span dir="ltr"><<a href="mailto:a@nevitus.org" target="_blank">a@nevitus.org</a>></span> wrote:<br>

<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi everyone.<br>
<br>
I have been working on the plugin API for over two weeks now, and I feel I am in<br>
a good place with where it stands now to talk about it and ask for suggestions.<br>
There are things that I'm still not quite sure of, and things that could be done<br>
better. I would appreciate suggestions to make this implementation of the<br>
plugins API better.<br>
<br>
Repo:               <a href="http://hg.pidgin.im/soc/2013/ankitkv/gobjectification" target="_blank">http://hg.pidgin.im/soc/2013/ankitkv/gobjectification</a><br>
Branch:             soc.2013.gobjectification.plugins<br>
Files of interest:  libpurple/plugins.[ch]<br>
<br>
---<br>
<br>
INTRODUCTION<br>
============<br>
<br>
One of the goals of my Google Summer of Code proposals was the introduction of<br>
GObject-based plugins. For this, I decided to start with a new API, that would<br>
provide the ability to register new types in the GObject type system, and would<br>
provide higher flexibility without sacrificing simplicity for libpurple and<br>
plugin authors. Due to the problems in the current plugin API, such as the<br>
difficulty in writing a loaded loader or a loaded protocol due to the<br>
differentiation between protocols, loaders, and standard plugins, I decided not<br>
to just add GObjectification to the current plugin API. Free language loaders<br>
was also an interesting prospect of this change.<br>
<br>
As it turns out, Gary (grim) had already written a very promising plugin library<br>
that pretty much did exactly what we want, GPlugin<br>
(<a href="https://bitbucket.org/rw_grim/gplugin" target="_blank">https://bitbucket.org/rw_grim/gplugin</a>). It is robust, lets you register new<br>
types into the GObject type system, and can support plugins written in different<br>
programming languages via loaders (although I believe only native C is fully<br>
functional as of yet). Evaluating all the requirements for the new plugin API<br>
with Ethan, it seemed that if I was to not use GPlugin, I would have ended up<br>
with an API whose functionality resembled that of GPlugin anyway, but instead<br>
would've taken longer to write. So rather than spending time reinventing the<br>
wheel, we decided to use GPlugin as the backbone for the new purple plugin API.<br>
<br>
<br>
BRIEF OVERVIEW OF GPLUGIN<br>
=========================<br>
<br>
A brief introduction to the basic data structures of GPlugin:<br>
<br>
1. GPluginPlugin<br>
   This is an abstract type that represents a plugin of any type in GPlugin.<br>
<br>
2. GPluginNativePlugin<br>
   It inherits GPluginPlugin and implements the GTypePlugin interface of the<br>
   GObject library. It represents a native C plugin. It allows registering new<br>
   types and interfaces into the type system.<br>
<br>
3. GPluginPluginLoader<br>
   It is an abstract type that represents a plugin loader. The plugin loader is<br>
   responsible for loading plugins of a particular language.<br>
<br>
4. GPluginNativePluginLoader<br>
   It is the loader for native C plugins. This is where the actual module<br>
   loading, unloading, and querying happens for native plugins.<br>
<br>
5. GPluginPluginInfo<br>
   This type holds the information about a plugin, such as the id, name, author,<br>
   website etc. This is a GObject instead of a struct, so that applications such<br>
   as libpurple can inherit it and add their own fields in the plugin's info.<br>
<br>
The gplugin_plugin_manager_* API provides the calls to add search paths, finding<br>
plugins, and loading/unloading of plugins. You don't really need to care much<br>
about loaders as GPlugin pretty much handles them transparently, unless you're<br>
writing a loader in which case you can register your loader using<br>
gplugin_plugin_manager_register_loader().<br>
<br>
Here is an example of a very basic plugin that you can write using GPlugin:<br>
<a href="https://bitbucket.org/rw_grim/gplugin/src/f64b9822d56dd25da027c26e47319f6817dffb0b/tests/plugins/basic-plugin.c?at=default" target="_blank">https://bitbucket.org/rw_grim/gplugin/src/f64b9822d56dd25da027c26e47319f6817dffb0b/tests/plugins/basic-plugin.c?at=default</a><br>


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