core cleanup: separate code for sipe_add_buddy()
[siplcs.git] / src / core / sipe-buddy.c
blob32bff6774f82484faa74cbd721f91856cb89500d
1 /**
2 * @file sipe-buddy.c
4 * pidgin-sipe
6 * Copyright (C) 2010-11 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <time.h>
29 #include <glib.h>
31 #include "sipe-common.h"
32 #include "http-conn.h" /* sipe-cal.h requires this */
33 #include "sipmsg.h"
34 #include "sip-soap.h"
35 #include "sipe-backend.h"
36 #include "sipe-buddy.h"
37 #include "sipe-cal.h"
38 #include "sipe-core.h"
39 #include "sipe-core-private.h"
40 #include "sipe-group.h"
41 #include "sipe-nls.h"
42 #include "sipe-ocs2007.h"
43 #include "sipe-schedule.h"
44 #include "sipe-subscriptions.h"
45 #include "sipe-utils.h"
46 #include "sipe-xml.h"
48 static void buddy_free(struct sipe_buddy *buddy)
50 #ifndef _WIN32
52 * We are calling g_hash_table_foreach_steal(). That means that no
53 * key/value deallocation functions are called. Therefore the glib
54 * hash code does not touch the key (buddy->name) or value (buddy)
55 * of the to-be-deleted hash node at all. It follows that we
57 * - MUST free the memory for the key ourselves and
58 * - ARE allowed to do it in this function
60 * Conclusion: glib must be broken on the Windows platform if sipe
61 * crashes with SIGTRAP when closing. You'll have to live
62 * with the memory leak until this is fixed.
64 g_free(buddy->name);
65 #endif
66 g_free(buddy->activity);
67 g_free(buddy->meeting_subject);
68 g_free(buddy->meeting_location);
69 g_free(buddy->note);
71 g_free(buddy->cal_start_time);
72 g_free(buddy->cal_free_busy_base64);
73 g_free(buddy->cal_free_busy);
74 g_free(buddy->last_non_cal_activity);
76 sipe_cal_free_working_hours(buddy->cal_working_hours);
78 g_free(buddy->device_name);
79 g_slist_free(buddy->groups);
80 g_free(buddy);
83 static gboolean buddy_free_cb(SIPE_UNUSED_PARAMETER gpointer key,
84 gpointer buddy,
85 SIPE_UNUSED_PARAMETER gpointer user_data)
87 buddy_free(buddy);
88 /* We must return TRUE as the key/value have already been deleted */
89 return(TRUE);
92 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
94 g_hash_table_foreach_steal(sipe_private->buddies,
95 buddy_free_cb,
96 NULL);
99 gchar *sipe_core_buddy_status(struct sipe_core_public *sipe_public,
100 const gchar *name,
101 const sipe_activity activity,
102 const gchar *status_text)
104 struct sipe_buddy *sbuddy;
105 const char *activity_str;
107 if (!sipe_public) return NULL; /* happens on pidgin exit */
109 sbuddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, name);
110 if (!sbuddy) return NULL;
112 activity_str = sbuddy->activity ? sbuddy->activity :
113 (activity == SIPE_ACTIVITY_BUSY) || (activity == SIPE_ACTIVITY_BRB) ?
114 status_text : NULL;
116 if (activity_str && sbuddy->note) {
117 return g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
118 } else if (activity_str) {
119 return g_strdup(activity_str);
120 } else if (sbuddy->note) {
121 return g_strdup_printf("<i>%s</i>", sbuddy->note);
122 } else {
123 return NULL;
127 gchar *sipe_buddy_get_alias(struct sipe_core_private *sipe_private,
128 const gchar *with)
130 sipe_backend_buddy pbuddy;
131 gchar *alias = NULL;
132 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
133 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, pbuddy);
135 return alias;
138 void sipe_core_buddy_group(struct sipe_core_public *sipe_public,
139 const gchar *who,
140 const gchar *old_group_name,
141 const gchar *new_group_name)
143 struct sipe_buddy * buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
144 struct sipe_group * old_group = NULL;
145 struct sipe_group * new_group;
147 SIPE_DEBUG_INFO("sipe_core_buddy_group: who:%s old_group_name:%s new_group_name:%s",
148 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
150 if(!buddy) { // buddy not in roaming list
151 return;
154 if (old_group_name) {
155 old_group = sipe_group_find_by_name(SIPE_CORE_PRIVATE, old_group_name);
157 new_group = sipe_group_find_by_name(SIPE_CORE_PRIVATE, new_group_name);
159 if (old_group) {
160 buddy->groups = g_slist_remove(buddy->groups, old_group);
161 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy %s removed from old group %s", who, old_group_name);
164 if (!new_group) {
165 sipe_group_create(SIPE_CORE_PRIVATE, new_group_name, who);
166 } else {
167 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
168 sipe_core_group_set_user(sipe_public, who);
172 void sipe_core_buddy_add(struct sipe_core_public *sipe_public,
173 const gchar *name,
174 const gchar *group_name)
176 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
178 if (!g_hash_table_lookup(sipe_private->buddies, name)) {
179 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
181 SIPE_DEBUG_INFO("sipe_core_buddy_add: %s", name);
183 b->name = g_strdup(name);
184 b->just_added = TRUE;
185 g_hash_table_insert(sipe_private->buddies, b->name, b);
187 /* @TODO should go to callback */
188 sipe_subscribe_presence_single(sipe_private, b->name);
190 } else {
191 SIPE_DEBUG_INFO("sipe_core_buddy_add: buddy %s already in internal list",
192 name);
195 sipe_core_buddy_group(SIPE_CORE_PUBLIC,
196 name,
197 NULL,
198 group_name);
202 * Unassociates buddy from group first.
203 * Then see if no groups left, removes buddy completely.
204 * Otherwise updates buddy groups on server.
206 void sipe_core_buddy_remove(struct sipe_core_public *sipe_public,
207 const gchar *name,
208 const gchar *group_name)
210 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
211 struct sipe_buddy *b = g_hash_table_lookup(sipe_private->buddies,
212 name);
214 if (!b) return;
216 if (group_name) {
217 struct sipe_group *g = sipe_group_find_by_name(sipe_private,
218 group_name);
219 if (g) {
220 b->groups = g_slist_remove(b->groups, g);
221 SIPE_DEBUG_INFO("sipe_core_buddy_remove: buddy %s removed from group %s",
222 name, g->name);
226 if (g_slist_length(b->groups) < 1) {
227 gchar *action_name = sipe_utils_presence_key(name);
228 sipe_schedule_cancel(sipe_private, action_name);
229 g_free(action_name);
231 g_hash_table_remove(sipe_private->buddies, name);
233 if (b->name) {
234 gchar *request = g_strdup_printf("<m:URI>%s</m:URI>",
235 b->name);
236 sip_soap_request(sipe_private,
237 "deleteContact",
238 request);
239 g_free(request);
242 buddy_free(b);
243 } else {
244 /* updates groups on server */
245 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
250 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
251 const gchar *name,
252 const gchar *status_name,
253 gboolean is_online)
255 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
256 gchar *note = NULL;
257 gboolean is_oof_note = FALSE;
258 const gchar *activity = NULL;
259 gchar *calendar = NULL;
260 const gchar *meeting_subject = NULL;
261 const gchar *meeting_location = NULL;
262 gchar *access_text = NULL;
263 GSList *info = NULL;
265 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
267 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
268 sbi->label = (l); \
269 sbi->text = (t); \
270 info = g_slist_append(info, sbi); \
272 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
273 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
275 if (sipe_public) { //happens on pidgin exit
276 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
277 if (sbuddy) {
278 note = sbuddy->note;
279 is_oof_note = sbuddy->is_oof_note;
280 activity = sbuddy->activity;
281 calendar = sipe_cal_get_description(sbuddy);
282 meeting_subject = sbuddy->meeting_subject;
283 meeting_location = sbuddy->meeting_location;
285 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
286 gboolean is_group_access = FALSE;
287 const int container_id = sipe_ocs2007_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
288 const char *access_level = sipe_ocs2007_access_level_name(container_id);
289 access_text = is_group_access ?
290 g_strdup(access_level) :
291 g_strdup_printf(SIPE_OCS2007_INDENT_MARKED_FMT,
292 access_level);
296 if (is_online)
298 const gchar *status_str = activity ? activity : status_name;
300 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
302 if (is_online && !is_empty(calendar))
304 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
306 g_free(calendar);
307 if (!is_empty(meeting_location))
309 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
310 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
312 if (!is_empty(meeting_subject))
314 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
315 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
317 if (note)
319 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
320 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
321 g_strdup_printf("<i>%s</i>", note));
323 if (access_text) {
324 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
325 g_free(access_text);
328 return(info);
331 void sipe_buddy_update_property(struct sipe_core_private *sipe_private,
332 const char *uri,
333 sipe_buddy_info_fields propkey,
334 char *property_value)
336 GSList *buddies, *entry;
338 if (property_value)
339 property_value = g_strstrip(property_value);
341 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
342 while (entry) {
343 gchar *prop_str;
344 gchar *server_alias;
345 gchar *alias;
346 sipe_backend_buddy p_buddy = entry->data;
348 /* for Display Name */
349 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
350 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
351 if (property_value && sipe_is_bad_alias(uri, alias)) {
352 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
353 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
355 g_free(alias);
357 server_alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
358 if (!is_empty(property_value) &&
359 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
361 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
362 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
364 g_free(server_alias);
366 /* for other properties */
367 else {
368 if (!is_empty(property_value)) {
369 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
370 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
371 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
373 g_free(prop_str);
377 entry = entry->next;
379 g_slist_free(buddies);
382 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
383 struct sipmsg *msg,
384 SIPE_UNUSED_PARAMETER struct transaction *trans)
386 struct sipe_backend_search_results *results;
387 sipe_xml *searchResults;
388 const sipe_xml *mrow;
389 guint match_count = 0;
390 gboolean more = FALSE;
391 gchar *secondary;
393 /* valid response? */
394 if (msg->response != 200) {
395 SIPE_DEBUG_ERROR("process_search_contact_response: request failed (%d)",
396 msg->response);
397 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
398 _("Contact search failed"),
399 NULL);
400 return(FALSE);
403 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
405 /* valid XML? */
406 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
407 if (!searchResults) {
408 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
409 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
410 _("Contact search failed"),
411 NULL);
412 return(FALSE);
415 /* any matches? */
416 mrow = sipe_xml_child(searchResults, "Body/Array/row");
417 if (!mrow) {
418 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: no matches");
419 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
420 _("No contacts found"),
421 NULL);
423 sipe_xml_free(searchResults);
424 return(FALSE);
427 /* OK, we found something - show the results to the user */
428 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC);
429 if (!results) {
430 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: Unable to display the search results.");
431 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
432 _("Unable to display the search results"),
433 NULL);
435 sipe_xml_free(searchResults);
436 return FALSE;
439 for (/* initialized above */ ; mrow; mrow = sipe_xml_twin(mrow)) {
440 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
441 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
442 results,
443 uri_parts[1],
444 sipe_xml_attribute(mrow, "displayName"),
445 sipe_xml_attribute(mrow, "company"),
446 sipe_xml_attribute(mrow, "country"),
447 sipe_xml_attribute(mrow, "email"));
448 g_strfreev(uri_parts);
449 match_count++;
452 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
453 char *data = sipe_xml_data(mrow);
454 more = (g_strcasecmp(data, "true") == 0);
455 g_free(data);
458 secondary = g_strdup_printf(
459 dngettext(PACKAGE_NAME,
460 "Found %d contact%s:",
461 "Found %d contacts%s:", match_count),
462 match_count, more ? _(" (more matched your query)") : "");
464 sipe_backend_search_results_finalize(SIPE_CORE_PUBLIC,
465 results,
466 secondary);
467 g_free(secondary);
468 sipe_xml_free(searchResults);
470 return(TRUE);
473 #define SIPE_SOAP_SEARCH_ROW "<m:row m:attrib=\"%s\" m:value=\"%s\"/>"
475 void sipe_core_buddy_search(struct sipe_core_public *sipe_public,
476 const gchar *given_name,
477 const gchar *surname,
478 const gchar *company,
479 const gchar *country)
481 gchar **attrs = g_new(gchar *, 5);
482 guint i = 0;
484 if (!attrs) return;
486 #define ADD_QUERY_ROW(a, v) \
487 if (v) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, a, v)
489 ADD_QUERY_ROW("givenName", given_name);
490 ADD_QUERY_ROW("sn", surname);
491 ADD_QUERY_ROW("company", company);
492 ADD_QUERY_ROW("c", country);
494 if (i) {
495 gchar *query;
497 attrs[i] = NULL;
498 query = g_strjoinv(NULL, attrs);
499 SIPE_DEBUG_INFO("sipe_core_buddy_search: rows:\n%s",
500 query ? query : "");
501 sip_soap_directory_search(SIPE_CORE_PRIVATE,
502 100,
503 query,
504 process_search_contact_response,
505 NULL);
506 g_free(query);
509 g_strfreev(attrs);
513 Local Variables:
514 mode: c
515 c-file-style: "bsd"
516 indent-tabs-mode: t
517 tab-width: 8
518 End: