1 /*
2  * roster.c     -- Local roster implementation
3  *
4  * Copyright (C) 2005-2010 Mikael Berthe <mikael@lilotux.net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or (at
9  * your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <string.h>
21 
22 #include "roster.h"
23 #include "utils.h"
24 #include "hooks.h"
25 
26 extern void hlog_save_state(void);
27 
28 char *strrole[] = {   /* Should match enum in roster.h */
29   "none",
30   "moderator",
31   "participant",
32   "visitor"
33 };
34 
35 char *straffil[] = {  /* Should match enum in roster.h */
36   "none",
37   "owner",
38   "admin",
39   "member",
40   "outcast"
41 };
42 
43 char *strprintstatus[] = {  /* Should match enum in roster.h */
44   "default",
45   "none",
46   "in_and_out",
47   "all"
48 };
49 
50 char *strautowhois[] = {    /* Should match enum in roster.h */
51   "default",
52   "off",
53   "on",
54 };
55 
56 char *strflagjoins[] = {    /* Should match enum in roster.h */
57   "default",
58   "none",
59   "joins",
60   "all"
61 };
62 
63 /* Resource structure */
64 
65 typedef struct {
66   gchar *name;
67   gchar prio;
68   enum imstatus status;
69   gchar *status_msg;
70   time_t status_timestamp;
71   enum imrole role;
72   enum imaffiliation affil;
73   gchar *realjid;       /* for chatrooms, if buddy's real jid is known */
74   guint events;
75   char *caps;
76 #ifdef XEP0085
77   struct xep0085 xep85;
78 #endif
79 #ifdef HAVE_GPGME
80   struct pgp_data pgpdata;
81 #endif
82 } res_t;
83 
84 /* This is a private structure type for the roster_t */
85 
86 typedef struct {
87   gchar *name;
88   gchar *jid;
89   guint type;
90   enum subscr subscription;
91   GSList *resource;
92   res_t *active_resource;
93 
94   /* For groupchats */
95   gchar *nickname;
96   gchar *topic;
97   guint inside_room;
98   guint print_status;
99   guint auto_whois;
100   guint flag_joins;
101 
102   /* on_server is TRUE if the item is present on the server roster */
103   guint on_server;
104 
105   /* To keep track of last status message */
106   gchar *offline_status_message;
107 
108   /* Flag used for the UI */
109   guint flags;
110   guint ui_prio;  // Boolean, positive if "attention" is requested
111   guint unread;
112 
113   // list: user -> points to his group; group -> points to its users list
114   GSList *list;
115 } roster_t;
116 
117 
118 /* ### Variables ### */
119 
120 static guchar display_filter;
121 static GSList *groups;
122 static GSList *unread_list;
123 static GHashTable *unread_jids;
124 GList *buddylist;
125 static gboolean _rebuild_buddylist = FALSE;
126 GList *current_buddy;
127 GList *alternate_buddy;
128 GList *last_activity_buddy;
129 
130 static roster_t roster_special;
131 
132 static int  unread_jid_del(const char *jid);
133 
134 #define DFILTER_ALL     63
135 #define DFILTER_ONLINE  62
136 
137 
138 /* ### Initialization ### */
139 
roster_init(void)140 void roster_init(void)
141 {
142   roster_special.name = SPECIAL_BUFFER_STATUS_ID;
143   roster_special.type = ROSTER_TYPE_SPECIAL;
144 }
145 
146 /* ### Resources functions ### */
147 
free_resource_data(res_t * p_res)148 static inline void free_resource_data(res_t *p_res)
149 {
150   if (!p_res)
151     return;
152   g_free((gchar*)p_res->status_msg);
153   g_free((gchar*)p_res->name);
154   g_free((gchar*)p_res->realjid);
155 #ifdef HAVE_GPGME
156   g_free(p_res->pgpdata.sign_keyid);
157 #endif
158   g_free(p_res->caps);
159   g_free(p_res);
160 }
161 
free_all_resources(GSList ** reslist)162 static void free_all_resources(GSList **reslist)
163 {
164   GSList *lip;
165 
166   for (lip = *reslist; lip ; lip = g_slist_next(lip))
167     free_resource_data((res_t *)lip->data);
168   // Free all nodes but the first (which is static)
169   g_slist_free(*reslist);
170   *reslist = NULL;
171 }
172 
173 // Resources are sorted in ascending order
resource_compare_prio(res_t * a,res_t * b)174 static gint resource_compare_prio(res_t *a, res_t *b) {
175   if (a->prio < b->prio) return -1;
176   else                   return 1;
177 }
178 
179 //  get_resource(rost, resname)
180 // Return a pointer to the resource with name resname, in rost's resources list
181 // - if rost has no resources, return NULL
182 // - if resname is defined, return the match or NULL
183 // - if resname is NULL, the last resource is returned, currently
184 //   This could change in the future, because we should return the best one
185 //   (priority? last used? and fall back to the first resource)
186 //
get_resource(roster_t * rost,const char * resname)187 static res_t *get_resource(roster_t *rost, const char *resname)
188 {
189   GSList *p;
190   res_t *r = NULL;
191 
192   for (p = rost->resource; p; p = g_slist_next(p)) {
193     r = p->data;
194     if (resname && !strcmp(r->name, resname))
195       return r;
196   }
197 
198   // The last resource is one of the resources with the highest priority,
199   // however, we don't know if it is the more-recently-used.
200   if (!resname) return r;
201   return NULL;
202 }
203 
204 //  get_or_add_resource(rost, resname, priority)
205 // - if there is a "resname" resource in rost's resources, return a pointer
206 //   on this resource
207 // - if not, add the resource, set the name, and return a pointer on this
208 //   new resource
get_or_add_resource(roster_t * rost,const char * resname,gchar prio)209 static res_t *get_or_add_resource(roster_t *rost, const char *resname, gchar prio)
210 {
211   GSList *p;
212   res_t *nres;
213 
214   if (!resname) return NULL;
215 
216   for (p = rost->resource; p; p = g_slist_next(p)) {
217     res_t *r = p->data;
218     if (!strcmp(r->name, resname)) {
219       if (prio != r->prio) {
220         r->prio = prio;
221         rost->resource = g_slist_sort(rost->resource,
222                                       (GCompareFunc)&resource_compare_prio);
223       }
224       return r;
225     }
226   }
227 
228   // Resource not found
229   nres = g_new0(res_t, 1);
230   nres->name = g_strdup(resname);
231   nres->prio = prio;
232   rost->resource = g_slist_insert_sorted(rost->resource, nres,
233                                          (GCompareFunc)&resource_compare_prio);
234   return nres;
235 }
236 
del_resource(roster_t * rost,const char * resname)237 static void del_resource(roster_t *rost, const char *resname)
238 {
239   GSList *p;
240   GSList *p_res_elt = NULL;
241   res_t *p_res;
242 
243   if (!resname) return;
244 
245   for (p = rost->resource; p; p = g_slist_next(p)) {
246     res_t *r = p->data;
247     if (!strcmp(r->name, resname))
248       p_res_elt = p;
249   }
250 
251   if (!p_res_elt) return;   // Resource not found
252 
253   p_res = p_res_elt->data;
254 
255   // Keep a copy of the status message when a buddy goes offline
256   if (g_slist_length(rost->resource) == 1) {
257     g_free(rost->offline_status_message);
258     rost->offline_status_message = p_res->status_msg;
259     p_res->status_msg = NULL;
260   }
261 
262   if (rost->active_resource == p_res)
263     rost->active_resource = NULL;
264 
265   // Free allocations and delete resource node
266   free_resource_data(p_res);
267   rost->resource = g_slist_delete_link(rost->resource, p_res_elt);
268   return;
269 }
270 
271 
272 /* ### Roster functions ### */
273 
free_roster_user_data(roster_t * roster_usr)274 static inline void free_roster_user_data(roster_t *roster_usr)
275 {
276   if (!roster_usr)
277     return;
278   g_free((gchar*)roster_usr->jid);
279   //g_free((gchar*)roster_usr->active_resource);
280   g_free((gchar*)roster_usr->name);
281   g_free((gchar*)roster_usr->nickname);
282   g_free((gchar*)roster_usr->topic);
283   g_free((gchar*)roster_usr->offline_status_message);
284   free_all_resources(&roster_usr->resource);
285   g_free(roster_usr);
286 }
287 
288 // Comparison function used to search in the roster (compares jids and types)
roster_compare_jid_type(roster_t * a,roster_t * b)289 static gint roster_compare_jid_type(roster_t *a, roster_t *b) {
290   if (! (a->type & b->type))
291     return -1; // arbitrary (but should be != 0, of course)
292   return strcasecmp(a->jid, b->jid);
293 }
294 
295 // Comparison function used to search in the roster (compares names and types)
roster_compare_name_type(roster_t * a,roster_t * b)296 static gint roster_compare_name_type(roster_t *a, roster_t *b) {
297   if (! (a->type & b->type))
298     return -1; // arbitrary (but should be != 0, of course)
299   return strcmp(a->name, b->name);
300 }
301 
302 // Comparison function used to sort the roster (by name)
roster_compare_name(roster_t * a,roster_t * b)303 static gint roster_compare_name(roster_t *a, roster_t *b) {
304   return strcmp(a->name, b->name);
305 }
306 
307 // Finds a roster element (user, group, agent...), by jid or name
308 // If roster_type is 0, returns match of any type.
309 // Returns the roster GSList element, or NULL if jid/name not found
roster_find(const char * jidname,enum findwhat type,guint roster_type)310 GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type)
311 {
312   GSList *sl_roster_elt = groups;
313   GSList *resource;
314   roster_t sample;
315   GCompareFunc comp;
316 
317   if (!jidname) return NULL;
318 
319   if (!roster_type)
320     roster_type = ROSTER_TYPE_USER  | ROSTER_TYPE_ROOM |
321                   ROSTER_TYPE_AGENT | ROSTER_TYPE_GROUP;
322 
323   sample.type = roster_type;
324   if (type == jidsearch) {
325     sample.jid = (gchar*)jidname;
326     comp = (GCompareFunc)&roster_compare_jid_type;
327   } else if (type == namesearch) {
328     sample.name = (gchar*)jidname;
329     comp = (GCompareFunc)&roster_compare_name_type;
330   } else
331     return NULL;    // Should not happen...
332 
333   while (sl_roster_elt) {
334     roster_t *roster_elt = (roster_t *)sl_roster_elt->data;
335     if (roster_type & ROSTER_TYPE_GROUP) {
336       if ((type == namesearch) && !strcmp(jidname, roster_elt->name))
337         return sl_roster_elt;
338     }
339     resource = g_slist_find_custom(roster_elt->list, &sample, comp);
340     if (resource) return resource;
341     sl_roster_elt = g_slist_next(sl_roster_elt);
342   }
343   return NULL;
344 }
345 
346 // Returns pointer to new group, or existing group with that name
roster_add_group(const char * name)347 GSList *roster_add_group(const char *name)
348 {
349   roster_t *roster_grp;
350   GSList *p_group;
351 
352   // #1 Check name doesn't already exist
353   p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP);
354   if (!p_group) {
355     // #2 Create the group node
356     roster_grp = g_new0(roster_t, 1);
357     roster_grp->name = g_strdup(name);
358     roster_grp->type = ROSTER_TYPE_GROUP;
359     // #3 Insert (sorted)
360     groups = g_slist_insert_sorted(groups, roster_grp,
361             (GCompareFunc)&roster_compare_name);
362     p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP);
363   }
364   return p_group;
365 }
366 
367 // Comparison function used to sort the unread list by ui (attn) priority
_roster_compare_uiprio(roster_t * a,roster_t * b)368 static gint _roster_compare_uiprio(roster_t *a, roster_t *b) {
369   return (b->ui_prio - a->ui_prio);
370 }
371 
372 // Returns a pointer to the new user, or existing user with that name
373 // Note: if onserver is -1, the flag won't be changed.
roster_add_user(const char * jid,const char * name,const char * group,guint type,enum subscr esub,gint onserver)374 GSList *roster_add_user(const char *jid, const char *name, const char *group,
375                         guint type, enum subscr esub, gint onserver)
376 {
377   roster_t *roster_usr;
378   roster_t *my_group;
379   GSList *slist;
380 
381   if ((type != ROSTER_TYPE_USER) &&
382       (type != ROSTER_TYPE_ROOM) &&
383       (type != ROSTER_TYPE_AGENT)) {
384     // XXX Error message?
385     return NULL;
386   }
387 
388   // Let's be arbitrary: default group has an empty name ("").
389   if (!group)  group = "";
390 
391   // #1 Check this user doesn't already exist
392   slist = roster_find(jid, jidsearch, 0);
393   if (slist) {
394     char *oldgroupname;
395     // That's an update
396     roster_usr = slist->data;
397     roster_usr->subscription = esub;
398     if (onserver >= 0)
399       buddy_setonserverflag(slist->data, onserver);
400     if (name)
401       buddy_setname(slist->data, (char*)name);
402     // Let's check if the group name has changed
403     oldgroupname = ((roster_t *)((GSList*)roster_usr->list)->data)->name;
404     if (group && strcmp(oldgroupname, group)) {
405       buddy_setgroup(slist->data, (char*)group);
406       // Note: buddy_setgroup() updates the user lists so we cannot
407       // use slist anymore.
408       return roster_find(jid, jidsearch, 0);
409     }
410     return slist;
411   }
412   // #2 add group if necessary
413   slist = roster_add_group(group);
414   if (!slist) return NULL;
415   my_group = (roster_t *)slist->data;
416   // #3 Create user node
417   roster_usr = g_new0(roster_t, 1);
418   roster_usr->jid   = g_strdup(jid);
419   if (name) {
420     roster_usr->name  = g_strdup(name);
421   } else {
422     roster_usr->name = jidtodisp(jid);
423   }
424   if (unread_jid_del(jid)) {
425     roster_usr->flags |= ROSTER_FLAG_MSG;
426     // Append the roster_usr to unread_list
427     unread_list = g_slist_insert_sorted(unread_list, roster_usr,
428                                         (GCompareFunc)&_roster_compare_uiprio);
429   }
430   roster_usr->type = type;
431   roster_usr->subscription = esub;
432   roster_usr->list = slist;    // (my_group SList element)
433   if (onserver == 1)
434     roster_usr->on_server = TRUE;
435   // #4 Insert node (sorted)
436   my_group->list = g_slist_insert_sorted(my_group->list, roster_usr,
437                                          (GCompareFunc)&roster_compare_name);
438   buddylist_defer_build();
439   return roster_find(jid, jidsearch, type);
440 }
441 
442 // Removes user (jid) from roster, frees allocated memory
roster_del_user(const char * jid)443 void roster_del_user(const char *jid)
444 {
445   GSList *sl_user, *sl_group;
446   GSList **sl_group_listptr;
447   roster_t *roster_usr;
448   GSList *node;
449 
450   sl_user = roster_find(jid, jidsearch,
451                         ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
452   if (sl_user == NULL)
453     return;
454   roster_usr = (roster_t *)sl_user->data;
455 
456   // Remove (if present) from unread messages list
457   node = g_slist_find(unread_list, roster_usr);
458   if (node) unread_list = g_slist_delete_link(unread_list, node);
459   // If there is a pending unread message, keep track of it
460   if (roster_usr->flags & ROSTER_FLAG_MSG)
461     unread_jid_add(roster_usr->jid);
462 
463   sl_group = roster_usr->list;
464 
465   // Let's free roster_usr memory (jid, name, status message...)
466   free_roster_user_data(roster_usr);
467 
468   // That's a little complex, we need to dereference twice
469   sl_group_listptr = &((roster_t *)(sl_group->data))->list;
470   *sl_group_listptr = g_slist_delete_link(*sl_group_listptr, sl_user);
471 
472   // We need to rebuild the list
473   if (current_buddy)
474     buddylist_defer_build();
475   // TODO What we could do, too, is to check if the deleted node is
476   // current_buddy, in which case we could move current_buddy to the
477   // previous (or next) node.
478 }
479 
480 // Free all roster data and call buddylist_build() to free the buddylist.
roster_free(void)481 void roster_free(void)
482 {
483   GSList *sl_grp = groups;
484 
485   // Free unread_list
486   if (unread_list) {
487     g_slist_free(unread_list);
488     unread_list = NULL;
489   }
490 
491   // Walk through groups
492   while (sl_grp) {
493     roster_t *roster_grp = (roster_t *)sl_grp->data;
494     GSList *sl_usr = roster_grp->list;
495     // Walk through this group users
496     while (sl_usr) {
497       roster_t *roster_usr = (roster_t *)sl_usr->data;
498       // If there is a pending unread message, keep track of it
499       if (roster_usr->flags & ROSTER_FLAG_MSG)
500         unread_jid_add(roster_usr->jid);
501       // Free roster_usr data (jid, name, status message...)
502       free_roster_user_data(roster_usr);
503       sl_usr = g_slist_next(sl_usr);
504     }
505     // Free group's users list
506     if (roster_grp->list)
507       g_slist_free(roster_grp->list);
508     // Free group's name and jid
509     g_free((gchar*)roster_grp->jid);
510     g_free((gchar*)roster_grp->name);
511     g_free(roster_grp);
512     sl_grp = g_slist_next(sl_grp);
513   }
514   // Free groups list
515   if (groups) {
516     g_slist_free(groups);
517     groups = NULL;
518     // Update (i.e. free) buddylist
519     if (buddylist)
520       buddylist_defer_build();
521   }
522 }
523 
524 //  roster_setstatus()
525 // Note: resname, role, affil and realjid are for room members only
roster_setstatus(const char * jid,const char * resname,gchar prio,enum imstatus bstat,const char * status_msg,time_t status_time,enum imrole role,enum imaffiliation affil,const char * realjid)526 void roster_setstatus(const char *jid, const char *resname, gchar prio,
527                       enum imstatus bstat, const char *status_msg,
528                       time_t status_time,
529                       enum imrole role, enum imaffiliation affil,
530                       const char *realjid)
531 {
532   GSList *sl_user;
533   roster_t *roster_usr;
534   res_t *p_res;
535 
536   sl_user = roster_find(jid, jidsearch,
537                         ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
538   // If we can't find it, we add it
539   if (sl_user == NULL)
540     sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER,
541                               sub_none, -1);
542 
543   // If there is no resource name, we can leave now
544   if (!resname) return;
545 
546   roster_usr = (roster_t *)sl_user->data;
547 
548   // New or updated resource
549   p_res = get_or_add_resource(roster_usr, resname, prio);
550   p_res->status = bstat;
551   if (p_res->status_msg) {
552     g_free((gchar*)p_res->status_msg);
553     p_res->status_msg = NULL;
554   }
555   if (status_msg)
556     p_res->status_msg = g_strdup(status_msg);
557   if (!status_time)
558     time(&status_time);
559   p_res->status_timestamp = status_time;
560 
561   p_res->role = role;
562   p_res->affil = affil;
563 
564   if (p_res->realjid) {
565     g_free((gchar*)p_res->realjid);
566     p_res->realjid = NULL;
567   }
568   if (realjid)
569     p_res->realjid = g_strdup(realjid);
570 
571   // If bstat is offline, we MUST delete the resource, actually
572   if (bstat == offline) {
573     del_resource(roster_usr, resname);
574     return;
575   }
576 }
577 
578 //  roster_setflags()
579 // Set one or several flags to value (TRUE/FALSE)
roster_setflags(const char * jid,guint flags,guint value)580 void roster_setflags(const char *jid, guint flags, guint value)
581 {
582   GSList *sl_user;
583   roster_t *roster_usr;
584 
585   sl_user = roster_find(jid, jidsearch,
586                         ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
587   if (sl_user == NULL)
588     return;
589 
590   roster_usr = (roster_t *)sl_user->data;
591   if (value)
592     roster_usr->flags |= flags;
593   else
594     roster_usr->flags &= ~flags;
595 }
596 
597 //  roster_unread_check()
roster_unread_check(void)598 static void roster_unread_check(void)
599 {
600   guint unread_count = 0;
601   gpointer unread_ptr, first_unread;
602   guint muc_unread = 0, muc_attention = 0;
603   guint attention_count = 0;
604 
605   unread_ptr = first_unread = unread_msg(NULL);
606   if (first_unread) {
607     do {
608       guint type = buddy_gettype(unread_ptr);
609       unread_count++;
610 
611       if (type & ROSTER_TYPE_ROOM) {
612         muc_unread++;
613         if (buddy_getuiprio(unread_ptr) >= ROSTER_UI_PRIO_MUC_HL_MESSAGE)
614           muc_attention++;
615       } else {
616         if (buddy_getuiprio(unread_ptr) >= ROSTER_UI_PRIO_ATTENTION_MESSAGE)
617           attention_count++;
618       }
619       unread_ptr = unread_msg(unread_ptr);
620     } while (unread_ptr && unread_ptr != first_unread);
621   }
622 
623   hk_unread_list_change(unread_count, attention_count,
624                         muc_unread, muc_attention);
625 }
626 
627 //  roster_msg_setflag()
628 // Set the ROSTER_FLAG_MSG to the given value for the given jid.
629 // It will update the buddy's group message flag.
630 // Update the unread messages list too.
roster_msg_setflag(const char * jid,guint special,guint value)631 void roster_msg_setflag(const char *jid, guint special, guint value)
632 {
633   GSList *sl_user;
634   roster_t *roster_usr, *roster_grp;
635   int new_roster_item = FALSE;
636   guint unread_list_modified = FALSE;
637 
638   if (special) {
639     //sl_user = roster_find(jid, namesearch, ROSTER_TYPE_SPECIAL);
640     //if (!sl_user) return;
641     //roster_usr = (roster_t *)sl_user->data;
642     roster_usr = &roster_special;
643     if (value) {
644       if (!(roster_usr->flags & ROSTER_FLAG_MSG))
645         unread_list_modified = TRUE;
646       roster_usr->flags |= ROSTER_FLAG_MSG;
647       // Append the roster_usr to unread_list, but avoid duplicates
648       if (!g_slist_find(unread_list, roster_usr))
649         unread_list = g_slist_insert_sorted(unread_list, roster_usr,
650                                         (GCompareFunc)&_roster_compare_uiprio);
651     } else {
652       if (roster_usr->flags & ROSTER_FLAG_MSG)
653         unread_list_modified = TRUE;
654       roster_usr->flags &= ~ROSTER_FLAG_MSG;
655       roster_usr->ui_prio = 0;
656       if (unread_list) {
657         GSList *node = g_slist_find(unread_list, roster_usr);
658         if (node)
659           unread_list = g_slist_delete_link(unread_list, node);
660       }
661     }
662     goto roster_msg_setflag_return;
663   }
664 
665   sl_user = roster_find(jid, jidsearch,
666                         ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
667   // If we can't find it, we add it
668   if (sl_user == NULL) {
669     sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER, sub_none, -1);
670     new_roster_item = TRUE;
671   }
672 
673   roster_usr = (roster_t *)sl_user->data;
674   roster_grp = (roster_t *)roster_usr->list->data;
675   if (value) {
676     if (!(roster_usr->flags & ROSTER_FLAG_MSG))
677       unread_list_modified = TRUE;
678     // Message flag is TRUE.  This is easy, we just have to set both flags
679     // to TRUE...
680     roster_usr->flags |= ROSTER_FLAG_MSG;
681     roster_grp->flags |= ROSTER_FLAG_MSG; // group
682     // Append the roster_usr to unread_list, but avoid duplicates
683     if (!g_slist_find(unread_list, roster_usr))
684       unread_list = g_slist_insert_sorted(unread_list, roster_usr,
685                                       (GCompareFunc)&_roster_compare_uiprio);
686   } else {
687     // Message flag is FALSE.
688     guint msg = FALSE;
689     if (roster_usr->flags & ROSTER_FLAG_MSG)
690       unread_list_modified = TRUE;
691     roster_usr->flags &= ~ROSTER_FLAG_MSG;
692     roster_usr->ui_prio = 0;
693     if (unread_list) {
694       GSList *node = g_slist_find(unread_list, roster_usr);
695       if (node)
696         unread_list = g_slist_delete_link(unread_list, node);
697     }
698     // For the group value we need to watch all buddies in this group;
699     // if one is flagged, then the group will be flagged.
700     // I will re-use sl_user and roster_usr here, as they aren't used
701     // anymore.
702     sl_user = roster_grp->list;
703     while (sl_user) {
704       roster_usr = (roster_t *)sl_user->data;
705       if (roster_usr->flags & ROSTER_FLAG_MSG) {
706         msg = TRUE;
707         break;
708       }
709       sl_user = g_slist_next(sl_user);
710     }
711     if (!msg)
712       roster_grp->flags &= ~ROSTER_FLAG_MSG;
713     else
714       roster_grp->flags |= ROSTER_FLAG_MSG;
715       // Actually the "else" part is useless, because the group
716       // ROSTER_FLAG_MSG should already be set...
717   }
718 
719   if (buddylist && (new_roster_item || !g_list_find(buddylist, roster_usr)))
720     buddylist_defer_build();
721 
722 roster_msg_setflag_return:
723   if (unread_list_modified) {
724     hlog_save_state();
725     roster_unread_check();
726   }
727 }
728 
729 //  roster_msg_update_unread()
730 // If increment is true, increment the unread messages count for jid by 1.
731 // If increment is false, reset the unread messages count for jid to 0.
roster_msg_update_unread(const char * jid,gboolean increment)732 void roster_msg_update_unread(const char *jid, gboolean increment)
733 {
734   GSList *sl_user;
735   roster_t *roster_usr;
736 
737   sl_user = roster_find(jid, jidsearch,
738                         ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
739   if (!sl_user)
740     return;
741 
742   roster_usr = (roster_t *)sl_user->data;
743 
744   if (increment)
745     roster_usr->unread++;
746   else
747     roster_usr->unread = 0;
748 }
749 
750 //  roster_setuiprio(jid, special, prio_value, action)
751 // Set the "attention" priority value for the given roster item.
752 // Note that this function doesn't create the roster item if it doesn't exist.
roster_setuiprio(const char * jid,guint special,guint value,enum setuiprio_ops action)753 void roster_setuiprio(const char *jid, guint special, guint value,
754                       enum setuiprio_ops action)
755 {
756   guint oldval, newval;
757   roster_t *roster_usr;
758 
759   if (special) {
760     roster_usr = &roster_special;
761   } else {
762     GSList *sl_user = roster_find(jid, jidsearch,
763                         ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
764     if (!sl_user)
765       return;
766 
767     roster_usr = (roster_t *)sl_user->data;
768   }
769   oldval = roster_usr->ui_prio;
770 
771   if (action == prio_max)
772     newval = MAX(oldval, value);
773   else if (action == prio_inc)
774     newval = oldval + value;
775   else // prio_set
776     newval = value;
777 
778   roster_usr->ui_prio = newval;
779   unread_list = g_slist_sort(unread_list,
780                              (GCompareFunc)&_roster_compare_uiprio);
781   roster_unread_check();
782 }
783 
roster_getuiprio(const char * jid,guint special)784 guint roster_getuiprio(const char *jid, guint special)
785 {
786   roster_t *roster_usr;
787   GSList *sl_user;
788 
789   if (special) {
790     roster_usr = &roster_special;
791     return roster_usr->ui_prio;
792   }
793 
794   sl_user = roster_find(jid, jidsearch,
795                         ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
796   if (!sl_user)
797     return 0;
798   roster_usr = (roster_t *)sl_user->data;
799   return roster_usr->ui_prio;
800 }
801 
roster_getname(const char * jid)802 const char *roster_getname(const char *jid)
803 {
804   GSList *sl_user;
805   roster_t *roster_usr;
806 
807   sl_user = roster_find(jid, jidsearch,
808                         ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
809   if (sl_user == NULL)
810     return NULL; // Not in the roster...
811 
812   roster_usr = (roster_t *)sl_user->data;
813   return roster_usr->name;
814 }
815 
roster_getnickname(const char * jid)816 const char *roster_getnickname(const char *jid)
817 {
818   GSList *sl_user;
819   roster_t *roster_usr;
820 
821   sl_user = roster_find(jid, jidsearch,
822                         ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
823   if (sl_user == NULL)
824     return NULL; // Not in the roster...
825 
826   roster_usr = (roster_t *)sl_user->data;
827   return roster_usr->nickname;
828 }
829 
roster_settype(const char * jid,guint type)830 void roster_settype(const char *jid, guint type)
831 {
832   GSList *sl_user;
833   roster_t *roster_usr;
834 
835   if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
836     return;
837 
838   roster_usr = (roster_t *)sl_user->data;
839   roster_usr->type = type;
840 }
841 
roster_getstatus(const char * jid,const char * resname)842 enum imstatus roster_getstatus(const char *jid, const char *resname)
843 {
844   GSList *sl_user;
845   roster_t *roster_usr;
846   res_t *p_res;
847 
848   sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
849   if (sl_user == NULL)
850     return offline; // Not in the roster, anyway...
851 
852   roster_usr = (roster_t *)sl_user->data;
853   p_res = get_resource(roster_usr, resname);
854   if (p_res)
855     return p_res->status;
856   return offline;
857 }
858 
roster_getstatusmsg(const char * jid,const char * resname)859 const char *roster_getstatusmsg(const char *jid, const char *resname)
860 {
861   GSList *sl_user;
862   roster_t *roster_usr;
863   res_t *p_res;
864 
865   sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
866   if (sl_user == NULL)
867     return NULL; // Not in the roster, anyway...
868 
869   roster_usr = (roster_t *)sl_user->data;
870   p_res = get_resource(roster_usr, resname);
871   if (p_res)
872     return p_res->status_msg;
873   return roster_usr->offline_status_message;
874 }
875 
roster_getprio(const char * jid,const char * resname)876 char roster_getprio(const char *jid, const char *resname)
877 {
878   GSList *sl_user;
879   roster_t *roster_usr;
880   res_t *p_res;
881 
882   sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
883   if (sl_user == NULL)
884     return offline; // Not in the roster, anyway...
885 
886   roster_usr = (roster_t *)sl_user->data;
887   p_res = get_resource(roster_usr, resname);
888   if (p_res)
889     return p_res->prio;
890   return 0;
891 }
892 
roster_gettype(const char * jid)893 guint roster_gettype(const char *jid)
894 {
895   GSList *sl_user;
896   roster_t *roster_usr;
897 
898   if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
899     return 0;
900 
901   roster_usr = (roster_t *)sl_user->data;
902   return roster_usr->type;
903 }
904 
roster_getsubscription(const char * jid)905 guint roster_getsubscription(const char *jid)
906 {
907   GSList *sl_user;
908   roster_t *roster_usr;
909 
910   if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
911     return 0;
912 
913   roster_usr = (roster_t *)sl_user->data;
914   return roster_usr->subscription;
915 }
916 
917 //  roster_unsubscribed()
918 // We have lost buddy's presence updates; this function clears the status
919 // message, sets the buddy offline and frees the resources
roster_unsubscribed(const char * jid)920 void roster_unsubscribed(const char *jid)
921 {
922   GSList *sl_user;
923   roster_t *roster_usr;
924 
925   sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
926   if (sl_user == NULL)
927     return;
928 
929   roster_usr = (roster_t *)sl_user->data;
930   free_all_resources(&roster_usr->resource);
931 }
932 
933 
934 /* ### BuddyList functions ### */
935 
936 //  buddylist_set_hide_offline_buddies(hide)
937 // "hide" values: 1=hide 0=show_all -1=invert
buddylist_set_hide_offline_buddies(int hide)938 void buddylist_set_hide_offline_buddies(int hide)
939 {
940   if (hide < 0) {               // NEG   (invert)
941     if (display_filter == DFILTER_ALL)
942       display_filter = DFILTER_ONLINE;
943     else
944       display_filter = DFILTER_ALL;
945   } else if (hide == 0) {       // FALSE (don't hide -- andfo_)
946     display_filter = DFILTER_ALL;
947   } else {                      // TRUE  (hide -- andfo)
948     display_filter = DFILTER_ONLINE;
949   }
950   buddylist_defer_build();
951 }
952 
buddylist_isset_filter(void)953 int buddylist_isset_filter(void)
954 {
955   return (display_filter != DFILTER_ALL);
956 }
957 
buddylist_is_status_filtered(enum imstatus status)958 int buddylist_is_status_filtered(enum imstatus status)
959 {
960   return display_filter & (1 << status);
961 }
962 
buddylist_set_filter(guchar filter)963 void buddylist_set_filter(guchar filter)
964 {
965   display_filter = filter;
966 }
967 
buddylist_get_filter(void)968 guchar buddylist_get_filter(void)
969 {
970   return display_filter;
971 }
972 
buddylist_defer_build(void)973 void buddylist_defer_build(void)
974 {
975   _rebuild_buddylist = TRUE;
976 }
977 
978 //  buddylist_build()
979 // Creates the buddylist from the roster entries.
buddylist_build(void)980 void buddylist_build(void)
981 {
982   GSList *sl_roster_elt = groups;
983   roster_t *roster_elt;
984   roster_t *roster_current_buddy = NULL;
985   roster_t *roster_alternate_buddy = NULL;
986   roster_t *roster_last_activity_buddy = NULL;
987   int shrunk_group;
988 
989   if (_rebuild_buddylist == FALSE)
990     return;
991   _rebuild_buddylist = FALSE;
992 
993   // We need to remember which buddy is selected.
994   if (current_buddy)
995     roster_current_buddy = BUDDATA(current_buddy);
996   current_buddy = NULL;
997   if (alternate_buddy)
998     roster_alternate_buddy = BUDDATA(alternate_buddy);
999   alternate_buddy = NULL;
1000   if (last_activity_buddy)
1001     roster_last_activity_buddy = BUDDATA(last_activity_buddy);
1002   last_activity_buddy = NULL;
1003 
1004   // Destroy old buddylist
1005   if (buddylist) {
1006     g_list_free(buddylist);
1007     buddylist = NULL;
1008   }
1009 
1010   buddylist = g_list_append(buddylist, &roster_special);
1011 
1012   // Create the new list
1013   while (sl_roster_elt) {
1014     GSList *sl_roster_usrelt;
1015     roster_t *roster_usrelt;
1016     guint pending_group = TRUE;
1017     roster_elt = (roster_t *) sl_roster_elt->data;
1018 
1019     shrunk_group = roster_elt->flags & ROSTER_FLAG_HIDE;
1020 
1021     sl_roster_usrelt = roster_elt->list;
1022     while (sl_roster_usrelt) {
1023       roster_usrelt = (roster_t *) sl_roster_usrelt->data;
1024 
1025       // Buddy will be added if either:
1026       // - buddy's status matches the display_filter
1027       // - buddy has a lock (for example the buddy window is currently open)
1028       // - buddy has a pending (non-read) message
1029       // - group isn't hidden (shrunk)
1030       // - this is the current_buddy
1031       if (roster_usrelt == roster_current_buddy ||
1032           buddylist_is_status_filtered(buddy_getstatus((gpointer)roster_usrelt,
1033                                                        NULL)) ||
1034           (buddy_getflags((gpointer)roster_usrelt) &
1035                (ROSTER_FLAG_LOCK | ROSTER_FLAG_USRLOCK | ROSTER_FLAG_MSG))) {
1036         // This user should be added.  Maybe the group hasn't been added yet?
1037         if (pending_group) {
1038           // It hasn't been done yet
1039           buddylist = g_list_append(buddylist, roster_elt);
1040           pending_group = FALSE;
1041         }
1042         // Add user
1043         // XXX Should we add the user if there is a message and
1044         //     the group is shrunk? If so, we'd need to check LOCK flag too,
1045         //     perhaps...
1046         if (!shrunk_group)
1047           buddylist = g_list_append(buddylist, roster_usrelt);
1048       }
1049 
1050       sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
1051     }
1052     sl_roster_elt = g_slist_next(sl_roster_elt);
1053   }
1054 
1055   // Check if we can find our saved current_buddy...
1056   if (roster_current_buddy)
1057     current_buddy = g_list_find(buddylist, roster_current_buddy);
1058   if (roster_alternate_buddy)
1059     alternate_buddy = g_list_find(buddylist, roster_alternate_buddy);
1060   if (roster_last_activity_buddy)
1061     last_activity_buddy = g_list_find(buddylist, roster_last_activity_buddy);
1062   // current_buddy initialization
1063   if (!current_buddy || (g_list_position(buddylist, current_buddy) == -1))
1064     current_buddy = g_list_first(buddylist);
1065 }
1066 
1067 //  buddy_hide_group(roster, hide)
1068 // "hide" values: 1=hide 0=show_all -1=invert
buddy_hide_group(gpointer rosterdata,int hide)1069 void buddy_hide_group(gpointer rosterdata, int hide)
1070 {
1071   roster_t *roster_usr = rosterdata;
1072   if (hide > 0)                     // TRUE   (hide)
1073     roster_usr->flags |= ROSTER_FLAG_HIDE;
1074   else if (hide < 0)                // NEG    (invert)
1075     roster_usr->flags ^= ROSTER_FLAG_HIDE;
1076   else                              // FALSE  (don't hide)
1077     roster_usr->flags &= ~ROSTER_FLAG_HIDE;
1078 }
1079 
buddy_getjid(gpointer rosterdata)1080 const char *buddy_getjid(gpointer rosterdata)
1081 {
1082   roster_t *roster_usr = rosterdata;
1083   if (!rosterdata)
1084     return NULL;
1085   return roster_usr->jid;
1086 }
1087 
1088 //  buddy_setgroup()
1089 // Change the group of current buddy
1090 //
1091 // Note: buddy_setgroup() updates the user lists.
1092 //
buddy_setgroup(gpointer rosterdata,char * newgroupname)1093 void buddy_setgroup(gpointer rosterdata, char *newgroupname)
1094 {
1095   roster_t *roster_usr = rosterdata;
1096   GSList **sl_group;
1097   GSList *sl_newgroup;
1098   roster_t *my_newgroup;
1099 
1100   // A group has no group :)
1101   if (roster_usr->type & ROSTER_TYPE_GROUP) return;
1102 
1103   // Add newgroup if necessary
1104   if (!newgroupname)  newgroupname = "";
1105   sl_newgroup = roster_add_group(newgroupname);
1106   if (!sl_newgroup) return;
1107   my_newgroup = (roster_t *)sl_newgroup->data;
1108 
1109   // Remove the buddy from current group
1110   sl_group = &((roster_t *)((GSList*)roster_usr->list)->data)->list;
1111   *sl_group = g_slist_remove(*sl_group, rosterdata);
1112 
1113   // Remove old group if it is empty
1114   if (!*sl_group) {
1115     roster_t *roster_grp = (roster_t *)((GSList*)roster_usr->list)->data;
1116     g_free((gchar*)roster_grp->jid);
1117     g_free((gchar*)roster_grp->name);
1118     g_free(roster_grp);
1119     groups = g_slist_remove(groups, roster_grp);
1120   }
1121 
1122   // Add the buddy to its new group
1123   roster_usr->list = sl_newgroup;    // (my_newgroup SList element)
1124   my_newgroup->list = g_slist_insert_sorted(my_newgroup->list, roster_usr,
1125                                             (GCompareFunc)&roster_compare_name);
1126 
1127   buddylist_defer_build();
1128 }
1129 
buddy_setname(gpointer rosterdata,char * newname)1130 void buddy_setname(gpointer rosterdata, char *newname)
1131 {
1132   roster_t *roster_usr = rosterdata;
1133   GSList **sl_group;
1134 
1135   // TODO For groups, we need to check for unicity
1136   // However, renaming a group boils down to moving all its buddies to
1137   // another group, so calling this function is not really necessary...
1138   if (roster_usr->type & ROSTER_TYPE_GROUP) return;
1139 
1140   if (roster_usr->name) {
1141     g_free((gchar*)roster_usr->name);
1142     roster_usr->name = NULL;
1143   }
1144   if (newname)
1145     roster_usr->name = g_strdup(newname);
1146 
1147   // We need to resort the group list
1148   sl_group = &((roster_t *)((GSList*)roster_usr->list)->data)->list;
1149   *sl_group = g_slist_sort(*sl_group, (GCompareFunc)&roster_compare_name);
1150 
1151   buddylist_defer_build();
1152 }
1153 
buddy_getname(gpointer rosterdata)1154 const char *buddy_getname(gpointer rosterdata)
1155 {
1156   roster_t *roster_usr = rosterdata;
1157   return roster_usr->name;
1158 }
1159 
1160 //  buddy_setnickname(buddy, newnickname)
1161 // Only for chatrooms
buddy_setnickname(gpointer rosterdata,const char * newname)1162 void buddy_setnickname(gpointer rosterdata, const char *newname)
1163 {
1164   roster_t *roster_usr = rosterdata;
1165 
1166   if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; // XXX Error message?
1167 
1168   if (roster_usr->nickname) {
1169     g_free((gchar*)roster_usr->nickname);
1170     roster_usr->nickname = NULL;
1171   }
1172   if (newname)
1173     roster_usr->nickname = g_strdup(newname);
1174 }
1175 
buddy_getnickname(gpointer rosterdata)1176 const char *buddy_getnickname(gpointer rosterdata)
1177 {
1178   roster_t *roster_usr = rosterdata;
1179   return roster_usr->nickname;
1180 }
1181 
1182 //  buddy_setinsideroom(buddy, inside)
1183 // Only for chatrooms
buddy_setinsideroom(gpointer rosterdata,guint inside)1184 void buddy_setinsideroom(gpointer rosterdata, guint inside)
1185 {
1186   roster_t *roster_usr = rosterdata;
1187 
1188   if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return;
1189 
1190   roster_usr->inside_room = inside;
1191 }
1192 
buddy_getinsideroom(gpointer rosterdata)1193 guint buddy_getinsideroom(gpointer rosterdata)
1194 {
1195   roster_t *roster_usr = rosterdata;
1196   return roster_usr->inside_room;
1197 }
1198 
1199 //  buddy_settopic(buddy, newtopic)
1200 // Only for chatrooms
buddy_settopic(gpointer rosterdata,const char * newtopic)1201 void buddy_settopic(gpointer rosterdata, const char *newtopic)
1202 {
1203   roster_t *roster_usr = rosterdata;
1204 
1205   if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return;
1206 
1207   if (roster_usr->topic) {
1208     g_free((gchar*)roster_usr->topic);
1209     roster_usr->topic = NULL;
1210   }
1211   if (newtopic)
1212     roster_usr->topic = g_strdup(newtopic);
1213 }
1214 
buddy_gettopic(gpointer rosterdata)1215 const char *buddy_gettopic(gpointer rosterdata)
1216 {
1217   roster_t *roster_usr = rosterdata;
1218   return roster_usr->topic;
1219 }
1220 
buddy_setprintstatus(gpointer rosterdata,enum room_printstatus pstatus)1221 void buddy_setprintstatus(gpointer rosterdata, enum room_printstatus pstatus)
1222 {
1223   roster_t *roster_usr = rosterdata;
1224   roster_usr->print_status = pstatus;
1225 }
1226 
buddy_getprintstatus(gpointer rosterdata)1227 enum room_printstatus buddy_getprintstatus(gpointer rosterdata)
1228 {
1229   roster_t *roster_usr = rosterdata;
1230   return roster_usr->print_status;
1231 }
1232 
buddy_setautowhois(gpointer rosterdata,enum room_autowhois awhois)1233 void buddy_setautowhois(gpointer rosterdata, enum room_autowhois awhois)
1234 {
1235   roster_t *roster_usr = rosterdata;
1236   roster_usr->auto_whois = awhois;
1237 }
1238 
buddy_getautowhois(gpointer rosterdata)1239 enum room_autowhois buddy_getautowhois(gpointer rosterdata)
1240 {
1241   roster_t *roster_usr = rosterdata;
1242   return roster_usr->auto_whois;
1243 }
1244 
buddy_setflagjoins(gpointer rosterdata,enum room_flagjoins fjoins)1245 void buddy_setflagjoins(gpointer rosterdata, enum room_flagjoins fjoins)
1246 {
1247   roster_t *roster_usr = rosterdata;
1248   roster_usr->flag_joins = fjoins;
1249 }
1250 
buddy_getflagjoins(gpointer rosterdata)1251 enum room_flagjoins buddy_getflagjoins(gpointer rosterdata)
1252 {
1253   roster_t *roster_usr = rosterdata;
1254   return roster_usr->flag_joins;
1255 }
1256 
1257 //  buddy_getgroupname()
1258 // Returns a pointer on buddy's group name.
buddy_getgroupname(gpointer rosterdata)1259 const char *buddy_getgroupname(gpointer rosterdata)
1260 {
1261   roster_t *roster_usr = rosterdata;
1262 
1263   if (roster_usr->type & ROSTER_TYPE_GROUP)
1264     return roster_usr->name;
1265 
1266   if (roster_usr->type & ROSTER_TYPE_SPECIAL)
1267     return NULL;
1268 
1269   // This is a user
1270   return ((roster_t *)((GSList*)roster_usr->list)->data)->name;
1271 }
1272 
1273 //  buddy_getgroup()
1274 // Returns a pointer on buddy's group.
buddy_getgroup(gpointer rosterdata)1275 gpointer buddy_getgroup(gpointer rosterdata)
1276 {
1277   roster_t *roster_usr = rosterdata;
1278 
1279   if (roster_usr->type & ROSTER_TYPE_GROUP)
1280     return rosterdata;
1281 
1282   if (roster_usr->type & ROSTER_TYPE_SPECIAL)
1283     return NULL;
1284 
1285   // This is a user
1286   return (gpointer)((GSList*)roster_usr->list)->data;
1287 }
1288 
buddy_settype(gpointer rosterdata,guint type)1289 void buddy_settype(gpointer rosterdata, guint type)
1290 {
1291   roster_t *roster_usr = rosterdata;
1292   roster_usr->type = type;
1293 }
1294 
buddy_gettype(gpointer rosterdata)1295 guint buddy_gettype(gpointer rosterdata)
1296 {
1297   roster_t *roster_usr = rosterdata;
1298   return roster_usr->type;
1299 }
1300 
buddy_getsubscription(gpointer rosterdata)1301 guint buddy_getsubscription(gpointer rosterdata)
1302 {
1303   roster_t *roster_usr = rosterdata;
1304   return roster_usr->subscription;
1305 }
1306 
buddy_getstatus(gpointer rosterdata,const char * resname)1307 enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname)
1308 {
1309   roster_t *roster_usr = rosterdata;
1310   res_t *p_res = get_resource(roster_usr, resname);
1311   if (p_res)
1312     return p_res->status;
1313   return offline;
1314 }
1315 
buddy_getstatusmsg(gpointer rosterdata,const char * resname)1316 const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname)
1317 {
1318   roster_t *roster_usr = rosterdata;
1319   res_t *p_res = get_resource(roster_usr, resname);
1320   if (p_res)
1321     return p_res->status_msg;
1322   return roster_usr->offline_status_message;
1323 }
1324 
buddy_getstatustime(gpointer rosterdata,const char * resname)1325 time_t buddy_getstatustime(gpointer rosterdata, const char *resname)
1326 {
1327   roster_t *roster_usr = rosterdata;
1328   res_t *p_res = get_resource(roster_usr, resname);
1329   if (p_res)
1330     return p_res->status_timestamp;
1331   return 0;
1332 }
1333 
buddy_getresourceprio(gpointer rosterdata,const char * resname)1334 gchar buddy_getresourceprio(gpointer rosterdata, const char *resname)
1335 {
1336   roster_t *roster_usr = rosterdata;
1337   res_t *p_res = get_resource(roster_usr, resname);
1338   if (p_res)
1339     return p_res->prio;
1340   return 0;
1341 }
1342 
buddy_resource_getevents(gpointer rosterdata,const char * resname)1343 guint buddy_resource_getevents(gpointer rosterdata, const char *resname)
1344 {
1345   roster_t *roster_usr = rosterdata;
1346   res_t *p_res = get_resource(roster_usr, resname);
1347   if (p_res)
1348     return p_res->events;
1349   return ROSTER_EVENT_NONE;
1350 }
1351 
buddy_resource_setevents(gpointer rosterdata,const char * resname,guint events)1352 void buddy_resource_setevents(gpointer rosterdata, const char *resname,
1353                               guint events)
1354 {
1355   roster_t *roster_usr = rosterdata;
1356   res_t *p_res = get_resource(roster_usr, resname);
1357   if (p_res)
1358     p_res->events = events;
1359 }
1360 
buddy_resource_getcaps(gpointer rosterdata,const char * resname)1361 char *buddy_resource_getcaps(gpointer rosterdata, const char *resname)
1362 {
1363   roster_t *roster_usr = rosterdata;
1364   res_t *p_res = get_resource(roster_usr, resname);
1365   if (p_res)
1366     return p_res->caps;
1367   return NULL;
1368 }
1369 
buddy_resource_setcaps(gpointer rosterdata,const char * resname,const char * caps)1370 void buddy_resource_setcaps(gpointer rosterdata, const char *resname,
1371                             const char *caps)
1372 {
1373   roster_t *roster_usr = rosterdata;
1374   res_t *p_res = get_resource(roster_usr, resname);
1375   if (p_res) {
1376     g_free(p_res->caps);
1377     p_res->caps = g_strdup(caps);
1378   }
1379 }
1380 
buddy_resource_xep85(gpointer rosterdata,const char * resname)1381 struct xep0085 *buddy_resource_xep85(gpointer rosterdata, const char *resname)
1382 {
1383 #ifdef XEP0085
1384   roster_t *roster_usr = rosterdata;
1385   res_t *p_res = get_resource(roster_usr, resname);
1386   if (p_res)
1387     return &p_res->xep85;
1388 #endif
1389   return NULL;
1390 }
1391 
buddy_resource_pgp(gpointer rosterdata,const char * resname)1392 struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname)
1393 {
1394 #ifdef HAVE_GPGME
1395   roster_t *roster_usr = rosterdata;
1396   res_t *p_res = get_resource(roster_usr, resname);
1397   if (p_res)
1398     return &p_res->pgpdata;
1399 #endif
1400   return NULL;
1401 }
1402 
buddy_getrole(gpointer rosterdata,const char * resname)1403 enum imrole buddy_getrole(gpointer rosterdata, const char *resname)
1404 {
1405   roster_t *roster_usr = rosterdata;
1406   res_t *p_res = get_resource(roster_usr, resname);
1407   if (p_res)
1408     return p_res->role;
1409   return role_none;
1410 }
1411 
buddy_getaffil(gpointer rosterdata,const char * resname)1412 enum imaffiliation buddy_getaffil(gpointer rosterdata, const char *resname)
1413 {
1414   roster_t *roster_usr = rosterdata;
1415   res_t *p_res = get_resource(roster_usr, resname);
1416   if (p_res)
1417     return p_res->affil;
1418   return affil_none;
1419 }
1420 
buddy_getrjid(gpointer rosterdata,const char * resname)1421 const char *buddy_getrjid(gpointer rosterdata, const char *resname)
1422 {
1423   roster_t *roster_usr = rosterdata;
1424   res_t *p_res = get_resource(roster_usr, resname);
1425   if (p_res)
1426     return p_res->realjid;
1427   return NULL;
1428 }
1429 
1430 //  buddy_getresources(roster_data)
1431 // Return a singly-linked-list of resource names
1432 // Note: the caller should free the list (and data) after use
1433 // If roster_data is null, the current buddy is selected
buddy_getresources(gpointer rosterdata)1434 GSList *buddy_getresources(gpointer rosterdata)
1435 {
1436   roster_t *roster_usr = rosterdata;
1437   GSList *reslist = NULL, *lp;
1438 
1439   if (!roster_usr) {
1440     if (!current_buddy) return NULL;
1441     roster_usr = BUDDATA(current_buddy);
1442   }
1443   for (lp = roster_usr->resource; lp; lp = g_slist_next(lp))
1444     reslist = g_slist_append(reslist, g_strdup(((res_t *)lp->data)->name));
1445 
1446   return reslist;
1447 }
1448 
1449 //  buddy_getresources_locale(roster_data)
1450 // Same as buddy_getresources() but names are converted to user's locale
1451 // Note: the caller should free the list (and data) after use
buddy_getresources_locale(gpointer rosterdata)1452 GSList *buddy_getresources_locale(gpointer rosterdata)
1453 {
1454   GSList *reslist, *lp;
1455 
1456   reslist = buddy_getresources(rosterdata);
1457   // Convert each item to UI's locale
1458   for (lp = reslist; lp; lp = g_slist_next(lp)) {
1459     gchar *oldname = lp->data;
1460     lp->data = from_utf8(oldname);
1461     if (lp->data)
1462       g_free(oldname);
1463     else
1464       lp->data = oldname;
1465   }
1466   return reslist;
1467 }
1468 
1469 //  buddy_getactiveresource(roster_data)
1470 // Returns name of active (selected for chat) resource
buddy_getactiveresource(gpointer rosterdata)1471 const char *buddy_getactiveresource(gpointer rosterdata)
1472 {
1473   roster_t *roster_usr = rosterdata;
1474   res_t *resource;
1475 
1476   if (!roster_usr) {
1477     if (!current_buddy) return NULL;
1478     roster_usr = BUDDATA(current_buddy);
1479   }
1480 
1481   resource = roster_usr->active_resource;
1482   if (!resource) return NULL;
1483   return resource->name;
1484 }
1485 
buddy_setactiveresource(gpointer rosterdata,const char * resname)1486 void buddy_setactiveresource(gpointer rosterdata, const char *resname)
1487 {
1488   roster_t *roster_usr = rosterdata;
1489   res_t *p_res = NULL;
1490   if (resname)
1491     p_res = get_resource(roster_usr, resname);
1492   roster_usr->active_resource = p_res;
1493 }
1494 
1495 /*
1496 //  buddy_isresource(roster_data)
1497 // Return true if there is at least one resource
1498 // (which means, for a room, that it isn't empty)
1499 int buddy_isresource(gpointer rosterdata)
1500 {
1501   roster_t *roster_usr = rosterdata;
1502   if (!roster_usr)
1503     return FALSE;
1504   if (roster_usr->resource)
1505     return TRUE;
1506   return FALSE;
1507 }
1508 */
1509 
1510 //  buddy_resource_setname(roster_data, oldname, newname)
1511 // Useful for nickname change in a MUC room
buddy_resource_setname(gpointer rosterdata,const char * resname,const char * newname)1512 void buddy_resource_setname(gpointer rosterdata, const char *resname,
1513                             const char *newname)
1514 {
1515   roster_t *roster_usr = rosterdata;
1516   res_t *p_res = get_resource(roster_usr, resname);
1517   if (p_res) {
1518     if (p_res->name) {
1519       g_free((gchar*)p_res->name);
1520       p_res->name = NULL;
1521     }
1522     if (newname)
1523       p_res->name = g_strdup(newname);
1524   }
1525 }
1526 
1527 //  buddy_del_all_resources()
1528 // Remove all resources from the specified buddy
buddy_del_all_resources(gpointer rosterdata)1529 void buddy_del_all_resources(gpointer rosterdata)
1530 {
1531   roster_t *roster_usr = rosterdata;
1532 
1533   while (roster_usr->resource) {
1534     res_t *r = roster_usr->resource->data;
1535     del_resource(roster_usr, r->name);
1536   }
1537 }
1538 
1539 //  buddy_setflags()
1540 // Set one or several flags to value (TRUE/FALSE)
buddy_setflags(gpointer rosterdata,guint flags,guint value)1541 void buddy_setflags(gpointer rosterdata, guint flags, guint value)
1542 {
1543   roster_t *roster_usr = rosterdata;
1544   if (value)
1545     roster_usr->flags |= flags;
1546   else
1547     roster_usr->flags &= ~flags;
1548 }
1549 
buddy_getflags(gpointer rosterdata)1550 guint buddy_getflags(gpointer rosterdata)
1551 {
1552   roster_t *roster_usr = rosterdata;
1553   return roster_usr->flags;
1554 }
1555 
buddy_getuiprio(gpointer rosterdata)1556 guint buddy_getuiprio(gpointer rosterdata)
1557 {
1558   roster_t *roster_usr = rosterdata;
1559   return roster_usr->ui_prio;
1560 }
1561 
buddy_getunread(gpointer rosterdata)1562 guint buddy_getunread(gpointer rosterdata)
1563 {
1564   roster_t *roster_usr = rosterdata;
1565   return roster_usr->unread;
1566 }
1567 
1568 //  buddy_setonserverflag()
1569 // Set the on_server flag
buddy_setonserverflag(gpointer rosterdata,guint onserver)1570 void buddy_setonserverflag(gpointer rosterdata, guint onserver)
1571 {
1572   roster_t *roster_usr = rosterdata;
1573   roster_usr->on_server = onserver;
1574 }
1575 
buddy_getonserverflag(gpointer rosterdata)1576 guint buddy_getonserverflag(gpointer rosterdata)
1577 {
1578   roster_t *roster_usr = rosterdata;
1579   return roster_usr->on_server;
1580 }
1581 
1582 //  buddy_search_jid(jid)
1583 // Look for a buddy with specified jid.
1584 // Search begins at buddylist; if no match is found in the the buddylist,
1585 // return NULL;
buddy_search_jid(const char * jid)1586 GList *buddy_search_jid(const char *jid)
1587 {
1588   GList *buddy;
1589   roster_t *roster_usr;
1590 
1591   buddylist_build();
1592   if (!buddylist) return NULL;
1593 
1594   for (buddy = buddylist; buddy; buddy = g_list_next(buddy)) {
1595     roster_usr = (roster_t *)buddy->data;
1596     if (roster_usr->jid && !strcasecmp(roster_usr->jid, jid))
1597       return buddy;
1598   }
1599   return NULL;
1600 }
1601 
1602 //  buddy_search(string)
1603 // Look for a buddy whose name or jid contains string.
1604 // Search begins at current_buddy; if no match is found in the the buddylist,
1605 // return NULL;
buddy_search(char * string)1606 GList *buddy_search(char *string)
1607 {
1608   GList *buddy = current_buddy;
1609   roster_t *roster_usr;
1610   buddylist_build();
1611   if (!buddylist || !current_buddy) return NULL;
1612   for (;;) {
1613     gchar *jid_locale, *name_locale;
1614     char *found = NULL;
1615 
1616     buddy = g_list_next(buddy);
1617     if (!buddy)
1618       buddy = buddylist;
1619 
1620     roster_usr = (roster_t *)buddy->data;
1621 
1622     jid_locale = from_utf8(roster_usr->jid);
1623     if (jid_locale) {
1624       found = strcasestr(jid_locale, string);
1625       g_free(jid_locale);
1626       if (found)
1627         return buddy;
1628     }
1629     name_locale = from_utf8(roster_usr->name);
1630     if (name_locale) {
1631       found = strcasestr(name_locale, string);
1632       g_free(name_locale);
1633       if (found)
1634         return buddy;
1635     }
1636 
1637     if (buddy == current_buddy)
1638       return NULL; // Back to the beginning, and no match found
1639   }
1640 }
1641 
1642 //  foreach_buddy(roster_type, pfunction, param)
1643 // Call pfunction(buddy, param) for each buddy from the roster with
1644 // type matching roster_type.
foreach_buddy(guint roster_type,void (* pfunc)(gpointer rosterdata,void * param),void * param)1645 void foreach_buddy(guint roster_type,
1646                    void (*pfunc)(gpointer rosterdata, void *param),
1647                    void *param)
1648 {
1649   GSList *sl_roster_elt = groups;
1650   roster_t *roster_elt;
1651   GSList *sl_roster_usrelt;
1652   roster_t *roster_usrelt;
1653 
1654   while (sl_roster_elt) {       // group list loop
1655     roster_elt = (roster_t *) sl_roster_elt->data;
1656     if (roster_elt->type & ROSTER_TYPE_SPECIAL)
1657       continue; // Skip special items
1658     sl_roster_usrelt = roster_elt->list;
1659     while (sl_roster_usrelt) {  // user list loop
1660       roster_usrelt = (roster_t *) sl_roster_usrelt->data;
1661 
1662       if (roster_usrelt->type & roster_type)
1663         pfunc(roster_usrelt, param);
1664 
1665       sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
1666     }
1667     sl_roster_elt = g_slist_next(sl_roster_elt);
1668   }
1669 }
1670 
1671 //  foreach_group_member(group, pfunction, param)
1672 // Call pfunction(buddy, param) for each buddy in the specified group.
foreach_group_member(gpointer groupdata,void (* pfunc)(gpointer rosterdata,void * param),void * param)1673 void foreach_group_member(gpointer groupdata,
1674                    void (*pfunc)(gpointer rosterdata, void *param),
1675                    void *param)
1676 {
1677   roster_t *roster_elt;
1678   GSList *sl_roster_usrelt;
1679   roster_t *roster_usrelt;
1680 
1681   roster_elt = groupdata;
1682 
1683   if (!(roster_elt->type & ROSTER_TYPE_GROUP))
1684     return;
1685 
1686   sl_roster_usrelt = roster_elt->list;
1687   while (sl_roster_usrelt) {  // user list loop
1688     GSList *next_sl_usrelt;
1689     roster_usrelt = (roster_t *) sl_roster_usrelt->data;
1690 
1691     next_sl_usrelt = g_slist_next(sl_roster_usrelt);
1692     pfunc(roster_usrelt, param);
1693     sl_roster_usrelt = next_sl_usrelt;
1694   }
1695 }
1696 
1697 //  compl_list(type)
1698 // Returns a list of jid's or groups.  (For commands completion)
1699 // type: ROSTER_TYPE_USER (jid's) or ROSTER_TYPE_GROUP (group names)
1700 // The list should be freed by the caller after use.
compl_list(guint type)1701 GSList *compl_list(guint type)
1702 {
1703   GSList *list = NULL;
1704   GSList *sl_roster_elt = groups;
1705   roster_t *roster_elt;
1706   GSList *sl_roster_usrelt;
1707   roster_t *roster_usrelt;
1708 
1709   while (sl_roster_elt) {       // group list loop
1710     roster_elt = (roster_t *) sl_roster_elt->data;
1711 
1712     if (roster_elt->type & ROSTER_TYPE_SPECIAL)
1713       continue; // Skip special items
1714 
1715     if (type == ROSTER_TYPE_GROUP) { // (group names)
1716       if (roster_elt->name && *(roster_elt->name))
1717         list = g_slist_append(list, from_utf8(roster_elt->name));
1718     } else { // ROSTER_TYPE_USER (jid) (or agent, or chatroom...)
1719       sl_roster_usrelt = roster_elt->list;
1720       while (sl_roster_usrelt) {  // user list loop
1721         roster_usrelt = (roster_t *) sl_roster_usrelt->data;
1722 
1723         if (roster_usrelt->jid)
1724           list = g_slist_append(list, from_utf8(roster_usrelt->jid));
1725 
1726         sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
1727       }
1728     }
1729     sl_roster_elt = g_slist_next(sl_roster_elt);
1730   }
1731 
1732   return list;
1733 }
1734 
1735 //  unread_msg(rosterdata)
1736 // Return the next buddy with an unread message.  If the parameter is NULL,
1737 // return the first buddy with an unread message.
unread_msg(gpointer rosterdata)1738 gpointer unread_msg(gpointer rosterdata)
1739 {
1740   GSList *unread, *next_unread;
1741 
1742   if (!unread_list)
1743     return NULL;
1744 
1745   // First unread message
1746   if (!rosterdata)
1747     return unread_list->data;
1748 
1749   unread = g_slist_find(unread_list, rosterdata);
1750   if (!unread)
1751     return unread_list->data;
1752 
1753   next_unread = g_slist_next(unread);
1754   if (next_unread)
1755     return next_unread->data;
1756   return unread_list->data;
1757 }
1758 
1759 
1760 /* ### "unread_jids" functions ###
1761  *
1762  * The unread_jids hash table is used to keep track of the buddies with
1763  * unread messages when a disconnection occurs.
1764  * When removing a buddy with an unread message from the roster, the
1765  * jid should be added to the unread_jids table.  When adding a buddy to
1766  * the roster, we check if (s)he had a pending unread message.
1767  */
1768 
1769 //  unread_jid_add(jid)
1770 // Add jid to the unread_jids hash table
unread_jid_add(const char * jid)1771 void unread_jid_add(const char *jid)
1772 {
1773   if (!unread_jids) {
1774     // Initialize unread_jids hash table
1775     unread_jids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1776   }
1777   // The 2nd unread_jids is an arbitrary non-null pointer:
1778   g_hash_table_insert(unread_jids, g_strdup(jid), unread_jids);
1779 }
1780 
1781 //  unread_jid_del(jid)
1782 // Return TRUE if jid is found in the table (and remove it), FALSE if not
unread_jid_del(const char * jid)1783 static int unread_jid_del(const char *jid)
1784 {
1785   if (!unread_jids)
1786     return FALSE;
1787   return g_hash_table_remove(unread_jids, jid);
1788 }
1789 
1790 //  unread_jid_get_list()
1791 // Return the JID list.
1792 // The content of the list should not be modified or freed.
1793 // The caller should call g_list_free() after use.
unread_jid_get_list(void)1794 GList *unread_jid_get_list(void)
1795 {
1796   if (!unread_jids)
1797     return NULL;
1798 
1799   return g_hash_table_get_keys(unread_jids);
1800 }
1801 
1802 /* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2:  For Vim users... */
1803