[GSoC] GObjectification Summary

Ankit Vani a at nevitus.org
Thu Sep 26 12:30:10 EDT 2013


Hi everyone :)

I was a student of Google Summer of Code this year, and it has been a great
experience working with Pidgin. My project for the summer was GObjectification
of libpurple. This involved remodeling libpurple components around the GObject
and the object oriented paradigm, and to introduce new plugin and protocol APIs
to take advantage of the new architecture. I updated most of the big components
of libpurple, introduced appropriate type hierarchy, changed the way some
components are used, and implemented a new plugin API. A new, more flexible way
of handling protocols, made possible with the new plugin API has also been
introduced. I have successfully completed almost all the requirements of my
project, except loading of non-native plugins in the new plugin API -- which I'm
working on now.

When I began, I took a look at the old gobjectification branch, which had quite
a few things GObjectified already. However, the biggest problem with the old
branch was that it was old -- it was very far behind the default branch, and
merging would have been a very difficult, tedious and error-prone task. Thus, I
decided to start from scratch based on the default branch.

To avoid the same problems we had with the gobjectification branch, I have kept
my branches up to date with default, merging regularly and often, and thus in
small managable amounts. My repository can be found at [1].


soc.2013.gobjectification (ready to merge)
==========================================

I began my work on the soc.2013.gobjectification branch. To get acquainted to
libpurple, I started off with smaller things, such as circular buffer and then
ciphers. I used the original gobjectification branch for reference, and
proceeded with the same style of GObjectification used there at first. After
GObjectification of ciphers, I was comfortable with GObjectification in purple,
and moved on to accounts. I started gaining more exposure to the libpurple
source and the way its used once I start with accounts and other core components
that came later. Eventually, the code was just writing itself :)

My usual sequence for GObjectification of a particular component was:
- Add the boilerplate GObject code to the header files, and define the instance
  and class structs. Move the members to a private struct in the .c file.
- Add new types (such as  subclasses) if needed to the header files
- Update the API functions to use the new types. Delete (eg. *_destroy
  functions), add and rename some functions as required.
- Start refactoring the source based on the new header file content, keep
  changing the API if required during the course of refactoring
- Update the .c file to implement the new APIs
- Update finch and pidgin to use the new API

At this point, the various subsystems that use GObjectified stuff are:

1.  Accounts          (PurpleAccount)
2.  Ciphers           (PurpleCipher, PurpleAESCipher, PurpleDESCipher,
                       PurpleDES3Cipher, PurpleHMACCipher, PurplePBKDF2Cipher,
                       PurpleRC4Cipher)
3.  Hashes            (PurpleHash, PurpleMD4Hash, PurpleMD5Hash, PurpleSHA1Hash,
                       PurpleSHA265Hash)
4.  Buddy list        (PurpleBuddyList, PurpleBlistNode, PurpleCountingNode,
                       PurpleBuddy, PurpleContact, PurpleChat, PurpleGroup)
5.  Circular buffers  (PurpleCircularBuffer)
6.  Connections       (PurpleConnection)
7.  Conversations     (PurpleConversation, PurpleIMConversation,
                       PurpleChatConversation, PurpleChatUser)
8.  Presence          (PurplePresence)
9.  Roomlists         (PurpleRoomlist)
10. Statuses          (PurpleStatus)
11. Whiteboards       (PurpleWhiteboard)
12. File transfers    (PurpleXfer)

For more details regarding some of these, you can read my progress reports at
[2], [3], [4]. However, some things may have changed since then.

Other than these, many other types are GBoxed and thus also have GTypes. A few
of these are temporarily GBoxed that we should probably turn into a GObject
(indicated by a TODO on the get_type functions). GTypes are also available for
enums, which have auto-generated GEnums in enums.h and enums.c, using the
glib-mkenums tool.

Having a GType for all these types eliminated the need of PurpleType and
PurpleValue, which have now been removed. Most of the places that used
PurpleType and PurpleValue use GType now instead (eg. signal registration), and
some places use GValue (eg. account options).

I have updated the .dox files wherever appropriate, and have updated ChangeLog
and ChangeLog.API. However, the total amount of changes were too numerous to
list in ChangeLog.API, and thus for GObjectified components, I have also added a
note suggesting to take a look at the documentation of that component for
details.

This branch should be ready to merge, if no one has any objections.


soc.2013.gobjectification.plugins
=================================

Plugins
-------

After GSoC midterms, I started with the other big part of my project -- a
GObject-friendly plugin API and an interface-based protocol API. I started this
in a seperate branch, soc.2013.gobjectification.plugins based on
soc.2013.gobjectification, to ensure that the components GObjectified other than
plugins and protocols would be ready to merge, even if I run into problems with
the new plugin API. This turned out to be a good decision, since as of right
now, a little more work is needed with plugins -- which I'm working on. However,
soc.2013.gobjectification can be merged now and would reduce the amount of
merging needed when plugins and protocols are merged.

After I evaluated the requirements for the new plugin API, I found out that
Gary Kramlich had written a nice plugin library -- GPlugin [5], that satisfied
all the requirements I had for the new plugin API for purple. It is robust, lets
you register new types into the GObject type system, and can support plugins
written in different languages via GObject introspection. After discussing with
my mentor Ethan, and Gary, I decided to use GPlugin as the backbone for the new
purple plugin API. Over the course of the program, I have also made changes in
GPlugin, which Gary merged.

I had posted an update on the mailing list regarding the new plugin API, which
you can see at [6]. There have been quite a few changes since then -- to both
the purple API and to GPlugin, but the overall idea remains the same.

Porting old plugins to the new API is usually quite simple:
1. Change PURPLE_INIT_PLUGIN(static_name, plugin_init, info_struct) to
   PURPLE_PLUGIN_INIT(static_name, plugin_query, plugin_load, plugin_unload).
2. plugin_query, plugin_load and plugin_unload are mandatory. Move plugin_init
   code to plugin_load if possible (adding pref paths goes here now), otherwise
   to plugin_query if you strictly need it to be run only once (only for crazy
   hackery, since plugin_query is not meant for this).
3. Remove the PurplePluginInfo struct, and return a PurplePluginInfo from
   plugin_query using purple_plugin_info_new(). If you have a UI requirement
   such as Pidgin or Finch, use pidgin_plugin_info_new() or
   finch_plugin_info_new().
4. Add a GError **error argument to plugin_load and plugin_unload, and if
   returning FALSE, set the error using g_set_error().

However, the new API lets you register new types in the type system from
plugins. This is made possible by the purple_plugin_register_type() and
purple_plugin_add_interface() functions. However, some convinience macros make
life much easier:

1. PURPLE_DEFINE_TYPE: Defines a *_get_type() function; and a *_register_type()
                       function for use in your plugin's load function. You must
                       define an instance initialization function *_init() and a
                       class initialization function *_class_init() for the
                       type. The type will be registered statically if used in a
                       static protocol or if plugins support is disabled.

2. PURPLE_DEFINE_TYPE_EXTENDED: A more general version of PURPLE_DEFINE_TYPE()
                                which allows you to specify GTypeFlags and
                                custom code.

3. PURPLE_IMPLEMENT_INTERFACE_STATIC: Static interface addition in
                                      PURPLE_DEFINE_TYPE_EXTENDED(). You should
                                      use this macro if the interface is a part
                                      of the libpurple core.

4. PURPLE_IMPLEMENT_INTERFACE: Interface addition in
                               PURPLE_DEFINE_TYPE_EXTENDED(). You should use
                               this macro if the interface lives in the plugin.

There are more specific versions of these macros that you shouldn't need to use
directly. They are used by the above listed generalized macros.

The new plugin API does not have a seperate IPC API. However, I have added two
new example plugins to demonstrate using types defined in another plugin:

1. caesarcipher:          Defines a new type, CaesarCipher (implementation of
                          the classic Caesar cipher), which inherits
                          PurpleCipher.
2. caesarcipher_consumer: Uses the CaesarCipher type and displays encrypted and
                          decrypted text.


Protocols
---------

Starting with the new plugin API, protocols are no longer a seperate type of
plugins. In fact, any plugin can define a protocol and add it to the protocols
subsystem using purple_protocols_add(). A protocol is uniquely identified by the
protocol ID, for example "aim", "xmpp", "yahoo".

Earlier, a PurplePlugin instance of type protocol (prpl) used to represent a
protocol. Now, a PurpleProtocol object instance does this.

The PurpleProtocol object holds data such as the protocol ID, protocol name,
options, user splits, icon spec, and whiteboard operations.

The huge PurplePluginProtocolInfo struct has been broken down into smaller
interfaces. The functions provided by a protocol are set these interfaces, which
are:

0.  PurpleProtocolClass:          not really an interface, but here's where the
                                  mandatory functions go
1.  PurpleProtocolClientIface:    functions related to UI, utility or just some
                                  general stuff many protocols implement
2.  PurpleProtocolServerIface:    server-related functions
3.  PurpleProtocolIMIface:        IM functions
4.  PurpleProtocolChatIface:      Chat functions
5.  PurpleProtocolPrivacyIface:   Privacy functions
6.  PurpleProtocolXferIface:      File transfer functions
7.  PurpleProtocolRoomlistIface:  Room listing functions
8.  PurpleProtocolAttentionIface: Attention functions
9.  PurpleProtocolMediaIface:     Media functions
10. PurpleProtocolFactoryIface:   Functions that return a protocol-specific
                                  subclass of a core libpurple type
                                  (eg. XMPPConnection). This has not been
                                  implemented in the protocols yet, but we
                                  should eventually do this and remove
                                  proto_data members from PurpleConnection,
                                  PurpleRoomlist, PurpleWhiteboard and
                                  PurpleXfer.

A protocol implements interfaces according to the features it provides. To check
if a protocol supports IM features, we can use
PURPLE_PROTOCOL_HAS_IM_IFACE(protocol). Or if more specifically we want to check
if the protocol implements the send_typing IM function, we can use
PURPLE_PROTOCOL_IMPLEMENTS(protocol, IM_IFACE, send_typing). All protocol
functions are called via functions such as
purple_protocol_im_iface_send_typing().

Basically, to define a new protocol type, say RandomProtocol, these things must
be done:
1. Along with the boilerplate GObject code, add the object RandomProtocol to
   random.h, which must inherit PurpleProtocol.
2. In random.c,
   a. random_protocol_init() must set the protocol's ID, name, options,
      user splits, icon spec, and whiteboard operations.
   b. random_protocol_class_init() must set the mandatory protocol functions
      login, close, status_types and list_icon.
   c. random_protocol_*_iface_init() must initialize the various interfaces the
      protocol implements
   d. PURPLE_DEFINE_TYPE_EXTENDED(
          RandomProtocol, random_protocol, PURPLE_TYPE_PROTOCOL, 0,

          PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE,
                                            random_protocol_client_iface_init)

          PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE,
                                            random_protocol_server_iface_init)
          .
          .
          .
      );
   e. Call random_protocol_register_type(plugin) from plugin_load.
   f. Add the protocol to the protocols list in plugin_load by calling:
      my_protocol = purple_protocols_add(RANDOM_TYPE_PROTOCOL, error).
   d. Remove the protocol in plugin_unload by calling:
      purple_protocols_remove(my_protocol, error).

For more detail, please see the protocols in soc.2013.gobjectification.plugins
branch in [1].

Also, regarding the in-tree protocol plugins jabber, oscar and yahoo, these are
now built as single plugins that provide multiple protocols.


Regarding plugins in other languages
------------------------------------

As of now, soc.2013.gobjectification.plugins branch is not mergable because only
native C plugins work. The current in-tree loaders are now broken and will no
longer be necessary. Loaders will be implemented in GPlugin. However, GObject
introspection must be implemented in libpurple, pidgin and finch so that GPlugin
can load purple plugins in other languages. Introspection scans the sources for
types and functions, and generates a typelib that GPlugin loaders can use. There
is no need to manually write binding code, and it will be possible to write
plugins in any language that supports GObject introspection, and has a loader in
GPlugin (currently, GPlugin has a python loader).

As of this moment, introspection is set up, but annotations need to be added in
the comments of our sources. GObject introspection reads annotations from
gtk-doc style comments. So we will also be moving from doxygen to gtk-doc. A
bonus advantage of this is gtk-doc can auto-generate object hierarchies,
property documentation and signal documentation from comments which doxygen
cannot.

----


It has been a great experience working on Pidgin with you guys. I would like to
thank everyone in the Pidgin community, who helped me out, gave suggestions, and
constructive feedback when I needed it. I would like to thank Gary Kramlich, who
has been of great help in deciding how to best GObjectify particular components,
and of even greater help with the new plugin API, and my numorous doubts
regarding a lot of things!

And lastly, a very special thanks to my mentor, Ethan Blanton, for guiding me
throughout the project, pointing me in the right directions, explaining many
things about how libpurple works, and answering quite a few silly questions.
There had been times when I was stuck with choices to make, with no idea which
way to go, but I could always count on Ethan to help me through all such times.
I would also like to thank him for having a certain confidence in me, and
believing that I'll be able to handle things. :)

I love Pidgin, and I am definitely not going to stop here. I hope to keep
learning new things, and be able to contribute in different ways.

Thanks,
Ankit Vani

[1] - http://hg.pidgin.im/soc/2013/ankitkv/gobjectification/
[2] - http://pidgin.im/pipermail/devel/2013-June/023016.html
[3] - http://pidgin.im/pipermail/devel/2013-June/023055.html
[4] - http://pidgin.im/pipermail/devel/2013-July/023080.html
[5] - https://bitbucket.org/rw_grim/gplugin
[6] - http://pidgin.im/pipermail/devel/2013-August/023123.html



More information about the Devel mailing list