view mcabber/src/roster.c @ 147:7571de4aed73

[/trunk] Changeset 159 by mikael * Fix a bug in buddylist_build() * We now lock the current buddy even not when being in chat mode. For example, if we're writing to s.o. and he leaves just before we press enter, we won't write to the wrong buddy... If the current_buddy is a group, we lock it too. * Remove MCABBER_TESTUNIT ifdef in roster.h (test program isn't up-to-date anymore...)
author mikael
date Fri, 29 Apr 2005 19:56:28 +0000
parents 6533a231a65e
children c3624b2a7059
line wrap: on
line source

/*
 * roster.c     -- Local roster implementation
 * 
 * Copyright (C) 2005 Mikael Berthe <bmikael@lists.lilotux.net>
 *
 * This program 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 2 of the License, or (at
 * your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include <string.h>

#include "roster.h"


/* This is a private structure type for the roster */

typedef struct {
  const char *name;
  const char *jid;
  guint type;
  enum imstatus status;
  guint flags;
  // list: user -> points to his group; group -> points to its users list
  GSList *list;
} roster;


/* ### Variables ### */

static int hide_offline_buddies;
static GSList *groups;
GList *buddylist;
GList *current_buddy;

#ifdef MCABBER_TESTUNIT
// Export groups for testing routines
GSList **pgroups = &groups;
#endif


/* ### Roster functions ### */

// Comparison function used to search in the roster (compares jids and types)
gint roster_compare_jid_type(roster *a, roster *b) {
  if (! (a->type & b->type))
    return -1; // arbitrary (but should be != , of course)
  return strcasecmp(a->jid, b->jid);
}

// Comparison function used to sort the roster (by name)
gint roster_compare_name(roster *a, roster *b) {
  return strcasecmp(a->name, b->name);
}

// Finds a roster element (user, group, agent...), by jid or name
// If roster_type is 0, returns match of any type.
// Returns the roster GSList element, or NULL if jid/name not found
GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type)
{
  GSList *sl_roster_elt = groups;
  GSList *res;
  roster sample;
  GCompareFunc comp;

  if (!jidname)
    return NULL;    // should not happen

  if (!roster_type)
    roster_type = ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_GROUP;

  sample.type = roster_type;
  if (type == jidsearch) {
    sample.jid = jidname;
    comp = (GCompareFunc)&roster_compare_jid_type;
  } else if (type == namesearch) {
    sample.name = jidname;
    comp = (GCompareFunc)&roster_compare_name;
  } else
    return NULL;    // should not happen

  while (sl_roster_elt) {
    roster *roster_elt = (roster*)sl_roster_elt->data;
    if (roster_type & ROSTER_TYPE_GROUP) {
      if ((type == namesearch) && !strcasecmp(jidname, roster_elt->name))
        return sl_roster_elt;
    } else {
      res = g_slist_find_custom(roster_elt->list, &sample, comp);
      if (res)
        return res;
    }
    sl_roster_elt = g_slist_next(sl_roster_elt);
  }
  return NULL;
}

// Returns pointer to new group, or existing group with that name
GSList *roster_add_group(const char *name)
{
  roster *roster_grp;
  // #1 Check name doesn't already exist
  if (!roster_find(name, namesearch, ROSTER_TYPE_GROUP)) {
    // #2 Create the group node
    roster_grp = g_new0(roster, 1);
    roster_grp->name = g_strdup(name);
    roster_grp->type = ROSTER_TYPE_GROUP;
    // #3 Insert (sorted)
    groups = g_slist_insert_sorted(groups, roster_grp,
            (GCompareFunc)&roster_compare_name);
  }
  return roster_find(name, namesearch, ROSTER_TYPE_GROUP);
}

