core cleanup: separate code for sipe_backend_account_status_and_note()
[siplcs.git] / src / core / sipe-buddy.c
blobafcfab2f576dac1d68a810daf8de407ab02a8551
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"
47 #include "sipe.h"
49 static void buddy_free(struct sipe_buddy *buddy)
51 #ifndef _WIN32
53 * We are calling g_hash_table_foreach_steal(). That means that no
54 * key/value deallocation functions are called. Therefore the glib
55 * hash code does not touch the key (buddy->name) or value (buddy)
56 * of the to-be-deleted hash node at all. It follows that we
58 * - MUST free the memory for the key ourselves and
59 * - ARE allowed to do it in this function
61 * Conclusion: glib must be broken on the Windows platform if sipe
62 * crashes with SIGTRAP when closing. You'll have to live
63 * with the memory leak until this is fixed.
65 g_free(buddy->name);
66 #endif
67 g_free(buddy->activity);
68 g_free(buddy->meeting_subject);
69 g_free(buddy->meeting_location);
70 g_free(buddy->note);
72 g_free(buddy->cal_start_time);
73 g_free(buddy->cal_free_busy_base64);
74 g_free(buddy->cal_free_busy);
75 g_free(buddy->last_non_cal_activity);
77 sipe_cal_free_working_hours(buddy->cal_working_hours);
79 g_free(buddy->device_name);
80 g_slist_free(buddy->groups);
81 g_free(buddy);
84 static gboolean buddy_free_cb(SIPE_UNUSED_PARAMETER gpointer key,
85 gpointer buddy,
86 SIPE_UNUSED_PARAMETER gpointer user_data)
88 buddy_free(buddy);
89 /* We must return TRUE as the key/value have already been deleted */
90 return(TRUE);
93 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
95 g_hash_table_foreach_steal(sipe_private->buddies,
96 buddy_free_cb,
97 NULL);
100 gchar *sipe_core_buddy_status(struct sipe_core_public *sipe_public,
101 const gchar *name,
102 const sipe_activity activity,
103 const gchar *status_text)
105 struct sipe_buddy *sbuddy;
106 const char *activity_str;
108 if (!sipe_public) return NULL; /* happens on pidgin exit */
110 sbuddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, name);
111 if (!sbuddy) return NULL;
113 activity_str = sbuddy->activity ? sbuddy->activity :
114 (activity == SIPE_ACTIVITY_BUSY) || (activity == SIPE_ACTIVITY_BRB) ?
115 status_text : NULL;
117 if (activity_str && sbuddy->note) {
118 return g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
119 } else if (activity_str) {
120 return g_strdup(activity_str);
121 } else if (sbuddy->note) {
122 return g_strdup_printf("<i>%s</i>", sbuddy->note);
123 } else {
124 return NULL;
128 gchar *sipe_buddy_get_alias(struct sipe_core_private *sipe_private,
129 const gchar *with)
131 sipe_backend_buddy pbuddy;
132 gchar *alias = NULL;
133 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
134 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, pbuddy);
136 return alias;
139 void sipe_core_buddy_group(struct sipe_core_public *sipe_public,
140 const gchar *who,
141 const gchar *old_group_name,
142 const gchar *new_group_name)
144 struct sipe_buddy * buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
145 struct sipe_group * old_group = NULL;
146 struct sipe_group * new_group;
148 SIPE_DEBUG_INFO("sipe_core_buddy_group: who:%s old_group_name:%s new_group_name:%s",
149 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
151 if(!buddy) { // buddy not in roaming list
152 return;
155 if (old_group_name) {
156 old_group = sipe_group_find_by_name(SIPE_CORE_PRIVATE, old_group_name);
158 new_group = sipe_group_find_by_name(SIPE_CORE_PRIVATE, new_group_name);
160 if (old_group) {
161 buddy->groups = g_slist_remove(buddy->groups, old_group);
162 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy %s removed from old group %s", who, old_group_name);
165 if (!new_group) {
166 sipe_group_create(SIPE_CORE_PRIVATE, new_group_name, who);
167 } else {
168 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
169 sipe_core_group_set_user(sipe_public, who);
173 void sipe_core_buddy_add(struct sipe_core_public *sipe_public,
174 const gchar *name,
175 const gchar *group_name)
177 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
179 if (!g_hash_table_lookup(sipe_private->buddies, name)) {
180 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
182 SIPE_DEBUG_INFO("sipe_core_buddy_add: %s", name);
184 b->name = g_strdup(name);
185 b->just_added = TRUE;
186 g_hash_table_insert(sipe_private->buddies, b->name, b);
188 /* @TODO should go to callback */
189 sipe_subscribe_presence_single(sipe_private, b->name);
191 } else {
192 SIPE_DEBUG_INFO("sipe_core_buddy_add: buddy %s already in internal list",
193 name);
196 sipe_core_buddy_group(SIPE_CORE_PUBLIC,
197 name,
198 NULL,
199 group_name);
203 * Unassociates buddy from group first.
204 * Then see if no groups left, removes buddy completely.
205 * Otherwise updates buddy groups on server.
207 void sipe_core_buddy_remove(struct sipe_core_public *sipe_public,
208 const gchar *name,
209 const gchar *group_name)
211 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
212 struct sipe_buddy *b = g_hash_table_lookup(sipe_private->buddies,
213 name);
215 if (!b) return;
217 if (group_name) {
218 struct sipe_group *g = sipe_group_find_by_name(sipe_private,
219 group_name);
220 if (g) {
221 b->groups = g_slist_remove(b->groups, g);
222 SIPE_DEBUG_INFO("sipe_core_buddy_remove: buddy %s removed from group %s",
223 name, g->name);
227 if (g_slist_length(b->groups) < 1) {
228 gchar *action_name = sipe_utils_presence_key(name);
229 sipe_schedule_cancel(sipe_private, action_name);
230 g_free(action_name);
232 g_hash_table_remove(sipe_private->buddies, name);
234 if (b->name) {
235 gchar *request = g_strdup_printf("<m:URI>%s</m:URI>",
236 b->name);
237 sip_soap_request(sipe_private,
238 "deleteContact",
239 request);
240 g_free(request);
243 buddy_free(b);
244 } else {
245 /* updates groups on server */
246 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
251 void sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
252 const gchar* uri,
253 const gchar *status_id)
255 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
256 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies,
257 uri);
259 if (!sbuddy) return;
261 /* Check if on 2005 system contact's calendar,
262 * then set/preserve it.
264 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
265 sipe_backend_buddy_set_status(sipe_public, uri, status_id);
266 } else {
267 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
271 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
272 const gchar *name,
273 const gchar *status_name,
274 gboolean is_online)
276 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
277 gchar *note = NULL;
278 gboolean is_oof_note = FALSE;
279 const gchar *activity = NULL;
280 gchar *calendar = NULL;
281 const gchar *meeting_subject = NULL;
282 const gchar *meeting_location = NULL;
283 gchar *access_text = NULL;
284 GSList *info = NULL;
286 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
288 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
289 sbi->label = (l); \
290 sbi->text = (t); \
291 info = g_slist_append(info, sbi); \
293 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
294 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
296 if (sipe_public) { //happens on pidgin exit
297 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
298 if (sbuddy) {
299 note = sbuddy->note;
300 is_oof_note = sbuddy->is_oof_note;
301 activity = sbuddy->activity;
302 calendar = sipe_cal_get_description(sbuddy);
303 meeting_subject = sbuddy->meeting_subject;
304 meeting_location = sbuddy->meeting_location;
306 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
307 gboolean is_group_access = FALSE;
308 const int container_id = sipe_ocs2007_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
309 const char *access_level = sipe_ocs2007_access_level_name(container_id);
310 access_text = is_group_access ?
311 g_strdup(access_level) :
312 g_strdup_printf(SIPE_OCS2007_INDENT_MARKED_FMT,
313 access_level);
317 if (is_online)
319 const gchar *status_str = activity ? activity : status_name;
321 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
323 if (is_online && !is_empty(calendar))
325 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
327 g_free(calendar);
328 if (!is_empty(meeting_location))
330 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
331 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
333 if (!is_empty(meeting_subject))
335 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
336 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
338 if (note)
340 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
341 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
342 g_strdup_printf("<i>%s</i>", note));
344 if (access_text) {
345 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
346 g_free(access_text);
349 return(info);
352 void sipe_buddy_update_property(struct sipe_core_private *sipe_private,
353 const char *uri,
354 sipe_buddy_info_fields propkey,
355 char *property_value)
357 GSList *buddies, *entry;
359 if (property_value)
360 property_value = g_strstrip(property_value);
362 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
363 while (entry) {
364 gchar *prop_str;
365 gchar *server_alias;
366 gchar *alias;
367 sipe_backend_buddy p_buddy = entry->data;
369 /* for Display Name */
370 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
371 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
372 if (property_value && sipe_is_bad_alias(uri, alias)) {
373 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
374 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
376 g_free(alias);
378 server_alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
379 if (!is_empty(property_value) &&
380 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
382 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
383 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
385 g_free(server_alias);
387 /* for other properties */
388 else {
389 if (!is_empty(property_value)) {
390 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
391 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
392 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
394 g_free(prop_str);
398 entry = entry->next;
400 g_slist_free(buddies);
403 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
404 struct sipmsg *msg,
405 SIPE_UNUSED_PARAMETER struct transaction *trans)
407 struct sipe_backend_search_results *results;
408 sipe_xml *searchResults;
409 const sipe_xml *mrow;
410 guint match_count = 0;
411 gboolean more = FALSE;
412 gchar *secondary;
414 /* valid response? */
415 if (msg->response != 200) {
416 SIPE_DEBUG_ERROR("process_search_contact_response: request failed (%d)",
417 msg->response);
418 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
419 _("Contact search failed"),
420 NULL);
421 return(FALSE);
424 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
426 /* valid XML? */
427 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
428 if (!searchResults) {
429 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
430 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
431 _("Contact search failed"),
432 NULL);
433 return(FALSE);
436 /* any matches? */
437 mrow = sipe_xml_child(searchResults, "Body/Array/row");
438 if (!mrow) {
439 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: no matches");
440 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
441 _("No contacts found"),
442 NULL);
444 sipe_xml_free(searchResults);
445 return(FALSE);
448 /* OK, we found something - show the results to the user */
449 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC);
450 if (!results) {
451 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: Unable to display the search results.");
452 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
453 _("Unable to display the search results"),
454 NULL);
456 sipe_xml_free(searchResults);
457 return FALSE;
460 for (/* initialized above */ ; mrow; mrow = sipe_xml_twin(mrow)) {
461 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
462 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
463 results,
464 uri_parts[1],
465 sipe_xml_attribute(mrow, "displayName"),
466 sipe_xml_attribute(mrow, "company"),
467 sipe_xml_attribute(mrow, "country"),
468 sipe_xml_attribute(mrow, "email"));
469 g_strfreev(uri_parts);
470 match_count++;
473 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
474 char *data = sipe_xml_data(mrow);
475 more = (g_strcasecmp(data, "true") == 0);
476 g_free(data);
479 secondary = g_strdup_printf(
480 dngettext(PACKAGE_NAME,
481 "Found %d contact%s:",
482 "Found %d contacts%s:", match_count),
483 match_count, more ? _(" (more matched your query)") : "");
485 sipe_backend_search_results_finalize(SIPE_CORE_PUBLIC,
486 results,
487 secondary);
488 g_free(secondary);
489 sipe_xml_free(searchResults);
491 return(TRUE);
494 #define SIPE_SOAP_SEARCH_ROW "<m:row m:attrib=\"%s\" m:value=\"%s\"/>"
496 void sipe_core_buddy_search(struct sipe_core_public *sipe_public,
497 const gchar *given_name,
498 const gchar *surname,
499 const gchar *company,
500 const gchar *country)
502 gchar **attrs = g_new(gchar *, 5);
503 guint i = 0;
505 if (!attrs) return;
507 #define ADD_QUERY_ROW(a, v) \
508 if (v) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, a, v)
510 ADD_QUERY_ROW("givenName", given_name);
511 ADD_QUERY_ROW("sn", surname);
512 ADD_QUERY_ROW("company", company);
513 ADD_QUERY_ROW("c", country);
515 if (i) {
516 gchar *query;
518 attrs[i] = NULL;
519 query = g_strjoinv(NULL, attrs);
520 SIPE_DEBUG_INFO("sipe_core_buddy_search: rows:\n%s",
521 query ? query : "");
522 sip_soap_directory_search(SIPE_CORE_PRIVATE,
523 100,
524 query,
525 process_search_contact_response,
526 NULL);
527 g_free(query);
530 g_strfreev(attrs);
534 Local Variables:
535 mode: c
536 c-file-style: "bsd"
537 indent-tabs-mode: t
538 tab-width: 8
539 End: