summary refs log tree commit diff stats
path: root/compiler/cgmeth.nim
blob: a0c16f2ed42c9079794327d027453a5f1c0f5c5b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#
#
#           The Nim Compiler
#        (c) Copyright 2013 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module implements code generation for methods.

import
  intsets, options, ast, msgs, idents, renderer, types, magicsys,
  sempass2, strutils, modulegraphs, lineinfos

proc genConv(n: PNode, d: PType, downcast: bool; conf: ConfigRef): PNode =
  var dest = skipTypes(d, abstractPtrs)
  var source = skipTypes(n.typ, abstractPtrs)
  if (source.kind == tyObject) and (dest.kind == tyObject):
    var diff = inheritanceDiff(dest, source)
    if diff == high(int):
      # no subtype relation, nothing to do
      result = n
    elif diff < 0:
      result = newNodeIT(nkObjUpConv, n.info, d)
      result.add n
      if downcast: internalError(conf, n.info, "cgmeth.genConv: no upcast allowed")
    elif diff > 0:
      result = newNodeIT(nkObjDownConv, n.info, d)
      result.add n
      if not downcast:
        internalError(conf, n.info, "cgmeth.genConv: no downcast allowed")
    else:
      result = n
  else:
    result = n

proc getDispatcher*(s: PSym): PSym =
  ## can return nil if is has no dispatcher.
  if dispatcherPos < s.ast.len:
    result = s.ast[dispatcherPos].sym
    doAssert sfDispatcher in result.flags

proc methodCall*(n: PNode; conf: ConfigRef): PNode =
  result = n
  # replace ordinary method by dispatcher method:
  let disp = getDispatcher(result[0].sym)
  if disp != nil:
    result[0].sym = disp
    # change the arguments to up/downcasts to fit the dispatcher's parameters:
    for i in 1..<result.len:
      result[i] = genConv(result[i], disp.typ[i], true, conf)
  else:
    localError(conf, n.info, "'" & $result[0] & "' lacks a dispatcher")

type
  MethodResult = enum No, Invalid, Yes

proc sameMethodBucket(a, b: PSym; multiMethods: bool): MethodResult =
  if a.name.id != b.name.id: return
  if a.typ.len != b.typ.len:
    return

  for i in 1..<a.typ.len:
    var aa = a.typ[i]
    var bb = b.typ[i]
    while true:
      aa = skipTypes(aa, {tyGenericInst, tyAlias})
      bb = skipTypes(bb, {tyGenericInst, tyAlias})
      if aa.kind == bb.kind and aa.kind in {tyVar, tyPtr, tyRef, tyLent}:
        aa = aa.lastSon
        bb = bb.lastSon
      else:
        break
    if sameType(a.typ[i], b.typ[i]):
      if aa.kind == tyObject and result != Invalid:
        result = Yes
    elif aa.kind == tyObject and bb.kind == tyObject and (i == 1 or multiMethods):
      let diff = inheritanceDiff(bb, aa)
      if diff < 0:
        if result != Invalid:
          result = Yes
        else:
          return No
      elif diff != high(int) and sfFromGeneric notin (a.flags+b.flags):
        result = Invalid
      else:
        return No
    else:
      return No
  if result == Yes:
    # check for return type:
    if not sameTypeOrNil(a.typ[0], b.typ[0]):
      if b.typ[0] != nil and b.typ[0].kind == tyUntyped:
        # infer 'auto' from the base to make it consistent:
        b.typ[0] = a.typ[0]
      else:
        return No

proc attachDispatcher(s: PSym, dispatcher: PNode) =
  if dispatcherPos < s.ast.len:
    # we've added a dispatcher already, so overwrite it
    s.ast[dispatcherPos] = dispatcher
  else:
    setLen(s.ast.sons, dispatcherPos+1)
    if s.ast[resultPos] == nil:
      s.ast[resultPos] = newNodeI(nkEmpty, s.info)
    s.ast[dispatcherPos] = dispatcher

proc createDispatcher(s: PSym; idgen: IdGenerator): PSym =
  var disp = copySym(s, nextId(idgen))
  incl(disp.flags, sfDispatcher)
  excl(disp.flags, sfExported)
  disp.typ = copyType(disp.typ, nextId(idgen), disp.typ.owner)
  # we can't inline the dispatcher itself (for now):
  if disp.typ.callConv == ccInline: disp.typ.callConv = ccNimCall
  disp.ast = copyTree(s.ast)
  disp.ast[bodyPos] = newNodeI(nkEmpty, s.info)
  disp.loc.r = nil
  if s.typ[0] != nil:
    if disp.ast.len > resultPos:
      disp.ast[resultPos].sym = copySym(s.ast[resultPos].sym, nextId(idgen))
    else:
      # We've encountered a method prototype without a filled-in
      # resultPos slot. We put a placeholder in there that will
      # be updated in fixupDispatcher().
      disp.ast.add newNodeI(nkEmpty, s.info)
  attachDispatcher(s, newSymNode(disp))
  # attach to itself to prevent bugs:
  attachDispatcher(disp, newSymNode(disp))
  return disp

proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) =
  # We may have constructed the dispatcher from a method prototype
  # and need to augment the incomplete dispatcher with information
  # from later definitions, particularly the resultPos slot. Also,
  # the lock level of the dispatcher needs to be updated/checked
  # against that of the method.
  if disp.ast.len > resultPos and meth.ast.len > resultPos and
     disp.ast[resultPos].kind == nkEmpty:
    disp.ast[resultPos] = copyTree(meth.ast[resultPos])

  # The following code works only with lock levels, so we disable
  # it when they're not available.
  when declared(TLockLevel):
    proc `<`(a, b: TLockLevel): bool {.borrow.}
    proc `==`(a, b: TLockLevel): bool {.borrow.}
    if disp.typ.lockLevel == UnspecifiedLockLevel:
      disp.typ.lockLevel = meth.typ.lockLevel
    elif meth.typ.lockLevel != UnspecifiedLockLevel and
         meth.typ.lockLevel != disp.typ.lockLevel:
      message(conf, meth.info, warnLockLevel,
        "method has lock level $1, but another method has $2" %
        [$meth.typ.lockLevel, $disp.typ.lockLevel])
      # XXX The following code silences a duplicate warning in
      # checkMethodeffects() in sempass2.nim for now.
      if disp.typ.lockLevel < meth.typ.lockLevel:
        disp.typ.lockLevel = meth.typ.lockLevel

proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym, fromCache: bool) =
  var witness: PSym
  for i in 0..<g.methods.len:
    let disp = g.methods[i].dispatcher
    case sameMethodBucket(disp, s, multimethods = optMultiMethods in g.config.globalOptions)
    of Yes:
      g.methods[i].methods.add(s)
      attachDispatcher(s, disp.ast[dispatcherPos])
      fixupDispatcher(s, disp, g.config)
      #echo "fixup ", disp.name.s, " ", disp.id
      when useEffectSystem: checkMethodEffects(g, disp, s)
      if {sfBase, sfFromGeneric} * s.flags == {sfBase} and
           g.methods[i].methods[0] != s:
        # already exists due to forwarding definition?
        localError(g.config, s.info, "method is not a base")
      return
    of No: discard
    of Invalid:
      if witness.isNil: witness = g.methods[i].methods[0]
  # create a new dispatcher:
  g.methods.add((methods: @[s], dispatcher: createDispatcher(s, idgen)))
  #echo "adding ", s.info
  #if fromCache:
  #  internalError(s.info, "no method dispatcher found")
  if witness != nil:
    localError(g.config, s.info, "invalid declaration order; cannot attach '" & s.name.s &
                       "' to method defined here: " & g.config$witness.info)
  elif sfBase notin s.flags:
    message(g.config, s.info, warnUseBase)

proc relevantCol(methods: seq[PSym], col: int): bool =
  # returns true iff the position is relevant
  var t = methods[0].typ[col].skipTypes(skipPtrs)
  if t.kind == tyObject:
    for i in 1..high(methods):
      let t2 = skipTypes(methods[i].typ[col], skipPtrs)
      if not sameType(t2, t):
        return true

proc cmpSignatures(a, b: PSym, relevantCols: IntSet): int =
  for col in 1..<a.typ.len:
    if contains(relevantCols, col):
      var aa = skipTypes(a.typ[col], skipPtrs)
      var bb = skipTypes(b.typ[col], skipPtrs)
      var d = inheritanceDiff(aa, bb)
      if (d != high(int)) and d != 0:
        return d

proc sortBucket(a: var seq[PSym], relevantCols: IntSet) =
  # we use shellsort here; fast and simple
  var n = a.len
  var h = 1
  while true:
    h = 3 * h + 1
    if h > n: break
  while true:
    h = h div 3
    for i in h..<n:
      var v = a[i]
      var j = i
      while cmpSignatures(a[j - h], v, relevantCols) >= 0:
        a[j] = a[j - h]
        j = j - h
        if j < h: break
      a[j] = v
    if h == 1: break

proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet): PSym =
  var base = methods[0].ast[dispatcherPos].sym
  result = base
  var paramLen = base.typ.len
  var nilchecks = newNodeI(nkStmtList, base.info)
  var disp = newNodeI(nkIfStmt, base.info)
  var ands = getSysMagic(g, unknownLineInfo, "and", mAnd)
  var iss = getSysMagic(g, unknownLineInfo, "of", mOf)
  let boolType = getSysType(g, unknownLineInfo, tyBool)
  for col in 1..<paramLen:
    if contains(relevantCols, col):
      let param = base.typ.n[col].sym
      if param.typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}:
        nilchecks.add newTree(nkCall,
            newSymNode(getCompilerProc(g, "chckNilDisp")), newSymNode(param))
  for meth in 0..high(methods):
    var curr = methods[meth]      # generate condition:
    var cond: PNode = nil
    for col in 1..<paramLen:
      if contains(relevantCols, col):
        var isn = newNodeIT(nkCall, base.info, boolType)
        isn.add newSymNode(iss)
        let param = base.typ.n[col].sym
        isn.add newSymNode(param)
        isn.add newNodeIT(nkType, base.info, curr.typ[col])
        if cond != nil:
          var a = newNodeIT(nkCall, base.info, boolType)
          a.add newSymNode(ands)
          a.add cond
          a.add isn
          cond = a
        else:
          cond = isn
    let retTyp = base.typ[0]
    let call = newNodeIT(nkCall, base.info, retTyp)
    call.add newSymNode(curr)
    for col in 1..<paramLen:
      call.add genConv(newSymNode(base.typ.n[col].sym),
                           curr.typ[col], false, g.config)
    var ret: PNode
    if retTyp != nil:
      var a = newNodeI(nkFastAsgn, base.info)
      a.add newSymNode(base.ast[resultPos].sym)
      a.add call
      ret = newNodeI(nkReturnStmt, base.info)
      ret.add a
    else:
      ret = call
    if cond != nil:
      var a = newNodeI(nkElifBranch, base.info)
      a.add cond
      a.add ret
      disp.add a
    else:
      disp = ret
  nilchecks.add disp
  nilchecks.flags.incl nfTransf # should not be further transformed
  result.ast[bodyPos] = nilchecks

proc generateMethodDispatchers*(g: ModuleGraph): PNode =
  result = newNode(nkStmtList)
  for bucket in 0..<g.methods.len:
    var relevantCols = initIntSet()
    for col in 1..<g.methods[bucket].methods[0].typ.len:
      if relevantCol(g.methods[bucket].methods, col): incl(relevantCols, col)
      if optMultiMethods notin g.config.globalOptions:
        # if multi-methods are not enabled, we are interested only in the first field
        break
    sortBucket(g.methods[bucket].methods, relevantCols)
    result.add newSymNode(genDispatcher(g, g.methods[bucket].methods, relevantCols))
                                              
                                                           

         
                                              
                                              
                                                       





                                                              


     










                                                                      




                                                                             
                                                       









                                                              
 








                                                                            
                                                          







                             
                                                                           

                                                              
                                     
                                          
                                                       

     
                          
                                              
                                                       
 

                                                   

                                                         

                                                                            


                                

 



























                                                         
                                                                                       








                                     
                                                      




                                 
                                                         




                                      
                                                         

 





                                                      





                                                  





























                                                                                


                           


















                                                                      

                                           








                                                               

                                                     
                                  
                                                               












                                                                             

                                                                    


                                                              





                                                                          






                                                                               

                                                         
                                                                                       







                                                                         

                                                                   





                                                




























                                                                               


















                                                                                  








                                            



























                                                      












                                                
