friendlist.c 26.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
linphone
Copyright (C) 2010-2015 Belledonne Communications SARL

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "linphonecore.h"
#include "private.h"

23
#include <polarssl/md5.h>
24

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneFriendListCbs);

BELLE_SIP_INSTANCIATE_VPTR(LinphoneFriendListCbs, belle_sip_object_t,
	NULL, // destroy
	NULL, // clone
	NULL, // Marshall
	FALSE
);

static LinphoneFriendListCbs * linphone_friend_list_cbs_new(void) {
	return belle_sip_object_new(LinphoneFriendListCbs);
}

LinphoneFriendListCbs * linphone_friend_list_get_callbacks(const LinphoneFriendList *list) {
	return list->cbs;
}

LinphoneFriendListCbs * linphone_friend_list_cbs_ref(LinphoneFriendListCbs *cbs) {
	belle_sip_object_ref(cbs);
	return cbs;
}

void linphone_friend_list_cbs_unref(LinphoneFriendListCbs *cbs) {
	belle_sip_object_unref(cbs);
}

void *linphone_friend_list_cbs_get_user_data(const LinphoneFriendListCbs *cbs) {
	return cbs->user_data;
}

void linphone_friend_list_cbs_set_user_data(LinphoneFriendListCbs *cbs, void *ud) {
	cbs->user_data = ud;
}

LinphoneFriendListContactCreatedCb linphone_friend_list_cbs_get_contact_created(const LinphoneFriendListCbs *cbs) {
	return cbs->contact_created_cb;
}

void linphone_friend_list_cbs_set_contact_created(LinphoneFriendListCbs *cbs, LinphoneFriendListContactCreatedCb cb) {
	cbs->contact_created_cb = cb;
}

LinphoneFriendListContactDeletedCb linphone_friend_list_cbs_get_contact_deleted(const LinphoneFriendListCbs *cbs) {
	return cbs->contact_deleted_cb;
}

void linphone_friend_list_cbs_set_contact_deleted(LinphoneFriendListCbs *cbs, LinphoneFriendListContactDeletedCb cb) {
	cbs->contact_deleted_cb = cb;
}
74

75 76 77 78 79 80 81 82
LinphoneFriendListContactUpdatedCb linphone_friend_list_cbs_get_contact_updated(const LinphoneFriendListCbs *cbs) {
	return cbs->contact_updated_cb;
}

void linphone_friend_list_cbs_set_contact_updated(LinphoneFriendListCbs *cbs, LinphoneFriendListContactUpdatedCb cb) {
	cbs->contact_updated_cb = cb;
}

83 84 85 86 87 88 89 90
LinphoneFriendListSyncStateChangedCb linphone_friend_list_cbs_get_sync_status_changed(const LinphoneFriendListCbs *cbs) {
	return cbs->sync_state_changed_cb;
}

void linphone_friend_list_cbs_set_sync_status_changed(LinphoneFriendListCbs *cbs, LinphoneFriendListSyncStateChangedCb cb) {
	cbs->sync_state_changed_cb = cb;
}

91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
static char * create_resource_list_xml(const LinphoneFriendList *list) {
	char *xml_content = NULL;
	MSList *elem;
	xmlBufferPtr buf;
	xmlTextWriterPtr writer;
	int err;

	if (ms_list_size(list->friends) <= 0) return NULL;

	buf = xmlBufferCreate();
	if (buf == NULL) {
		ms_error("%s: Error creating the XML buffer", __FUNCTION__);
		return NULL;
	}
	writer = xmlNewTextWriterMemory(buf, 0);
	if (writer == NULL) {
		ms_error("%s: Error creating the XML writer", __FUNCTION__);
		return NULL;
	}

	xmlTextWriterSetIndent(writer,1);
	err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL);
	if (err >= 0) {
		err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"resource-lists", (const xmlChar *)"urn:ietf:params:xml:ns:resource-lists");
	}
	if (err >= 0) {
		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"xsi",
						    NULL, (const xmlChar *)"http://www.w3.org/2001/XMLSchema-instance");
	}

	if (err>= 0) {
		err = xmlTextWriterStartElement(writer, (const xmlChar *)"list");
	}
	for (elem = list->friends; elem != NULL; elem = elem->next) {
125 126
		LinphoneFriend *lf = (LinphoneFriend *)elem->data;
		char *uri = linphone_address_as_string_uri_only(lf->uri);
127 128 129 130 131 132 133 134 135 136
		if (err >= 0) {
			err = xmlTextWriterStartElement(writer, (const xmlChar *)"entry");
		}
		if (err >= 0) {
			err = xmlTextWriterWriteAttribute(writer, (const xmlChar *)"uri", (const xmlChar *)uri);
		}
		if (err >= 0) {
			/* Close the "entry" element. */
			err = xmlTextWriterEndElement(writer);
		}
Ghislain MARY's avatar
Ghislain MARY committed
137
		if (uri) ms_free(uri);
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
	}
	if (err >= 0) {
		/* Close the "list" element. */
		err = xmlTextWriterEndElement(writer);
	}

	if (err >= 0) {
		/* Close the "resource-lists" element. */
		err = xmlTextWriterEndElement(writer);
	}
	if (err >= 0) {
		err = xmlTextWriterEndDocument(writer);
	}
	if (err > 0) {
		/* xmlTextWriterEndDocument returns the size of the content. */
		xml_content = ms_strdup((char *)buf->content);
	}
	xmlFreeTextWriter(writer);
	xmlBufferFree(buf);

	return xml_content;
}

161 162 163 164 165 166
static void linphone_friend_list_parse_multipart_related_body(LinphoneFriendList *list, const LinphoneContent *body, const char *first_part_body) {
	xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
	xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
	xml_ctx->doc = xmlReadDoc((const unsigned char*)first_part_body, 0, NULL, 0);
	if (xml_ctx->doc != NULL) {
		char xpath_str[MAX_XPATH_LENGTH];
167
		LinphoneFriend *lf;
168 169
		LinphoneContent *presence_part;
		xmlXPathObjectPtr resource_object;
170 171
		const char *version_str = NULL;
		const char *full_state_str = NULL;
172
		const char *uri = NULL;
173 174
		bool_t full_state = FALSE;
		int version;
175 176 177 178
		int i;

		if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end;
		xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"rlmi", (const xmlChar *)"urn:ietf:params:xml:ns:rlmi");
179 180 181 182 183 184 185

		version_str = linphone_get_xml_attribute_text_content(xml_ctx, "/rlmi:list", "version");
		if (version_str == NULL) {
			ms_warning("rlmi+xml: No version attribute in list");
			goto end;
		}
		version = atoi(version_str);
Ghislain MARY's avatar
Ghislain MARY committed
186
		linphone_free_xml_text_content(version_str);
187 188
		if (version < list->expected_notification_version) {
			ms_warning("rlmi+xml: Discarding received notification with version %d because %d was expected", version, list->expected_notification_version);
189
			linphone_friend_list_update_subscriptions(list, NULL, FALSE); /* Refresh subscription to get new full state notify. */
190 191 192 193 194 195 196 197 198 199 200
			goto end;
		}

		full_state_str = linphone_get_xml_attribute_text_content(xml_ctx, "/rlmi:list", "fullState");
		if (full_state_str == NULL) {
			ms_warning("rlmi+xml: No fullState attribute in list");
			goto end;
		}
		if ((strcmp(full_state_str, "true") == 0) || (strcmp(full_state_str, "1") == 0)) {
			MSList *l = list->friends;
			for (; l != NULL; l = l->next) {
201 202
				lf = (LinphoneFriend *)l->data;
				linphone_friend_set_presence_model(lf, NULL);
203 204 205 206 207 208 209 210 211 212
			}
			full_state = TRUE;
		}
		linphone_free_xml_text_content(full_state_str);
		if ((list->expected_notification_version == 0) && (full_state == FALSE)) {
			ms_warning("rlmi+xml: Notification with version 0 is not full state, this is not valid");
			goto end;
		}
		list->expected_notification_version = version + 1;

213 214 215 216 217 218
		resource_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/rlmi:list/rlmi:resource");
		if ((resource_object != NULL) && (resource_object->nodesetval != NULL)) {
			for (i = 1; i <= resource_object->nodesetval->nodeNr; i++) {
				snprintf(xpath_str, sizeof(xpath_str), "/rlmi:list/rlmi:resource[%i]/@uri", i);
				uri = linphone_get_xml_text_content(xml_ctx, xpath_str);
				if (uri == NULL) continue;
219 220
				lf = linphone_friend_list_find_friend_by_uri(list, uri);
				if (lf != NULL) {
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
					const char *state = NULL;
					snprintf(xpath_str, sizeof(xpath_str),"/rlmi:list/rlmi:resource[%i]/rlmi:instance/@state", i);
					state = linphone_get_xml_text_content(xml_ctx, xpath_str);
					if ((state != NULL) && (strcmp(state, "active") == 0)) {
						const char *cid = NULL;
						snprintf(xpath_str, sizeof(xpath_str),"/rlmi:list/rlmi:resource[%i]/rlmi:instance/@cid", i);
						cid = linphone_get_xml_text_content(xml_ctx, xpath_str);
						if (cid != NULL) {
							presence_part = linphone_content_find_part_by_header(body, "Content-Id", cid);
							if (presence_part == NULL) {
								ms_warning("rlmi+xml: Cannot find part with Content-Id: %s", cid);
							} else {
								SalPresenceModel *presence = NULL;
								linphone_notify_parse_presence(linphone_content_get_type(presence_part), linphone_content_get_subtype(presence_part), linphone_content_get_string_buffer(presence_part), &presence);
								if (presence != NULL) {
236 237
									lf->presence_received = TRUE;
									linphone_friend_set_presence_model(lf, (LinphonePresenceModel *)presence);
238
									if (full_state == FALSE) {
239
										linphone_core_notify_notify_presence_received(list->lc, lf);
240
									}
241
								}
Ghislain MARY's avatar
Ghislain MARY committed
242
								linphone_content_unref(presence_part);
243 244 245 246 247
							}
						}
						if (cid != NULL) linphone_free_xml_text_content(cid);
					}
					if (state != NULL) linphone_free_xml_text_content(state);
248
					lf->subscribe_active = TRUE;
249 250 251 252 253
				}
				linphone_free_xml_text_content(uri);
			}
		}
		if (resource_object != NULL) xmlXPathFreeObject(resource_object);
254 255 256 257

		if (full_state == TRUE) {
			MSList *l = list->friends;
			for (; l != NULL; l = l->next) {
258 259 260
				lf = (LinphoneFriend *)l->data;
				if (linphone_friend_is_presence_received(lf) == TRUE) {
					linphone_core_notify_notify_presence_received(list->lc, lf);
261 262 263
				}
			}
		}
264 265 266 267 268 269 270 271
	} else {
		ms_warning("Wrongly formatted rlmi+xml body: %s", xml_ctx->errorBuffer);
	}

end:
	linphone_xmlparsing_context_destroy(xml_ctx);
}

272 273 274 275
static bool_t linphone_friend_list_has_subscribe_inactive(const LinphoneFriendList *list) {
	MSList *l = list->friends;
	bool_t has_subscribe_inactive = FALSE;
	for (; l != NULL; l = l->next) {
276 277
		LinphoneFriend *lf = (LinphoneFriend *)l->data;
		if (lf->subscribe_active != TRUE) {
278 279 280 281 282 283 284
			has_subscribe_inactive = TRUE;
			break;
		}
	}
	return has_subscribe_inactive;
}

285 286
static LinphoneFriendList * linphone_friend_list_new(void) {
	LinphoneFriendList *list = belle_sip_object_new(LinphoneFriendList);
287
	list->cbs = linphone_friend_list_cbs_new();
288 289 290 291
	belle_sip_object_ref(list);
	return list;
}

