about summary refs log tree commit diff stats
path: root/src/xmpp/roster_list.c
diff options
context:
space:
mode:
authorJames Booth <boothj5@gmail.com>2016-07-24 15:08:47 +0100
committerJames Booth <boothj5@gmail.com>2016-07-24 15:08:47 +0100
commit90b9b48ab957f72aba9db664104ec04df986877e (patch)
tree9aafd02af415d24d2c5b5b5025fcc2b49a9a9303 /src/xmpp/roster_list.c
parentcc32bb773811cbdc098f27ce32cb389e6a0163a9 (diff)
downloadprofani-tty-90b9b48ab957f72aba9db664104ec04df986877e.tar.gz
Moved roster_list.c
Diffstat (limited to 'src/xmpp/roster_list.c')
-rw-r--r--src/xmpp/roster_list.c668
1 files changed, 668 insertions, 0 deletions
diff --git a/src/xmpp/roster_list.c b/src/xmpp/roster_list.c
new file mode 100644
index 00000000..789449c2
--- /dev/null
+++ b/src/xmpp/roster_list.c
@@ -0,0 +1,668 @@
+/*
+ * roster_list.c
+ *
+ * Copyright (C) 2012 - 2016 James Booth <boothj5@gmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <glib.h>
+#include <assert.h>
+
+#include "roster_list.h"
+#include "xmpp/resource.h"
+#include "xmpp/contact.h"
+#include "xmpp/jid.h"
+#include "tools/autocomplete.h"
+#include "config/preferences.h"
+
+typedef struct prof_roster_t {
+    // contacts, indexed on barejid
+    GHashTable *contacts;
+
+    // nicknames
+    Autocomplete name_ac;
+
+    // barejids
+    Autocomplete barejid_ac;
+
+    // fulljids
+    Autocomplete fulljid_ac;
+
+    // nickname to barejid map
+    GHashTable *name_to_barejid;
+
+    // groups
+    Autocomplete groups_ac;
+    GHashTable *group_count;
+} ProfRoster;
+
+static ProfRoster *roster = NULL;
+
+static gboolean _key_equals(void *key1, void *key2);
+static gboolean _datetimes_equal(GDateTime *dt1, GDateTime *dt2);
+static void _replace_name(const char *const current_name, const char *const new_name, const char *const barejid);
+static void _add_name_and_barejid(const char *const name, const char *const barejid);
+static gint _compare_name(PContact a, PContact b);
+static gint _compare_presence(PContact a, PContact b);
+
+void
+roster_create(void)
+{
+    assert(roster == NULL);
+
+    roster = malloc(sizeof(ProfRoster));
+    roster->contacts = g_hash_table_new_full(g_str_hash, (GEqualFunc)_key_equals, g_free, (GDestroyNotify)p_contact_free);
+    roster->name_ac = autocomplete_new();
+    roster->barejid_ac = autocomplete_new();
+    roster->fulljid_ac = autocomplete_new();
+    roster->name_to_barejid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+    roster->groups_ac = autocomplete_new();
+    roster->group_count = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+}
+
+void
+roster_destroy(void)
+{
+    assert(roster != NULL);
+
+    g_hash_table_destroy(roster->contacts);
+    autocomplete_free(roster->name_ac);
+    autocomplete_free(roster->barejid_ac);
+    autocomplete_free(roster->fulljid_ac);
+    g_hash_table_destroy(roster->name_to_barejid);
+    autocomplete_free(roster->groups_ac);
+    g_hash_table_destroy(roster->group_count);
+
+    free(roster);
+    roster = NULL;
+}
+
+gboolean
+roster_update_presence(const char *const barejid, Resource *resource, GDateTime *last_activity)
+{
+    assert(roster != NULL);
+
+    assert(barejid != NULL);
+    assert(resource != NULL);
+
+    PContact contact = roster_get_contact(barejid);
+    if (contact == NULL) {
+        return FALSE;
+    }
+    if (!_datetimes_equal(p_contact_last_activity(contact), last_activity)) {
+        p_contact_set_last_activity(contact, last_activity);
+    }
+    p_contact_set_presence(contact, resource);
+    Jid *jid = jid_create_from_bare_and_resource(barejid, resource->name);
+    autocomplete_add(roster->fulljid_ac, jid->fulljid);
+    jid_destroy(jid);
+
+    return TRUE;
+}
+
+PContact
+roster_get_contact(const char *const barejid)
+{
+    assert(roster != NULL);
+
+    gchar *barejidlower = g_utf8_strdown(barejid, -1);
+    PContact contact = g_hash_table_lookup(roster->contacts, barejidlower);
+    g_free(barejidlower);
+
+    return contact;
+}
+
+char*
+roster_get_msg_display_name(const char *const barejid, const char *const resource)
+{
+    assert(roster != NULL);
+
+    GString *result = g_string_new("");
+
+    PContact contact = roster_get_contact(barejid);
+    if (contact) {
+        if (p_contact_name(contact)) {
+            g_string_append(result, p_contact_name(contact));
+        } else {
+            g_string_append(result, barejid);
+        }
+    } else {
+        g_string_append(result, barejid);
+    }
+
+    if (resource && prefs_get_boolean(PREF_RESOURCE_MESSAGE)) {
+        g_string_append(result, "/");
+        g_string_append(result, resource);
+    }
+
+    char *result_str = result->str;
+    g_string_free(result, FALSE);
+
+    return result_str;
+}
+
+gboolean
+roster_contact_offline(const char *const barejid, const char *const resource, const char *const status)
+{
+    assert(roster != NULL);
+
+    PContact contact = roster_get_contact(barejid);
+
+    if (contact == NULL) {
+        return FALSE;
+    }
+    if (resource == NULL) {
+        return TRUE;
+    } else {
+        gboolean result = p_contact_remove_resource(contact, resource);
+        if (result == TRUE) {
+            Jid *jid = jid_create_from_bare_and_resource(barejid, resource);
+            autocomplete_remove(roster->fulljid_ac, jid->fulljid);
+            jid_destroy(jid);
+        }
+
+        return result;
+    }
+}
+
+void
+roster_reset_search_attempts(void)
+{
+    assert(roster != NULL);
+
+    autocomplete_reset(roster->name_ac);
+    autocomplete_reset(roster->barejid_ac);
+    autocomplete_reset(roster->fulljid_ac);
+    autocomplete_reset(roster->groups_ac);
+}
+
+void
+roster_change_name(PContact contact, const char *const new_name)
+{
+    assert(roster != NULL);
+
+    assert(contact != NULL);
+
+    const char *current_name = NULL;
+    const char *barejid = p_contact_barejid(contact);
+
+    if (p_contact_name(contact)) {
+        current_name = strdup(p_contact_name(contact));
+    }
+
+    p_contact_set_name(contact, new_name);
+    _replace_name(current_name, new_name, barejid);
+}
+
+void
+roster_remove(const char *const name, const char *const barejid)
+{
+    assert(roster != NULL);
+
+    autocomplete_remove(roster->barejid_ac, barejid);
+    autocomplete_remove(roster->name_ac, name);
+    g_hash_table_remove(roster->name_to_barejid, name);
+
+    // remove each fulljid
+    PContact contact = roster_get_contact(barejid);
+    if (contact) {
+        GList *resources = p_contact_get_available_resources(contact);
+        while (resources) {
+            GString *fulljid = g_string_new(strdup(barejid));
+            g_string_append(fulljid, "/");
+            g_string_append(fulljid, resources->data);
+            autocomplete_remove(roster->fulljid_ac, fulljid->str);
+            g_string_free(fulljid, TRUE);
+            resources = g_list_next(resources);
+        }
+        g_list_free(resources);
+
+        GSList *groups = p_contact_groups(contact);
+        GSList *curr = groups;
+        while (curr) {
+            gchar *group = curr->data;
+            if (g_hash_table_contains(roster->group_count, group)) {
+                int count = GPOINTER_TO_INT(g_hash_table_lookup(roster->group_count, group));
+                count--;
+                if (count < 1) {
+                    g_hash_table_remove(roster->group_count, group);
+                    autocomplete_remove(roster->groups_ac, group);
+                } else {
+                    g_hash_table_insert(roster->group_count, strdup(group), GINT_TO_POINTER(count));
+                }
+            }
+            curr = g_slist_next(curr);
+        }
+    }
+
+    // remove the contact
+    g_hash_table_remove(roster->contacts, barejid);
+}
+
+void
+roster_update(const char *const barejid, const char *const name, GSList *groups, const char *const subscription,
+    gboolean pending_out)
+{
+    assert(roster != NULL);
+
+    PContact contact = roster_get_contact(barejid);
+    assert(contact != NULL);
+
+    p_contact_set_subscription(contact, subscription);
+    p_contact_set_pending_out(contact, pending_out);
+
+    const char * const new_name = name;
+    const char * current_name = NULL;
+    if (p_contact_name(contact)) {
+        current_name = strdup(p_contact_name(contact));
+    }
+
+    p_contact_set_name(contact, new_name);
+    _replace_name(current_name, new_name, barejid);
+
+    GSList *curr_new_group = groups;
+    while (curr_new_group) {
+        char *new_group = curr_new_group->data;
+
+        // contact added to group
+        if (!p_contact_in_group(contact, new_group)) {
+
+            // group doesn't yet exist
+            if (!g_hash_table_contains(roster->group_count, new_group)) {
+                g_hash_table_insert(roster->group_count, strdup(new_group), GINT_TO_POINTER(1));
+                autocomplete_add(roster->groups_ac, curr_new_group->data);
+
+            // increment count
+            } else {
+                int count = GPOINTER_TO_INT(g_hash_table_lookup(roster->group_count, new_group));
+                g_hash_table_insert(roster->group_count, strdup(new_group), GINT_TO_POINTER(count + 1));
+            }
+        }
+        curr_new_group = g_slist_next(curr_new_group);
+    }
+
+    GSList *old_groups = p_contact_groups(contact);
+    GSList *curr_old_group = old_groups;
+    while (curr_old_group) {
+        char *old_group = curr_old_group->data;
+        // removed from group
+        if (!g_slist_find_custom(groups, old_group, (GCompareFunc)g_strcmp0)) {
+            if (g_hash_table_contains(roster->group_count, old_group)) {
+                int count = GPOINTER_TO_INT(g_hash_table_lookup(roster->group_count, old_group));
+                count--;
+                if (count < 1) {
+                    g_hash_table_remove(roster->group_count, old_group);
+                    autocomplete_remove(roster->groups_ac, old_group);
+                } else {
+                    g_hash_table_insert(roster->group_count, strdup(old_group), GINT_TO_POINTER(count));
+                }
+            }
+        }
+
+        curr_old_group = g_slist_next(curr_old_group);
+    }
+
+    p_contact_set_groups(contact, groups);
+}
+
+gboolean
+roster_add(const char *const barejid, const char *const name, GSList *groups, const char *const subscription,
+    gboolean pending_out)
+{
+    assert(roster != NULL);
+
+    PContact contact = roster_get_contact(barejid);
+    if (contact) {
+        return FALSE;
+    }
+
+    contact = p_contact_new(barejid, name, groups, subscription, NULL, pending_out);
+
+    // add groups
+    GSList *curr_new_group = groups;
+    while (curr_new_group) {
+        char *new_group = curr_new_group->data;
+        if (g_hash_table_contains(roster->group_count, new_group)) {
+            int count = GPOINTER_TO_INT(g_hash_table_lookup(roster->group_count, new_group));
+            g_hash_table_insert(roster->group_count, strdup(new_group), GINT_TO_POINTER(count + 1));
+        } else {
+            g_hash_table_insert(roster->group_count, strdup(new_group), GINT_TO_POINTER(1));
+            autocomplete_add(roster->groups_ac, new_group);
+        }
+
+        curr_new_group = g_slist_next(curr_new_group);
+    }
+
+    g_hash_table_insert(roster->contacts, strdup(barejid), contact);
+    autocomplete_add(roster->barejid_ac, barejid);
+    _add_name_and_barejid(name, barejid);
+
+    return TRUE;
+}
+
+char*
+roster_barejid_from_name(const char *const name)
+{
+    assert(roster != NULL);
+
+    if (name) {
+        return g_hash_table_lookup(roster->name_to_barejid, name);
+    } else {
+        return NULL;
+    }
+}
+
+GSList*
+roster_get_contacts_by_presence(const char *const presence)
+{
+    assert(roster != NULL);
+
+    GSList *result = NULL;
+    GHashTableIter iter;
+    gpointer key;
+    gpointer value;
+
+    g_hash_table_iter_init(&iter, roster->contacts);
+    while (g_hash_table_iter_next(&iter, &key, &value)) {
+        PContact contact = (PContact)value;
+        if (g_strcmp0(p_contact_presence(contact), presence) == 0) {
+            result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_name);
+        }
+    }
+
+    // return all contact structs
+    return result;
+}
+
+GSList*
+roster_get_contacts(roster_ord_t order)
+{
+    assert(roster != NULL);
+
+    GSList *result = NULL;
+    GHashTableIter iter;
+    gpointer key;
+    gpointer value;
+
+    GCompareFunc cmp_func;
+    if (order == ROSTER_ORD_PRESENCE) {
+        cmp_func = (GCompareFunc) _compare_presence;
+    } else {
+        cmp_func = (GCompareFunc) _compare_name;
+    }
+
+    g_hash_table_iter_init(&iter, roster->contacts);
+    while (g_hash_table_iter_next(&iter, &key, &value)) {
+        result = g_slist_insert_sorted(result, value, cmp_func);
+    }
+
+    // return all contact structs
+    return result;
+}
+
+GSList*
+roster_get_contacts_online(void)
+{
+    assert(roster != NULL);
+
+    GSList *result = NULL;
+    GHashTableIter iter;
+    gpointer key;
+    gpointer value;
+
+    g_hash_table_iter_init(&iter, roster->contacts);
+    while (g_hash_table_iter_next(&iter, &key, &value)) {
+        if(strcmp(p_contact_presence(value), "offline"))
+            result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_name);
+    }
+
+    // return all contact structs
+    return result;
+}
+
+gboolean
+roster_has_pending_subscriptions(void)
+{
+    assert(roster != NULL);
+
+    GHashTableIter iter;
+    gpointer key;
+    gpointer value;
+
+    g_hash_table_iter_init(&iter, roster->contacts);
+    while (g_hash_table_iter_next(&iter, &key, &value)) {
+        PContact contact = (PContact) value;
+        if (p_contact_pending_out(contact)) {
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+char*
+roster_contact_autocomplete(const char *const search_str)
+{
+    assert(roster != NULL);
+
+    return autocomplete_complete(roster->name_ac, search_str, TRUE);
+}
+
+char*
+roster_fulljid_autocomplete(const char *const search_str)
+{
+    assert(roster != NULL);
+
+    return autocomplete_complete(roster->fulljid_ac, search_str, TRUE);
+}
+
+GSList*
+roster_get_group(const char *const group, roster_ord_t order)
+{
+    assert(roster != NULL);
+
+    GSList *result = NULL;
+    GHashTableIter iter;
+    gpointer key;
+    gpointer value;
+
+    GCompareFunc cmp_func;
+    if (order == ROSTER_ORD_PRESENCE) {
+        cmp_func = (GCompareFunc) _compare_presence;
+    } else {
+        cmp_func = (GCompareFunc) _compare_name;
+    }
+
+    g_hash_table_iter_init(&iter, roster->contacts);
+    while (g_hash_table_iter_next(&iter, &key, &value)) {
+        GSList *groups = p_contact_groups(value);
+        if (group == NULL) {
+            if (groups == NULL) {
+                result = g_slist_insert_sorted(result, value, cmp_func);
+            }
+        } else {
+            while (groups) {
+                if (strcmp(groups->data, group) == 0) {
+                    result = g_slist_insert_sorted(result, value, cmp_func);
+                    break;
+                }
+                groups = g_slist_next(groups);
+            }
+        }
+    }
+
+    // return all contact structs
+    return result;
+}
+
+GSList*
+roster_get_groups(void)
+{
+    assert(roster != NULL);
+
+    return autocomplete_create_list(roster->groups_ac);
+}
+
+char*
+roster_group_autocomplete(const char *const search_str)
+{
+    assert(roster != NULL);
+
+    return autocomplete_complete(roster->groups_ac, search_str, TRUE);
+}
+
+char*
+roster_barejid_autocomplete(const char *const search_str)
+{
+    assert(roster != NULL);
+
+    return autocomplete_complete(roster->barejid_ac, search_str, TRUE);
+}
+
+static gboolean
+_key_equals(void *key1, void *key2)
+{
+    gchar *str1 = (gchar *) key1;
+    gchar *str2 = (gchar *) key2;
+
+    return (g_strcmp0(str1, str2) == 0);
+}
+
+static gboolean
+_datetimes_equal(GDateTime *dt1, GDateTime *dt2)
+{
+    if ((dt1 == NULL) && (dt2 == NULL)) {
+        return TRUE;
+    } else if ((dt1 == NULL) && (dt2 != NULL)) {
+        return FALSE;
+    } else if ((dt1 != NULL) && (dt2 == NULL)) {
+        return FALSE;
+    } else {
+        return g_date_time_equal(dt1, dt2);
+    }
+}
+
+static void
+_replace_name(const char *const current_name, const char *const new_name, const char *const barejid)
+{
+    assert(roster != NULL);
+
+    // current handle exists already
+    if (current_name) {
+        autocomplete_remove(roster->name_ac, current_name);
+        g_hash_table_remove(roster->name_to_barejid, current_name);
+        _add_name_and_barejid(new_name, barejid);
+    // no current handle
+    } else if (new_name) {
+        autocomplete_remove(roster->name_ac, barejid);
+        g_hash_table_remove(roster->name_to_barejid, barejid);
+        _add_name_and_barejid(new_name, barejid);
+    }
+}
+
+static void
+_add_name_and_barejid(const char *const name, const char *const barejid)
+{
+    assert(roster != NULL);
+
+    if (name) {
+        autocomplete_add(roster->name_ac, name);
+        g_hash_table_insert(roster->name_to_barejid, strdup(name), strdup(barejid));
+    } else {
+        autocomplete_add(roster->name_ac, barejid);
+        g_hash_table_insert(roster->name_to_barejid, strdup(barejid), strdup(barejid));
+    }
+}
+
+static gint
+_compare_name(PContact a, PContact b)
+{
+    const char * utf8_str_a = NULL;
+    const char * utf8_str_b = NULL;
+
+    if (p_contact_name_collate_key(a)) {
+        utf8_str_a = p_contact_name_collate_key(a);
+    } else {
+        utf8_str_a = p_contact_barejid_collate_key(a);
+    }
+    if (p_contact_name_collate_key(b)) {
+        utf8_str_b = p_contact_name_collate_key(b);
+    } else {
+        utf8_str_b = p_contact_barejid_collate_key(b);
+    }
+
+    gint result = g_strcmp0(utf8_str_a, utf8_str_b);
+
+    return result;
+}
+
+static gint
+_get_presence_weight(const char *presence)
+{
+    if (g_strcmp0(presence, "chat") == 0) {
+        return 0;
+    } else if (g_strcmp0(presence, "online") == 0) {
+        return 1;
+    } else if (g_strcmp0(presence, "away") == 0) {
+        return 2;
+    } else if (g_strcmp0(presence, "xa") == 0) {
+        return 3;
+    } else if (g_strcmp0(presence, "dnd") == 0) {
+        return 4;
+    } else { // offline
+        return 5;
+    }
+}
+
+static gint
+_compare_presence(PContact a, PContact b)
+{
+    const char *presence_a = p_contact_presence(a);
+    const char *presence_b = p_contact_presence(b);
+
+    // if presence different, order by presence
+    if (g_strcmp0(presence_a, presence_b) != 0) {
+        int weight_a = _get_presence_weight(presence_a);
+        int weight_b = _get_presence_weight(presence_b);
+        if (weight_a < weight_b) {
+            return -1;
+        } else {
+            return 1;
+        }
+
+    // otherwise order by name
+    } else {
+        return _compare_name(a, b);
+    }
+}