about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorJames Booth <boothj5@gmail.com>2013-12-14 15:34:17 +0000
committerJames Booth <boothj5@gmail.com>2013-12-14 15:34:17 +0000
commit107fdd355e487793e53799ce3ebed0659263a2f7 (patch)
treee7b2996921be06fd6ab4c8ff25f30e1b82dda3a4
parent3f6b40246a243c072770700d67c9818a7d293f6c (diff)
downloadprofani-tty-107fdd355e487793e53799ce3ebed0659263a2f7.tar.gz
Added simple mock test, refactored roster
-rw-r--r--Makefile.am52
-rw-r--r--src/command/command.c8
-rw-r--r--src/command/command.h2
-rw-r--r--src/profanity.c2
-rw-r--r--src/roster_list.c475
-rw-r--r--src/roster_list.h58
-rw-r--r--src/ui/console.c2
-rw-r--r--src/ui/core.c2
-rw-r--r--src/ui/inputwin.c1
-rw-r--r--src/ui/muc_window.h4
-rw-r--r--src/ui/notifier.h33
-rw-r--r--src/ui/ui.h17
-rw-r--r--src/ui/window.h4
-rw-r--r--src/ui/windows.c1
-rw-r--r--src/ui/windows.h4
-rw-r--r--src/xmpp/bookmark.h9
-rw-r--r--src/xmpp/capabilities.h4
-rw-r--r--src/xmpp/connection.h4
-rw-r--r--src/xmpp/iq.c1
-rw-r--r--src/xmpp/iq.h4
-rw-r--r--src/xmpp/message.c1
-rw-r--r--src/xmpp/message.h4
-rw-r--r--src/xmpp/presence.h4
-rw-r--r--src/xmpp/roster.c531
-rw-r--r--src/xmpp/roster.h4
-rw-r--r--src/xmpp/stanza.h4
-rw-r--r--src/xmpp/xmpp.h40
-rw-r--r--tests/test_roster_list.c (renamed from tests/test_roster.c)6
-rw-r--r--tests/testsuite.c2
-rw-r--r--tests/testsuite.h2
30 files changed, 703 insertions, 582 deletions
diff --git a/Makefile.am b/Makefile.am
index 88bed76f..e001a87e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -18,6 +18,7 @@ core_sources = \
 	src/profanity.h src/chat_session.c \
 	src/chat_session.h src/muc.c src/muc.h src/jid.h src/jid.c \
 	src/resource.c src/resource.h \
+	src/roster_list.c src/roster_list.h \
 	src/xmpp/xmpp.h src/xmpp/capabilities.c src/xmpp/connection.c \
 	src/xmpp/iq.c src/xmpp/message.c src/xmpp/presence.c src/xmpp/stanza.c \
 	src/xmpp/stanza.h src/xmpp/message.h src/xmpp/iq.h src/xmpp/presence.h \
@@ -26,7 +27,7 @@ core_sources = \
 	src/xmpp/bookmark.c src/xmpp/bookmark.h \
 	src/ui/ui.h src/ui/window.c src/ui/window.h src/ui/core.c \
 	src/ui/titlebar.c src/ui/statusbar.c src/ui/inputwin.c \
-	src/ui/console.c src/ui/notifier.c src/ui/notifier.h \
+	src/ui/console.c src/ui/notifier.c \
     src/ui/windows.c src/ui/windows.h \
     src/ui/muc_window.c src/ui/muc_window.h \
 	src/command/command.h src/command/command.c src/command/history.c \
@@ -39,10 +40,47 @@ core_sources = \
 	src/config/preferences.c src/config/preferences.h \
 	src/config/theme.c src/config/theme.h
 
+#test_sources = \
+#	src/contact.c src/contact.h src/log.c src/common.c \
+#	src/log.h src/profanity.c src/common.h \
+#	src/profanity.h src/chat_session.c \
+#	src/chat_session.h src/muc.c src/muc.h src/jid.h src/jid.c \
+#	src/resource.c src/resource.h \
+#	src/roster_list.c src/roster_list.h \
+#	src/xmpp/xmpp.h tests/xmpp/mock_xmpp.c \
+#	src/ui/ui.h tests/ui/mock_ui.c \
+#	src/command/command.h src/command/command.c src/command/history.c \
+#	src/command/history.h src/tools/parser.c \
+#	src/tools/parser.h \
+#	src/tools/autocomplete.c src/tools/autocomplete.h \
+#	src/tools/history.c src/tools/history.h \
+#	src/tools/tinyurl.c src/tools/tinyurl.h \
+#	src/config/accounts.c src/config/accounts.h \
+#	src/config/preferences.c src/config/preferences.h \
+#	src/config/theme.c src/config/theme.h \
+#	tests/test_roster_list.c tests/test_common.c tests/test_history.c \
+#	tests/test_autocomplete.c tests/testsuite.c tests/test_parser.c \
+#	tests/test_jid.c tests/test_command.c
+
 test_sources = \
-	tests/test_roster.c tests/test_common.c tests/test_history.c \
-	tests/test_autocomplete.c tests/testsuite.c tests/test_parser.c \
-	tests/test_jid.c
+	src/contact.c src/contact.h src/log.c src/common.c \
+	src/log.h src/profanity.c src/common.h \
+	src/profanity.h src/chat_session.c \
+	src/chat_session.h src/muc.c src/muc.h src/jid.h src/jid.c \
+	src/resource.c src/resource.h \
+	src/roster_list.c src/roster_list.h \
+	src/xmpp/xmpp.h tests/xmpp/mock_xmpp.c \
+	src/ui/ui.h tests/ui/mock_ui.c \
+	src/command/command.h src/command/command.c src/command/history.c \
+	src/command/history.h src/tools/parser.c \
+	src/tools/parser.h \
+	src/tools/autocomplete.c src/tools/autocomplete.h \
+	src/tools/history.c src/tools/history.h \
+	src/tools/tinyurl.c src/tools/tinyurl.h \
+	src/config/accounts.c src/config/accounts.h \
+	src/config/preferences.c src/config/preferences.h \
+	src/config/theme.c src/config/theme.h \
+	tests/test_command.c
 
 main_source = src/main.c
 
@@ -51,8 +89,10 @@ git_sources = \
 
 if INCLUDE_GIT_VERSION
 with_git_sources = $(git_sources) $(core_sources)
+tests_with_git_sources = $(git_sources) $(test_sources)
 else
 with_git_sources = $(core_sources)
+tests_with_git_sources = $(test_sources)
 endif
 
 bin_PROGRAMS = profanity
@@ -60,7 +100,7 @@ profanity_SOURCES = $(with_git_sources) $(main_source)
 
 TESTS = tests/testsuite
 check_PROGRAMS = tests/testsuite
-tests_testsuite_SOURCES = $(with_git_sources) $(test_sources)
-tests_testsuite_LDADD = -lheadunit -lstdc++
+tests_testsuite_SOURCES = $(tests_with_git_sources)
+tests_testsuite_LDADD = -lheadunit -lstdc++ -lcmocka
 
 man_MANS = docs/profanity.1
diff --git a/src/command/command.c b/src/command/command.c
index a213d141..0ab8b013 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -36,6 +36,7 @@
 #include "config/preferences.h"
 #include "config/theme.h"
 #include "contact.h"
+#include "roster_list.h"
 #include "jid.h"
 #include "log.h"
 #include "muc.h"
@@ -133,7 +134,6 @@ static gboolean _cmd_prefs(gchar **args, struct cmd_help_t help);
 static gboolean _cmd_priority(gchar **args, struct cmd_help_t help);
 static gboolean _cmd_quit(gchar **args, struct cmd_help_t help);
 static gboolean _cmd_reconnect(gchar **args, struct cmd_help_t help);
-static gboolean _cmd_rooms(gchar **args, struct cmd_help_t help);
 static gboolean _cmd_bookmark(gchar **args, struct cmd_help_t help);
 static gboolean _cmd_roster(gchar **args, struct cmd_help_t help);
 static gboolean _cmd_software(gchar **args, struct cmd_help_t help);
@@ -2455,7 +2455,7 @@ _cmd_roster(gchar **args, struct cmd_help_t help)
 
         char *jid = args[1];
 
-        roster_remove(jid);
+        roster_send_remove(jid);
 
         return TRUE;
     }
@@ -2916,13 +2916,13 @@ _cmd_decline(gchar **args, struct cmd_help_t help)
     return TRUE;
 }
 
-static gboolean
+gboolean
 _cmd_rooms(gchar **args, struct cmd_help_t help)
 {
     jabber_conn_status_t conn_status = jabber_get_connection_status();
 
     if (conn_status != JABBER_CONNECTED) {
-        cons_show("You are not currenlty connected.");
+        cons_show("You are not currently connected.");
         return TRUE;
     }
 
diff --git a/src/command/command.h b/src/command/command.h
index c20413c8..8743d662 100644
--- a/src/command/command.h
+++ b/src/command/command.h
@@ -49,4 +49,6 @@ void cmd_history_append(char *inp);
 char *cmd_history_previous(char *inp, int *size);
 char *cmd_history_next(char *inp, int *size);
 
+gboolean _cmd_rooms(gchar **args, struct cmd_help_t help);
+
 #endif
diff --git a/src/profanity.c b/src/profanity.c
index 6bd7e2f0..d712d426 100644
--- a/src/profanity.c
+++ b/src/profanity.c
@@ -42,10 +42,10 @@
 #include "command/command.h"
 #include "common.h"
 #include "contact.h"
+#include "roster_list.h"
 #include "log.h"
 #include "muc.h"
 #include "resource.h"
-#include "ui/notifier.h"
 #include "ui/ui.h"
 #include "xmpp/xmpp.h"
 
diff --git a/src/roster_list.c b/src/roster_list.c
new file mode 100644
index 00000000..37031ca4
--- /dev/null
+++ b/src/roster_list.c
@@ -0,0 +1,475 @@
+/*
+ * roster_list.c
+ *
+ * Copyright (C) 2012, 2013 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <string.h>
+#include <glib.h>
+#include <assert.h>
+
+#include "roster_list.h"
+#include "resource.h"
+#include "contact.h"
+#include "jid.h"
+#include "tools/autocomplete.h"
+#include "xmpp/xmpp.h"
+#include "profanity.h"
+
+// nicknames
+static Autocomplete name_ac;
+
+// barejids
+static Autocomplete barejid_ac;
+
+// fulljids
+static Autocomplete fulljid_ac;
+
+// groups
+static Autocomplete groups_ac;
+
+// contacts, indexed on barejid
+static GHashTable *contacts;
+
+// nickname to jid map
+static GHashTable *name_to_barejid;
+
+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_contacts(PContact a, PContact b);
+
+void
+roster_clear(void)
+{
+    autocomplete_clear(name_ac);
+    autocomplete_clear(barejid_ac);
+    autocomplete_clear(fulljid_ac);
+    autocomplete_clear(groups_ac);
+    g_hash_table_destroy(contacts);
+    contacts = g_hash_table_new_full(g_str_hash, (GEqualFunc)_key_equals, g_free,
+        (GDestroyNotify)p_contact_free);
+    g_hash_table_destroy(name_to_barejid);
+    name_to_barejid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+        g_free);
+}
+
+gboolean
+roster_update_presence(const char * const barejid, Resource *resource,
+    GDateTime *last_activity)
+{
+    assert(barejid != NULL);
+    assert(resource != NULL);
+
+    PContact contact = g_hash_table_lookup(contacts, 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(fulljid_ac, jid->fulljid);
+    jid_destroy(jid);
+
+    return TRUE;
+}
+
+PContact
+roster_get_contact(const char * const barejid)
+{
+    return g_hash_table_lookup(contacts, barejid);
+}
+
+gboolean
+roster_contact_offline(const char * const barejid,
+    const char * const resource, const char * const status)
+{
+    PContact contact = g_hash_table_lookup(contacts, 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(fulljid_ac, jid->fulljid);
+            jid_destroy(jid);
+        }
+
+        return result;
+    }
+}
+
+void
+roster_reset_search_attempts(void)
+{
+    autocomplete_reset(name_ac);
+    autocomplete_reset(barejid_ac);
+    autocomplete_reset(fulljid_ac);
+    autocomplete_reset(groups_ac);
+}
+
+void
+roster_init(void)
+{
+    name_ac = autocomplete_new();
+    barejid_ac = autocomplete_new();
+    fulljid_ac = autocomplete_new();
+    groups_ac = autocomplete_new();
+    contacts = g_hash_table_new_full(g_str_hash, (GEqualFunc)_key_equals, g_free,
+        (GDestroyNotify)p_contact_free);
+    name_to_barejid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+        g_free);
+}
+
+void
+roster_free(void)
+{
+    autocomplete_free(name_ac);
+    autocomplete_free(barejid_ac);
+    autocomplete_free(fulljid_ac);
+    autocomplete_free(groups_ac);
+}
+
+void                                                                                  
+roster_change_name(const char * const barejid, const char * const new_name)
+{
+    PContact contact = g_hash_table_lookup(contacts, barejid);                   
+    const char * current_name = NULL;                                            
+    if (p_contact_name(contact) != NULL) {                                       
+        current_name = strdup(p_contact_name(contact));                          
+    }                                                                            
+                                                                                 
+    if (contact != NULL) {                                                       
+        p_contact_set_name(contact, new_name);                                   
+        _replace_name(current_name, new_name, barejid);                          
+                                                                                 
+        GSList *groups = p_contact_groups(contact);
+        roster_send_name_change(barejid, new_name, groups);
+    }
+}
+
+void
+roster_remove(const char * const name, const char * const barejid)
+{
+    autocomplete_remove(barejid_ac, barejid);
+    autocomplete_remove(name_ac, name);
+    g_hash_table_remove(name_to_barejid, name);
+
+    // remove each fulljid
+    PContact contact = roster_get_contact(barejid);
+    if (contact != NULL) {
+        GList *resources = p_contact_get_available_resources(contact);
+        while (resources != NULL) {
+            GString *fulljid = g_string_new(strdup(barejid));
+            g_string_append(fulljid, "/");
+            g_string_append(fulljid, resources->data);
+            autocomplete_remove(fulljid_ac, fulljid->str);
+            g_string_free(fulljid, TRUE);
+            resources = g_list_next(resources);
+        }
+    }
+
+    // remove the contact
+    g_hash_table_remove(contacts, barejid);
+}
+
+void
+roster_update(const char * const barejid, const char * const name,
+    GSList *groups, const char * const subscription, gboolean pending_out)
+{
+    PContact contact = g_hash_table_lookup(contacts, barejid);
+
+    if (contact == NULL) {
+        roster_add(barejid, name, groups, subscription, pending_out, FALSE);
+    } else {
+        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) != NULL) {
+            current_name = strdup(p_contact_name(contact));
+        }
+
+        p_contact_set_name(contact, new_name);
+        p_contact_set_groups(contact, groups);
+        _replace_name(current_name, new_name, barejid);
+
+        // add groups
+        while (groups != NULL) {
+            autocomplete_add(groups_ac, groups->data);
+            groups = g_slist_next(groups);
+        }
+    }
+}
+
+gboolean
+roster_add(const char * const barejid, const char * const name, GSList *groups,
+    const char * const subscription, gboolean pending_out, gboolean from_initial)
+{
+    gboolean added = FALSE;
+    PContact contact = g_hash_table_lookup(contacts, barejid);
+
+    if (contact == NULL) {
+        contact = p_contact_new(barejid, name, groups, subscription, NULL,
+            pending_out);
+
+        // add groups
+        while (groups != NULL) {
+            autocomplete_add(groups_ac, groups->data);
+            groups = g_slist_next(groups);
+        }
+
+        g_hash_table_insert(contacts, strdup(barejid), contact);
+        autocomplete_add(barejid_ac, barejid);
+        _add_name_and_barejid(name, barejid);
+
+        if (!from_initial) {
+            prof_handle_roster_add(barejid, name);
+        }
+
+        added = TRUE;
+    }
+
+    return added;
+}
+
+char *
+roster_barejid_from_name(const char * const name)
+{
+    return g_hash_table_lookup(name_to_barejid, name);
+}
+
+GSList *
+roster_get_contacts(void)
+{
+    GSList *result = NULL;
+    GHashTableIter iter;
+    gpointer key;
+    gpointer value;
+
+    g_hash_table_iter_init(&iter, contacts);
+    while (g_hash_table_iter_next(&iter, &key, &value)) {
+        result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_contacts);
+    }
+
+    // resturn all contact structs
+    return result;
+}
+
+gboolean
+roster_has_pending_subscriptions(void)
+{
+    GHashTableIter iter;
+    gpointer key;
+    gpointer value;
+
+    g_hash_table_iter_init(&iter, 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_find_contact(char *search_str)
+{
+    return autocomplete_complete(name_ac, search_str);
+}
+
+char *
+roster_find_resource(char *search_str)
+{
+    return autocomplete_complete(fulljid_ac, search_str);
+}
+
+GSList *
+roster_get_group(const char * const group)
+{
+    GSList *result = NULL;
+    GHashTableIter iter;
+    gpointer key;
+    gpointer value;
+
+    g_hash_table_iter_init(&iter, contacts);
+    while (g_hash_table_iter_next(&iter, &key, &value)) {
+        GSList *groups = p_contact_groups(value);
+        while (groups != NULL) {
+            if (strcmp(groups->data, group) == 0) {
+                result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_contacts);
+                break;
+            }
+            groups = g_slist_next(groups);
+        }
+    }
+
+    // resturn all contact structs
+    return result;
+}
+
+void
+roster_add_to_group(const char * const group, const char * const barejid)
+{
+    PContact contact = g_hash_table_lookup(contacts, barejid);
+
+    if (contact != NULL) {
+        if (p_contact_in_group(contact, group)) {
+            if (p_contact_name(contact) != NULL) {
+                prof_handle_already_in_group(p_contact_name(contact), group);
+            } else {
+                prof_handle_already_in_group(p_contact_barejid(contact), group);
+            }
+            return;
+        }
+
+        roster_send_add_to_group(group, contact);
+
+    }
+}
+
+void
+roster_remove_from_group(const char * const group, const char * const barejid)
+{
+    PContact contact = g_hash_table_lookup(contacts, barejid);
+
+    if (contact != NULL) {
+        if (!p_contact_in_group(contact, group)) {
+            if (p_contact_name(contact) != NULL) {
+                prof_handle_not_in_group(p_contact_name(contact), group);
+            } else {
+                prof_handle_not_in_group(p_contact_barejid(contact), group);
+            }
+            return;
+        }
+
+        roster_send_remove_from_group(group, contact);
+    }
+}
+
+GSList *
+roster_get_groups(void)
+{
+    return autocomplete_get_list(groups_ac);
+}
+
+char *
+roster_find_group(char *search_str)
+{
+    return autocomplete_complete(groups_ac, search_str);
+}
+
+char *
+roster_find_jid(char *search_str)
+{
+    return autocomplete_complete(barejid_ac, search_str);
+}
+
+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)
+{
+    // current handle exists already
+    if (current_name != NULL) {
+        autocomplete_remove(name_ac, current_name);
+        g_hash_table_remove(name_to_barejid, current_name);
+        _add_name_and_barejid(new_name, barejid);
+    // no current handle
+    } else if (new_name != NULL) {
+        autocomplete_remove(name_ac, barejid);
+        g_hash_table_remove(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)
+{
+    if (name != NULL) {
+        autocomplete_add(name_ac, name);
+        g_hash_table_insert(name_to_barejid, strdup(name), strdup(barejid));
+    } else {
+        autocomplete_add(name_ac, barejid);
+        g_hash_table_insert(name_to_barejid, strdup(barejid), strdup(barejid));
+    }
+}
+
+static
+gint _compare_contacts(PContact a, PContact b)
+{
+    const char * utf8_str_a = NULL;
+    const char * utf8_str_b = NULL;
+
+    if (p_contact_name(a) != NULL) {
+        utf8_str_a = p_contact_name(a);
+    } else {
+        utf8_str_a = p_contact_barejid(a);
+    }
+    if (p_contact_name(b) != NULL) {
+        utf8_str_b = p_contact_name(b);
+    } else {
+        utf8_str_b = p_contact_barejid(b);
+    }
+
+    gchar *key_a = g_utf8_collate_key(utf8_str_a, -1);
+    gchar *key_b = g_utf8_collate_key(utf8_str_b, -1);
+
+    gint result = g_strcmp0(key_a, key_b);
+
+    g_free(key_a);
+    g_free(key_b);
+
+    return result;
+}
diff --git a/src/roster_list.h b/src/roster_list.h
new file mode 100644
index 00000000..9382081b
--- /dev/null
+++ b/src/roster_list.h
@@ -0,0 +1,58 @@
+/*
+ * roster_list.h
+ *
+ * Copyright (C) 2012, 2013 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef ROSTER_LIST_H
+#define ROSTER_LIST_H
+
+#include <glib.h>
+
+#include "resource.h"
+#include "contact.h"
+
+void roster_clear(void);
+gboolean roster_update_presence(const char * const barejid, Resource *resource,
+    GDateTime *last_activity);
+PContact roster_get_contact(const char * const barejid);
+gboolean roster_contact_offline(const char * const barejid,
+    const char * const resource, const char * const status);
+void roster_reset_search_attempts(void);
+void roster_init(void);
+void roster_free(void);
+void roster_change_name(const char * const barejid, const char * const new_name);
+void roster_remove(const char * const name, const char * const barejid);
+void roster_update(const char * const barejid, const char * const name,
+    GSList *groups, const char * const subscription, gboolean pending_out);
+gboolean roster_add(const char * const barejid, const char * const name, GSList *groups,
+    const char * const subscription, gboolean pending_out, gboolean from_initial);
+char * roster_barejid_from_name(const char * const name);
+GSList * roster_get_contacts(void);
+gboolean roster_has_pending_subscriptions(void);
+char * roster_find_contact(char *search_str);
+char * roster_find_resource(char *search_str);
+GSList * roster_get_group(const char * const group);
+GSList * roster_get_groups(void);
+void roster_add_to_group(const char * const group, const char * const barejid);
+void roster_remove_from_group(const char * const group, const char * const barejid);
+char * roster_find_group(char *search_str);
+char * roster_find_jid(char *search_str);
+
+#endif
diff --git a/src/ui/console.c b/src/ui/console.c
index 822da420..00e891db 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -31,9 +31,9 @@
 
 #include "command/command.h"
 #include "common.h"
+#include "roster_list.h"
 #include "config/preferences.h"
 #include "config/theme.h"
-#include "ui/notifier.h"
 #include "ui/window.h"
 #include "ui/windows.h"
 #include "ui/ui.h"
diff --git a/src/ui/core.c b/src/ui/core.c
index c5cfe284..507cfb02 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -44,10 +44,10 @@
 #include "config/preferences.h"
 #include "config/theme.h"
 #include "contact.h"
+#include "roster_list.h"
 #include "jid.h"
 #include "log.h"
 #include "muc.h"
-#include "ui/notifier.h"
 #include "ui/ui.h"
 #include "ui/window.h"
 #include "ui/windows.h"
diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c
index d5b882d7..3306a246 100644
--- a/src/ui/inputwin.c
+++ b/src/ui/inputwin.c
@@ -40,6 +40,7 @@
 #include "config/theme.h"
 #include "log.h"
 #include "profanity.h"
+#include "roster_list.h"
 #include "ui/ui.h"
 #include "ui/windows.h"
 #include "xmpp/xmpp.h"
diff --git a/src/ui/muc_window.h b/src/ui/muc_window.h
index e67954a7..0b1f13ab 100644
--- a/src/ui/muc_window.h
+++ b/src/ui/muc_window.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef MUC_WINDOW_H
-#define MUC_WINDOW_H
+#ifndef UI_MUC_WINDOW_H
+#define UI_MUC_WINDOW_H
 
 #include <glib.h>
 
diff --git a/src/ui/notifier.h b/src/ui/notifier.h
deleted file mode 100644
index 9db789e8..00000000
--- a/src/ui/notifier.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * notifier.h
- *
- * Copyright (C) 2012, 2013 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 <http://www.gnu.org/licenses/>.
- *
- */
-
-void notifier_init(void);
-void notifier_uninit(void);
-
-void notify_typing(const char * const handle);
-void notify_message(const char * const handle, int win);
-void notify_room_message(const char * const handle, const char * const room,
-    int win);
-void notify_remind(void);
-void notify_invite(const char * const from, const char * const room,
-    const char * const reason);
-void notify_subscription(const char * const from);
diff --git a/src/ui/ui.h b/src/ui/ui.h
index 13e96f27..5ec4debd 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef UI_H
-#define UI_H
+#ifndef UI_UI_H
+#define UI_UI_H
 
 #include "config.h"
 
@@ -242,4 +242,17 @@ void inp_block(void);
 void inp_get_password(char *passwd);
 void inp_replace_input(char *input, const char * const new_input, int *size);
 
+// desktop notifier actions
+void notifier_init(void);
+void notifier_uninit(void);
+
+void notify_typing(const char * const handle);
+void notify_message(const char * const handle, int win);
+void notify_room_message(const char * const handle, const char * const room,
+    int win);
+void notify_remind(void);
+void notify_invite(const char * const from, const char * const room,
+    const char * const reason);
+void notify_subscription(const char * const from);
+
 #endif
diff --git a/src/ui/window.h b/src/ui/window.h
index dea6214d..69e446fa 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef WINDOW_H
-#define WINDOW_H
+#ifndef UI_WINDOW_H
+#define UI_WINDOW_H
 
 #include "config.h"
 
diff --git a/src/ui/windows.c b/src/ui/windows.c
index 88c811e5..001f1bfc 100644
--- a/src/ui/windows.c
+++ b/src/ui/windows.c
@@ -33,6 +33,7 @@
 #endif
 
 #include "common.h"
+#include "roster_list.h"
 #include "config/theme.h"
 #include "ui/ui.h"
 #include "ui/window.h"
diff --git a/src/ui/windows.h b/src/ui/windows.h
index 3cf593e8..4467e507 100644
--- a/src/ui/windows.h
+++ b/src/ui/windows.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef WINDOWS_H
-#define WINDOWS_H
+#ifndef UI_WINDOWS_H
+#define UI_WINDOWS_H
 
 void wins_init(void);
 ProfWin * wins_get_console(void);
diff --git a/src/xmpp/bookmark.h b/src/xmpp/bookmark.h
index e15b6eab..8b5418b0 100644
--- a/src/xmpp/bookmark.h
+++ b/src/xmpp/bookmark.h
@@ -1,6 +1,6 @@
 
-#ifndef BOOKMARK_H
-#define BOOKMARK_H
+#ifndef XMPP_BOOKMARK_H
+#define XMPP_BOOKMARK_H
 
 #include <glib.h>
 
@@ -13,10 +13,5 @@ struct bookmark_t {
 typedef struct bookmark_t Bookmark;
 
 void bookmark_request(void);
-void bookmark_add(const char *jid, const char *nick, gboolean autojoin);
-void bookmark_remove(const char *jid, gboolean autojoin);
-const GList *bookmark_get_list(void);
-char *bookmark_find(char *search_str);
-void bookmark_autocomplete_reset(void);
 
 #endif
diff --git a/src/xmpp/capabilities.h b/src/xmpp/capabilities.h
index d97b84b2..45d97e0f 100644
--- a/src/xmpp/capabilities.h
+++ b/src/xmpp/capabilities.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef CAPABILITIES_H
-#define CAPABILITIES_H
+#ifndef XMPP_CAPABILITIES_H
+#define XMPP_CAPABILITIES_H
 
 #include <strophe.h>
 
diff --git a/src/xmpp/connection.h b/src/xmpp/connection.h
index b5701252..f11bc8c8 100644
--- a/src/xmpp/connection.h
+++ b/src/xmpp/connection.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef CONNECTION_H
-#define CONNECTION_H
+#ifndef XMPP_CONNECTION_H
+#define XMPP_CONNECTION_H
 
 #include <strophe.h>
 
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index 1ddfcb34..fdabe771 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -38,6 +38,7 @@
 #include "xmpp/capabilities.h"
 #include "xmpp/connection.h"
 #include "xmpp/stanza.h"
+#include "roster_list.h"
 #include "xmpp/xmpp.h"
 
 #define HANDLE(ns, type, func) xmpp_handler_add(conn, func, ns, STANZA_NAME_IQ, type, ctx)
diff --git a/src/xmpp/iq.h b/src/xmpp/iq.h
index 86966e26..64844228 100644
--- a/src/xmpp/iq.h
+++ b/src/xmpp/iq.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef IQ_H
-#define IQ_H
+#ifndef XMPP_IQ_H
+#define XMPP_IQ_H
 
 void iq_add_handlers(void);
 void iq_roster_request(void);
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index 8a977490..a1644587 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -33,6 +33,7 @@
 #include "xmpp/connection.h"
 #include "xmpp/message.h"
 #include "xmpp/roster.h"
+#include "roster_list.h"
 #include "xmpp/stanza.h"
 #include "xmpp/xmpp.h"
 
diff --git a/src/xmpp/message.h b/src/xmpp/message.h
index 639ae558..3bfa60ef 100644
--- a/src/xmpp/message.h
+++ b/src/xmpp/message.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef MESSAGE_H
-#define MESSAGE_H
+#ifndef XMPP_MESSAGE_H
+#define XMPP_MESSAGE_H
 
 void message_add_handlers(void);
 
diff --git a/src/xmpp/presence.h b/src/xmpp/presence.h
index fa63d78e..fb13a699 100644
--- a/src/xmpp/presence.h
+++ b/src/xmpp/presence.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef PRESENCE_H
-#define PRESENCE_H
+#ifndef XMPP_PRESENCE_H
+#define XMPP_PRESENCE_H
 
 void presence_sub_requests_init(void);
 void presence_add_handlers(void);
diff --git a/src/xmpp/roster.c b/src/xmpp/roster.c
index 5fbb7be7..51c34ea1 100644
--- a/src/xmpp/roster.c
+++ b/src/xmpp/roster.c
@@ -32,30 +32,13 @@
 #include "tools/autocomplete.h"
 #include "xmpp/connection.h"
 #include "xmpp/roster.h"
+#include "roster_list.h"
 #include "xmpp/stanza.h"
 #include "xmpp/xmpp.h"
 
 #define HANDLE(type, func) xmpp_handler_add(conn, func, XMPP_NS_ROSTER, \
 STANZA_NAME_IQ, type, ctx)
 
-// nicknames
-static Autocomplete name_ac;
-
-// barejids
-static Autocomplete barejid_ac;
-
-// fulljids
-static Autocomplete fulljid_ac;
-
-// groups
-static Autocomplete groups_ac;
-
-// contacts, indexed on barejid
-static GHashTable *contacts;
-
-// nickname to jid map
-static GHashTable *name_to_barejid;
-
 // callback data for group commands
 typedef struct _group_data {
     char *name;
@@ -77,14 +60,7 @@ _group_remove_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     void * const userdata);
 
 // helper functions
-static void _add_name_and_barejid(const char * const name,
-    const char * const barejid);
-static void _replace_name(const char * const current_name,
-    const char * const new_name, const char * const barejid);
 GSList * _get_groups_from_item(xmpp_stanza_t *item);
-static gboolean _key_equals(void *key1, void *key2);
-static gboolean _datetimes_equal(GDateTime *dt1, GDateTime *dt2);
-static gint _compare_contacts(PContact a, PContact b);
 
 void
 roster_add_handlers(void)
@@ -106,52 +82,6 @@ roster_request(void)
 }
 
 void
-roster_init(void)
-{
-    name_ac = autocomplete_new();
-    barejid_ac = autocomplete_new();
-    fulljid_ac = autocomplete_new();
-    groups_ac = autocomplete_new();
-    contacts = g_hash_table_new_full(g_str_hash, (GEqualFunc)_key_equals, g_free,
-        (GDestroyNotify)p_contact_free);
-    name_to_barejid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-        g_free);
-}
-
-void
-roster_clear(void)
-{
-    autocomplete_clear(name_ac);
-    autocomplete_clear(barejid_ac);
-    autocomplete_clear(fulljid_ac);
-    autocomplete_clear(groups_ac);
-    g_hash_table_destroy(contacts);
-    contacts = g_hash_table_new_full(g_str_hash, (GEqualFunc)_key_equals, g_free,
-        (GDestroyNotify)p_contact_free);
-    g_hash_table_destroy(name_to_barejid);
-    name_to_barejid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-        g_free);
-}
-
-void
-roster_free()
-{
-    autocomplete_free(name_ac);
-    autocomplete_free(barejid_ac);
-    autocomplete_free(fulljid_ac);
-    autocomplete_free(groups_ac);
-}
-
-void
-roster_reset_search_attempts(void)
-{
-    autocomplete_reset(name_ac);
-    autocomplete_reset(barejid_ac);
-    autocomplete_reset(fulljid_ac);
-    autocomplete_reset(groups_ac);
-}
-
-void
 roster_add_new(const char * const barejid, const char * const name)
 {
     xmpp_conn_t * const conn = connection_get_conn();
@@ -162,7 +92,7 @@ roster_add_new(const char * const barejid, const char * const name)
 }
 
 void
-roster_remove(const char * const barejid)
+roster_send_remove(const char * const barejid)
 {
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
@@ -171,180 +101,46 @@ roster_remove(const char * const barejid)
     xmpp_stanza_release(iq);
 }
 
-gboolean
-roster_add(const char * const barejid, const char * const name, GSList *groups,
-    const char * const subscription, gboolean pending_out, gboolean from_initial)
-{
-    gboolean added = FALSE;
-    PContact contact = g_hash_table_lookup(contacts, barejid);
-
-    if (contact == NULL) {
-        contact = p_contact_new(barejid, name, groups, subscription, NULL,
-            pending_out);
-
-        // add groups
-        while (groups != NULL) {
-            autocomplete_add(groups_ac, groups->data);
-            groups = g_slist_next(groups);
-        }
-
-        g_hash_table_insert(contacts, strdup(barejid), contact);
-        autocomplete_add(barejid_ac, barejid);
-        _add_name_and_barejid(name, barejid);
-
-        if (!from_initial) {
-            prof_handle_roster_add(barejid, name);
-        }
-
-        added = TRUE;
-    }
-
-    return added;
-}
-
 void
-roster_update(const char * const barejid, const char * const name,
-    GSList *groups, const char * const subscription, gboolean pending_out)
-{
-    PContact contact = g_hash_table_lookup(contacts, barejid);
-
-    if (contact == NULL) {
-        roster_add(barejid, name, groups, subscription, pending_out, FALSE);
-    } else {
-        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) != NULL) {
-            current_name = strdup(p_contact_name(contact));
-        }
-
-        p_contact_set_name(contact, new_name);
-        p_contact_set_groups(contact, groups);
-        _replace_name(current_name, new_name, barejid);
-
-        // add groups
-        while (groups != NULL) {
-            autocomplete_add(groups_ac, groups->data);
-            groups = g_slist_next(groups);
-        }
-    }
-}
-
-gboolean
-roster_update_presence(const char * const barejid, Resource *resource,
-    GDateTime *last_activity)
+roster_send_name_change(const char * const barejid, const char * const new_name, GSList *groups)
 {
-    assert(barejid != NULL);
-    assert(resource != NULL);
-
-    PContact contact = g_hash_table_lookup(contacts, 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(fulljid_ac, jid->fulljid);
-    jid_destroy(jid);
-
-    return TRUE;
-}
-
-gboolean
-roster_contact_offline(const char * const barejid,
-    const char * const resource, const char * const status)
-{
-    PContact contact = g_hash_table_lookup(contacts, 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(fulljid_ac, jid->fulljid);
-            jid_destroy(jid);
-        }
-
-        return result;
-    }
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    xmpp_stanza_t *iq = stanza_create_roster_set(ctx, NULL, barejid, new_name,
+        groups);
+    xmpp_send(conn, iq);
+    xmpp_stanza_release(iq);
 }
 
 void
-roster_change_name(const char * const barejid, const char * const new_name)
+roster_send_add_to_group(const char * const group, PContact contact)
 {
-    PContact contact = g_hash_table_lookup(contacts, barejid);
-    const char * current_name = NULL;
-    if (p_contact_name(contact) != NULL) {
-        current_name = strdup(p_contact_name(contact));
+    GSList *groups = p_contact_groups(contact);
+    GSList *new_groups = NULL;
+    while (groups != NULL) {
+        new_groups = g_slist_append(new_groups, strdup(groups->data));
+        groups = g_slist_next(groups);
     }
 
-    if (contact != NULL) {
-        p_contact_set_name(contact, new_name);
-        _replace_name(current_name, new_name, barejid);
-
-        GSList *groups = p_contact_groups(contact);
-
-        xmpp_conn_t * const conn = connection_get_conn();
-        xmpp_ctx_t * const ctx = connection_get_ctx();
-        xmpp_stanza_t *iq = stanza_create_roster_set(ctx, NULL, barejid, new_name,
-            groups);
-        xmpp_send(conn, iq);
-        xmpp_stanza_release(iq);
+    new_groups = g_slist_append(new_groups, strdup(group));
+    // add an id handler to handle the response
+    char *unique_id = get_unique_id();
+    GroupData *data = malloc(sizeof(GroupData));
+    data->group = strdup(group);
+    if (p_contact_name(contact) != NULL) {
+        data->name = strdup(p_contact_name(contact));
+    } else {
+        data->name = strdup(p_contact_barejid(contact));
     }
-}
-
-void
-roster_add_to_group(const char * const group, const char * const barejid)
-{
-    PContact contact = g_hash_table_lookup(contacts, barejid);
-
-    if (contact != NULL) {
-        if (p_contact_in_group(contact, group)) {
-            if (p_contact_name(contact) != NULL) {
-                prof_handle_already_in_group(p_contact_name(contact), group);
-            } else {
-                prof_handle_already_in_group(p_contact_barejid(contact), group);
-            }
-            return;
-        }
-
-        GSList *groups = p_contact_groups(contact);
-        GSList *new_groups = NULL;
-        while (groups != NULL) {
-            new_groups = g_slist_append(new_groups, strdup(groups->data));
-            groups = g_slist_next(groups);
-        }
 
-        new_groups = g_slist_append(new_groups, strdup(group));
-
-        xmpp_conn_t * const conn = connection_get_conn();
-        xmpp_ctx_t * const ctx = connection_get_ctx();
-
-        // add an id handler to handle the response
-        char *unique_id = get_unique_id();
-        GroupData *data = malloc(sizeof(GroupData));
-        data->group = strdup(group);
-        if (p_contact_name(contact) != NULL) {
-            data->name = strdup(p_contact_name(contact));
-        } else {
-            data->name = strdup(p_contact_barejid(contact));
-        }
-
-        xmpp_id_handler_add(conn, _group_add_handler, unique_id, data);
-        xmpp_stanza_t *iq = stanza_create_roster_set(ctx, unique_id, barejid,
-            p_contact_name(contact), new_groups);
-        xmpp_send(conn, iq);
-        xmpp_stanza_release(iq);
-        free(unique_id);
-    }
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    xmpp_id_handler_add(conn, _group_add_handler, unique_id, data);
+    xmpp_stanza_t *iq = stanza_create_roster_set(ctx, unique_id, p_contact_barejid(contact),
+        p_contact_name(contact), new_groups);
+    xmpp_send(conn, iq);
+    xmpp_stanza_release(iq);
+    free(unique_id);
 }
 
 static int
@@ -362,50 +158,36 @@ _group_add_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
 }
 
 void
-roster_remove_from_group(const char * const group, const char * const barejid)
+roster_send_remove_from_group(const char * const group, PContact contact)
 {
-    PContact contact = g_hash_table_lookup(contacts, barejid);
-
-    if (contact != NULL) {
-        if (!p_contact_in_group(contact, group)) {
-            if (p_contact_name(contact) != NULL) {
-                prof_handle_not_in_group(p_contact_name(contact), group);
-            } else {
-                prof_handle_not_in_group(p_contact_barejid(contact), group);
-            }
-            return;
-        }
-
-        GSList *groups = p_contact_groups(contact);
-        GSList *new_groups = NULL;
-        while (groups != NULL) {
-            if (strcmp(groups->data, group) != 0) {
-                new_groups = g_slist_append(new_groups, strdup(groups->data));
-            }
-            groups = g_slist_next(groups);
+    GSList *groups = p_contact_groups(contact);
+    GSList *new_groups = NULL;
+    while (groups != NULL) {
+        if (strcmp(groups->data, group) != 0) {
+            new_groups = g_slist_append(new_groups, strdup(groups->data));
         }
+        groups = g_slist_next(groups);
+    }
 
-        xmpp_conn_t * const conn = connection_get_conn();
-        xmpp_ctx_t * const ctx = connection_get_ctx();
-
-        // add an id handler to handle the response
-        char *unique_id = get_unique_id();
-        GroupData *data = malloc(sizeof(GroupData));
-        data->group = strdup(group);
-        if (p_contact_name(contact) != NULL) {
-            data->name = strdup(p_contact_name(contact));
-        } else {
-            data->name = strdup(p_contact_barejid(contact));
-        }
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
 
-        xmpp_id_handler_add(conn, _group_remove_handler, unique_id, data);
-        xmpp_stanza_t *iq = stanza_create_roster_set(ctx, unique_id, barejid,
-            p_contact_name(contact), new_groups);
-        xmpp_send(conn, iq);
-        xmpp_stanza_release(iq);
-        free(unique_id);
+    // add an id handler to handle the response
+    char *unique_id = get_unique_id();
+    GroupData *data = malloc(sizeof(GroupData));
+    data->group = strdup(group);
+    if (p_contact_name(contact) != NULL) {
+        data->name = strdup(p_contact_name(contact));
+    } else {
+        data->name = strdup(p_contact_barejid(contact));
     }
 
+    xmpp_id_handler_add(conn, _group_remove_handler, unique_id, data);
+    xmpp_stanza_t *iq = stanza_create_roster_set(ctx, unique_id, p_contact_barejid(contact),
+        p_contact_name(contact), new_groups);
+    xmpp_send(conn, iq);
+    xmpp_stanza_release(iq);
+    free(unique_id);
 }
 
 static int
@@ -422,107 +204,6 @@ _group_remove_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     return 0;
 }
 
-gboolean
-roster_has_pending_subscriptions(void)
-{
-    GHashTableIter iter;
-    gpointer key;
-    gpointer value;
-
-    g_hash_table_iter_init(&iter, contacts);
-    while (g_hash_table_iter_next(&iter, &key, &value)) {
-        PContact contact = (PContact) value;
-        if (p_contact_pending_out(contact)) {
-            return TRUE;
-        }
-    }
-
-    return FALSE;
-}
-
-GSList *
-roster_get_contacts(void)
-{
-    GSList *result = NULL;
-    GHashTableIter iter;
-    gpointer key;
-    gpointer value;
-
-    g_hash_table_iter_init(&iter, contacts);
-    while (g_hash_table_iter_next(&iter, &key, &value)) {
-        result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_contacts);
-    }
-
-    // resturn all contact structs
-    return result;
-}
-
-GSList *
-roster_get_groups(void)
-{
-    return autocomplete_get_list(groups_ac);
-}
-
-GSList *
-roster_get_group(const char * const group)
-{
-    GSList *result = NULL;
-    GHashTableIter iter;
-    gpointer key;
-    gpointer value;
-
-    g_hash_table_iter_init(&iter, contacts);
-    while (g_hash_table_iter_next(&iter, &key, &value)) {
-        GSList *groups = p_contact_groups(value);
-        while (groups != NULL) {
-            if (strcmp(groups->data, group) == 0) {
-                result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_contacts);
-                break;
-            }
-            groups = g_slist_next(groups);
-        }
-    }
-
-    // resturn all contact structs
-    return result;
-}
-
-char *
-roster_find_contact(char *search_str)
-{
-    return autocomplete_complete(name_ac, search_str);
-}
-
-char *
-roster_find_jid(char *search_str)
-{
-    return autocomplete_complete(barejid_ac, search_str);
-}
-
-char *
-roster_find_resource(char *search_str)
-{
-    return autocomplete_complete(fulljid_ac, search_str);
-}
-
-char *
-roster_find_group(char *search_str)
-{
-    return autocomplete_complete(groups_ac, search_str);
-}
-
-char *
-roster_barejid_from_name(const char * const name)
-{
-    return g_hash_table_lookup(name_to_barejid, name);
-}
-
-PContact
-roster_get_contact(const char * const barejid)
-{
-    return g_hash_table_lookup(contacts, barejid);
-}
-
 static int
 _roster_handle_push(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     void * const userdata)
@@ -556,26 +237,8 @@ _roster_handle_push(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
         if (name == NULL) {
             name = barejid;
         }
-        autocomplete_remove(barejid_ac, barejid);
-        autocomplete_remove(name_ac, name);
-        g_hash_table_remove(name_to_barejid, name);
-
-        // remove each fulljid
-        PContact contact = roster_get_contact(barejid);
-        if (contact != NULL) {
-            GList *resources = p_contact_get_available_resources(contact);
-            while (resources != NULL) {
-                GString *fulljid = g_string_new(strdup(barejid));
-                g_string_append(fulljid, "/");
-                g_string_append(fulljid, resources->data);
-                autocomplete_remove(fulljid_ac, fulljid->str);
-                g_string_free(fulljid, TRUE);
-                resources = g_list_next(resources);
-            }
-        }
 
-        // remove the contact
-        g_hash_table_remove(contacts, barejid);
+        roster_remove(name, barejid);
 
         prof_handle_roster_remove(barejid);
 
@@ -642,35 +305,6 @@ _roster_handle_result(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     return 1;
 }
 
-static void
-_add_name_and_barejid(const char * const name, const char * const barejid)
-{
-    if (name != NULL) {
-        autocomplete_add(name_ac, name);
-        g_hash_table_insert(name_to_barejid, strdup(name), strdup(barejid));
-    } else {
-        autocomplete_add(name_ac, barejid);
-        g_hash_table_insert(name_to_barejid, strdup(barejid), strdup(barejid));
-    }
-}
-
-static void
-_replace_name(const char * const current_name, const char * const new_name,
-    const char * const barejid)
-{
-    // current handle exists already
-    if (current_name != NULL) {
-        autocomplete_remove(name_ac, current_name);
-        g_hash_table_remove(name_to_barejid, current_name);
-        _add_name_and_barejid(new_name, barejid);
-    // no current handle
-    } else if (new_name != NULL) {
-        autocomplete_remove(name_ac, barejid);
-        g_hash_table_remove(name_to_barejid, barejid);
-        _add_name_and_barejid(new_name, barejid);
-    }
-}
-
 GSList *
 _get_groups_from_item(xmpp_stanza_t *item)
 {
@@ -689,54 +323,3 @@ _get_groups_from_item(xmpp_stanza_t *item)
 
     return groups;
 }
-
-static
-gboolean _key_equals(void *key1, void *key2)
-{
-    gchar *str1 = (gchar *) key1;
-    gchar *str2 = (gchar *) key2;
-
-    return (g_strcmp0(str1, str2) == 0);
-}
-
-static
-gint _compare_contacts(PContact a, PContact b)
-{
-    const char * utf8_str_a = NULL;
-    const char * utf8_str_b = NULL;
-
-    if (p_contact_name(a) != NULL) {
-        utf8_str_a = p_contact_name(a);
-    } else {
-        utf8_str_a = p_contact_barejid(a);
-    }
-    if (p_contact_name(b) != NULL) {
-        utf8_str_b = p_contact_name(b);
-    } else {
-        utf8_str_b = p_contact_barejid(b);
-    }
-
-    gchar *key_a = g_utf8_collate_key(utf8_str_a, -1);
-    gchar *key_b = g_utf8_collate_key(utf8_str_b, -1);
-
-    gint result = g_strcmp0(key_a, key_b);
-
-    g_free(key_a);
-    g_free(key_b);
-
-    return result;
-}
-
-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);
-    }
-}
diff --git a/src/xmpp/roster.h b/src/xmpp/roster.h
index 8aba28f4..1faaba86 100644
--- a/src/xmpp/roster.h
+++ b/src/xmpp/roster.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef ROSTER_H
-#define ROSTER_H
+#ifndef XMPP_ROSTER_H
+#define XMPP_ROSTER_H
 
 void roster_add_handlers(void);
 void roster_request(void);
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 108b0806..6b647258 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef STANZA_H
-#define STANZA_H
+#ifndef XMPP_STANZA_H
+#define XMPP_STANZA_H
 
 #include <strophe.h>
 
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index f1b3ba6c..21c25aa4 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef XMPP_H
-#define XMPP_H
+#ifndef XMPP_XMPP_H
+#define XMPP_XMPP_H
 
 #include <strophe.h>
 
@@ -124,32 +124,16 @@ void iq_disco_items_request(gchar *jid);
 Capabilities* caps_get(const char * const caps_str);
 void caps_close(void);
 
-void roster_clear(void);
-gboolean roster_update_presence(const char * const barejid,
-    Resource *resource, GDateTime *last_activity);
-PContact roster_get_contact(const char * const barejid);
-gboolean roster_contact_offline(const char * const barejid,
-    const char * const resource, const char * const status);
-void roster_reset_search_attempts(void);
-void roster_init(void);
-void roster_free(void);
-gboolean roster_has_pending_subscriptions(void);
-GSList * roster_get_contacts(void);
-char * roster_find_contact(char *search_str);
-char * roster_find_jid(char *search_str);
-char * roster_find_resource(char *search_str);
-char * roster_find_group(char *search_str);
-gboolean roster_add(const char * const barejid, const char * const name,
-    GSList *groups, const char * const subscription, gboolean pending_out,
-    gboolean from_initial);
-void roster_change_name(const char * const barejid, const char * const new_name);
-char * roster_barejid_from_name(const char * const name);
+void bookmark_add(const char *jid, const char *nick, gboolean autojoin);
+void bookmark_remove(const char *jid, gboolean autojoin);
+const GList *bookmark_get_list(void);
+char *bookmark_find(char *search_str);
+void bookmark_autocomplete_reset(void);
+
+void roster_send_name_change(const char * const barejid, const char * const new_name, GSList *groups);
+void roster_send_add_to_group(const char * const group, PContact contact);
+void roster_send_remove_from_group(const char * const group, PContact contact);
 void roster_add_new(const char * const barejid, const char * const name);
-void roster_remove(const char * const barejid);
-GSList * roster_get_group(const char * const group);
-void roster_add_to_group(const char * const group, const char * const barejid);
-void roster_remove_from_group(const char * const group,
-    const char * const barejid);
-GSList * roster_get_groups(void);
+void roster_send_remove(const char * const barejid);
 
 #endif
diff --git a/tests/test_roster.c b/tests/test_roster_list.c
index 0b69dc03..1281beb3 100644
--- a/tests/test_roster.c
+++ b/tests/test_roster_list.c
@@ -6,7 +6,7 @@
 #include <glib.h>
 
 #include "contact.h"
-#include "xmpp/xmpp.h"
+#include "roster_list.h"
 
 static void beforetest(void)
 {
@@ -266,9 +266,9 @@ static void find_twice_returns_first_when_two_match_and_reset(void)
     free(result2);
 }
 
-void register_roster_tests(void)
+void register_roster_list_tests(void)
 {
-    TEST_MODULE("roster tests");
+    TEST_MODULE("roster_list tests");
     BEFORETEST(beforetest);
     AFTERTEST(aftertest);
     TEST(empty_list_when_none_added);
diff --git a/tests/testsuite.c b/tests/testsuite.c
index 888e9bcd..da798b67 100644
--- a/tests/testsuite.c
+++ b/tests/testsuite.c
@@ -4,7 +4,7 @@
 int main(void)
 {
     register_history_tests();
-    register_roster_tests();
+    register_roster_list_tests();
     register_common_tests();
     register_autocomplete_tests();
     register_parser_tests();
diff --git a/tests/testsuite.h b/tests/testsuite.h
index 73d48d23..a13b66c0 100644
--- a/tests/testsuite.h
+++ b/tests/testsuite.h
@@ -2,7 +2,7 @@
 #define TESTSUITE_H
 
 void register_history_tests(void);
-void register_roster_tests(void);
+void register_roster_list_tests(void);
 void register_common_tests(void);
 void register_autocomplete_tests(void);
 void register_parser_tests(void);