// Returns a pointer to the new user, or existing user with that name
GSList *roster_add_user(const char *jid, const char *name, const char *group,
        guint type)
{
  roster *roster_usr;
  roster *my_group;
  GSList *slist;

  if ((type != ROSTER_TYPE_USER) && (type != ROSTER_TYPE_AGENT)) {
    // XXX Error message?
    return NULL;
  }

  // Let's be arbitrary: default group has an empty name ("").
  if (!group)  group = "";

  // #1 Check this user doesn't already exist
  if ((slist = roster_find(jid, jidsearch, type)) != NULL)
    return slist;
  // #2 add group if necessary
  slist = roster_add_group(group);
  if (!slist) return NULL;
  my_group = (roster*)slist->data;
  // #3 Create user node
  roster_usr = g_new0(roster, 1);
  roster_usr->jid   = g_strdup(jid);
  if (name) {
    roster_usr->name  = g_strdup(name);
  } else {
    gchar *p, *str = g_strdup(jid);
    p = strstr(str, "/");
    if (p)  *p = '\0';
    roster_usr->name = g_strdup(str);
    g_free(str);
  }
  roster_usr->type  = type; //ROSTER_TYPE_USER;
  roster_usr->list  = slist;    // (my_group SList element)
  // #4 Insert node (sorted)
  my_group->list = g_slist_insert_sorted(my_group->list, roster_usr,
          (GCompareFunc)&roster_compare_name);
  return roster_find(jid, jidsearch, type);
}

// Removes user (jid) from roster, frees allocated memory
void roster_del_user(const char *jid)
{
  GSList *sl_user, *sl_group;
  GSList **sl_group_listptr;
  roster *roster_usr;

  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
  if (sl_user == NULL)
    return;
  // Let's free memory (jid, name)
  roster_usr = (roster*)sl_user->data;
  if (roster_usr->jid)
    g_free((gchar*)roster_usr->jid);
  if (roster_usr->name)
    g_free((gchar*)roster_usr->name);

  // That's a little complex, we need to dereference twice
  sl_group = ((roster*)sl_user->data)->list;
  sl_group_listptr = &((roster*)(sl_group->data))->list;
  *sl_group_listptr = g_slist_delete_link(*sl_group_listptr, sl_user);

  // We need to rebuild the list
  if (current_buddy)
    buddylist_build();
  // TODO What we should do, too, is to check if the deleted node is
  // current_buddy, in which case we could move current_buddy to the
  // previous (or next) node.
}

void roster_setstatus(const char *jid, enum imstatus bstat)
{
  GSList *sl_user;
  roster *roster_usr;

  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
  // If we can't find it, we add it
  if (sl_user == NULL)
    sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER);

  roster_usr = (roster*)sl_user->data;
  roster_usr->status = bstat;
}

//  roster_setflags()
// Set one or several flags to value (TRUE/FALSE)
void roster_setflags(const char *jid, guint flags, guint value)
{
  GSList *sl_user;
  roster *roster_usr;

  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
  if (sl_user == NULL)
    return;

  roster_usr = (roster*)sl_user->data;
  if (value)
    roster_usr->flags |= flags;
  else
    roster_usr->flags &= ~flags;
}
    
void roster_settype(const char *jid, guint type)
{
  GSList *sl_user;
  roster *roster_usr;

  if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
    return;

  roster_usr = (roster*)sl_user->data;
  roster_usr->type = type;
}

enum imstatus roster_getstatus(const char *jid)
{
  GSList *sl_user;
  roster *roster_usr;

  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
  if (sl_user == NULL)
    return offline; // Not in the roster, anyway...

  roster_usr = (roster*)sl_user->data;
  return roster_usr->status;
}

guint roster_gettype(const char *jid)
{
  GSList *sl_user;
  roster *roster_usr;

  if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
    return 0;

  roster_usr = (roster*)sl_user->data;
  return roster_usr->type;
}

inline guint roster_exists(const char *jidname, enum findwhat type,
        guint roster_type)
{
  if (roster_find(jidname, type, roster_type))
    return TRUE;
  return FALSE;
}

// char *roster_getgroup(...)   / Or *GSList?  Which use??
// ... setgroup(char*) ??
// guchar roster_getflags(...)
// guchar roster_getname(...)   / setname ??
// roster_del_group?


/* ### BuddyList functions ### */

//  buddylist_set_hide_offline_buddies(hide)
// "hide" values: 1=hide 0=show_all -1=invert
void buddylist_set_hide_offline_buddies(int hide)
{
  if (hide < 0)                     // NEG   (invert)
    hide_offline_buddies = !hide_offline_buddies;
  else if (hide == 0)               // FALSE (don't hide)
    hide_offline_buddies = 0;
  else                              // TRUE  (hide)
    hide_offline_buddies = 1;
}

inline int buddylist_get_hide_offline_buddies(void)
{
  return hide_offline_buddies;
}

//  buddylist_build()
// Creates the buddylist from the roster entries.
void buddylist_build(void)
{
  GSList *sl_roster_elt = groups;
  roster *roster_elt;
  roster *roster_current_buddy = NULL;
  int shrunk_group;

  // We need to remember which buddy is selected.
  if (current_buddy)
    roster_current_buddy = BUDDATA(current_buddy);
  current_buddy = NULL;

  // Destroy old buddylist
  if (buddylist) {
    g_list_free(buddylist);
    buddylist = NULL;
  }

  // Create the new list
  while (sl_roster_elt) {
    GSList *sl_roster_usrelt;
    roster *roster_usrelt;
    guint pending_group = FALSE;
    roster_elt = (roster*) sl_roster_elt->data;

    // Add the group now unless hide_offline_buddies is set,
    // in which case we'll add it only if an online buddy belongs to it.
    // We take care to keep the current_buddy in the list, too.
    if (!hide_offline_buddies || roster_elt == roster_current_buddy)
      buddylist = g_list_append(buddylist, roster_elt);
    else
      pending_group = TRUE;

    shrunk_group = roster_elt->flags & ROSTER_FLAG_HIDE;

    sl_roster_usrelt = roster_elt->list;
    while (sl_roster_usrelt) {
      roster_usrelt = (roster*) sl_roster_usrelt->data;

      // Buddy will be added if either:
      // - hide_offline_buddies is FALSE
      // - buddy is not offline
      // - buddy has a lock (for example the buddy window is currently open)
      // - buddy has a pending (non-read) message
      // - group isn't hidden (shrunk)
      // - this is the current_buddy
      if (!hide_offline_buddies || roster_usrelt == roster_current_buddy ||
          (buddy_getstatus((gpointer)roster_usrelt) != offline) ||
          (buddy_getflags((gpointer)roster_usrelt) &
               (ROSTER_FLAG_LOCK | ROSTER_FLAG_MSG))) {
        // This user should be added.  Maybe the group hasn't been added yet?
        if (pending_group &&
            (hide_offline_buddies || roster_usrelt == roster_current_buddy)) {
          // It hasn't been done yet
          buddylist = g_list_append(buddylist, roster_elt);
          pending_group = FALSE;
        }
        // Add user
        // XXX Should we add the user if there is a message and
        //     the group is shrunk? If so, we'd need to check LOCK flag too,
        //     perhaps...
        if (!shrunk_group)
          buddylist = g_list_append(buddylist, roster_usrelt);
      }

      sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
    }
    sl_roster_elt = g_slist_next(sl_roster_elt);
  }

  // Check if we can find our saved current_buddy...
  if (roster_current_buddy)
    current_buddy = g_list_find(buddylist, roster_current_buddy);
  // current_buddy initialization
  if (!current_buddy || (g_list_position(buddylist, current_buddy) == -1))
    current_buddy = g_list_first(buddylist);
  // XXX Maybe we should set update_roster to TRUE there?
}

//  buddy_hide_group(roster, hide)
// "hide" values: 1=hide 0=show_all -1=invert
void buddy_hide_group(gpointer rosterdata, int hide)
{
  roster *roster = rosterdata;
  if (hide > 0)                     // TRUE   (hide)
    roster->flags |= ROSTER_FLAG_HIDE;
  else if (hide < 0)                // NEG    (invert)
    roster->flags ^= ROSTER_FLAG_HIDE;
  else                              // FALSE  (don't hide)
    roster->flags &= ~ROSTER_FLAG_HIDE;
}

const char *buddy_getjid(gpointer rosterdata)
{
  roster *roster = rosterdata;
  return roster->jid;
}

const char *buddy_getname(gpointer rosterdata)
{
  roster *roster = rosterdata;
  return roster->name;
}

guint buddy_gettype(gpointer rosterdata)
{
  roster *roster = rosterdata;
  return roster->type;
}

enum imstatus buddy_getstatus(gpointer rosterdata)
{
  roster *roster = rosterdata;
  return roster->status;
}

//  buddy_setflags()
// Set one or several flags to value (TRUE/FALSE)
void buddy_setflags(gpointer rosterdata, guint flags, guint value)
{
  roster *roster = rosterdata;
  if (value)
    roster->flags |= flags;
  else
    roster->flags &= ~flags;
}

guint buddy_getflags(gpointer rosterdata)
{
  roster *roster = rosterdata;
  return roster->flags;
}