292 293 294
static void linphone_friend_list_destroy(LinphoneFriendList *list) {
	if (list->display_name != NULL) ms_free(list->display_name);
	if (list->rls_uri != NULL) ms_free(list->rls_uri);
295
	if (list->content_digest != NULL) ms_free(list->content_digest);
296
	if (list->event != NULL) linphone_event_unref(list->event);
297 298
	if (list->uri != NULL) ms_free(list->uri);
	if (list->cbs) linphone_friend_list_cbs_unref(list->cbs);
Sylvain Berfini's avatar
Sylvain Berfini committed
299 300
	if (list->dirty_friends_to_update) list->dirty_friends_to_update = ms_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref);
	if (list->friends) list->friends = ms_list_free_with_data(list->friends, (void (*)(void *))_linphone_friend_release);
301 302 303 304 305 306 307 308 309 310 311 312
}

BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneFriendList);

BELLE_SIP_INSTANCIATE_VPTR(LinphoneFriendList, belle_sip_object_t,
	(belle_sip_object_destroy_t)linphone_friend_list_destroy,
	NULL, // clone
	NULL, // marshal
	TRUE
);


313 314 315
LinphoneFriendList * linphone_core_create_friend_list(LinphoneCore *lc) {
	LinphoneFriendList *list = linphone_friend_list_new();
	list->lc = lc;
316 317 318 319 320 321 322 323
	return list;
}

LinphoneFriendList * linphone_friend_list_ref(LinphoneFriendList *list) {
	belle_sip_object_ref(list);
	return list;
}

324 325 326
void _linphone_friend_list_release(LinphoneFriendList *list){
	/*drops all references to core and unref*/
	list->lc = NULL;
Simon Morlat's avatar
Simon Morlat committed
327 328 329 330
	if (list->event != NULL) {
		linphone_event_unref(list->event);
		list->event = NULL;
	}
331 332 333 334
	if (list->cbs) {
		linphone_friend_list_cbs_unref(list->cbs);
		list->cbs = NULL;
	}
335 336 337 338 339 340
	if (list->dirty_friends_to_update) {
		list->dirty_friends_to_update = ms_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref);
	}
	if (list->friends) {
		list->friends = ms_list_free_with_data(list->friends, (void (*)(void *))_linphone_friend_release);
	}
341
	linphone_friend_list_unref(list);
342 343
}

344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
void linphone_friend_list_unref(LinphoneFriendList *list) {
	belle_sip_object_unref(list);
}

void * linphone_friend_list_get_user_data(const LinphoneFriendList *list) {
	return list->user_data;
}

void linphone_friend_list_set_user_data(LinphoneFriendList *list, void *ud) {
	list->user_data = ud;
}

const char * linphone_friend_list_get_display_name(const LinphoneFriendList *list) {
	return list->display_name;
}

void linphone_friend_list_set_display_name(LinphoneFriendList *list, const char *display_name) {
	if (list->display_name != NULL) {
		ms_free(list->display_name);
		list->display_name = NULL;
	}
	if (display_name != NULL) {
		list->display_name = ms_strdup(display_name);
367
		linphone_core_store_friends_list_in_db(list->lc, list);
368 369 370 371 372 373 374 375 376 377 378 379 380 381
	}
}

const char * linphone_friend_list_get_rls_uri(const LinphoneFriendList *list) {
	return list->rls_uri;
}

void linphone_friend_list_set_rls_uri(LinphoneFriendList *list, const char *rls_uri) {
	if (list->rls_uri != NULL) {
		ms_free(list->rls_uri);
		list->rls_uri = NULL;
	}
	if (rls_uri != NULL) {
		list->rls_uri = ms_strdup(rls_uri);
382
		linphone_core_store_friends_list_in_db(list->lc, list);
383 384 385
	}
}

386
static LinphoneFriendListStatus _linphone_friend_list_add_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t synchronize) {
Sylvain Berfini's avatar
Sylvain Berfini committed
387 388 389
	if (!list || !lf->uri || lf->friend_list) {
		if (!list)
			ms_error("linphone_friend_list_add_friend(): invalid list, null");
390
		if (!lf->uri)
391
			ms_error("linphone_friend_list_add_friend(): invalid friend, no sip uri");
392
		if (lf->friend_list)
393
			ms_error("linphone_friend_list_add_friend(): invalid friend, already in list");
Simon Morlat's avatar
Simon Morlat committed
394 395
		return LinphoneFriendListInvalidFriend;
	}
396
	if (ms_list_find(list->friends, lf) != NULL) {
397
		char *tmp = NULL;
398
		const LinphoneAddress *addr = linphone_friend_get_address(lf);
399 400 401 402
		if (addr) tmp = linphone_address_as_string(addr);
		ms_warning("Friend %s already in list [%s], ignored.", tmp ? tmp : "unknown", list->display_name);
		if (tmp) ms_free(tmp);
	} else {
403
		return linphone_friend_list_import_friend(list, lf, synchronize);
404 405 406 407
	}
	return LinphoneFriendListOK;
}

408 409 410 411 412 413 414 415
LinphoneFriendListStatus linphone_friend_list_add_friend(LinphoneFriendList *list, LinphoneFriend *lf) {
	return _linphone_friend_list_add_friend(list, lf, TRUE);
}

LinphoneFriendListStatus linphone_friend_list_add_local_friend(LinphoneFriendList *list, LinphoneFriend *lf) {
	return _linphone_friend_list_add_friend(list, lf, FALSE);
}

416 417 418 419 420 421
LinphoneFriendListStatus linphone_friend_list_import_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t synchronize) {
	if (!lf->uri || lf->friend_list) {
		if (!lf->uri)
			ms_error("linphone_friend_list_add_friend(): invalid friend, no sip uri");
		if (lf->friend_list)
			ms_error("linphone_friend_list_add_friend(): invalid friend, already in list");
Sylvain Berfini's avatar
Sylvain Berfini committed
422 423
		return LinphoneFriendListInvalidFriend;
	}
424
	lf->friend_list = list;
425
	lf->lc = list->lc;
Sylvain Berfini's avatar
Sylvain Berfini committed
426
	list->friends = ms_list_append(list->friends, linphone_friend_ref(lf));
427 428 429
	if (synchronize) {
		list->dirty_friends_to_update = ms_list_append(list->dirty_friends_to_update, linphone_friend_ref(lf));
	}
430 431 432
#ifdef FRIENDS_SQL_STORAGE_ENABLED
	linphone_core_store_friend_in_db(lf->lc, lf);
#endif
433 434 435
	return LinphoneFriendListOK;
}

436 437 438 439
static void carddav_done(LinphoneCardDavContext *cdc, bool_t success, const char *msg) {	
	if (cdc && cdc->friend_list->cbs->sync_state_changed_cb) {
		cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, success ? LinphoneFriendListSyncSuccessful : LinphoneFriendListSyncFailure, msg);
	}
440 441 442
	linphone_carddav_context_destroy(cdc);
}

443
static LinphoneFriendListStatus _linphone_friend_list_remove_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t remove_from_server) {
444
	MSList *elem = ms_list_find(list->friends, lf);
445
	if (elem == NULL) return LinphoneFriendListNonExistentFriend;
446 447

#ifdef FRIENDS_SQL_STORAGE_ENABLED
448
	linphone_core_remove_friend_from_db(lf->lc, lf);
449
#endif
450 451 452 453 454 455 456 457 458 459
	if (remove_from_server) {
		LinphoneVCard *lvc = linphone_friend_get_vcard(lf);
		if (lvc && linphone_vcard_get_uid(lvc)) {
			LinphoneCardDavContext *cdc = linphone_carddav_context_new(list);
			cdc->sync_done_cb = carddav_done;
			if (cdc->friend_list->cbs->sync_state_changed_cb) {
				cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL);
			}
			linphone_carddav_delete_vcard(cdc, lf);
		}
460
	}
461
	
462 463 464 465
	lf->friend_list = NULL;
	linphone_friend_unref(lf);
	list->friends = ms_list_remove_link(list->friends, elem);
	return LinphoneFriendListOK;
466
}
467

468 469 470 471
LinphoneFriendListStatus linphone_friend_list_remove_friend(LinphoneFriendList *list, LinphoneFriend *lf) {
	return _linphone_friend_list_remove_friend(list, lf, TRUE);
}

472 473 474 475
const MSList * linphone_friend_list_get_friends(const LinphoneFriendList *list) {
	return list->friends;
}

476 477 478 479 480 481 482 483 484
void linphone_friend_list_update_dirty_friends(LinphoneFriendList *list) {
	LinphoneCardDavContext *cdc = linphone_carddav_context_new(list);
	MSList *dirty_friends = list->dirty_friends_to_update;
	
	if (cdc) {
		cdc->sync_done_cb = carddav_done;
		while (dirty_friends) {
			LinphoneFriend *lf = (LinphoneFriend *)dirty_friends->data;
			if (lf) {
485 486 487
				if (cdc->friend_list->cbs->sync_state_changed_cb) {
					cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL);
				}
488 489 490 491
				linphone_carddav_put_vcard(cdc, lf);
			}
			dirty_friends = ms_list_next(dirty_friends);
		}
492
		list->dirty_friends_to_update = ms_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref);
493 494 495 496
	}
}

static void carddav_created(LinphoneCardDavContext *cdc, LinphoneFriend *lf) {
497
	if (cdc) {
498
		LinphoneFriendList *lfl = cdc->friend_list;
499
		linphone_friend_list_import_friend(lfl, lf, FALSE);
500
		if (cdc->friend_list->cbs->contact_created_cb) {
501
			cdc->friend_list->cbs->contact_created_cb(lfl, lf);
502
		}
503 504 505 506
	}
}

static void carddav_removed(LinphoneCardDavContext *cdc, LinphoneFriend *lf) {
507
	if (cdc) {
508
		LinphoneFriendList *lfl = cdc->friend_list;
509
		_linphone_friend_list_remove_friend(lfl, lf, FALSE);
510
		if (cdc->friend_list->cbs->contact_deleted_cb) {
511
			cdc->friend_list->cbs->contact_deleted_cb(lfl, lf);
512
		}
513 514 515
	}
}

516
static void carddav_updated(LinphoneCardDavContext *cdc, LinphoneFriend *lf_new, LinphoneFriend *lf_old) {
517 518 519 520
	if (cdc) {
		LinphoneFriendList *lfl = cdc->friend_list;
		MSList *elem = ms_list_find(lfl->friends, lf_old);
		if (elem) {
521
			elem->data = linphone_friend_ref(lf_new);
522
		}
523 524
		linphone_core_store_friend_in_db(lf_new->lc, lf_new);
		
525 526 527
		if (cdc->friend_list->cbs->contact_updated_cb) {
			cdc->friend_list->cbs->contact_updated_cb(lfl, lf_new, lf_old);
		}
528
		linphone_friend_unref(lf_old);
529
	}
530 531
}

532 533 534 535 536 537
void linphone_friend_list_synchronize_friends_from_server(LinphoneFriendList *list) {
	LinphoneCardDavContext *cdc = linphone_carddav_context_new(list);
	
	if (cdc) {
		cdc->contact_created_cb = carddav_created;
		cdc->contact_removed_cb = carddav_removed;
538
		cdc->contact_updated_cb = carddav_updated;
539
		cdc->sync_done_cb = carddav_done;
540 541 542
		if (cdc && cdc->friend_list->cbs->sync_state_changed_cb) {
			cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL);
		}
543 544 545 546
		linphone_carddav_synchronize(cdc);
	}
}

547
LinphoneFriend * linphone_friend_list_find_friend_by_address(const LinphoneFriendList *list, const LinphoneAddress *address) {
548
	LinphoneFriend *lf = NULL;
549 550
	const MSList *elem;
	for (elem = list->friends; elem != NULL; elem = elem->next) {
551 552 553
		lf = (LinphoneFriend *)elem->data;
		if (linphone_address_weak_equal(lf->uri, address))
			return lf;
554 555 556 557 558 559
	}
	return NULL;
}

LinphoneFriend * linphone_friend_list_find_friend_by_uri(const LinphoneFriendList *list, const char *uri) {
	LinphoneAddress *address = linphone_address_new(uri);
560
	LinphoneFriend *lf = address ? linphone_friend_list_find_friend_by_address(list, address) : NULL;
561
	if (address) linphone_address_unref(address);
562
	return lf;
563 564 565 566 567 568
}

LinphoneFriend * linphone_friend_list_find_friend_by_ref_key(const LinphoneFriendList *list, const char *ref_key) {
	const MSList *elem;
	if (ref_key == NULL) return NULL;
	for (elem = list->friends; elem != NULL; elem = elem->next) {
569 570
		LinphoneFriend *lf = (LinphoneFriend *)elem->data;
		if ((lf->refkey != NULL) && (strcmp(lf->refkey, ref_key) == 0)) return lf;
571 572 573 574 575 576 577
	}
	return NULL;
}

LinphoneFriend * linphone_friend_list_find_friend_by_inc_subscribe(const LinphoneFriendList *list, SalOp *op) {
	const MSList *elem;
	for (elem = list->friends; elem != NULL; elem = elem->next) {
578 579
		LinphoneFriend *lf = (LinphoneFriend *)elem->data;
		if (ms_list_find(lf->insubs, op)) return lf;
580 581 582 583 584 585 586
	}
	return NULL;
}

LinphoneFriend * linphone_friend_list_find_friend_by_out_subscribe(const LinphoneFriendList *list, SalOp *op) {
	const MSList *elem;
	for (elem = list->friends; elem != NULL; elem = elem->next) {
587 588
		LinphoneFriend *lf = (LinphoneFriend *)elem->data;
		if (lf->outsub && ((lf->outsub == op) || sal_op_is_forked_of(lf->outsub, op))) return lf;
589 590 591 592 593 594 595 596 597 598 599 600
	}
	return NULL;
}

void linphone_friend_list_close_subscriptions(LinphoneFriendList *list) {
	 /* FIXME we should wait until subscription to complete. */
	if (list->friends)
		ms_list_for_each(list->friends, (void (*)(void *))linphone_friend_close_subscriptions);
}

void linphone_friend_list_update_subscriptions(LinphoneFriendList *list, LinphoneProxyConfig *cfg, bool_t only_when_registered) {
	const MSList *elem;
601
	if (list->rls_uri != NULL) {
602 603
		LinphoneAddress *address = linphone_address_new(list->rls_uri);
		char *xml_content = create_resource_list_xml(list);
604
		if ((address != NULL) && (xml_content != NULL) && (linphone_friend_list_has_subscribe_inactive(list) == TRUE)) {
605
			unsigned char digest[16];
606
			md5((unsigned char *)xml_content, strlen(xml_content), digest);
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
			if ((list->event != NULL) && (list->content_digest != NULL) && (memcmp(list->content_digest, digest, sizeof(digest)) == 0)) {
				/* The content has not changed, only refresh the event. */
				linphone_event_refresh_subscribe(list->event);
			} else {
				LinphoneContent *content;
				int expires = lp_config_get_int(list->lc->config, "sip", "rls_presence_expires", 3600);
				list->expected_notification_version = 0;
				if (list->content_digest != NULL) ms_free(list->content_digest);
				list->content_digest = ms_malloc(sizeof(digest));
				memcpy(list->content_digest, digest, sizeof(digest));
				if (list->event != NULL) {
					linphone_event_terminate(list->event);
					linphone_event_unref(list->event);
				}
				list->event = linphone_core_create_subscribe(list->lc, address, "presence", expires);
				linphone_event_set_internal(list->event, TRUE);
				linphone_event_add_custom_header(list->event, "Require", "recipient-list-subscribe");
				linphone_event_add_custom_header(list->event, "Supported", "eventlist");
				linphone_event_add_custom_header(list->event, "Accept", "multipart/related, application/pidf+xml, application/rlmi+xml");
				linphone_event_add_custom_header(list->event, "Content-Disposition", "recipient-list");
				content = linphone_core_create_content(list->lc);
				linphone_content_set_type(content, "application");
				linphone_content_set_subtype(content, "resource-lists+xml");
				linphone_content_set_string_buffer(content, xml_content);
				linphone_event_send_subscribe(list->event, content);
				linphone_content_unref(content);
633
				linphone_event_set_user_data(list->event, list);
634
			}
635 636 637 638 639
		}
		if (address != NULL) linphone_address_unref(address);
		if (xml_content != NULL) ms_free(xml_content);
	} else {
		for (elem = list->friends; elem != NULL; elem = elem->next) {
640 641
			LinphoneFriend *lf = (LinphoneFriend *)elem->data;
			linphone_friend_update_subscribes(lf, cfg, only_when_registered);
642
		}
643 644 645 646 647 648
	}
}