/*
 * roster.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 <assert.h>
#include <string.h>

#include <glib.h>
#include <strophe.h>

#include "log.h"
#include "profanity.h"
#include "tools/autocomplete.h"
#include "xmpp/connection.h"
#include "xmpp/roster.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;

// event handlers
static int _roster_handle_push(xmpp_conn_t * const conn,
    xmpp_stanza_t * const stanza, void * const userdata);
static int _roster_handle_result(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)
{
    xmpp_conn_t * const conn = connection_get_conn();
    xmpp_ctx_t * const ctx = connection_get_ctx();
    HANDLE(STANZA_TYPE_SET,    _roster_handle_push);
    HANDLE(STANZA_TYPE_RESULT, _roster_handle_result);
}

void
roster_request(void)
{
    xmpp_conn_t * const conn = connection_get_conn();
    xmpp_ctx_t * const ctx = connection_get_ctx();
    xmpp_stanza_t *iq = stanza_create_roster_iq(ctx);
    xmpp_send(conn, iq);
    xmpp_stanza_release(iq);
}

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();
    xmpp_ctx_t * const ctx = connection_get_ctx();
    xmpp_stanza_t *iq = stanza_create_roster_set(ctx, barejid, name, NULL);
    xmpp_send(conn, iq);
    xmpp_stanza_release(iq);
}

void
roster_remove(const char * const barejid)
{
    xmpp_conn_t * const conn = connection_get_conn();
    xmpp_ctx_t * const ctx = connection_get_ctx();
    xmpp_stanza_t *iq = stanza_create_roster_remove_set(ctx, barejid);
    xmpp_send(conn, iq);
    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, strdup(groups->data));
            groups = g_slist_next(groups);
        }

        g_hash_table_insert(contacts, strdup(barejid), contact);
        autocomplete_add(barejid_ac, strdup(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, strdup(groups->data));
            groups = g_slist_next(groups);
        }
    }
}

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, strdup(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;
    }
}

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);

        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, barejid, new_name,
            groups);
        xmpp_send(conn, iq);
        xmpp_stanza_release(iq);
    }
}

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;
}

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_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)
{
    xmpp_stanza_t *query =
        xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
    xmpp_stanza_t *item =
        xmpp_stanza_get_child_by_name(query, STANZA_NAME_ITEM);

    if (item == NULL) {
        return 1;
    }

    // if from attribute exists and it is not current users barejid, ignore push
    Jid *my_jid = jid_create(jabber_get_fulljid());
    const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
    if ((from != NULL) && (strcmp(from, my_jid->barejid) != 0)) {
        jid_destroy(my_jid);
        return 1;
    }
    jid_destroy(my_jid);

    const char *barejid = xmpp_stanza_get_attribute(item, STANZA_ATTR_JID);
    const char *name = xmpp_stanza_get_attribute(item, STANZA_ATTR_NAME);
    const char *sub = xmpp_stanza_get_attribute(item, STANZA_ATTR_SUBSCRIPTION);
    const char *ask = xmpp_stanza_get_attribute(item, STANZA_ATTR_ASK);

    // remove from roster
    if (g_strcmp0(sub, "remove") == 0) {
        // remove barejid and name
        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);
        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, strdup(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);

        prof_handle_roster_remove(barejid);

    // otherwise update local roster
    } else {

        // check for pending out subscriptions
        gboolean pending_out = FALSE;
        if ((ask != NULL) && (strcmp(ask, "subscribe") == 0)) {
            pending_out = TRUE;
        }

        GSList *groups = _get_groups_from_item(item);

        // update the local roster
        roster_update(barejid, name, groups, sub, pending_out);
    }

    return 1;
}

static int
_roster_handle_result(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
    void * const userdata)
{
    const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);

    // handle initial roster response
    if (g_strcmp0(id, "roster") == 0) {
        xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza,
            STANZA_NAME_QUERY);
        xmpp_stanza_t *item = xmpp_stanza_get_children(query);

        while (item != NULL) {
            const char *barejid =
                xmpp_stanza_get_attribute(item, STANZA_ATTR_JID);
            const char *name =
                xmpp_stanza_get_attribute(item, STANZA_ATTR_NAME);
            const char *sub =
                xmpp_stanza_get_attribute(item, STANZA_ATTR_SUBSCRIPTION);

            gboolean pending_out = FALSE;
            const char *ask = xmpp_stanza_get_attribute(item, STANZA_ATTR_ASK);
            if (g_strcmp0(ask, "subscribe") == 0) {
                pending_out = TRUE;
            }

            GSList *groups = _get_groups_from_item(item);

            gboolean added = roster_add(barejid, name, groups, sub, pending_out, TRUE);

            if (!added) {
                log_warning("Attempt to add contact twice: %s", barejid);
            }

            item = xmpp_stanza_get_next(item);
        }

        contact_presence_t conn_presence =
            accounts_get_login_presence(jabber_get_account_name());
        presence_update(conn_presence, NULL, 0);
    }

    return 1;
}

static void
_add_name_and_barejid(const char * const name, const char * const barejid)
{
    if (name != NULL) {
        autocomplete_add(name_ac, strdup(name));
        g_hash_table_insert(name_to_barejid, strdup(name), strdup(barejid));
    } else {
        autocomplete_add(name_ac, strdup(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)
{
    GSList *groups = NULL;
    xmpp_stanza_t *group_element = xmpp_stanza_get_children(item);

    while (group_element != NULL) {
        if (strcmp(xmpp_stanza_get_name(group_element), STANZA_NAME_GROUP) == 0) {
            char *groupname = xmpp_stanza_get_text(group_element);
            if (groupname != NULL) {
                groups = g_slist_append(groups, groupname);
            }
        }
        group_element = xmpp_stanza_get_next(group_element);
    }

    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);
    }
}