ucs: first stab at search functionality
[siplcs.git] / src / core / sipe-ucs.c
blob07ec9a53b0e3a32a181e01349949c3a025ccb303
1 /**
2 * @file sipe-ucs.c
4 * pidgin-sipe
6 * Copyright (C) 2013-2014 SIPE Project <http://sipe.sourceforge.net/>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * Implementation for Unified Contact Store [MS-OXWSCOS]
25 * <http://msdn.microsoft.com/en-us/library/jj194130.aspx>
28 #include <string.h>
30 #include <glib.h>
31 #include <time.h>
33 #include "sipe-backend.h"
34 #include "sipe-buddy.h"
35 #include "sipe-common.h"
36 #include "sipe-core.h"
37 #include "sipe-core-private.h"
38 #include "sipe-digest.h"
39 #include "sipe-ews-autodiscover.h"
40 #include "sipe-group.h"
41 #include "sipe-http.h"
42 #include "sipe-nls.h"
43 #include "sipe-subscriptions.h"
44 #include "sipe-ucs.h"
45 #include "sipe-utils.h"
46 #include "sipe-xml.h"
48 struct sipe_ucs_transaction {
49 GSList *pending_requests;
52 typedef void (ucs_callback)(struct sipe_core_private *sipe_private,
53 struct sipe_ucs_transaction *trans,
54 const sipe_xml *body,
55 gpointer callback_data);
57 struct ucs_request {
58 gchar *body;
59 ucs_callback *cb;
60 gpointer cb_data;
61 struct sipe_ucs_transaction *transaction;
62 struct sipe_http_request *request;
65 struct sipe_ucs {
66 struct ucs_request *active_request;
67 GSList *transactions;
68 GSList *default_transaction;
69 gchar *ews_url;
70 time_t last_response;
71 guint group_id;
72 gboolean migrated;
73 gboolean shutting_down;
76 static void sipe_ucs_request_free(struct sipe_core_private *sipe_private,
77 struct ucs_request *data)
79 struct sipe_ucs *ucs = sipe_private->ucs;
80 struct sipe_ucs_transaction *trans = data->transaction;
82 /* remove request from transaction */
83 trans->pending_requests = g_slist_remove(trans->pending_requests,
84 data);
85 sipe_private->ucs->active_request = NULL;
87 /* remove completed transactions (except default transaction) */
88 if (!trans->pending_requests &&
89 (trans != ucs->default_transaction->data)) {
90 ucs->transactions = g_slist_remove(ucs->transactions,
91 trans);
92 g_free(trans);
95 if (data->request)
96 sipe_http_request_cancel(data->request);
97 if (data->cb)
98 /* Callback: aborted */
99 (*data->cb)(sipe_private, NULL, NULL, data->cb_data);
100 g_free(data->body);
101 g_free(data);
104 static void sipe_ucs_next_request(struct sipe_core_private *sipe_private);
105 static void sipe_ucs_http_response(struct sipe_core_private *sipe_private,
106 guint status,
107 SIPE_UNUSED_PARAMETER GSList *headers,
108 const gchar *body,
109 gpointer callback_data)
111 struct ucs_request *data = callback_data;
113 SIPE_DEBUG_INFO("sipe_ucs_http_response: code %d", status);
114 data->request = NULL;
116 if ((status == SIPE_HTTP_STATUS_OK) && body) {
117 sipe_xml *xml = sipe_xml_parse(body, strlen(body));
118 const sipe_xml *soap_body = sipe_xml_child(xml, "Body");
119 /* Callback: success */
120 (*data->cb)(sipe_private,
121 data->transaction,
122 soap_body,
123 data->cb_data);
124 sipe_xml_free(xml);
125 } else {
126 /* Callback: failed */
127 (*data->cb)(sipe_private, NULL, NULL, data->cb_data);
130 /* already been called */
131 data->cb = NULL;
133 sipe_ucs_request_free(sipe_private, data);
134 sipe_ucs_next_request(sipe_private);
137 static void sipe_ucs_next_request(struct sipe_core_private *sipe_private)
139 struct sipe_ucs *ucs = sipe_private->ucs;
140 struct sipe_ucs_transaction *trans;
142 if (ucs->active_request || ucs->shutting_down || !ucs->ews_url)
143 return;
145 trans = ucs->transactions->data;
146 while (trans->pending_requests) {
147 struct ucs_request *data = trans->pending_requests->data;
148 gchar *soap = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
149 "<soap:Envelope"
150 " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
151 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
152 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
153 " >"
154 " <soap:Header>"
155 " <t:RequestServerVersion Version=\"Exchange2013\" />"
156 " </soap:Header>"
157 " <soap:Body>"
158 " %s"
159 " </soap:Body>"
160 "</soap:Envelope>",
161 data->body);
162 struct sipe_http_request *request = sipe_http_request_post(sipe_private,
163 ucs->ews_url,
164 NULL,
165 soap,
166 "text/xml; charset=UTF-8",
167 sipe_ucs_http_response,
168 data);
169 g_free(soap);
171 if (request) {
172 g_free(data->body);
173 data->body = NULL;
174 data->request = request;
176 ucs->active_request = data;
178 sipe_core_email_authentication(sipe_private,
179 request);
180 sipe_http_request_allow_redirect(request);
181 sipe_http_request_ready(request);
183 break;
184 } else {
185 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_next_request: failed to create HTTP connection");
186 sipe_ucs_request_free(sipe_private, data);
191 static gboolean sipe_ucs_http_request(struct sipe_core_private *sipe_private,
192 struct sipe_ucs_transaction *trans,
193 gchar *body, /* takes ownership */
194 ucs_callback *callback,
195 gpointer callback_data)
197 struct sipe_ucs *ucs = sipe_private->ucs;
199 if (!ucs || ucs->shutting_down) {
200 SIPE_DEBUG_ERROR("sipe_ucs_http_request: new UCS request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
201 "Body: %s\n",
202 body ? body : "<EMPTY>");
203 g_free(body);
204 return(FALSE);
206 } else {
207 struct ucs_request *data = g_new0(struct ucs_request, 1);
209 data->cb = callback;
210 data->cb_data = callback_data;
211 data->body = body;
213 if (!trans)
214 trans = ucs->default_transaction->data;
215 data->transaction = trans;
216 trans->pending_requests = g_slist_append(trans->pending_requests,
217 data);
219 sipe_ucs_next_request(sipe_private);
220 return(TRUE);
224 struct sipe_ucs_transaction *sipe_ucs_transaction(struct sipe_core_private *sipe_private)
226 struct sipe_ucs *ucs = sipe_private->ucs;
227 struct sipe_ucs_transaction *trans;
229 if (!ucs)
230 return(NULL);
232 /* always insert new transactions before default transaction */
233 trans = g_new0(struct sipe_ucs_transaction, 1);
234 ucs->transactions = g_slist_insert_before(ucs->transactions,
235 ucs->default_transaction,
236 trans);
238 return(trans);
241 static void sipe_ucs_get_user_photo_response(struct sipe_core_private *sipe_private,
242 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
243 const sipe_xml *body,
244 gpointer callback_data)
246 gchar *uri = callback_data;
247 const sipe_xml *node = sipe_xml_child(body,
248 "GetUserPhotoResponse/PictureData");
250 if (node) {
251 gchar *base64;
252 gsize photo_size;
253 guchar *photo;
254 guchar digest[SIPE_DIGEST_SHA1_LENGTH];
255 gchar *digest_string;
257 /* decode photo data */
258 base64 = sipe_xml_data(node);
259 photo = g_base64_decode(base64, &photo_size);
260 g_free(base64);
262 /* EWS doesn't provide a hash -> calculate SHA-1 digest */
263 sipe_digest_sha1(photo, photo_size, digest);
264 digest_string = buff_to_hex_str(digest,
265 SIPE_DIGEST_SHA1_LENGTH);
267 /* backend frees "photo" */
268 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC,
269 uri,
270 photo,
271 photo_size,
272 digest_string);
273 g_free(digest_string);
276 g_free(uri);
279 void sipe_ucs_get_photo(struct sipe_core_private *sipe_private,
280 const gchar *uri)
282 gchar *payload = g_strdup(uri);
283 gchar *body = g_strdup_printf("<m:GetUserPhoto>"
284 " <m:Email>%s</m:Email>"
285 " <m:SizeRequested>HR48x48</m:SizeRequested>"
286 "</m:GetUserPhoto>",
287 sipe_get_no_sip_uri(uri));
289 if (!sipe_ucs_http_request(sipe_private,
290 NULL,
291 body,
292 sipe_ucs_get_user_photo_response,
293 payload))
294 g_free(payload);
297 static void sipe_ucs_search_response(struct sipe_core_private *sipe_private,
298 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
299 const sipe_xml *body,
300 gpointer callback_data)
302 const sipe_xml *persona_node;
303 struct sipe_backend_search_results *results = NULL;
304 guint match_count = 0;
306 for (persona_node = sipe_xml_child(body,
307 "FindPeopleResponse/People/Persona");
308 persona_node;
309 persona_node = sipe_xml_twin(persona_node)) {
310 const sipe_xml *address = sipe_xml_child(persona_node,
311 "ImAddress");
313 /* only display Persona nodes which have an "ImAddress" node */
314 if (address) {
315 gchar *uri;
316 gchar *displayname;
317 gchar *company;
318 gchar *email;
320 /* OK, we found something - show the results to the user */
321 match_count++;
322 if (!results) {
323 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
324 callback_data);
325 if (!results) {
326 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_search_response: Unable to display the search results.");
327 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
328 callback_data,
329 _("Unable to display the search results"));
330 return;
334 uri = sipe_xml_data(address);
335 displayname = sipe_xml_data(sipe_xml_child(persona_node,
336 "DisplayName"));
337 company = sipe_xml_data(sipe_xml_child(persona_node,
338 "CompanyName"));
339 email = sipe_xml_data(sipe_xml_child(persona_node,
340 "EmailAddress/EmailAddress"));
342 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
343 results,
344 sipe_get_no_sip_uri(uri),
345 displayname,
346 company,
347 NULL,
348 email);
350 g_free(email);
351 g_free(company);
352 g_free(displayname);
353 g_free(uri);
357 if (match_count > 0)
358 sipe_buddy_search_contacts_finalize(sipe_private,
359 results,
360 match_count,
361 FALSE);
362 else
363 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
364 callback_data,
365 _("No contacts found"));
368 void sipe_ucs_search(struct sipe_core_private *sipe_private,
369 struct sipe_backend_search_token *token,
370 const gchar *given_name,
371 SIPE_UNUSED_PARAMETER const gchar *surname,
372 SIPE_UNUSED_PARAMETER const gchar *email,
373 SIPE_UNUSED_PARAMETER const gchar *sipid,
374 SIPE_UNUSED_PARAMETER const gchar *company,
375 SIPE_UNUSED_PARAMETER const gchar *country)
377 /* search GAL for matching entries */
378 gchar *body = g_markup_printf_escaped("<m:FindPeople>"
379 " <m:PersonaShape>"
380 " <t:BaseShape>IdOnly</t:BaseShape>"
381 " <t:AdditionalProperties>"
382 " <t:FieldURI FieldURI=\"persona:CompanyName\"/>"
383 " <t:FieldURI FieldURI=\"persona:DisplayName\"/>"
384 " <t:FieldURI FieldURI=\"persona:EmailAddress\"/>"
385 " <t:FieldURI FieldURI=\"persona:ImAddress\"/>"
386 /* Locations doesn't seem to work
387 " <t:FieldURI FieldURI=\"persona:Locations\"/>"
389 " </t:AdditionalProperties>"
390 " </m:PersonaShape>"
391 " <m:IndexedPageItemView BasePoint=\"Beginning\" MaxEntriesReturned=\"100\" Offset=\"0\"/>"
393 * I have no idea why Exchnage doesn't accept this
394 * FieldURI for restrictions. Without it the search
395 * will return users that don't have an ImAddress
396 * and we need to filter them out ourselves :-(
397 " <m:Restriction>"
398 " <t:Exists>"
399 " <t:FieldURI FieldURI=\"persona:ImAddress\"/>"
400 " </t:Exists>"
401 " </m:Restriction>"
403 " <m:ParentFolderId>"
404 " <t:DistinguishedFolderId Id=\"directory\"/>"
405 " </m:ParentFolderId>"
406 " <m:QueryString>%s</m:QueryString>"
407 "</m:FindPeople>",
408 given_name);
410 if (!sipe_ucs_http_request(sipe_private,
411 NULL,
412 body,
413 sipe_ucs_search_response,
414 token))
415 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
416 token,
417 _("Contact search failed"));
420 static void sipe_ucs_ignore_response(struct sipe_core_private *sipe_private,
421 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
422 SIPE_UNUSED_PARAMETER const sipe_xml *body,
423 SIPE_UNUSED_PARAMETER gpointer callback_data)
425 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_ignore_response: done");
426 sipe_private->ucs->last_response = time(NULL);
429 static void ucs_extract_keys(const sipe_xml *persona_node,
430 const gchar **key,
431 const gchar **change)
433 const sipe_xml *attr_node;
436 * extract Exchange key - play the guessing game :-(
438 * We can't use the "DisplayName" node, because the text is localized.
440 * Assume that IsQuickContact == "true" and IsHidden == "false" means
441 * this Attribution node contains the information for the Lync contact.
443 for (attr_node = sipe_xml_child(persona_node,
444 "Attributions/Attribution");
445 attr_node;
446 attr_node = sipe_xml_twin(attr_node)) {
447 const sipe_xml *id_node = sipe_xml_child(attr_node,
448 "SourceId");
449 gchar *hidden = sipe_xml_data(sipe_xml_child(attr_node,
450 "IsHidden"));
451 gchar *quick = sipe_xml_data(sipe_xml_child(attr_node,
452 "IsQuickContact"));
453 if (id_node &&
454 sipe_strcase_equal(hidden, "false") &&
455 sipe_strcase_equal(quick, "true")) {
456 *key = sipe_xml_attribute(id_node, "Id");
457 *change = sipe_xml_attribute(id_node, "ChangeKey");
458 g_free(quick);
459 g_free(hidden);
460 break;
462 g_free(quick);
463 g_free(hidden);
467 static void sipe_ucs_add_new_im_contact_to_group_response(struct sipe_core_private *sipe_private,
468 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
469 const sipe_xml *body,
470 gpointer callback_data)
472 gchar *who = callback_data;
473 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private, who);
474 const sipe_xml *persona_node = sipe_xml_child(body,
475 "AddNewImContactToGroupResponse/Persona");
477 sipe_private->ucs->last_response = time(NULL);
479 if (persona_node &&
480 buddy &&
481 is_empty(buddy->exchange_key) &&
482 is_empty(buddy->change_key)) {
483 const gchar *key = NULL;
484 const gchar *change = NULL;
486 ucs_extract_keys(persona_node, &key, &change);
488 if (!is_empty(key) && !is_empty(change)) {
490 sipe_buddy_add_keys(sipe_private,
491 buddy,
492 key,
493 change);
495 SIPE_DEBUG_INFO("sipe_ucs_add_new_im_contact_to_group_response: persona URI '%s' key '%s' change '%s'",
496 buddy->name, key, change);
500 g_free(who);
503 void sipe_ucs_group_add_buddy(struct sipe_core_private *sipe_private,
504 struct sipe_ucs_transaction *trans,
505 struct sipe_group *group,
506 struct sipe_buddy *buddy,
507 const gchar *who)
509 /* existing or new buddy? */
510 if (buddy && buddy->exchange_key) {
511 gchar *body = g_strdup_printf("<m:AddImContactToGroup>"
512 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
513 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
514 "</m:AddImContactToGroup>",
515 buddy->exchange_key,
516 buddy->change_key,
517 group->exchange_key,
518 group->change_key);
520 sipe_ucs_http_request(sipe_private,
521 trans,
522 body,
523 sipe_ucs_ignore_response,
524 NULL);
525 } else {
526 gchar *payload = g_strdup(who);
527 gchar *body = g_strdup_printf("<m:AddNewImContactToGroup>"
528 " <m:ImAddress>%s</m:ImAddress>"
529 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
530 "</m:AddNewImContactToGroup>",
531 sipe_get_no_sip_uri(who),
532 group->exchange_key,
533 group->change_key);
535 if (!sipe_ucs_http_request(sipe_private,
536 trans,
537 body,
538 sipe_ucs_add_new_im_contact_to_group_response,
539 payload))
540 g_free(payload);
544 void sipe_ucs_group_remove_buddy(struct sipe_core_private *sipe_private,
545 struct sipe_ucs_transaction *trans,
546 struct sipe_group *group,
547 struct sipe_buddy *buddy)
549 if (group) {
551 * If a contact is removed from last group, it will also be
552 * removed from contact list completely. The documentation has
553 * a RemoveContactFromImList operation, but that doesn't seem
554 * to work at all, i.e. it is always rejected by the server.
556 gchar *body = g_strdup_printf("<m:RemoveImContactFromGroup>"
557 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
558 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
559 "</m:RemoveImContactFromGroup>",
560 buddy->exchange_key,
561 buddy->change_key,
562 group->exchange_key,
563 group->change_key);
565 sipe_ucs_http_request(sipe_private,
566 trans,
567 body,
568 sipe_ucs_ignore_response,
569 NULL);
573 static struct sipe_group *ucs_create_group(struct sipe_core_private *sipe_private,
574 const sipe_xml *group_node)
576 const sipe_xml *id_node = sipe_xml_child(group_node,
577 "ExchangeStoreId");
578 const gchar *key = sipe_xml_attribute(id_node, "Id");
579 const gchar *change = sipe_xml_attribute(id_node, "ChangeKey");
580 struct sipe_group *group = NULL;
582 if (!(is_empty(key) || is_empty(change))) {
583 gchar *name = sipe_xml_data(sipe_xml_child(group_node,
584 "DisplayName"));
585 group = sipe_group_add(sipe_private,
586 name,
587 key,
588 change,
589 /* sipe_group must have unique ID */
590 ++sipe_private->ucs->group_id);
591 g_free(name);
594 return(group);
597 static void sipe_ucs_add_im_group_response(struct sipe_core_private *sipe_private,
598 struct sipe_ucs_transaction *trans,
599 const sipe_xml *body,
600 gpointer callback_data)
602 gchar *who = callback_data;
603 const sipe_xml *group_node = sipe_xml_child(body,
604 "AddImGroupResponse/ImGroup");
605 struct sipe_group *group = ucs_create_group(sipe_private, group_node);
607 sipe_private->ucs->last_response = time(NULL);
609 if (group) {
610 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
611 who);
613 if (buddy)
614 sipe_buddy_insert_group(buddy, group);
616 sipe_ucs_group_add_buddy(sipe_private,
617 trans,
618 group,
619 buddy,
620 who);
623 g_free(who);
626 void sipe_ucs_group_create(struct sipe_core_private *sipe_private,
627 struct sipe_ucs_transaction *trans,
628 const gchar *name,
629 const gchar *who)
631 gchar *payload = g_strdup(who);
632 /* new_name can contain restricted characters */
633 gchar *body = g_markup_printf_escaped("<m:AddImGroup>"
634 " <m:DisplayName>%s</m:DisplayName>"
635 "</m:AddImGroup>",
636 name);
638 if (!sipe_ucs_http_request(sipe_private,
639 trans,
640 body,
641 sipe_ucs_add_im_group_response,
642 payload))
643 g_free(payload);
646 void sipe_ucs_group_rename(struct sipe_core_private *sipe_private,
647 struct sipe_group *group,
648 const gchar *new_name)
650 /* new_name can contain restricted characters */
651 gchar *body = g_markup_printf_escaped("<m:SetImGroup>"
652 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
653 " <m:NewDisplayName>%s</m:NewDisplayName>"
654 "</m:SetImGroup>",
655 group->exchange_key,
656 group->change_key,
657 new_name);
659 sipe_ucs_http_request(sipe_private,
660 NULL,
661 body,
662 sipe_ucs_ignore_response,
663 NULL);
666 void sipe_ucs_group_remove(struct sipe_core_private *sipe_private,
667 struct sipe_group *group)
669 gchar *body = g_strdup_printf("<m:RemoveImGroup>"
670 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
671 "</m:RemoveImGroup>",
672 group->exchange_key,
673 group->change_key);
675 sipe_ucs_http_request(sipe_private,
676 NULL,
677 body,
678 sipe_ucs_ignore_response,
679 NULL);
682 static void sipe_ucs_get_im_item_list_response(struct sipe_core_private *sipe_private,
683 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
684 const sipe_xml *body,
685 SIPE_UNUSED_PARAMETER gpointer callback_data)
687 const sipe_xml *node = sipe_xml_child(body,
688 "GetImItemListResponse/ImItemList");
690 if (node) {
691 const sipe_xml *persona_node;
692 const sipe_xml *group_node;
693 GHashTable *uri_to_alias = g_hash_table_new_full(g_str_hash,
694 g_str_equal,
695 NULL,
696 g_free);
698 /* Start processing contact list */
699 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
700 sipe_group_update_start(sipe_private);
701 sipe_buddy_update_start(sipe_private);
702 } else
703 sipe_backend_buddy_list_processing_start(SIPE_CORE_PUBLIC);
705 for (persona_node = sipe_xml_child(node, "Personas/Persona");
706 persona_node;
707 persona_node = sipe_xml_twin(persona_node)) {
708 gchar *address = sipe_xml_data(sipe_xml_child(persona_node,
709 "ImAddress"));
710 const gchar *key = NULL;
711 const gchar *change = NULL;
713 ucs_extract_keys(persona_node, &key, &change);
715 if (!(is_empty(address) || is_empty(key) || is_empty(change))) {
716 gchar *alias = sipe_xml_data(sipe_xml_child(persona_node,
717 "DisplayName"));
719 * it seems to be undefined if ImAddress node
720 * contains "sip:" prefix or not...
722 gchar *uri = sip_uri(address);
723 struct sipe_buddy *buddy = sipe_buddy_add(sipe_private,
724 uri,
725 key,
726 change);
727 g_free(uri);
729 /* hash table takes ownership of alias */
730 g_hash_table_insert(uri_to_alias,
731 buddy->name,
732 alias);
734 SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: persona URI '%s' key '%s' change '%s'",
735 buddy->name, key, change);
737 g_free(address);
740 for (group_node = sipe_xml_child(node, "Groups/ImGroup");
741 group_node;
742 group_node = sipe_xml_twin(group_node)) {
743 struct sipe_group *group = ucs_create_group(sipe_private,
744 group_node);
746 if (group) {
747 const sipe_xml *member_node;
749 for (member_node = sipe_xml_child(group_node,
750 "MemberCorrelationKey/ItemId");
751 member_node;
752 member_node = sipe_xml_twin(member_node)) {
753 struct sipe_buddy *buddy = sipe_buddy_find_by_exchange_key(sipe_private,
754 sipe_xml_attribute(member_node,
755 "Id"));
756 if (buddy)
757 sipe_buddy_add_to_group(sipe_private,
758 buddy,
759 group,
760 g_hash_table_lookup(uri_to_alias,
761 buddy->name));
766 g_hash_table_destroy(uri_to_alias);
768 /* Finished processing contact list */
769 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
770 sipe_buddy_update_finish(sipe_private);
771 sipe_group_update_finish(sipe_private);
772 } else {
773 sipe_buddy_cleanup_local_list(sipe_private);
774 sipe_backend_buddy_list_processing_finish(SIPE_CORE_PUBLIC);
775 sipe_subscribe_presence_initial(sipe_private);
780 static void ucs_get_im_item_list(struct sipe_core_private *sipe_private)
782 if (sipe_private->ucs->migrated)
783 sipe_ucs_http_request(sipe_private,
784 /* prioritize over pending default requests */
785 sipe_ucs_transaction(sipe_private),
786 g_strdup("<m:GetImItemList/>"),
787 sipe_ucs_get_im_item_list_response,
788 NULL);
791 static void ucs_set_ews_url(struct sipe_core_private *sipe_private,
792 const gchar *ews_url)
794 struct sipe_ucs *ucs = sipe_private->ucs;
796 SIPE_DEBUG_INFO("ucs_set_ews_url: '%s'", ews_url);
797 ucs->ews_url = g_strdup(ews_url);
799 /* this will trigger sending of the first deferred request */
800 ucs_get_im_item_list(sipe_private);
803 static void ucs_ews_autodiscover_cb(struct sipe_core_private *sipe_private,
804 const struct sipe_ews_autodiscover_data *ews_data,
805 SIPE_UNUSED_PARAMETER gpointer callback_data)
807 struct sipe_ucs *ucs = sipe_private->ucs;
808 const gchar *ews_url;
810 if (!ucs || !ews_data)
811 return;
813 ews_url = ews_data->ews_url;
814 if (is_empty(ews_url)) {
815 SIPE_DEBUG_ERROR_NOFORMAT("ucs_ews_autodiscover_cb: can't detect EWS URL, contact list operations will not work!");
816 return;
819 ucs_set_ews_url(sipe_private, ews_url);
822 gboolean sipe_ucs_is_migrated(struct sipe_core_private *sipe_private)
824 return(sipe_private->ucs ? sipe_private->ucs->migrated : FALSE);
827 void sipe_ucs_init(struct sipe_core_private *sipe_private,
828 gboolean migrated)
830 struct sipe_ucs *ucs;
832 if (sipe_private->ucs) {
833 struct sipe_ucs *ucs = sipe_private->ucs;
836 * contact list update trigger -> request list again
838 * If the trigger arrives less than 10 seconds after our
839 * last UCS response, then ignore it, because it is caused
840 * by our own changes to the contact list.
842 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
843 if ((time(NULL) - ucs->last_response) >= 10)
844 ucs_get_im_item_list(sipe_private);
845 else
846 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_init: ignoring this contact list update - triggered by our last change");
849 ucs->last_response = 0;
850 return;
853 sipe_private->ucs = ucs = g_new0(struct sipe_ucs, 1);
854 ucs->migrated = migrated;
856 /* create default transaction */
857 sipe_ucs_transaction(sipe_private);
858 ucs->default_transaction = ucs->transactions;
860 if (migrated) {
861 /* user specified a service URL? */
862 const gchar *ews_url = sipe_backend_setting(SIPE_CORE_PUBLIC, SIPE_SETTING_EMAIL_URL);
864 if (is_empty(ews_url))
865 sipe_ews_autodiscover_start(sipe_private,
866 ucs_ews_autodiscover_cb,
867 NULL);
868 else
869 ucs_set_ews_url(sipe_private, ews_url);
873 void sipe_ucs_free(struct sipe_core_private *sipe_private)
875 struct sipe_ucs *ucs = sipe_private->ucs;
876 GSList *entry;
878 if (!ucs)
879 return;
881 /* UCS stack is shutting down: reject all new requests */
882 ucs->shutting_down = TRUE;
884 entry = ucs->transactions;
885 while (entry) {
886 struct sipe_ucs_transaction *trans = entry->data;
887 GSList *entry2 = trans->pending_requests;
889 /* transactions get deleted by sipe_ucs_request_free() */
890 entry = entry->next;
892 while (entry2) {
893 struct ucs_request *request = entry2->data;
895 /* transactions get deleted by sipe_ucs_request_free() */
896 entry2 = entry2->next;
898 sipe_ucs_request_free(sipe_private, request);
902 /* only default transaction is left... */
903 sipe_utils_slist_free_full(ucs->transactions, g_free);
905 g_free(ucs->ews_url);
906 g_free(ucs);
907 sipe_private->ucs = NULL;
911 Local Variables:
912 mode: c
913 c-file-style: "bsd"
914 indent-tabs-mode: t
915 tab-width: 8
916 End: