DEV Community

Gary Kramlich
Gary Kramlich

Posted on • Originally published at

Protocol/Service Agnosticism In Purple 3

This article was originally posted on Patreon and has been brought over here to get all of the Pidgin Development/History posts into one single place.

I'm not quite sure when it was created, but Pidgin and Gaim before it has had a goal when it comes to Protocol/Service Agnosticism. That is, "It's more important that "I want to talk to John" versus “I want to talk to John on his XMPP account at”

In Pidgin 2, we handled just about all of this via the Buddy List API, but that's also one of the places where Pidgin 2 really shows its age. There are things like buddies and groups that are still named after their AIM counter parts that go way back to the project creations as Gaim in 1998. There are even references that a buddy's name is a screenname which is very AIM specific.

API wise the naming of things wasn't exactly important because it was at least consistent. However, the API grew in some interesting ways to help enforce these ideas. Internally we have the Buddy List API which is of course the list of contacts that you have friended or connected with. Calling this list has never been actually been accurate because it's always been a tree...

The Buddy List is an n-ary tree, consisting of a symbolic root node that contained all of the groups. The groups then contained all of the contacts (more on that in a minute) and any chat rooms that were saved to your Buddy List. Finally the contacts held all of your buddies.

The contacts were a Gaim specific thing that allowed you to group Buddies from multiple protocols into an individual that was sorted by presence and user preference. This meant if you had an AIM and MSN contact for someone, you could merge them into a Contact and Gaim would use this throughout the program to meet a large portion of the Protocol/Service Agnosticism goal.

This API and data structures worked for a surprisingly long time, but it did start showing some issues when protocols like Google Plus and Facebook started allowing you to put a contact into multiple circles/groups. There were some workarounds for this, but it was obvious then that the abstraction was starting to fail.

Aside from the Buddy List we didn't actually have a way to store data about random contacts. So as the years went one and chat protocols started using unique identifiers for users rather than usernames the abstraction breakage really started to become clear.

A lot of the protocol plugins for modern protocols will add all contacts from the protocol that they see to your list in a single group so that it can keep a cache of users and their information. The Buddy List API allows you to store arbitrary data with a Buddy which the protocol plugins used to avoid having to constantly ask the service for it. This is less than ideal because it clutters the user interface with a bunch of stuff users don't care about and it's literally hacking this functionality into a clearly broken abstraction.

But the fun doesn't stop here. In purple 2, for every multiple user chat we have a different abstraction of PurpleConvChatBuddy. This allowed us to track stuff like roles/permissions in the chat and even a display name that's only used in the chat, but that was about it. Nothing for handling user identifiers, presence, etc.

Worst of all though was the way we identify contacts for direct messages. That is done via just a string and the account. So there is literally no way to store any extra data with them like a user identifier and a display name. Worst of all you could receive and direct message from someone's alias in a multiple user chat as well as their real username in which case libpurple would have no idea that these were the same person and it will create two separate conversations which totally breaks the aforementioned goal.

So, how do we fix all of this? It's taken us a few iterations, and in fact we haven't even finished implementing the current one, but the new idea is PurpleContactInfo. The main job of PurpleContactInfo is to give us one interface to represent contacts everywhere. It contains properties for user identifier, username, display name which is set by the contact themselves, local aliases set by the libpurple user, avatars which are used in many modern protocols, and presence. There are additional fields that allow some other cool things but we're going to focus on these for now.

This object alone lets us merge four APIs into a single cohesive one. The previous ones where PurpleBuddy, PurpleConvChatBuddy, the string representing dm remove users, and a large portion of the Account API.

PurpleContactInfo can be subclassed which is what PurpleAccount now does. This is done because an account is a remote contact for everyone not using libpurple and it has all of these same properties. We didn't recognize this at first, but when we did, we realized how much easier having this would be as stuff like the author of a message no longer had a distinction between a libpurple user and a remote user, everyone is a PurpleContactInfo now.

So PurpleContactInfo replaces PurpleBuddy, that's pretty easy to follow, but what about those old PurpleContact's? For those we've created a new PurplePerson class. While a PurplePerson has a lot in common with PurpleContactInfo there wasn't quite enough to make it a subclass.

PurplePerson holds a collection of PurpleContactInfo instances and considers one of them to be the "priority" contact info based on user preference and sorting on presence like what was done previously. It also allows the libpurple user to set a custom avatar and alias to be display for any PurpleContactInfo's in this person as well.

So now that we've got replacements for PurpleBuddy and PurpleContact that means we need to talk about PurpleGroup! The replacement for groups is actually very simple. Many months ago, I wrote a blog post about Tags. To quickly recap, Tags are a simple string value that can be associated with an object. They have a name and can be added to the object multiple times if the values are different.

When we created PurpleContactInfo, we gave it a PurpleTags object so that libpurple, user interfaces, and plugin developers can continue to add arbitrary data to contacts. In the case of libpurple and the protocols, they can use the group tag to add as many groups as they want. So if I wanted to add John to my work and pidgin groups, I would just add group:work and group:pidgin tags to the PurpleTags instance on his PurpleContactInfo's.

Obviously user interfaces will have to decide how they want to display these, but our plan for Pidgin 3 is to allow you to filter and sort any arbitrary tag including group.

When it comes to replacing PurpleConvChatBuddy we created PurpleConversationMember which subclasses PurpleContactInfo and adds an additional tags object as well as typing state. We skipped adding the conversation to these as they are all items of the PurpleConversation:members collection so you should always have the conversation readily available and we can then avoid a circular reference. When it comes to adding roles to them, we have some ideas but haven't put anything down yet. The idea is to have badges for each user that can include an identifier, display text, and an icon, but we need to prove that out yet.

Finally we have to talk about the replacement for the Buddy List API. But before we do there's one more piece we've added which is the new PurpleContact. When we decided we were going to create this, we renamed the existing PurpleContact to PurpleMetaContact which is what we will be calling that object going forward. The purpose of PurpleContact is to represent a saved contact/friend by associating their PurpleContactInfo with a PurpleAccount.

The replacement for the Buddy List API is PurpleContactManager. PurpleContactManager works like all of the other new managers which you can read up on in the post on managers. As the name implies, PurpleContactManager manages a collection of PurpleContact instances. It then uses the person property of PurpleContactInfo to create a GListModel of PurplePerson's which is used as the backing data store of the new ContactList display in Pidgin 3.

It also keeps track of every single contact that anyone tells it about which is how we're building the cache of mapping user identifiers to display names and the like that modern protocols need. This mean those hundreds or thousands of Discord users you may run into aren't on your friend list, and are never shown to you directly.

All of this will be accomplished by storing all of this data in a SQLite database so it will be persistent. Unfortunately this step isn't quite finished yet due to some silly dependencies in the startup of the application and the unit tests which we need to resolve before we can finish up that feature and get it merged. But the vast majority of this work is done and has been tested fairly well.

As we work towards fixing that issue we've been slowly migrating everything we can to the new ContactInfo based API, but there are some issues with that. A large part of that is that we're also overhauling and simplifying the Status API. So much so that there will be a future post about that.

Currently we have an open review request to port the Bonjour protocol plugin to both the new ContactInfo API as well as the new Status API. If you check out the review request, you'll see it's actually not too big of a change, but we still need to move everything else to it.

The remaining things for the new contacts API is port the other protocol plugins as well as Finch. We used a buggy synchronization API to allows us to build the new Contact List in Pidgin 3, but that API as well as all of the old Buddy List API, including PurpleBuddy, PurpleMetaContact, PurpleGroup, and PurpleConvChatBuddy will all be getting removed once that's done. We'll have some additional clean ups to do when it comes to some dialogs and menus, but that should be very straight forward.

I'm sure you all just learned more about contact management than you every wanted to know and I feel like I've barely scratched the surface here. If you have any questions, please feel free to ask!

I hope you're enjoying these posts! Remember they go live for patrons at 9AM CST on Mondays and go public at 12AM CST on Thursdays! If you'd like to support my work, you can join find a list of ways to do so here.

Top comments (0)