void linphone_friend_list_invalidate_subscriptions(LinphoneFriendList *list) {
	const MSList *elem;
	for (elem = list->friends; elem != NULL; elem = elem->next) {
649 650
		LinphoneFriend *lf = (LinphoneFriend *)elem->data;
		linphone_friend_invalidate_subscription(lf);
651 652 653 654 655 656
	}
}

void linphone_friend_list_notify_presence(LinphoneFriendList *list, LinphonePresenceModel *presence) {
	const MSList *elem;
	for(elem = list->friends; elem != NULL; elem = elem->next) {
657 658
		LinphoneFriend *lf = (LinphoneFriend *)elem->data;
		linphone_friend_notify(lf, presence);
659 660
	}
}
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682

void linphone_friend_list_notify_presence_received(LinphoneFriendList *list, LinphoneEvent *lev, const LinphoneContent *body) {
	if (linphone_content_is_multipart(body)) {
		LinphoneContent *first_part;
		const char *type = linphone_content_get_type(body);
		const char *subtype = linphone_content_get_subtype(body);

		if ((strcmp(type, "multipart") != 0) || (strcmp(subtype, "related") != 0)) {
			ms_warning("multipart presence notified but it is not 'multipart/related'");
			return;
		}

		first_part = linphone_content_get_part(body, 0);
		if (first_part == NULL) {
			ms_warning("'multipart/related' presence notified but it doesn't contain any part");
			return;
		}

		type = linphone_content_get_type(first_part);
		subtype = linphone_content_get_subtype(first_part);
		if ((strcmp(type, "application") != 0) || (strcmp(subtype, "rlmi+xml") != 0)) {
			ms_warning("multipart presence notified but first part is not 'application/rlmi+xml'");
Ghislain MARY's avatar
Ghislain MARY committed
683
			linphone_content_unref(first_part);
684 685 686 687
			return;
		}

		linphone_friend_list_parse_multipart_related_body(list, body, linphone_content_get_string_buffer(first_part));
Ghislain MARY's avatar
Ghislain MARY committed
688
		linphone_content_unref(first_part);
689 690
	}
}
691 692 693 694 695 696 697 698 699 700 701 702

const char * linphone_friend_list_get_uri(const LinphoneFriendList *list) {
	return list->uri;
}

void linphone_friend_list_set_uri(LinphoneFriendList *list, const char *uri) {
	if (list->uri != NULL) {
		ms_free(list->uri);
		list->uri = NULL;
	}
	if (uri != NULL) {
		list->uri = ms_strdup(uri);
703
		linphone_core_store_friends_list_in_db(list->lc, list);
704 705 706 707 708
	}
}

void linphone_friend_list_update_revision(LinphoneFriendList *list, int rev) {
	list->revision = rev;
709
	linphone_core_store_friends_list_in_db(list->lc, list);
710 711
}

712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
void linphone_friend_list_subscription_state_changed(LinphoneCore *lc, LinphoneEvent *lev, LinphoneSubscriptionState state) {
	LinphoneFriendList *list = (LinphoneFriendList *)linphone_event_get_user_data(lev);
	if (!list) {
		ms_warning("core [%p] Receiving unexpected state [%s] for event [%p], no associated friend list",lc
					, linphone_subscription_state_to_string(state)
				   , lev);
	} else {
		ms_message("Receiving new state [%s] for event [%p] for friend list [%p]"
				   , linphone_subscription_state_to_string(state)
				   , lev
				   , list);
		
		if (state == LinphoneSubscriptionOutgoingProgress) {
			ms_message("Resseting version count for friend list [%p]",list);
			list->expected_notification_version = 0;
		}
	}
}
730 731 732 733

LinphoneCore* linphone_friend_list_get_core(LinphoneFriendList *list) {
	return list->lc;
}